diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 242868e94f1..32de842ce29 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -782,6 +782,28 @@ fn add_allow_features(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder) { } } +/// Add all features as cfg +fn add_features(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) { + for feat in &unit.features { + cmd.arg("--cfg").arg(&format!("feature=\"{}\"", feat)); + } + + if cx.bcx.config.cli_unstable().check_cfg_features { + // This generate something like this: + // - values(feature) + // - values(feature, "foo", "bar") + let mut arg = String::from("values(feature"); + for (&feat, _) in unit.pkg.summary().features() { + arg.push_str(", \""); + arg.push_str(&feat); + arg.push_str("\""); + } + arg.push(')'); + + cmd.arg("-Zunstable-options").arg("--check-cfg").arg(&arg); + } +} + /// Add error-format flags to the command. /// /// Cargo always uses JSON output. This has several benefits, such as being @@ -978,9 +1000,7 @@ fn build_base_args( cmd.arg("--cfg").arg("test"); } - for feat in &unit.features { - cmd.arg("--cfg").arg(&format!("feature=\"{}\"", feat)); - } + add_features(cx, cmd, unit); let meta = cx.files().metadata(unit); cmd.arg("-C").arg(&format!("metadata={}", meta)); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 41557ef1a93..92467de941e 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -637,6 +637,7 @@ unstable_cli_options!( build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), config_include: bool = ("Enable the `include` key in config files"), credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), + check_cfg_features: bool = ("Enable compile-time checking of features in `cfg`"), doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"), doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"), dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), @@ -834,6 +835,7 @@ impl CliUnstable { "minimal-versions" => self.minimal_versions = parse_empty(k, v)?, "advanced-env" => self.advanced_env = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, + "check-cfg-features" => self.check_cfg_features = parse_empty(k, v)?, "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, // can also be set in .cargo/config or with and ENV "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 7ca9d7b9f6b..92b2fd213d8 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1078,6 +1078,20 @@ For instance: cargo doc -Z unstable-options -Z rustdoc-scrape-examples=examples ``` +### check-cfg-features + +* RFC: [#3013](https://github.com/rust-lang/rfcs/pull/3013) + +The `-Z check-cfg-features` argument tells Cargo to pass all possible features of a package to +`rustc` unstable `--check-cfg` command line as `--check-cfg=values(feature, ...)`. This enables +compile time checking of feature values in `#[cfg]`, `cfg!` and `#[cfg_attr]`. Note than this +command line options will probably become the default when stabilizing. +For instance: + +``` +cargo check -Z unstable-options -Z check-cfg-features +``` + ## Stabilized and removed features ### Compile progress diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index f3b8b16a69b..f404a61ab91 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -5948,3 +5948,167 @@ fn primary_package_env_var() { foo.cargo("test").run(); } + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [features] + f_a = [] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] foo v0.1.0 [..] +[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features_with_deps() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { path = "bar/" } + + [features] + f_a = [] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") + .build(); + + p.cargo("build -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] bar v0.1.0 [..] +[RUNNING] `rustc [..] --check-cfg 'values(feature)' [..] +[COMPILING] foo v0.1.0 [..] +[RUNNING] `rustc --crate-name foo [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features_with_opt_deps() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { path = "bar/", optional = true } + + [features] + default = ["bar"] + f_a = [] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") + .build(); + + p.cargo("build -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] bar v0.1.0 [..] +[RUNNING] `rustc [..] --check-cfg 'values(feature)' [..] +[COMPILING] foo v0.1.0 [..] +[RUNNING] `rustc --crate-name foo [..] --check-cfg 'values(feature, \"bar\", \"default\", \"f_a\", \"f_b\")' [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features_with_namespaced_features() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = { path = "bar/", optional = true } + + [features] + f_a = ["dep:bar"] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") + .build(); + + p.cargo("build -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] foo v0.1.0 [..] +[RUNNING] `rustc --crate-name foo [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index e37d572f568..7ddd65adfbe 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -3,6 +3,7 @@ use std::fmt::{self, Write}; use cargo_test_support::install::exe; +use cargo_test_support::is_nightly; use cargo_test_support::paths::CargoPathExt; use cargo_test_support::registry::Package; use cargo_test_support::tools; @@ -997,3 +998,39 @@ fn rustc_workspace_wrapper_excludes_published_deps() { .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name baz [..]") .run(); } + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [features] + f_a = [] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[CHECKING] foo v0.1.0 [..] +[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 1f79df90293..b8cebb9cd13 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -4499,3 +4499,40 @@ fn test_workspaces_cwd() { .with_stdout_contains("test test_integration_deep_cwd ... ok") .run(); } + +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support +#[cargo_test] +fn check_cfg_features() { + if !is_nightly() { + // --check-cfg is a nightly only rustc command line + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.1.0" + + [features] + f_a = [] + f_b = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("test -v -Z check-cfg-features") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] foo v0.1.0 [..] +[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..] +[FINISHED] test [unoptimized + debuginfo] target(s) in [..] +[RUNNING] [..] +", + ) + .run(); +}