第24章 Cargo 外部工具集成与 JSON 输出详解
Cargo 的目标之一是与第三方工具(如 IDE 和其他构建系统)实现简单的集成。为了方便集成,Cargo 提供了以下功能:
-
cargo metadata命令,以 JSON 格式输出包结构和依赖信息, -
--message-format标志,用于输出特定构建的信息,以及 -
对自定义子命令的支持。
包结构信息
你可以使用 cargo metadata 命令获取包结构和依赖信息。有关输出格式的详细信息,请参阅 cargo metadata 文档。
该格式是稳定且版本化的。调用 cargo metadata 时,建议显式传递 --format-version 标志,以避免向前不兼容的风险。
如果你使用 Rust,可以利用 cargo_metadata 库来解析输出。
JSON 消息
当传递 --message-format=json 时,Cargo 在构建过程中会输出以下信息:
-
编译器错误和警告,
-
生成的产物,
-
构建脚本的结果(例如,原生依赖项)。
输出以每行一个 JSON 对象的形式发送到 stdout。reason 字段用于区分不同种类的消息。
package_id 字段是引用包的唯一标识符,也作为许多命令中 --package 参数的值。语法规则见包 ID 规范一章。
注意:
--message-format=json仅控制 Cargo 和 Rustc 的输出。 它无法控制其他工具的输出, 例如cargo run --message-format=json, 或过程宏产生的任意输出。 在这些情况下,一种可能的解决方法是仅将开头为{的行解释为 JSON。
--message-format 选项还可以接受其他格式化参数,用于改变 JSON 消息的计算和渲染方式。更多详情,请参阅 build 命令文档 中关于 --message-format 选项的说明。
如果你正在使用 Rust,可以利用 cargo_metadata 这个 crate 来解析这些消息。
MSRV: 若要使
package_id成为 Package ID 规范(Specification),需要 1.77 版本。在此之前,它是一个不透明(opaque)字段。
编译器消息
“compiler-message” 消息包含来自编译器的输出,例如警告和错误。关于 rustc 消息格式的详情,请参阅 rustc JSON 章节,该格式嵌入在以下结构中:
{
/* "reason" 表示消息的类型。 */
"reason": "compiler-message",
/* Package ID,即引用该包时使用的唯一标识符。 */
"package_id": "file:///path/to/my-package#0.1.0",
/* 包清单文件的绝对路径。 */
"manifest_path": "/path/to/my-package/Cargo.toml",
/* 产生此消息的 Cargo target(lib、bin、example 等)。 */
"target": {
/* target 类型的数组。
- lib 类型的 target 列出清单中 `crate-type` 的值,
如 "lib"、"rlib"、"dylib"、"proc-macro" 等(默认 ["lib"])
- 二进制 target 是 ["bin"]
- 示例是 ["example"]
- 集成测试是 ["test"]
- 基准测试是 ["bench"]
- 构建脚本是 ["custom-build"]
*/
"kind": [
"lib"
],
/* crate 类型的数组。
- lib 和 example 库会列出清单中 `crate-type` 的值,
如 "lib"、"rlib"、"dylib"、"proc-macro" 等(默认 ["lib"])
- 其他类型的 target 均为 ["bin"]
*/
"crate_types": [
"lib"
],
/* target 的名称。
对于 lib 类型的 target,连字符会被替换为下划线。
*/
"name": "my_package",
/* target 根源文件的绝对路径。 */
"src_path": "/path/to/my-package/src/lib.rs",
/* target 的 Rust edition。
默认继承包的 edition。
*/
"edition": "2018",
/* 必需特性的数组。
如果未设置必需特性,则不包含此属性。
*/
"required-features": ["feat1"],
/* 该 target 是否应被 `cargo doc` 生成文档。 */
"doc": true,
/* 该 target 是否启用了文档测试,且与文档测试兼容。 */
"doctest": true
/* 该 target 是否应使用 `--test` 构建并运行。
*/
"test": true
},
/* 编译器发出的消息。
详见 https://doc.rust-lang.org/rustc/json.html。
*/
"message": {
/* ... */
}
}
Artifact 消息
每个编译步骤都会发出一条结构如下的 “compiler-artifact” 消息:
{
/* "reason" 指示消息类型。 */
"reason": "compiler-artifact",
/* 包 ID,用于引用该包的唯一标识符。 */
"package_id": "file:///path/to/my-package#0.1.0",
/* 包清单文件的绝对路径。 */
"manifest_path": "/path/to/my-package/Cargo.toml",
/* 生成这些构建产物的 Cargo 目标(lib、bin、example 等)。
详见上文 `compiler-message` 的定义。
*/
"target": {
"kind": [
"lib"
],
"crate_types": [
"lib"
],
"name": "my_package",
"src_path": "/path/to/my-package/src/lib.rs",
"edition": "2018",
"doc": true,
"doctest": true,
"test": true
},
/* "profile" 指示使用的编译器配置。 */
"profile": {
/* 优化级别。 */
"opt_level": "0",
/* 调试级别,取值可为整数 0、1 或 2,或字符串
"line-directives-only" 或 "line-tables-only"。若为 `null`,则表示采用
rustc 的默认值 0。
*/
"debuginfo": 2,
/* 是否启用调试断言。 */
"debug_assertions": true,
/* 是否启用溢出检查。 */
"overflow_checks": true,
/* 是否使用了 `--test` 标志。 */
"test": false
},
/* 已启用特性的数组。 */
"features": ["feat1", "feat2"],
/* 此步骤生成的文件数组。 */
"filenames": [
"/path/to/my-package/target/debug/libmy_package.rlib",
"/path/to/my-package/target/debug/deps/libmy_package-be9f3faac0a26ef0.rmeta"
],
/* 指向已创建可执行文件路径的字符串,若此步骤未生成可执行文件,则为 null。
*/
"executable": null,
/* 此步骤是否实际执行过。
若为 `true`,表示现有构建产物已是最新,且未执行 `rustc`。若为 `false`,
表示已运行 `rustc` 以生成构建产物。
*/
"fresh": true
}
构建脚本输出
“build-script-executed”消息包含构建脚本解析后的输出。请注意,即使构建脚本未运行,也会发出此消息,届时将显示之前缓存的值。关于构建脚本输出的更多详情,请参阅构建脚本章节。
{
/* "reason" 指明消息类型。 */
"reason": "build-script-executed",
/* 包 ID,用于指代该包的唯一标识符。 */
"package_id": "file:///path/to/my-package#0.1.0",
/* 要链接的库数组,由 `cargo::rustc-link-lib` 指令指示。
注意,字符串中可能带有 "KIND=" 前缀,其中 KIND 代表库类型。
*/
"linked_libs": ["foo", "static=bar"],
/* 包含在库搜索路径中的路径数组,由 `cargo::rustc-link-search`
指令指示。注意,字符串中可能带有 "KIND=" 前缀,其中 KIND
代表库类型。
*/
"linked_paths": ["/some/path", "native=/another/path"],
/* 要启用的 cfg 值数组,由 `cargo::rustc-cfg` 指令指示。
*/
"cfgs": ["cfg1", "cfg2=\"string\""],
/* 要设置的环境变量 [KEY, VALUE] 数组,
由 `cargo::rustc-env` 指令指示。
*/
"env": [
["SOME_KEY", "some value"],
["ANOTHER_KEY", "another value"]
],
/* 编译当前包时,用作 `OUT_DIR` 环境变量值的绝对路径。
*/
"out_dir": "/some/path/in/target/dir"
}
构建完成
“build-finished”消息在构建结束时发出。
{
/* "reason" 指明消息类型。 */
"reason": "build-finished",
/* 构建是否成功完成。 */
"success": true,
}
对于工具而言,此消息有助于判断何时停止读取 JSON 消息。诸如 cargo test 或 cargo run 等命令在构建完成后可能会产生额外的输出。此消息提示工具,Cargo 不会再产生额外的 JSON 消息,但后续仍可能生成其他输出(例如 cargo run 执行的程序所产生的输出)。
注意:目前有一个仅在 nightly 版本中提供的实验性功能,支持以 JSON 格式输出测试结果。启用该功能后,在 “build-finished” 消息之后可能会出现额外的测试相关 JSON 消息。
自定义子命令
Cargo 的设计支持在不修改 Cargo 本身的情况下扩展新的子命令。其实现方式是:将形如 cargo (?<command>[^ ]+) 的调用转换为对外部工具 cargo-${command} 的调用。该外部工具必须位于用户 $PATH 中的某个目录里。
注意:Cargo 默认会优先使用
$CARGO_HOME/bin中的外部工具,其次才是$PATH。用户可以把$CARGO_HOME/bin加入$PATH来覆盖这一优先级。
Cargo 调用自定义子命令时,和常规情况一样,第一个参数是子命令自身的文件名,第二个参数则是子命令的名称。例如,调用 cargo-${command} 时,第二个参数就是 ${command}。命令行上的其他参数会原样透传。
Cargo 还可以通过 cargo help ${command} 显示自定义子命令的帮助信息。Cargo 假定:如果子命令的第三个参数是 --help,它就会打印帮助信息。因此 cargo help ${command} 实际上会执行 cargo-${command} ${command} --help。
自定义子命令可以通过 CARGO 环境变量来回调 Cargo。也可以把 cargo crate 作为库来链接,但这种做法有缺点:
- Cargo 作为库并不稳定:API 可能随时变更,且不会经过弃用流程
- 链接的 Cargo 库版本可能与 Cargo 二进制文件的版本不一致
因此更推荐通过 CLI 接口来驱动 Cargo。可以使用 cargo metadata 命令获取当前项目的信息(cargo_metadata crate 为该命令提供了 Rust 接口)。