Build Scripts
Some packages need to compile third-party non-Rust code, for example Clibraries. Other packages need to link to C libraries which can either belocated on the system or possibly need to be built from source. Others stillneed facilities for functionality such as code generation before building (thinkparser generators).
Cargo does not aim to replace other tools that are well-optimized for thesetasks, but it does integrate with them with custom build scripts. Placing afile namedbuild.rs in the root of a package will cause Cargo to compilethat script and execute it just before building the package.
// Example custom build script.fn main() { // Tell Cargo that if the given file changes, to rerun this build script. println!("cargo::rerun-if-changed=src/hello.c"); // Use the `cc` crate to build a C file and statically link it. cc::Build::new() .file("src/hello.c") .compile("hello");}Some example use cases of build scripts are:
- Building a bundled C library.
- Finding a C library on the host system.
- Generating a Rust module from a specification.
- Performing any platform-specific configuration needed for the crate.
The sections below describe how build scripts work, and theexampleschapter shows a variety of examples on how to writescripts.
Note: The
package.buildmanifest key can beused to change the name of the build script, or disable it entirely.
Life Cycle of a Build Script
Just before a package is built, Cargo will compile a build script into anexecutable (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 Cargoby printing specially formatted commands prefixed withcargo:: to stdout.
The build script will be rebuilt if any of its source files or dependencieschange.
By default, Cargo will re-run the build script if any of the files in thepackage changes. Typically it is best to use thererun-if commands,described in thechange detection section below, tonarrow the focus of what triggers a build script to run again.
Once the build script successfully finishes executing, the rest of the packagewill be compiled. Scripts should exit with a non-zero exit code to halt thebuild if there is an error, in which case the build script’s output will bedisplayed on the terminal.
Inputs to the Build Script
When the build script is run, there are a number of inputs to the build script,all passed in the form ofenvironment variables.
In addition to environment variables, the build script’s current directory isthe source directory of the build script’s package.
Outputs of the Build Script
Build scripts may save any output files or intermediate artifacts in thedirectory specified in theOUT_DIR environment variable. Scriptsshould not modify any files outside of that directory.
Build scripts communicate with Cargo by printing to stdout. Cargo willinterpret each line that starts withcargo:: as an instruction that willinfluence compilation of the package. All other lines are ignored.
The order of
cargo::instructions printed by the build scriptmayaffect the order of arguments thatcargopasses torustc. In turn, theorder of arguments passed torustcmay affect the order of arguments passedto the linker. Therefore, you will want to pay attention to the order of thebuild script’s instructions. For example, if objectfooneeds to link againstlibrarybar, you may want to make sure that librarybar’scargo::rustc-link-libinstruction appearsafterinstructions to link objectfoo.
The output of the script is hidden from the terminal during normalcompilation. 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 thebuild script is run. If Cargo determines nothing has changed, it will notre-run the script, seechange detection below for more.
All the lines printed to stdout by a build script are written to a file liketarget/debug/build/<pkg>/output (the precise location may depend on yourconfiguration). The stderr output is also saved in that same directory.
The following is a summary of the instructions that Cargo recognizes, with eachone detailed below.
cargo::rerun-if-changed=PATH— Tells Cargo when tore-run the script.cargo::rerun-if-env-changed=VAR— Tells Cargo whento re-run the script.cargo::rustc-link-arg=FLAG— Passes custom flags to alinker for benchmarks, binaries,cdylibcrates, examples, and tests.cargo::rustc-link-arg-cdylib=FLAG— Passes customflags to a linker for cdylib crates.cargo::rustc-link-arg-bin=BIN=FLAG— Passes customflags to a linker for the binaryBIN.cargo::rustc-link-arg-bins=FLAG— Passes customflags to a linker for binaries.cargo::rustc-link-arg-tests=FLAG— Passes customflags to a linker for tests.cargo::rustc-link-arg-examples=FLAG— Passes customflags to a linker for examples.cargo::rustc-link-arg-benches=FLAG— Passes customflags to a linker for benchmarks.cargo::rustc-link-lib=LIB— Adds a library tolink.cargo::rustc-link-search=[KIND=]PATH— Adds to thelibrary search path.cargo::rustc-flags=FLAGS— Passes certain flags to thecompiler.cargo::rustc-cfg=KEY[="VALUE"]— Enables compile-timecfgsettings.cargo::rustc-check-cfg=CHECK_CFG– Register customcfgs asexpected for compile-time checking of configs.cargo::rustc-env=VAR=VALUE— Sets an environment variable.
cargo::error=MESSAGE— Displays an error on the terminal.
cargo::warning=MESSAGE— Displays a warning on theterminal.cargo::metadata=KEY=VALUE— Metadata, used bylinksscripts.
MSRV: 1.77 is required for
cargo::KEY=VALUEsyntax.To support older versions, use thecargo:KEY=VALUEsyntax.
cargo::rustc-link-arg=FLAG
Therustc-link-arg instruction tells Cargo to pass the-C link-arg=FLAGoption to the compiler, but only when building supported targets(benchmarks, binaries,cdylib crates, examples, and tests). Its usage ishighly platform specific. It is useful to set the shared library version orlinker script.
cargo::rustc-link-arg-cdylib=FLAG
Therustc-link-arg-cdylib instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when building acdylib library target. Its usage is highly platform specific. It is usefulto set the shared library version or the runtime-path.
For historical reasons, thecargo::rustc-cdylib-link-arg form is an aliasforcargo::rustc-link-arg-cdylib, and has the same meaning.
cargo::rustc-link-arg-bin=BIN=FLAG
Therustc-link-arg-bin instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when buildingthe binary target with nameBIN. Its usage is highly platform specific. It is usefulto set a linker script or other linker options.
cargo::rustc-link-arg-bins=FLAG
Therustc-link-arg-bins instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when building abinary target. Its usage is highly platform specific. It is usefulto set a linker script or other linker options.
cargo::rustc-link-arg-tests=FLAG
Therustc-link-arg-tests instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when building atests target.
cargo::rustc-link-arg-examples=FLAG
Therustc-link-arg-examples instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when building an examplestarget.
cargo::rustc-link-arg-benches=FLAG
Therustc-link-arg-benches instruction tells Cargo to pass the-C link-arg=FLAG option to the compiler, but only when building a benchmarktarget.
cargo::rustc-link-lib=LIB
Therustc-link-lib instruction tells Cargo to link the given library usingthe compiler’s-l flag. This is typically used to link anative library usingFFI.
TheLIB string is passed directly to rustc, so it supports any syntax that-l does.
Currently the fully supported syntax forLIB is[KIND[:MODIFIERS]=]NAME[:RENAME].
The-l flag is only passed to the library target of the package, unlessthere is no library target, in which case it is passed to all targets. This isdone because all other targets have an implicit dependency on the librarytarget, and the given library to link should only be included once. This meansthat if a package has both a library and a binary target, thelibrary hasaccess to the symbols from the given lib, and the binary should access themthrough the library target’s public API.
The optionalKIND may be one ofdylib,static, orframework. See therustc book for more detail.
cargo::rustc-link-search=[KIND=]PATH
Therustc-link-search instruction tells Cargo to pass the-Lflag to the compiler to add a directory to the library searchpath.
The optionalKIND may be one ofdependency,crate,native,framework, orall. See therustc book for more detail.
These paths are also added to thedynamic library search path environmentvariable if they are withintheOUT_DIR. Depending on this behavior is discouraged since this makes itdifficult to use the resulting binary. In general, it is best to avoidcreating dynamic libraries in a build script (using existing system librariesis fine).
cargo::rustc-flags=FLAGS
Therustc-flags instruction tells Cargo to pass the given space-separatedflags to the compiler. This only allows the-l and-L flags, and isequivalent to usingrustc-link-lib andrustc-link-search.
cargo::rustc-cfg=KEY[="VALUE"]
Therustc-cfg instruction tells Cargo to pass the given value to the--cfg flag to the compiler. This may be used for compile-timedetection of features to enableconditional compilation. Custom cfgsmust either be expected using thecargo::rustc-check-cfginstruction or usage will need to allow theunexpected_cfgslint to avoid unexpected cfgs warnings.
Note that this doesnot affect Cargo’s dependency resolution. This cannot beused to enable an optional dependency, or enable other Cargo features.
Be aware thatCargo features use the formfeature="foo".cfg valuespassed with this flag are not restricted to that form, and may provide just asingle identifier, or any arbitrary key/value pair. For example, emittingcargo::rustc-cfg=abc will then allow code to use#[cfg(abc)] (note the lackoffeature=). Or an arbitrary key/value pair may be used with an= symbollikecargo::rustc-cfg=my_component="foo". The key should be a Rustidentifier, the value should be a string.
cargo::rustc-check-cfg=CHECK_CFG
Add to the list of expected config names and values that is used when checkingthereachable cfg expressions with theunexpected_cfgs lint.
The syntax ofCHECK_CFG mirrors therustc--check-cfg flag, seeChecking conditional configurations for more details.
The instruction can be used like this:
#![allow(unused)]fn main() {// build.rsprintln!("cargo::rustc-check-cfg=cfg(foo, values(\"bar\"))");if foo_bar_condition { println!("cargo::rustc-cfg=foo=\"bar\"");}}
Note that all possible cfgs should be defined, regardless of which cfgs arecurrently enabled. This includes all possible values of a given cfg name.
It is recommended to group thecargo::rustc-check-cfg andcargo::rustc-cfg instructions as closely as possible in order toavoid typos, missing check-cfg, stale cfgs…
See also theconditional compilation example.
MSRV: Respected as of 1.80
cargo::rustc-env=VAR=VALUE
Therustc-env instruction tells Cargo to set the given environment variablewhen compiling the package. The value can be then retrieved by theenv!macro in the compiled crate. This is useful for embeddingadditional metadata in crate’s code, such as the hash of git HEAD or theunique identifier of a continuous integration server.
See also theenvironment variables automatically included byCargo.
Note: These environment variables are also set when running anexecutable with
cargo runorcargo test. However, this usage isdiscouraged since it ties the executable to Cargo’s execution environment.Normally, these environment variables should only be checked at compile-timewith theenv!macro.
cargo::error=MESSAGE
Theerror instruction tells Cargo to display an error after the build scripthas finished running, and then fail the build.
Note: Build script libraries should carefully consider if they want touse
cargo::errorversus returning aResult. It may be better to returnaResult, and allow the caller to decide if the error is fatal or not.The caller can then decide whether or not to display theErrvariantusingcargo::error.
MSRV: Respected as of 1.84
cargo::warning=MESSAGE
Thewarning instruction tells Cargo to display a warning after the buildscript has finished running. Warnings are only shown forpath dependencies(that is, those you’re working on locally), so for example warnings printedout incrates.io crates are not emitted by default, unless the build fails.The-vv “very verbose” flag may be used to have Cargo display warnings forall crates.
Build Dependencies
Build scripts are also allowed to have dependencies on other Cargo-based crates.Dependencies are declared through thebuild-dependencies section of themanifest.
[build-dependencies]cc = "1.0.46"The build scriptdoes not have access to the dependencies listed in thedependencies ordev-dependencies section (they’re not built yet!). Also,build dependencies are not available to the package itself unless alsoexplicitly added in the[dependencies] table.
It is recommended to carefully consider each dependency you add, weighingagainst the impact on compile time, licensing, maintenance, etc. Cargo willattempt to reuse a dependency if it is shared between build dependencies andnormal dependencies. However, this is not always possible, for example whencross-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 scriptneeds to be run again. By default, it takes a conservative approach of alwaysre-running the build script if any file within the package is changed (or thelist of files controlled by theexclude andinclude fields). For mostcases, this is not a good choice, so it is recommended that every build scriptemit at least one of thererun-if instructions (described below). If theseare emitted, then Cargo will only re-run the script if the given value haschanged. If Cargo is re-running the build scripts of your own crate or adependency and you don’t know why, see“Why is Cargo rebuilding my code?” in theFAQ.
cargo::rerun-if-changed=PATH
Thererun-if-changed instruction tells Cargo to re-run the build script ifthe file at the given path has changed. Currently, Cargo only uses thefilesystem last-modified “mtime” timestamp to determine if the file haschanged. It compares against an internal cached timestamp of when the buildscript last ran.
If the path points to a directory, it will scan the entire directory forany modifications.
If the build script inherently does not need to re-run under any circumstance,then emittingcargo::rerun-if-changed=build.rs is a simple way to prevent itfrom being re-run (otherwise, the default if norerun-if instructions areemitted is to scan the entire package directory for changes). Cargoautomatically handles whether or not the script itself needs to be recompiled,and of course the script will be re-run after it has been recompiled.Otherwise, specifyingbuild.rs is redundant and unnecessary.
cargo::rerun-if-env-changed=NAME
Thererun-if-env-changed instruction tells Cargo to re-run the build scriptif the value of an environment variable of the given name has changed.
Note that the environment variables here are intended for global environmentvariables likeCC and such, it is not possible to use this for environmentvariables likeTARGET thatCargo sets for build scripts. Theenvironment variables in use are those received bycargo invocations, notthose received by the executable of the build script.
As of 1.46, usingenv! andoption_env! insource code will automatically detect changes and trigger rebuilds.rerun-if-env-changed is no longer needed for variables already referenced bythese macros.
Thelinks Manifest Key
Thepackage.links key may be set in theCargo.toml manifest to declarethat the package links with the given native library. The purpose of thismanifest key is to give Cargo an understanding about the set of nativedependencies that a package has, as well as providing a principled system ofpassing metadata between package build scripts.
[package]# ...links = "foo"This manifest states that the package links to thelibfoo native library.When using thelinks key, the package must have a build script, and thebuild script should use therustc-link-lib instruction tolink the library.
Primarily, Cargo requires that there is at most one package perlinks value.In other words, it is forbidden to have two packages link to the same nativelibrary. This helps prevent duplicate symbols between crates. Note, however,that there areconventions in place to alleviate this.
Build scripts can generate an arbitrary set of metadata in the form ofkey-value pairs. This metadata is set with thecargo::metadata=KEY=VALUEinstruction.
The metadata is passed to the build scripts ofdependent packages. Forexample, if the packagefoo depends onbar, which linksbaz, then ifbar generateskey=value as part of its build script metadata, then thebuild script offoo will have the environment variablesDEP_BAZ_KEY=value(note that the value of thelinks key is used).See the“Using anothersys crate” for an example ofhow this can be used.
Note that metadata is only passed to immediate dependents, not transitivedependents.
MSRV: 1.77 is required for
cargo::metadata=KEY=VALUE.To support older versions, usecargo:KEY=VALUE(unsupported directives are assumed to be metadata keys).
*-sys Packages
Some Cargo packages that link to system libraries have a naming convention ofhaving a-sys suffix. Any package namedfoo-sys should provide two majorpieces of functionality:
- The library crate should link to the native library
libfoo. This will oftenprobe the current system forlibfoobefore resorting to building fromsource. - The library crate should providedeclarations for types and functions in
libfoo, butnot higher-level abstractions.
The set of*-sys packages provides a common set of dependencies for linkingto native libraries. There are a number of benefits earned from having thisconvention of native-library-related packages:
- Common dependencies on
foo-sysalleviates the rule about one package pervalue oflinks. - Other
-syspackages can take advantage of theDEP_NAME_KEY=valueenvironment variables to better integrate with other packages. See the“Using anothersyscrate” example. - A common dependency allows centralizing logic on discovering
libfooitself(or building it from source). - These dependencies are easilyoverridable.
It is common to have a companion package without the-sys suffix thatprovides a safe, high-level abstractions on top of the sys package. Forexample, thegit2 crate provides a high-level interface to thelibgit2-sys crate.
Overriding Build Scripts
If a manifest contains alinks key, then Cargo supports overriding the buildscript specified with a custom library. The purpose of this functionality is toprevent running the build script in question altogether and instead supply themetadata ahead of time.
To override a build script, place the following configuration in any acceptableconfig.toml file.
[target.x86_64-unknown-linux-gnu.foo]rustc-link-lib = ["foo"]rustc-link-search = ["/path/to/foo"]rustc-flags = "-L /some/path"rustc-cfg = ['key="value"']rustc-env = {key = "value"}rustc-cdylib-link-arg = ["…"]metadata_key1 = "value"metadata_key2 = "value"With this configuration, if a package declares that it links tofoo then thebuild script willnot be compiled or run, and the metadata specified willbe used instead.
Thewarning,rerun-if-changed, andrerun-if-env-changed keys should notbe used and will be ignored.
Jobserver
Cargo andrustc use thejobserver protocol, developed for GNU make, tocoordinate concurrency across processes. It is essentially a semaphore thatcontrols the number of jobs running concurrently. The concurrency may be setwith the--jobs flag, which defaults to the number of logical CPUs.
Each build script inherits one job slot from Cargo, and should endeavor toonly use one CPU while it runs. If the script wants to use more CPUs inparallel, it should use thejobserver crate to coordinate with Cargo.
As an example, thecc crate may enable the optionalparallel featurewhich will use the jobserver protocol to attempt to build multiple C filesat the same time.