构建脚本
一些包需要编译第三方非 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::
为前缀的特殊格式化命令打印到 stdout 来与 Cargo 通信。
如果其任何源文件或依赖项发生更改,则将重新构建构建脚本。
默认情况下,如果包中的任何文件发生更改,Cargo 将重新运行构建脚本。通常最好使用下面变更检测部分中描述的 rerun-if
命令来缩小触发构建脚本再次运行的范围。
一旦构建脚本成功完成执行,将编译包的其余部分。如果出现错误,脚本应以非零退出代码退出以停止构建,在这种情况下,构建脚本的输出将显示在终端上。
构建脚本的输入
运行构建脚本时,构建脚本有许多输入,所有输入都以环境变量的形式传递。
除了环境变量之外,构建脚本的当前目录是构建脚本包的源目录。
构建脚本的输出
构建脚本可以将任何输出文件或中间工件保存在OUT_DIR
环境变量中指定的目录中。脚本不应修改该目录之外的任何文件。
构建脚本通过打印到 stdout 来与 Cargo 通信。Cargo 将以 cargo::
开头的每一行解释为将影响包编译的指令。所有其他行都将被忽略。
构建脚本打印的
cargo::
指令的顺序可能会影响cargo
传递给rustc
的参数顺序。反过来,传递给rustc
的参数顺序可能会影响传递给链接器的参数顺序。因此,您需要注意构建脚本指令的顺序。例如,如果对象foo
需要链接到库bar
,您可能需要确保库bar
的cargo::rustc-link-lib
指令出现在链接对象foo
的指令之后。
在正常编译期间,脚本的输出将从终端隐藏。如果您想直接在终端中查看输出,请使用 -vv
标志以“非常详细”的方式调用 Cargo。这仅在运行构建脚本时发生。如果 Cargo 确定没有任何更改,它将不会重新运行该脚本,请参阅下面的变更检测以获取更多信息。
构建脚本打印到 stdout 的所有行都将写入类似 target/debug/build/<pkg>/output
的文件中(确切位置可能取决于您的配置)。stderr 输出也保存在同一目录中。
以下是 Cargo 识别的指令的摘要,每个指令将在下面详细说明。
cargo::rerun-if-changed=PATH
— 告知 Cargo 何时重新运行脚本。cargo::rerun-if-env-changed=VAR
— 告知 Cargo 何时重新运行脚本。cargo::rustc-link-arg=FLAG
— 将自定义标志传递给基准、二进制文件、cdylib
crates、示例和测试的链接器。cargo::rustc-link-arg-bin=BIN=FLAG
— 将自定义标志传递给二进制文件BIN
的链接器。cargo::rustc-link-arg-bins=FLAG
— 将自定义标志传递给二进制文件的链接器。cargo::rustc-link-arg-tests=FLAG
— 将自定义标志传递给测试的链接器。cargo::rustc-link-arg-examples=FLAG
— 将自定义标志传递给示例的链接器。cargo::rustc-link-arg-benches=FLAG
— 将自定义标志传递给基准的链接器。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::rustc-cdylib-link-arg=FLAG
— 将自定义标志传递给 cdylib crates 的链接器。
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
选项传递给编译器,但仅在构建受支持的目标(基准、二进制文件、cdylib
crates、示例和测试)时传递。它的用法高度依赖于平台。它对于设置共享库版本或链接器脚本很有用。
cargo::rustc-link-arg-bin=BIN=FLAG
rustc-link-arg-bin
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建名称为 BIN
的二进制目标时传递。它的用法高度依赖于平台。它对于设置链接器脚本或其他链接器选项很有用。
cargo::rustc-link-arg-bins=FLAG
rustc-link-arg-bins
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建二进制目标时传递。它的用法高度依赖于平台。它对于设置链接器脚本或其他链接器选项很有用。
cargo::rustc-link-lib=LIB
rustc-link-lib
指令告诉 Cargo 使用编译器的 -l
标志链接给定的库。这通常用于使用 FFI 链接本机库。
LIB
字符串直接传递给 rustc,因此它支持 -l
支持的任何语法。
目前,LIB
的完全支持语法是 [KIND[:MODIFIERS]=]NAME[:RENAME]
。
除非没有库目标,否则 -l
标志仅传递给包的库目标,在这种情况下,它将传递给所有目标。这样做是因为所有其他目标都隐式依赖于库目标,并且要链接的给定库应仅包含一次。这意味着,如果一个包同时具有库目标和二进制目标,则库可以访问给定库中的符号,而二进制目标应该通过库目标的公共 API 访问它们。
可选的 KIND
可以是 dylib
、static
或 framework
之一。有关更多详细信息,请参阅rustc 手册。
cargo::rustc-link-arg-tests=FLAG
rustc-link-arg-tests
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建测试目标时传递。
cargo::rustc-link-arg-examples=FLAG
rustc-link-arg-examples
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建示例目标时传递。
cargo::rustc-link-arg-benches=FLAG
rustc-link-arg-benches
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建基准目标时传递。
cargo::rustc-link-search=[KIND=]PATH
rustc-link-search
指令告诉 Cargo 将 -L
标志传递给编译器,以将目录添加到库搜索路径。
可选的 KIND
可以是 dependency
、crate
、native
、framework
或 all
之一。有关更多详细信息,请参阅rustc 手册。
如果这些路径在 OUT_DIR
内,它们也会添加到动态库搜索路径环境变量中。不建议依赖此行为,因为它使生成的二进制文件难以使用。一般来说,最好避免在构建脚本中创建动态库(使用现有的系统库是可以的)。
cargo::rustc-flags=FLAGS
rustc-flags
指令告诉 Cargo 将给定的以空格分隔的标志传递给编译器。这仅允许 -l
和 -L
标志,并且等效于使用rustc-link-lib
和rustc-link-search
。
cargo::rustc-cfg=KEY[="VALUE"]
rustc-cfg
指令告诉 Cargo 将给定值传递给编译器的 --cfg
标志。这可以用于编译时检测要启用条件编译的功能。必须使用 cargo::rustc-check-cfg
指令来预期自定义 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
标志一致,有关更多详细信息,请参阅 检查条件配置。
该指令可以这样使用
#![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\""); } }
请注意,无论当前启用了哪些配置(cfgs),都应定义所有可能的配置。这包括给定配置名称的所有可能值。
建议将 cargo::rustc-check-cfg
和 cargo::rustc-cfg
指令尽可能紧密地分组在一起,以避免出现拼写错误、缺少 check-cfg、过时的配置等问题。
另请参阅条件编译示例。
MSRV: 自 1.80 起支持
cargo::rustc-env=VAR=VALUE
rustc-env
指令告诉 Cargo 在编译包时设置给定的环境变量。然后可以在已编译的 crate 中通过 env!
宏检索该值。这对于在 crate 的代码中嵌入额外的元数据非常有用,例如 git HEAD 的哈希值或持续集成服务器的唯一标识符。
注意:这些环境变量在通过
cargo run
或cargo test
运行可执行文件时也会设置。但是,不建议使用这种方式,因为它将可执行文件与 Cargo 的执行环境绑定在一起。通常,这些环境变量应该仅在编译时使用env!
宏进行检查。
cargo::rustc-cdylib-link-arg=FLAG
rustc-cdylib-link-arg
指令告诉 Cargo 将 -C link-arg=FLAG
选项传递给编译器,但仅在构建 cdylib
库目标时。它的用法高度依赖于平台。它对于设置共享库版本或运行时路径非常有用。
cargo::error=MESSAGE
error
指令告诉 Cargo 在构建脚本运行完成后显示错误,然后使构建失败。
注意:构建脚本库应仔细考虑是否要使用
cargo::error
而不是返回Result
。最好返回Result
,并允许调用者决定错误是否为致命错误。然后,调用者可以决定是否使用cargo::error
显示Err
变体。
MSRV: 自 1.84 起支持
cargo::warning=MESSAGE
warning
指令告诉 Cargo 在构建脚本运行完成后显示警告。警告仅针对 path
依赖项(即您在本地工作的依赖项)显示,因此,例如,除非构建失败,否则默认情况下不会发出在 crates.io crates 中打印的警告。可以使用 -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 或依赖项的构建脚本,而您不知道原因,请参阅 FAQ 中的“为什么 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
调用接收到的环境变量,而不是构建脚本的可执行文件接收到的环境变量。
links
清单键
可以在 Cargo.toml
清单中设置 package.links
键,以声明该包与给定的原生库链接。此清单键的目的是让 Cargo 了解包所具有的原生依赖项集,并提供在包构建脚本之间传递元数据的原则性系统。
[package]
# ...
links = "foo"
此清单声明该包链接到 libfoo
原生库。使用 links
键时,包必须具有构建脚本,并且构建脚本应使用rustc-link-lib
指令链接该库。
主要地,Cargo 要求每个 links
值最多有一个包。换句话说,禁止两个包链接到同一个原生库。这有助于防止 crate 之间出现重复的符号。但是,请注意,存在 一些约定来缓解这种情况。
构建脚本可以生成任意一组键值对形式的元数据。此元数据通过 cargo::metadata=KEY=VALUE
指令设置。
元数据将传递给依赖包的构建脚本。例如,如果包 bar
依赖于 foo
,那么如果 foo
生成 key=value
作为其构建脚本元数据的一部分,则 bar
的构建脚本将具有环境变量 DEP_FOO_KEY=value
。有关如何使用此功能的示例,请参阅“使用另一个 sys
crate”。
请注意,元数据仅传递给直接依赖项,而不是传递依赖项。
MSRV:
cargo::metadata=KEY=VALUE
需要 1.77 版本。要支持较旧的版本,请使用cargo:KEY=VAUE
(不支持的指令被假定为元数据键)。
*-sys
包
某些链接到系统库的 Cargo 包具有 -sys
后缀的命名约定。任何名为 foo-sys
的包都应提供两个主要功能
- 库 crate 应链接到原生库
libfoo
。这通常会在从源代码构建之前探测当前系统中的libfoo
。 - 库 crate 应为
libfoo
中的类型和函数提供声明,但不提供更高级别的抽象。
*-sys
包的集合为链接到原生库提供了一组通用的依赖项。拥有此原生库相关包的约定有许多好处
- 对
foo-sys
的通用依赖项缓解了每个links
值一个包的规则。 - 其他
-sys
包可以利用DEP_NAME_KEY=value
环境变量来更好地与其他包集成。请参阅 “使用另一个sys
crate” 示例。 - 通用依赖项允许集中发现
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
键,它们将被忽略。
作业服务器
Cargo 和 rustc
使用为 GNU make 开发的 作业服务器协议,以协调跨进程的并发性。它本质上是一个信号量,用于控制并发运行的作业数量。可以使用 --jobs
标志设置并发性,该标志默认为逻辑 CPU 的数量。
每个构建脚本都从 Cargo 继承一个作业槽,并且应努力在运行时仅使用一个 CPU。如果脚本想要并行使用更多 CPU,则应使用 jobserver
crate 与 Cargo 协调。
例如,cc
crate 可以启用可选的 parallel
功能,该功能将使用作业服务器协议尝试同时构建多个 C 文件。