Skip to content

Commit 03fbb2e

Browse files
committed
Add -Z check-cfg-features to enable compile-time checking of features
1 parent 46c9b51 commit 03fbb2e

File tree

6 files changed

+230
-3
lines changed

6 files changed

+230
-3
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,28 @@ fn add_allow_features(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder) {
782782
}
783783
}
784784

785+
/// Add all features as cfg
786+
fn add_features(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) {
787+
for feat in &unit.features {
788+
cmd.arg("--cfg").arg(&format!("feature=\"{}\"", feat));
789+
}
790+
791+
if cx.bcx.config.cli_unstable().check_cfg_features {
792+
// This generate something like this:
793+
// - values(feature)
794+
// - values(feature, "foo", "bar")
795+
let mut arg = String::from("values(feature");
796+
for (&feat, _) in unit.pkg.summary().features() {
797+
arg.push_str(", \"");
798+
arg.push_str(&feat);
799+
arg.push_str("\"");
800+
}
801+
arg.push(')');
802+
803+
cmd.arg("-Zunstable-options").arg("--check-cfg").arg(&arg);
804+
}
805+
}
806+
785807
/// Add error-format flags to the command.
786808
///
787809
/// Cargo always uses JSON output. This has several benefits, such as being
@@ -978,9 +1000,7 @@ fn build_base_args(
9781000
cmd.arg("--cfg").arg("test");
9791001
}
9801002

981-
for feat in &unit.features {
982-
cmd.arg("--cfg").arg(&format!("feature=\"{}\"", feat));
983-
}
1003+
add_features(cx, cmd, unit);
9841004

9851005
let meta = cx.files().metadata(unit);
9861006
cmd.arg("-C").arg(&format!("metadata={}", meta));

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,7 @@ unstable_cli_options!(
637637
build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"),
638638
config_include: bool = ("Enable the `include` key in config files"),
639639
credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"),
640+
check_cfg_features: bool = ("Enable compile-time checking of features in `cfg`"),
640641
doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"),
641642
doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
642643
dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
@@ -834,6 +835,7 @@ impl CliUnstable {
834835
"minimal-versions" => self.minimal_versions = parse_empty(k, v)?,
835836
"advanced-env" => self.advanced_env = parse_empty(k, v)?,
836837
"config-include" => self.config_include = parse_empty(k, v)?,
838+
"check-cfg-features" => self.check_cfg_features = parse_empty(k, v)?,
837839
"dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
838840
// can also be set in .cargo/config or with and ENV
839841
"mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,

src/doc/src/reference/unstable.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,20 @@ For instance:
10781078
cargo doc -Z unstable-options -Z rustdoc-scrape-examples=examples
10791079
```
10801080

1081+
### check-cfg-features
1082+
1083+
* RFC: [#3013](https://github.com/rust-lang/rfcs/pull/3013)
1084+
1085+
The `-Z check-cfg-features` argument tells Cargo to pass all possible features of a package to
1086+
`rustc` unstable `--check-cfg` command line as `--check-cfg=values(feature, ...)`. This enables
1087+
compile time checking of feature values in `#[cfg]`, `cfg!` and `#[cfg_attr]`. Note than this
1088+
command line options will probably become the default when stabilizing.
1089+
For instance:
1090+
1091+
```
1092+
cargo check -Z unstable-options -Z check-cfg-features
1093+
```
1094+
10811095
## Stabilized and removed features
10821096

10831097
### Compile progress

tests/testsuite/build.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5948,3 +5948,122 @@ fn primary_package_env_var() {
59485948

59495949
foo.cargo("test").run();
59505950
}
5951+
5952+
#[cargo_test]
5953+
fn check_cfg_features() {
5954+
if !is_nightly() {
5955+
// --check-cfg is a nightly only rustc command line
5956+
return;
5957+
}
5958+
5959+
let p = project()
5960+
.file(
5961+
"Cargo.toml",
5962+
r#"
5963+
[project]
5964+
name = "foo"
5965+
version = "0.1.0"
5966+
5967+
[features]
5968+
f_a = []
5969+
f_b = []
5970+
"#,
5971+
)
5972+
.file("src/main.rs", "fn main() {}")
5973+
.build();
5974+
5975+
p.cargo("build -v -Z check-cfg-features")
5976+
.masquerade_as_nightly_cargo()
5977+
.with_stderr(
5978+
"\
5979+
[COMPILING] foo v0.1.0 [..]
5980+
[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..]
5981+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
5982+
",
5983+
)
5984+
.run();
5985+
}
5986+
5987+
#[cargo_test]
5988+
fn check_cfg_features_with_deps() {
5989+
if !is_nightly() {
5990+
// --check-cfg is a nightly only rustc command line
5991+
return;
5992+
}
5993+
5994+
let p = project()
5995+
.file(
5996+
"Cargo.toml",
5997+
r#"
5998+
[project]
5999+
name = "foo"
6000+
version = "0.1.0"
6001+
6002+
[dependencies]
6003+
bar = { path = "bar/" }
6004+
6005+
[features]
6006+
f_a = []
6007+
f_b = []
6008+
"#,
6009+
)
6010+
.file("src/main.rs", "fn main() {}")
6011+
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
6012+
.file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}")
6013+
.build();
6014+
6015+
p.cargo("build -v -Z check-cfg-features")
6016+
.masquerade_as_nightly_cargo()
6017+
.with_stderr(
6018+
"\
6019+
[COMPILING] bar v0.1.0 [..]
6020+
[RUNNING] `rustc [..] --check-cfg 'values(feature)' [..]
6021+
[COMPILING] foo v0.1.0 [..]
6022+
[RUNNING] `rustc --crate-name foo [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..]
6023+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
6024+
",
6025+
)
6026+
.run();
6027+
}
6028+
#[cargo_test]
6029+
fn check_cfg_features_with_opt_deps() {
6030+
if !is_nightly() {
6031+
// --check-cfg is a nightly only rustc command line
6032+
return;
6033+
}
6034+
6035+
let p = project()
6036+
.file(
6037+
"Cargo.toml",
6038+
r#"
6039+
[project]
6040+
name = "foo"
6041+
version = "0.1.0"
6042+
6043+
[dependencies]
6044+
bar = { path = "bar/", optional = true }
6045+
6046+
[features]
6047+
default = ["bar"]
6048+
f_a = []
6049+
f_b = []
6050+
"#,
6051+
)
6052+
.file("src/main.rs", "fn main() {}")
6053+
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
6054+
.file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}")
6055+
.build();
6056+
6057+
p.cargo("build -v -Z check-cfg-features")
6058+
.masquerade_as_nightly_cargo()
6059+
.with_stderr(
6060+
"\
6061+
[COMPILING] bar v0.1.0 [..]
6062+
[RUNNING] `rustc [..] --check-cfg 'values(feature)' [..]
6063+
[COMPILING] foo v0.1.0 [..]
6064+
[RUNNING] `rustc --crate-name foo [..] --check-cfg 'values(feature, \"bar\", \"default\", \"f_a\", \"f_b\")' [..]
6065+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
6066+
",
6067+
)
6068+
.run();
6069+
}

