Cargo Build Script

  • Some packages need to compile third-party non-Rust code, for example, C libraries.
  • Other packages need to link C libraries which can either be located on the system or possibly need to be build from source.
  • Others still need facilities for functionality such as code generation before building.

  • Cargo does not aim to replacce other tools that are well optimized for these tasks, but it does integrate with them with custom build scripts.
  • Placing a file named build.rs in the root of a package will cause Cargo to compiule that script and execute it just before building the package.
fn main() {
    println!("cargo::rerun-if-changed=src/hello.c");

    cc::Build::new()
        .file("src/hello.c")
        .compile("hello");
}
  • Some example use cases of build scripts are:
    • Building a bundled C library.
    • Finidng a C library on the host system
    • Generating a Rust module from a specification
    • Performing any platform specific configuration needed for the crate

Lifecycle of a Build Script

  • Just before a package is build, Cargo will compile a build script into an executable (if it has not already been built).
  • It will then run the script, which may perform any number of tasks.
  • The script may communicate with Cargo by printing specially formatted commands prefixed with cargo:: to stdout.
  • The build script will be rebuilt if any of its source files or dependencies change.
  • By default, Cargo will re-run the build script if any of the files in the package changes.
  • Typically, it is best to use rerun-if commands, described later in the change section below. to narrow the focus of what triggers a build script to run again.
  • Once the build script successfully finishes executing, the rest of the package will be compiled.

Inputs to the Build Script

  • When the build script is run, there are number of inputs to the build script, all passed in the form of environment variables.
  • In addition, to environment variables, the build script's current directory is the source directory of the build script's package.

Outputs of the Build Script

  • Build scripts may save any output files or intermediate artifacts in the directory specified in the OUT_DIR environment variable.
  • Scripts should not modify any files outside of that directory.

  • Build scripts communicate with Cargo by printing to stdout.
  • Cargo will interpet each line that starts with cargo:: as a special command.
  • All other lines are ignored.
  • The order of cargo:: instructions printed by the build script may affect the order of arguments that cargo passes to rustc.
  • In turn, the order of arguments passed to rustc may affect the order of arguments passed to the linker.
  • Therefore, you will want to pay attention to the order of the build script's instructions.
  • For example, if object foo needs to link against library bar, you may want to make sure that library bar's cargo::rustc-link-lib instruction appears after instructions to link object foo.

  • The output of the script is hidden from the terminal during normal compilation.
  • If you would like to see the output directly in your terminal, invoke Cargo as very verbose with the -vv flag.
  • This only happens when the build script is run.
  • If Cargo determines nothing has changed, it will not re-run the script.

  • All the lines printed to stdout by a build script are writte to a file like target/debug/build/<pkg>/output.
  • The stderr output is also saved in the same directory.

  • The following is a summary of the instructiosn that Cargo recognizes, with each one detailed below.
    • cargo::rerun-if-changed=PATH - Tells cargo when to re-run the build script
    • cargo::rerun-if-env-changed=VAR - Tells cargo when to re-run the build script
    • cargo::rustc-link-arg=FLAG - Passes custom flags to a linker for benchmarks, binaries, cdylilb crates, examples, and tests
    • cargo::rustc-link-arg-bin=BIN=FLAG - Passes custom flags to a linker for the binary BIN
    • cargo::rustc-link-arg-tests=FLAG - Passes custom flags to a linker for tests
    • cargo::rustc-link-arg-examples=FLAG - Passes custom flags to a linker for examples.
    • cargo::rustc-link-arg-benches=FLAG - Passes custom flags to a linker for benchmarks.
    • cargo::rustc-link-lib=LIB - Adds a library to link.
    • cargo::rustc-link-search=[KIND=]PATH - Adds to the library search path.
    • cargo::rustc-flags=FLAGS - Passes certain flags to the compiler.
    • cargo::rustc-cfg=KEY[="VALUE"] - Enables compile-time cfg settings.
    • cargo::rustc-check-cfg=CHECK_CFG - Register custom cfgs as expected for compile-time checking of configs.
    • cargo::rustc-env=VAR=VALUE - Sets an environment variable.
    • cargo::rustc-cdylib-link-arg=FLAG - Passes custom flags to a linker for cdylib crates.
    • cargo::error=MESSAGE - Displays an error on the terminal.
    • cargo::warning=MESSAGE - Displays a warning on the terminal.
    • cargo::metadata=KEY=VALUE - Metadata, used by links scripts.

Build Dependencies

  • Build scripts are also allowed to have dependencies on other Cargo-based crates.
  • Dependencies are declared through the [build-dependencies] section in the Cargo.toml file.
  • The build script does not have access to the dependencies listed in the dependencies section or dev-dependencies section.
  • Also build dependencies are not available to the package itself unless explicitly added in the dependencies section.

  • It is recommended to carefully consider each dependency you add, weighing against the impact on compile time, licensing, maintenance, etc.
  • Cargo will attempt to reused dependency if it is shared between build dependencies and normal dependencies.
  • However, this is not always possible, for example, when cross-compiling, so keep that in consideration of the impact on compile time.

Change Detection

  • When rebuilding a package, Cargo does not necessarily know if the build script needs to run again.
  • By default, it takes conservative approach of always re-running the build script if any file within the package is changed.
  • For most cases, this is not good choice, so it is recommended that every build script emit at least one of the rerun-if instructions.
  • If these are emitted, then Cargo will only re-run the script if the given value has changed.
  • The package.links key may be set in the Cargo.toml manifest to declare that the package links with the given native library.
  • The purpose of this manifest key is to give Cargo an understanding about the set of native dependencies that a package has, as well as providing a principled system of passing metadata between package build scripts.
[package]
links = "foo"
  • This manifest states that the package links to the libfoo native library.
  • When using the links key, the package must have a build script, and the build script should use the rustc-link-lib instruction to link the library.

  • Primarily, Cargo requires that there is at most one package per links value.
  • In other words, it is forbidden to have two packages link to the same native library.
  • This prevents duplicate symbols between crates.

  • Build scripts can generate an arbitrary set of metadata in the form of key-value pairs.
  • This metadata is set with the cargo::metadata=KEY=VALUE instruction.

  • The metadata is passed to the build script of dependent packages.
  • For example, if the package foo depends on bar, which links baz, then if bar generates key=value as part of its build script metadata, then build script of foo will have the environment variables DEP_BAZ_KEY=value.