编译时自动检查 cfgs

2024 年 5 月 6 日 · Urgau 代表 Cargo 团队

Cargo 和编译器团队很高兴地宣布,从 Rust 1.80(或 nightly-2024-05-05)开始,每个可达#[cfg]自动检查它们是否与预期配置名称和值匹配。

这有助于验证 crate 是否正确处理了针对不同目标平台或功能的条件编译。它确保 cfg 设置在预期和使用之间保持一致,有助于在开发过程的早期阶段捕获潜在的错误或错误。

这解决了新用户和高级用户常遇到的一个陷阱。

这是我们致力于提供以用户为中心的工具的又一步,我们迫切地期待着看到它最终得到解决,距离最初的RFC 30131已经两年多了。

功能概述

每次声明 Cargo 功能时,该功能都会转换为传递给rustc(Rust 编译器)的配置,以便它可以与众所周知的 cfgs一起验证,如果任何#[cfg]#![cfg_attr]cfg!具有意外配置,则会使用unexpected_cfgs lint 报告警告。

Cargo.toml:

[package]
name = "foo"

[features]
lasers = []
zapping = []

src/lib.rs:

#[cfg(feature = "lasers")]  // This condition is expected
                            // as "lasers" is an expected value
                            // of the `feature` cfg
fn shoot_lasers() {}

#[cfg(feature = "monkeys")] // This condition is UNEXPECTED
                            // as "monkeys" is NOT an expected
                            // value of the `feature` cfg
fn write_shakespeare() {}

#[cfg(windosw)]             // This condition is UNEXPECTED
                            // it's supposed to be `windows`
fn win() {}

cargo check:

cargo-check

预期自定义 cfgs

更新:本节是在 nightly-2024-05-19 发布时添加的。

从 Cargo 的角度来看:自定义 cfg 是既不是由rustc定义也不是由 Cargo 功能定义的。例如tokio_unstablehas_foo,... 但不包括feature = "lasers"unixdebug_assertions

一些 crate 可能会使用自定义 cfgs,例如loomfuzzingtokio_unstable,它们期望来自环境(RUSTFLAGS或其他方式),并且在编译时始终是静态已知的。对于这些情况,Cargo 通过[lints]表提供了一种方法来静态声明这些 cfgs 作为预期。

通过[lints.rust.unexpected_cfgs]下的特殊check-cfg配置来定义这些自定义 cfgs 作为预期。

Cargo.toml

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)', 'cfg(fuzzing)'] }

构建脚本中的自定义 cfgs

另一方面,一些 crate 使用由 crate build.rs 中的某些逻辑启用的自定义 cfgs。对于这些 crate,Cargo 提供了一个新的指令:cargo::rustc-check-cfg2(或对于旧版本的 Cargo,使用cargo:rustc-check-cfg)。

使用语法在rustc 手册检查配置部分进行了描述,但简而言之,--check-cfg的基本语法是

cfg(name, values("value1", "value2", ..., "valueN"))

请注意,每个自定义 cfgs 必须始终是预期的,无论 cfg 是否处于活动状态!

build.rs 示例

build.rs:

fn main() {
    println!("cargo::rustc-check-cfg=cfg(has_foo)");
    //        ^^^^^^^^^^^^^^^^^^^^^^ new with Cargo 1.80
    if has_foo() {
        println!("cargo::rustc-cfg=has_foo");
    }
}

每个cargo::rustc-cfg都应该有一个伴随的无条件cargo::rustc-check-cfg指令,以避免出现类似以下的警告:unexpected cfg condition name: has_foo

等效表

cargo::rustc-cfg cargo::rustc-check-cfg
foo cfg(foo)cfg(foo, values(none()))
foo="" cfg(foo, values(""))
foo="bar" cfg(foo, values("bar"))
foo="1"foo="2" cfg(foo, values("1", "2"))
foo="1"bar="2" cfg(foo, values("1"))cfg(bar, values("2"))
foofoo="bar" cfg(foo, values(none(), "bar"))

更多详细信息可以在rustc 手册中找到。

常见问题解答

可以禁用吗?

对于 Cargo 用户来说,该功能始终处于启用状态无法禁用,但与其他 lint 一样,可以对其进行控制:#![warn(unexpected_cfgs)]

lint 会影响依赖项吗?

不会,与大多数 lint 一样,unexpected_cfgs只会针对本地包报告,这得益于cap-lints

它如何与RUSTFLAGS环境交互?

您应该能够像以前一样使用RUSTFLAGS环境变量。目前,--cfg参数没有被检查,只有代码中的使用才会被检查。

这意味着执行RUSTFLAGS="--cfg tokio_unstable" cargo check不会报告任何警告,除非在您的本地 crate 中使用了tokio_unstable,在这种情况下,crate 作者需要确保该自定义 cfg 是预期的,并在该 crate 的build.rs中使用cargo::rustc-check-cfg

如何在没有build.rs的情况下预期自定义 cfgs?

更新:带有 nightly-2024-05-19 的 Cargo 现在提供了[lints.rust.unexpected_cfgs.check-cfg]配置来解决静态已知的自定义 cfgs。

目前没有其他方法可以预期自定义 cfg,除了在build.rs中使用cargo::rustc-check-cfg

不希望使用build.rs并且无法使用[lints.rust.unexpected_cfgs.check-cfg]的 crate 作者,建议使用 Cargo 功能。

它如何与其他构建系统交互?

默认情况下,非 Cargo 构建系统不受 lint 的影响。希望拥有相同功能的构建系统作者应该查看rustc文档中的--check-cfg标志,以详细了解如何实现相同的功能。

  1. 稳定版本实现和 RFC 3013 有很大差异,特别是--check-cfg只有一种形式:cfg()(而不是values()names()不完整,并且在细微之处与彼此不兼容)。

  2. cargo::rustc-check-cfg将在 Rust 1.80(或 nightly-2024-05-05)中开始工作。从 Rust 1.77 到 Rust 1.79(含),它会被静默忽略。在 Rust 1.76 及更早版本中,如果在没有不稳定的 Cargo 标志-Zcheck-cfg的情况下使用,则会发出警告。