tests/testsuite/check.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::fmt::{self, Write};
44

55
use cargo_test_support::install::exe;
6+
use cargo_test_support::is_nightly;
67
use cargo_test_support::paths::CargoPathExt;
78
use cargo_test_support::registry::Package;
89
use cargo_test_support::tools;
@@ -997,3 +998,38 @@ fn rustc_workspace_wrapper_excludes_published_deps() {
997998
.with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name baz [..]")
998999
.run();
9991000
}
1001+
1002+
#[cargo_test]
1003+
fn check_cfg_features() {
1004+
if !is_nightly() {
1005+
// --check-cfg is a nightly only rustc command line
1006+
return;
1007+
}
1008+
1009+
let p = project()
1010+
.file(
1011+
"Cargo.toml",
1012+
r#"
1013+
[project]
1014+
name = "foo"
1015+
version = "0.1.0"
1016+
1017+
[features]
1018+
f_a = []
1019+
f_b = []
1020+
"#,
1021+
)
1022+
.file("src/main.rs", "fn main() {}")
1023+
.build();
1024+
1025+
p.cargo("check -v -Z check-cfg-features")
1026+
.masquerade_as_nightly_cargo()
1027+
.with_stderr(
1028+
"\
1029+
[CHECKING] foo v0.1.0 [..]
1030+
[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..]
1031+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1032+
",
1033+
)
1034+
.run();
1035+
}

tests/testsuite/test.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4499,3 +4499,39 @@ fn test_workspaces_cwd() {
44994499
.with_stdout_contains("test test_integration_deep_cwd ... ok")
45004500
.run();
45014501
}
4502+
4503+
#[cargo_test]
4504+
fn check_cfg_features() {
4505+
if !is_nightly() {
4506+
// --check-cfg is a nightly only rustc command line
4507+
return;
4508+
}
4509+
4510+
let p = project()
4511+
.file(
4512+
"Cargo.toml",
4513+
r#"
4514+
[project]
4515+
name = "foo"
4516+
version = "0.1.0"
4517+
4518+
[features]
4519+
f_a = []
4520+
f_b = []
4521+
"#,
4522+
)
4523+
.file("src/main.rs", "fn main() {}")
4524+
.build();
4525+
4526+
p.cargo("test -v -Z check-cfg-features")
4527+
.masquerade_as_nightly_cargo()
4528+
.with_stderr(
4529+
"\
4530+
[COMPILING] foo v0.1.0 [..]
4531+
[RUNNING] `rustc [..] --check-cfg 'values(feature, \"f_a\", \"f_b\")' [..]
4532+
[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
4533+
[RUNNING] [..]
4534+
",
4535+
)
4536+
.run();
4537+
}

0 commit comments

Comments
 (0)