Build time tooling

This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code. Conventionally, build-time code lives in a build.rs file and is commonly referred to as a "build script". Common use cases include rust code generation and compilation of bundled C/C++/asm code. See crates.io's documentation on the matter⮳ for more information.

Compile and link statically to a bundled C library

cc cat-development-tools cat-development-tools::build-utils

To accommodate scenarios where additional C, C++, or assembly is required in a project, the cc⮳ crate offers a simple api for compiling bundled C/C++/asm code into static libraries (.a) that can be statically linked to by rustc.

The following example has some bundled C code (src/hello.c) that will be used from rust. Before compiling rust source code, the "build" file (build.rs) specified in Cargo.toml runs. Using the cc⮳ crate, a static library file will be produced (in this case, libhello.a, see cc::Build::compile⮳) which can then be used from rust by declaring the external function signatures in an compile⮳, which can then be used from rust by declaring the external function signatures in an extern block⮳ block.

Since the bundled C is very simple, only a single source file needs to be passed to cc::Build⮳. For more complex build requirements, cc::Build⮳ offers a full suite of builder methods for specifying cc::Build::include⮳ paths and extra compiler cc::Build::flags⮳.

Cargo.toml

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

[dependencies]
error-chain = "0.11"

build.rs

fn main() {
    // cc::Build::new().file("src/hello.c").compile("hello");
    // // outputs `libhello.a`
}

src/hello.c

#include <stdio.h>

void hello() {
    printf("Hello from C!\n");
}

void greet(const char* name) {
    printf("Hello, %s!\n", name);
}

src/main.rs

use std::ffi::CString;
use std::os::raw::c_char;

use anyhow::Result;

fn prompt(s: &str) -> Result<String> {
    use std::io::Write;
    print!("{}", s);
    std::io::stdout().flush()?;
    let mut input = String::new();
    std::io::stdin().read_line(&mut input)?;
    Ok(input.trim().to_string())
}

unsafe extern "C" {
    fn hello();
    fn greet(name: *const c_char);
}

fn main() -> Result<()> {
    // unsafe { hello() }
    let name = prompt("What's your name? ")?;
    let c_name = CString::new(name)?;
    // unsafe { greet(c_name.as_ptr()) }
    Ok(())
}

Compile and link statically to a bundled C++ library

cc cat-development-tools

Linking a bundled C++ library is very similar to linking a bundled C library. The two core differences when compiling and statically linking a bundled C++ library are specifying a C++ compiler via the builder method cc::Build::cpp⮳ and preventing name mangling by the C++ compiler by adding the extern "C" section at the top of our C++ source file.

Cargo.toml (static C++)

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs (static C++)

fn main() {
    // cc::Build::new()
    //     .cpp(true)
    //     .file("src/foo.cpp")
    //     .compile("foo");
    // println!("");
}

src/foo.cpp (static C++)

extern "C" {
    int multiply(int x, int y);
}

int multiply(int x, int y) {
    return x*y;
}

src/main.rs (static C++)

unsafe extern "C" {
    fn multiply(x: i32, y: i32) -> i32;
}

fn main() {
    // unsafe {
    //     println!("{}", multiply(5, 7));
    // }
}

Compile a C library while setting custom defines

cc cat-development-tools

It is simple to build bundled C code with custom defines using cc::Build::define⮳ The method takes an std::option::Option⮳ value, so it is possible to create defines such as #define APP_NAME "foo" as well as #define WELCOME (pass std::option::Option::None⮳ as the value for a value-less define). This example builds a bundled C file with dynamic defines set in build.rs and prints "Welcome to foo - version 1.0.2" when run. Cargo sets some environment variables⮳ which may be useful for some custom defines.

Cargo.toml (custom defines)

[package]
...
version = "1.0.2"
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs (custom defines)

fn main() {
    // cc::Build::new()
    //     .define("APP_NAME", "\"foo\"")
    //     .define(
    //         "VERSION",
    //         format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str(),
    //     )
    //     .define("WELCOME", None)
    //     .file("src/foo.c")
    //     .compile("foo");
}

src/foo.c (custom defines)

#include <stdio.h>

void print_app_info() {
#ifdef WELCOME
    printf("Welcome to ");
#endif
    printf("%s - version %s\n", APP_NAME, VERSION);
}

src/main.rs (custom defines)

unsafe extern "C" {
    fn print_app_info();
}

fn main() {
    // unsafe {
    //     print_app_info();
    // }
    println!("Printed app info.");
}