From 03fbb2eda8995d348ac3eb4564ce369844764f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 22 Feb 2022 13:37:56 +0100 Subject: [PATCH 1/3] Add -Z check-cfg-features to enable compile-time checking of features --- src/cargo/core/compiler/mod.rs | 26 ++++++- src/cargo/core/features.rs | 2 + src/doc/src/reference/unstable.md | 14 ++++ tests/testsuite/build.rs | 119 ++++++++++++++++++++++++++++++ tests/testsuite/check.rs | 36 +++++++++ tests/testsuite/test.rs | 36 +++++++++ 6 files changed, 230 insertions(+), 3 deletions(-) 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..2dc83dc2c14 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -5948,3 +5948,122 @@ fn primary_package_env_var() { foo.cargo("test").run(); } + +#[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(); +} + +#[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(); +} +#[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(); +} diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index e37d572f568..47aa0519528 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,38 @@ fn rustc_workspace_wrapper_excludes_published_deps() { .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name baz [..]") .run(); } + +#[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..5b979d80fdd 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -4499,3 +4499,39 @@ fn test_workspaces_cwd() { .with_stdout_contains("test test_integration_deep_cwd ... ok") .run(); } + +#[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(); +} From a864c815498a28c408fa8ddfb98d5d7bfb388b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 22 Feb 2022 18:33:03 +0100 Subject: [PATCH 2/3] Disable -Z check-cfg-features tests on windows due to weird normalization issues See https://github.com/rust-lang/cargo/runs/5290789288?check_suite_focus=true Where the expected is: --check-cfg 'values(feature, "f_a", "f_b")' But we got: --check-cfg "values(feature, /"f_a/", /"f_b/")" --- tests/testsuite/build.rs | 4 ++++ tests/testsuite/check.rs | 1 + tests/testsuite/test.rs | 1 + 3 files changed, 6 insertions(+) diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 2dc83dc2c14..92906f950e5 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -5949,6 +5949,7 @@ 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() { @@ -5984,6 +5985,7 @@ fn check_cfg_features() { .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() { @@ -6025,6 +6027,8 @@ fn check_cfg_features_with_deps() { ) .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() { diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index 47aa0519528..7ddd65adfbe 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -999,6 +999,7 @@ fn rustc_workspace_wrapper_excludes_published_deps() { .run(); } +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support #[cargo_test] fn check_cfg_features() { if !is_nightly() { diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 5b979d80fdd..b8cebb9cd13 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -4500,6 +4500,7 @@ fn test_workspaces_cwd() { .run(); } +#[cfg_attr(windows, ignore)] // weird normalization issue with windows and cargo-test-support #[cargo_test] fn check_cfg_features() { if !is_nightly() { From 4ac4f3d9ab4889e9d8031c9ed1a9584cc9e69064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 22 Feb 2022 18:43:12 +0100 Subject: [PATCH 3/3] Add test for -Z check-cfg-features with namespaced features --- tests/testsuite/build.rs | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 92906f950e5..f404a61ab91 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -6071,3 +6071,44 @@ fn check_cfg_features_with_opt_deps() { ) .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(); +}