构建脚本
有些包需要编译第三方非 Rust 代码,例如 C 库。其他包需要链接到 C 库,这些库可能位于系统上,也可能需要从源代码构建。还有一些包需要在构建之前执行某些功能,例如代码生成(比如解析器生成器)。
Cargo 的目标不是取代针对这些任务进行优化的其他工具,而是通过自定义构建脚本与它们集成。在包的根目录中放置一个名为 build.rs 的文件,将使 Cargo 在构建包之前编译该脚本并执行它。
// 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");
}
构建脚本的一些示例用例包括:
- 构建捆绑的 C 库。
- 在主机系统上查找 C 库。
- 根据规范生成 Rust 模块。
- 执行 crate 所需的任何特定于平台的配置。
以下部分描述了构建脚本的工作原理,而示例章节展示了各种编写脚本的示例。
注意:
package.build清单键可用于更改构建脚本的名称或完全禁用它。
构建脚本的生命周期
就在包构建之前,Cargo 会将构建脚本编译成可执行文件(如果尚未构建)。然后它会运行该脚本,该脚本可以执行任意数量的任务。脚本可以通过将以 cargo:: 开头的特殊格式命令打印到标准输出来与 Cargo 进行通信。
如果构建脚本的任何源文件或依赖项发生变化,它将被重建。
默认情况下,如果包中的任何文件发生变化,Cargo 将会重新运行构建脚本。通常最好使用 rerun-if 命令,如下面的变更检测部分所述,以缩小触发构建脚本再次运行的范围。
一旦构建脚本成功执行完成,包的其余部分将被编译。如果发生错误,脚本应以非零退出代码退出以停止构建,在这种情况下,构建脚本的输出将显示在终端上。
构建脚本的输入
运行构建脚本时,有许多输入传递给它,所有这些输入都以环境变量的形式传递。
除了环境变量外,构建脚本的当前目录是构建脚本所属包的源目录。
构建脚本的输出
构建脚本可以将任何输出文件或中间产物保存在 OUT_DIR 环境变量中指定的目录中。脚本不应修改该目录之外的任何文件。
构建脚本通过打印到标准输出来与 Cargo 通信。Cargo 会将每一行以 cargo:: 开头的行解释为会影响包编译的指令。其他所有行都会被忽略。
构建脚本打印的
cargo::指令的顺序可能影响cargo传递给rustc的参数顺序。反过来,传递给rustc的参数顺序可能影响传递给链接器的参数顺序。因此,您需要注意构建脚本指令的顺序。例如,如果对象foo需要链接到库bar,您可能需要确保库bar的cargo::rustc-link-lib指令出现在链接对象foo的指令之后。
正常编译期间,脚本的输出会隐藏在终端中。如果您希望直接在终端中看到输出,请使用 -vv 标志以“非常详细”模式调用 Cargo。这只发生在构建脚本运行时。如果 Cargo 判断没有变化,它就不会重新运行脚本,更多信息请参阅下面的变更检测。
构建脚本打印到标准输出的所有行都会写入到一个文件中,例如 target/debug/build/<pkg>/output(具体位置可能取决于您的配置)。标准错误输出也保存在同一目录中。
以下是 Cargo 识别的指令摘要,每个指令都在下面详细说明。
cargo::rerun-if-changed=PATH— 告诉 Cargo 何时重新运行脚本。cargo::rerun-if-env-changed=VAR— 告诉 Cargo 何时重新运行脚本。cargo::rustc-link-arg=FLAG— 为 benchmarks、 binaries、cdylibcrates、 examples 和 tests 传递自定义标志给链接器。cargo::rustc-link-arg-cdylib=FLAG— 为 cdylib crates 传递自定义标志给链接器。cargo::rustc-link-arg-bin=BIN=FLAG— 为 binaryBIN传递自定义标志给链接器。cargo::rustc-link-arg-bins=FLAG— 为 binaries 传递自定义标志给链接器。cargo::rustc-link-arg-tests=FLAG— 为 tests 传递自定义标志给链接器。cargo::rustc-link-arg-examples=FLAG— 为 examples 传递自定义标志给链接器。cargo::rustc-link-arg-benches=FLAG— 为 benchmarks 传递自定义标志给链接器。cargo::rustc-link-lib=LIB— 添加要链接的库。cargo::rustc-link-search=[KIND=]PATH— 添加到库搜索路径。cargo::rustc-flags=FLAGS— 传递某些标志给编译器。cargo::rustc-cfg=KEY[="VALUE"]— 启用编译时cfg设置。cargo::rustc-check-cfg=CHECK_CFG– 注册自定义cfg作为配置编译时检查的预期设置。cargo::rustc-env=VAR=VALUE— 设置环境变量。
cargo::error=MESSAGE— 在终端上显示错误。
cargo::warning=MESSAGE— 在终端上显示警告。cargo::metadata=KEY=VALUE— 元数据,由links脚本使用。
MSRV:
cargo::KEY=VALUE语法需要 1.77。要支持旧版本,请使用cargo:KEY=VALUE语法。
cargo::rustc-link-arg=FLAG
rustc-link-arg 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建支持的目标(benchmarks、 binaries、cdylib crates、 examples 和 tests)。它的用法高度平台特定。它对于设置共享库版本或链接器脚本非常有用。
cargo::rustc-link-arg-cdylib=FLAG
rustc-link-arg-cdylib 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建 cdylib 库目标。它的用法高度平台特定。它对于设置共享库版本或运行时路径非常有用。
出于历史原因,cargo::rustc-cdylib-link-arg 形式是 cargo::rustc-link-arg-cdylib 的别名,具有相同的含义。
cargo::rustc-link-arg-bin=BIN=FLAG
rustc-link-arg-bin 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建名为 BIN 的 binary 目标。它的用法高度平台特定。它对于设置链接器脚本或其他链接器选项非常有用。
cargo::rustc-link-arg-bins=FLAG
rustc-link-arg-bins 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建 binary 目标。它的用法高度平台特定。它对于设置链接器脚本或其他链接器选项非常有用。
cargo::rustc-link-arg-tests=FLAG
rustc-link-arg-tests 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建 tests 目标。
cargo::rustc-link-arg-examples=FLAG
rustc-link-arg-examples 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建 examples 目标。
cargo::rustc-link-arg-benches=FLAG
rustc-link-arg-benches 指令告诉 Cargo 将 -C link-arg=FLAG 选项传递给编译器,但仅限于构建 benchmark 目标。
cargo::rustc-link-lib=LIB
rustc-link-lib 指令告诉 Cargo 使用编译器的 -l 标志链接给定的库。这通常用于通过 FFI 链接 native 库。
LIB 字符串直接传递给 rustc,因此它支持 -l 支持的任何语法。
目前,LIB 完全支持的语法是 [KIND[:MODIFIERS]=]NAME[:RENAME]。
-l 标志仅传递给包的库目标,除非没有库目标,在这种情况下它会传递给所有目标。这样做是因为所有其他目标都隐式依赖于库目标,并且给定的要链接的库应该只包含一次。这意味着如果一个包同时有库目标和 binary 目标,则库可以访问给定库中的符号,而 binary 应该通过库目标的公共 API 访问它们。
可选的 KIND 可以是 dylib、static 或 framework 之一。更多详细信息请参阅 rustc 手册。
cargo::rustc-link-search=[KIND=]PATH
rustc-link-search 指令告诉 Cargo 将 -L 标志传递给编译器,以将目录添加到库搜索路径。
可选的 KIND 可以是 dependency、crate、native、framework 或 all 之一。更多详细信息请参阅 rustc 手册。
如果这些路径位于 OUT_DIR 中,它们也会被添加到动态库搜索路径环境变量中。不鼓励依赖此行为,因为这使得使用生成的 binary 变得困难。通常,最好避免在构建脚本中创建动态库(使用现有系统库是可以的)。
cargo::rustc-flags=FLAGS
rustc-flags 指令告诉 Cargo 将给定的以空格分隔的标志传递给编译器。这只允许使用 -l 和 -L 标志,并且等效于使用 rustc-link-lib 和 rustc-link-search。
cargo::rustc-cfg=KEY[="VALUE"]
rustc-cfg 指令告诉 Cargo 将给定值传递给编译器的 --cfg 标志。这可用于编译时检测功能以启用条件编译。自定义 cfg 必须使用 cargo::rustc-check-cfg 指令预期,或者用法需要允许 unexpected_cfgs lint 以避免意外 cfg 警告。
请注意,这不影响 Cargo 的依赖项解析。不能使用此方法启用可选依赖项或启用其他 Cargo 功能。
请注意,Cargo 功能使用 feature="foo" 的形式。使用此标志传递的 cfg 值不限于该形式,可以只提供一个标识符,或任何任意键/值对。例如,发出 cargo::rustc-cfg=abc 将允许代码使用 #[cfg(abc)](注意没有 feature=)。或者可以使用 = 符号使用任意键/值对,如 cargo::rustc-cfg=my_component="foo"。键应为 Rust 标识符,值应为字符串。
cargo::rustc-check-cfg=CHECK_CFG
添加到预期配置名称和值的列表,该列表用于使用 unexpected_cfgs lint 检查可到达的 cfg 表达式。
CHECK_CFG 的语法镜像了 rustc 的 --check-cfg 标志,更多详细信息请参阅Checking conditional configurations。
此指令可以这样使用:
#![allow(unused)] fn main() { // build.rs println!("cargo::rustc-check-cfg=cfg(foo, values(\"bar\"))"); if foo_bar_condition { println!("cargo::rustc-cfg=foo=\"bar\""); } }
请注意,应定义所有可能的 cfg,无论当前启用了哪些 cfg。这包括给定 cfg 名称的所有可能值。
建议将 cargo::rustc-check-cfg 和 cargo::rustc-cfg 指令尽可能地放在一起,以避免拼写错误、遗漏 check-cfg、过时的 cfg 等问题。
另请参阅条件编译示例。
MSRV: 1.80 版本开始支持。
cargo::rustc-env=VAR=VALUE
rustc-env 指令告诉 Cargo 在编译包时设置给定的环境变量。然后,可以在已编译的 crate 中通过 env! 宏检索该值。这对于在 crate 的代码中嵌入附加元数据很有用,例如 git HEAD 的哈希值或持续集成服务器的唯一标识符。
另请参阅Cargo 自动包含的环境变量。
注意:使用
cargo run或cargo test运行可执行文件时也会设置这些环境变量。但是,不鼓励这种用法,因为它将可执行文件绑定到 Cargo 的执行环境。通常,这些环境变量只应在编译时使用env!宏检查。
cargo::error=MESSAGE
error 指令告诉 Cargo 在构建脚本运行完成后显示错误,然后使构建失败。
注意:构建脚本库应仔细考虑是使用
cargo::error还是返回Result。最好返回Result,并允许调用者决定错误是否是致命的。然后调用者可以决定是否使用cargo::error显示Err变体。
MSRV: 1.84 版本开始支持。
cargo::warning=MESSAGE
warning 指令告诉 Cargo 在构建脚本运行完成后显示警告。警告仅针对 path 依赖项(即,您在本地处理的依赖项)显示,因此例如来自 crates.io crate 的警告默认情况下不会发出,除非构建失败。可以使用 -vv“非常详细”标志让 Cargo 显示所有 crate 的警告。
构建依赖
构建脚本也允许依赖于其他基于 Cargo 的 crate。依赖项通过清单的 build-dependencies 部分声明。
[build-dependencies]
cc = "1.0.46"
构建脚本无权访问 dependencies 或 dev-dependencies 部分中列出的依赖项(它们尚未构建!)。此外,构建依赖项除非在 [dependencies] 表中也明确添加,否则对包本身不可用。
建议仔细考虑您添加的每个依赖项,权衡其对编译时间、许可、维护等方面的影响。如果构建依赖项和普通依赖项共享某个依赖项,Cargo 将尝试重用它。但是,这并非总是可能,例如在交叉编译时,因此在考虑对编译时间的影响时要记住这一点。
变更检测
重建包时,Cargo 不一定知道是否需要再次运行构建脚本。默认情况下,它采取保守的方法,如果包中的任何文件发生变化(或由 exclude 和 include 字段控制的文件列表),则始终重新运行构建脚本。在大多数情况下,这不是一个好的选择,因此建议每个构建脚本至少发出一个 rerun-if 指令(如下所述)。如果发出了这些指令,则只有当给定值发生变化时,Cargo 才会重新运行脚本。如果 Cargo 正在重新运行您自己的 crate 或依赖项的构建脚本,并且您不知道原因,请参阅 常见问题解答中的“Cargo 为什么重建我的代码?”。
cargo::rerun-if-changed=PATH
rerun-if-changed 指令告诉 Cargo,如果给定路径的文件发生变化,则重新运行构建脚本。目前,Cargo 只使用文件系统的最后修改时间戳“mtime”来确定文件是否已更改。它将其与构建脚本上次运行时的内部缓存时间戳进行比较。
如果路径指向目录,它将扫描整个目录以查找任何修改。
如果构建脚本本身在任何情况下都不需要重新运行,则发出 cargo::rerun-if-changed=build.rs 是防止它被重新运行的一种简单方法(否则,如果没有发出 rerun-if 指令,默认是扫描整个包目录以查找变化)。Cargo 会自动处理脚本本身是否需要重新编译,并且在重新编译后脚本当然会重新运行。否则,指定 build.rs 是多余且不必要的。
cargo::rerun-if-env-changed=NAME
rerun-if-env-changed 指令告诉 Cargo,如果给定名称的环境变量的值发生变化,则重新运行构建脚本。
请注意,这里的环境变量是针对诸如 CC 之类的全局环境变量,不可能将其用于 Cargo 为构建脚本设置的 TARGET 等环境变量。使用的环境变量是 cargo 调用接收到的那些,而不是构建脚本可执行文件接收到的那些。
从 1.46 开始,在源代码中使用 env! 和 option_env! 将自动检测变化并触发重建。对于这些宏已引用的变量,不再需要 rerun-if-env-changed。
links 清单键
可以在 Cargo.toml 清单中设置 package.links 键,以声明包链接到给定的 native 库。此清单键的目的是让 Cargo 了解包所具有的 native 依赖项集合,并提供一个在包构建脚本之间传递元数据的原则性系统。
[package]
# ...
links = "foo"
此清单表明包链接到 libfoo native 库。使用 links 键时,包必须具有构建脚本,并且构建脚本应使用 rustc-link-lib 指令链接库。
主要地,Cargo 要求每个 links 值最多对应一个包。换句话说,禁止两个包链接到同一个 native 库。这有助于防止 crate 之间出现重复符号。但是,请注意,存在一些惯例来缓解此问题。
构建脚本可以生成任意一组以键值对形式表示的元数据。此元数据使用 cargo::metadata=KEY=VALUE 指令设置。
元数据会传递给依赖包的构建脚本。例如,如果包 foo 依赖于 bar,而 bar 链接到 baz,那么如果 bar 在其构建脚本元数据中生成了 key=value,则 foo 的构建脚本将拥有环境变量 DEP_BAZ_KEY=value(注意使用的是 links 键的值)。有关如何使用此功能的示例,请参阅“使用另一个 sys crate”。
请注意,元数据仅传递给直接依赖项,而不传递给传递性依赖项。
MSRV:
cargo::metadata=KEY=VALUE需要 1.77。要支持旧版本,请使用cargo:KEY=VALUE(不支持的指令假定为元数据键)。
*-sys 包
一些链接到系统库的 Cargo 包有一个命名惯例,即带有 -sys 后缀。任何名为 foo-sys 的包都应提供两项主要功能:
- 库 crate 应该链接到 native 库
libfoo。这通常会先探测当前系统是否存在libfoo,然后才从源代码构建。 - 库 crate 应该为
libfoo中的类型和函数提供声明,但不提供更高级别的抽象。
*-sys 包集合提供了一组用于链接到 native 库的通用依赖项。拥有这种与 native 库相关的包的惯例带来了一些好处:
- 对
foo-sys的通用依赖缓解了关于每个links值最多对应一个包的规则。 - 其他
-sys包可以利用DEP_NAME_KEY=value环境变量更好地与其他包集成。请参阅“使用另一个syscrate”示例。 - 通用依赖项允许集中处理发现
libfoo本身(或从源代码构建它)的逻辑。 - 这些依赖项很容易被覆盖。
通常会有一个没有 -sys 后缀的配套包,它在 sys 包之上提供安全、高级别的抽象。例如,git2 crate 为 libgit2-sys crate 提供了高级接口。
覆盖构建脚本
如果清单包含 links 键,Cargo 支持使用自定义库覆盖指定的构建脚本。此功能的目的是完全阻止运行该构建脚本,而是提前提供元数据。
要覆盖构建脚本,请在任何可接受的 config.toml 文件中放置以下配置。
[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"
通过此配置,如果一个包声明它链接到 foo,则构建脚本将不会被编译或运行,而是使用指定的元数据。
warning、rerun-if-changed 和 rerun-if-env-changed 键不应使用,并且将被忽略。
Jobserver
Cargo 和 rustc 使用为 GNU make 开发的 jobserver 协议来协调跨进程的并发。它本质上是一个信号量,控制同时运行的作业数量。并发数可以使用 --jobs 标志设置,默认为逻辑 CPU 的数量。
每个构建脚本从 Cargo 继承一个作业槽位,并应努力在其运行时只使用一个 CPU。如果脚本希望并行使用更多 CPU,它应该使用 jobserver crate 来与 Cargo 协调。
例如,cc crate 可以启用可选的 parallel 功能,该功能将使用 jobserver 协议尝试同时构建多个 C 文件。