入门 The Cargo Team 2026-09-13 15:48:21 · 0 阅读

第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 testcargo 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 接口)。

评论 (0)