Mixed C and C++ build with cc-rs

#1

I have the library version of ChucK I want to compile into the crate. Bindings are generated from c_chuck.h, a C wrapper for ChucK classes and stuff. The problem I’m running into is that ChucK has both .c and .cpp files that need to be compiled to objects, and then linked into libchuck.a or libchuck.so on Linux. As far as I can tell, cc-rs has a .cpp(true) option that enables C++ compilation, but once it’s set it applies to all included files. Thus cargo build fails because the build.rs is trying to compile .c files w/ g++. Is there a good example of a build.rs that successfully handles compiling both .c and .cpp object files and linking them in a static/dynamic library?

Here’s the relevant code from build.rs:

let mut builder = cc::Build::new();
builder.include("./src");
builder.include("./src/core");
builder.include("./src/core/lo");
builder.include("./src/core/regex");
builder.include("./src/host");
builder.include("./src/host/RtAudio");
builder.flag_if_supported("-B");
builder.flag_if_supported("--sysroot");
builder.flag_if_supported("-D__PLATFORM_LINUX__");
builder.flag_if_supported("-DHAVE_CONFIG_H");
builder.flag_if_supported("-DHAVE_POLL");
builder.flag_if_supported("-DHAVE_LIBPTHREAD");
builder.flag_if_supported("-DENABLE_THREADS");
builder.flag_if_supported("-D__CK_SNDFILE_NATIVE__");
builder.flag_if_supported("-D__LINUX_ALSA__");
builder.flag_if_supported("-DUSE_ALSA");
builder.flag_if_supported("-DUSE_DLTRICK_ALSA");
builder.flag_if_supported("-fPIC");
builder.flag_if_supported("-fno-strict-aliasing");
builder.flag_if_supported("-fstacore-protector-strong");
builder.flag_if_supported("-fno-plt");
builder.flag_if_supported("-fpermissive");
builder.flag_if_supported("-O3");
builder.flag_if_supported("-mtune=generic");
builder.flag_if_supported("-march=native");
builder.flag_if_supported("-pipe");
builder.file("./src/core/util_math.c");
builder.file("./src/core/util_network.c");
builder.file("./src/core/util_raw.c");
builder.file("./src/core/util_sndfile.c");
builder.file("./src/core/util_xforms.c");
builder.cpp(true);
builder.cpp_link_stdlib("stdc++");
builder.file("./src/c_chuck.cpp"); // C wrapper
builder.file("./src/core/chuck_absyn.cpp");
builder.file("./src/core/chuck_carrier.cpp");
builder.file("./src/core/chuck_compile.cpp");
builder.file("./src/core/chuck_dl.cpp");
builder.file("./src/core/chuck_emit.cpp");
builder.file("./src/core/chuck_errmsg.cpp");
builder.file("./src/core/chuck_frame.cpp");
builder.file("./src/core/chuck_instr.cpp");
builder.file("./src/core/chuck_io.cpp");
builder.file("./src/core/chuck_lang.cpp");
builder.file("./src/core/chuck_oo.cpp");
builder.file("./src/core/chuck_otf.cpp");
builder.file("./src/core/chuck_parse.cpp");
builder.file("./src/core/chuck_scan.cpp");
builder.file("./src/core/chuck_shell.cpp");
builder.file("./src/core/chuck_stats.cpp");
builder.file("./src/core/chuck_symbol.cpp");
builder.file("./src/core/chuck_table.cpp");
builder.file("./src/core/chuck_type.cpp");
builder.file("./src/core/chuck_ugen.cpp");
builder.file("./src/core/chuck_utils.cpp");
builder.file("./src/core/chuck_vm.cpp");
builder.file("./src/core/chuck.cpp");
builder.file("./src/core/hidio_sdl.cpp");
builder.file("./src/core/midiio_rtmidi.cpp");
builder.file("./src/core/rtmidi.cpp");
builder.file("./src/core/uana_extract.cpp");
builder.file("./src/core/uana_xform.cpp");
builder.file("./src/core/ugen_filter.cpp");
builder.file("./src/core/ugen_osc.cpp");
builder.file("./src/core/ugen_stk.cpp");
builder.file("./src/core/ugen_xxx.cpp");
builder.file("./src/core/ulib_machine.cpp");
builder.file("./src/core/ulib_math.cpp");
builder.file("./src/core/ulib_opsc.cpp");
builder.file("./src/core/ulib_regex.cpp");
builder.file("./src/core/ulib_std.cpp");
builder.file("./src/core/util_buffers.cpp");
builder.file("./src/core/util_console.cpp");
builder.file("./src/core/util_hid.cpp");
builder.file("./src/core/util_opsc.cpp");
builder.file("./src/core/util_serial.cpp");
builder.file("./src/core/util_string.cpp");
builder.file("./src/core/util_thread.cpp");
#[cfg(feature = "static")]
builder.compile("libchuck.a");
#[cfg(feature = "dynamic")]
builder.compile("libchuck.so")

And here’s the entire build.rs file for reference and context: build.rs

I am new to all of this so any helpful pointers and constructive criticism is most welcome.

#2

I think you need to use separate cc::Build instances for C build and C++ build.

2 Likes
#3

I thought that might be the case, my only question then would be how do link all those objects with the builder.compile step

#4

How would you have compiled everything from the command-line (e.g., imagining your project was in C)? Translating an explicit functional command into ::cc build.rs will be easier than tackling both issues at once.

#5

Good point. The original project uses the ubiquitous makefile approach, maybe I’ll just adapt that to my needs…

#6

I haven’t tested it, but I guess ::cc supports building object files and loading them, so the idea is the following:

  1. before the .cpp(true) line you go and compile a c_part.o file,

  2. then instance a new ::cc::Build for the following .cpp files part and compile a cpp_part.o;

  3. now all you need to do is linking both object files together into a library.

The only thing subtle here is knowing which flag goes where. If your Makefile uses CPPFLAGS, CFLAGS, CXXFLAGS and LDFLAGS, then in theory you use CPPFLAGS and CFLAGS to compile c_part.o; CPPFLAGS, (maybe CFLAGS and) CXXFLAGS for cpp_part.o; and then LDFLAGS for the “linking both into a library” part.

#7

Right. And the linking is done by the C++ compiler. Thanks, back to experimenting!

#8

As far as I know, both g++ and gcc delegate to ld when linking, so they should indeed behave the same.

#9

Any news on this? Any success or failure of the suggested ideas can be useful for future readers of this thread :slight_smile:

#10

Well it’s still a work in progress, you can follow along here https://github.com/tonal-glyph/ruckus/tree/master/chuck-sys

Command::new() is a good friend for build.rs :grinning:

1 Like