diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 630c2119ecc..aae5d390420 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -325,7 +325,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { let nightly_features_allowed = cx.bcx.config.nightly_features_allowed; let extra_check_cfg = match cx.bcx.config.cli_unstable().check_cfg { - Some((_, _, _, output)) => output, + Some((_, _, _, output, _)) => output, None => false, }; let targets: Vec = unit.pkg.targets().to_vec(); @@ -989,7 +989,7 @@ fn prev_build_output(cx: &mut Context<'_, '_>, unit: &Unit) -> (Option output, + Some((_, _, _, output, _)) => output, None => false, }, cx.bcx.config.nightly_features_allowed, diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 8e477607171..ae2693512e1 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1131,7 +1131,7 @@ fn features_args(unit: &Unit) -> Vec { /// Generate the --check-cfg arguments for the unit fn check_cfg_args(cx: &Context<'_, '_>, unit: &Unit) -> Vec { - if let Some((features, well_known_names, well_known_values, _output)) = + if let Some((features, well_known_names, well_known_values, _output, _cfgs)) = cx.bcx.config.cli_unstable().check_cfg { let mut args = Vec::with_capacity(unit.pkg.summary().features().len() * 2 + 4); @@ -1153,6 +1153,46 @@ fn check_cfg_args(cx: &Context<'_, '_>, unit: &Unit) -> Vec { args.push(arg); } + if let Some(cfgs) = unit.pkg.cfgs() { + let mut names = OsString::from("names("); + let mut has_names = false; + + for name in cfgs + .iter() + .filter_map(|(name, values)| values.as_ref().is_none().then_some(name)) + { + if has_names { + names.push(","); + } + names.push(name); + has_names = true; + } + + if has_names { + names.push(")"); + args.push(OsString::from("--check-cfg")); + args.push(names); + } + + for (name, values) in cfgs + .iter() + .filter_map(|(name, values)| values.as_ref().map(|values| (name, values))) + { + let mut arg = OsString::from("values("); + + arg.push(name); + for val in values { + arg.push(", \""); + arg.push(&val); + arg.push("\""); + } + arg.push(")"); + + args.push(OsString::from("--check-cfg")); + args.push(arg); + } + } + if well_known_names { args.push(OsString::from("--check-cfg")); args.push(OsString::from("names()")); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index e38cdd3d3cb..4b631840d15 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -671,7 +671,7 @@ unstable_cli_options!( 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"), #[serde(deserialize_with = "deserialize_check_cfg")] - check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), + check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool, /*cfgs:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), 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"), @@ -774,7 +774,7 @@ where fn deserialize_check_cfg<'de, D>( deserializer: D, -) -> Result, D::Error> +) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -789,20 +789,28 @@ where fn parse_check_cfg( it: impl Iterator>, -) -> CargoResult> { +) -> CargoResult> { let mut features = false; let mut well_known_names = false; let mut well_known_values = false; let mut output = false; + let mut cfgs = false; + let mut had_options = false; for e in it { match e.as_ref() { "features" => features = true, "names" => well_known_names = true, "values" => well_known_values = true, "output" => output = true, - _ => bail!("unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs"), + "cfgs" => cfgs = true, + _ => bail!("unstable check-cfg only takes `features`, `names`, `values`, `output` or `cfgs` as valid inputs"), } + had_options = true; + } + + if !had_options { + bail!("unstable check-cfg must take one or more of these options: `features`, `names`, `values`, `output` or `cfgs`"); } Ok(Some(( @@ -810,6 +818,7 @@ fn parse_check_cfg( well_known_names, well_known_values, output, + cfgs, ))) } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index c37dd1cb455..ef376e1fd38 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -64,6 +64,7 @@ pub struct Manifest { default_run: Option, metabuild: Option>, resolve_behavior: Option, + cfgs: Option>>>, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -405,6 +406,7 @@ impl Manifest { original: Rc, metabuild: Option>, resolve_behavior: Option, + cfgs: Option>>>, ) -> Manifest { Manifest { summary, @@ -430,6 +432,7 @@ impl Manifest { default_run, metabuild, resolve_behavior, + cfgs, } } @@ -497,6 +500,9 @@ impl Manifest { pub fn links(&self) -> Option<&str> { self.links.as_deref() } + pub fn cfgs(&self) -> &Option>>> { + &self.cfgs + } pub fn workspace_config(&self) -> &WorkspaceConfig { &self.workspace diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 1c7532cb3e6..fe923a026d6 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -87,6 +87,7 @@ pub struct SerializedPackage { dependencies: Vec, targets: Vec, features: BTreeMap>, + // cfgs: BTreeMap>>, manifest_path: PathBuf, metadata: Option, publish: Option>, @@ -164,6 +165,10 @@ impl Package { pub fn authors(&self) -> &Vec { &self.manifest().metadata().authors } + /// Gets the cfgs. + pub fn cfgs(&self) -> &Option>>> { + self.manifest().cfgs() + } /// Returns `None` if the package is set to publish. /// Returns `Some(allowed_registries)` if publishing is limited to specified diff --git a/src/cargo/util/config/target.rs b/src/cargo/util/config/target.rs index 8190529a686..1a14d81f0fd 100644 --- a/src/cargo/util/config/target.rs +++ b/src/cargo/util/config/target.rs @@ -138,7 +138,7 @@ fn parse_links_overrides( ) -> CargoResult> { let mut links_overrides = BTreeMap::new(); let extra_check_cfg = match config.cli_unstable().check_cfg { - Some((_, _, _, output)) => output, + Some((_, _, _, output, _)) => output, None => false, }; diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index bc8db23cedf..1e782a02a8c 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -360,6 +360,7 @@ pub struct TomlManifest { #[serde(rename = "build_dependencies")] build_dependencies2: Option>, features: Option>>, + cfgs: Option>, target: Option>, replace: Option>, patch: Option>>, @@ -367,6 +368,23 @@ pub struct TomlManifest { badges: Option>>>, } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TomlCfg { + List(Vec), + Values { values: bool }, +} + +impl TomlCfg { + pub(crate) fn into_option_vec(self) -> Option> { + match self { + TomlCfg::List(l) => Some(l), + TomlCfg::Values { values: true } => Some(Vec::new()), + TomlCfg::Values { values: false } => None, + } + } +} + #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct TomlProfiles(BTreeMap); @@ -1558,6 +1576,7 @@ impl TomlManifest { )?, build_dependencies2: None, features: self.features.clone(), + cfgs: self.cfgs.clone(), target: match self.target.as_ref().map(|target_map| { target_map .iter() @@ -1793,6 +1812,15 @@ impl TomlManifest { ))); } + if me.cfgs.is_some() + && matches!( + &config.cli_unstable().check_cfg, + None | Some((_, _, _, _, false)) + ) + { + bail!("`[cfgs]` section requires `-Zcheck-cfg=cfgs`"); + } + let rust_version = if let Some(rust_version) = &package.rust_version { let rust_version = rust_version .clone() @@ -2220,6 +2248,12 @@ impl TomlManifest { } } + let cfgs = me.cfgs.as_ref().map(|cfgs| { + cfgs.iter() + .map(|(name, toml_cfg)| (name.clone(), toml_cfg.clone().into_option_vec())) + .collect() + }); + let default_kind = package .default_target .as_ref() @@ -2249,6 +2283,7 @@ impl TomlManifest { build_dependencies: build_deps, build_dependencies2: None, features: me.features.clone(), + cfgs: me.cfgs.clone(), target, replace: me.replace.clone(), patch: me.patch.clone(), @@ -2281,6 +2316,7 @@ impl TomlManifest { Rc::new(resolved_toml), package.metabuild.clone().map(|sov| sov.0), resolve_behavior, + cfgs, ); if package.license_file.is_some() && package.license.is_some() { manifest.warnings_mut().add_warning( @@ -2344,6 +2380,9 @@ impl TomlManifest { if me.features.is_some() { bail!("this virtual manifest specifies a [features] section, which is not allowed"); } + if me.cfgs.is_some() { + bail!("this virtual manifest specifies a [cfgs] section, which is not allowed"); + } if me.target.is_some() { bail!("this virtual manifest specifies a [target] section, which is not allowed"); } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 75d828cf25e..257ed8513e2 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1197,6 +1197,7 @@ It's values are: - `names`: enables well known names checking via `--check-cfg=names()`. - `values`: enables well known values checking via `--check-cfg=values()`. - `output`: enable the use of `rustc-check-cfg` in build script. + - `cfgs`: enable the use of the `#[cfgs]` in the `Cargo.toml` manifest. For instance: @@ -1204,10 +1205,18 @@ For instance: cargo check -Z unstable-options -Z check-cfg=features cargo check -Z unstable-options -Z check-cfg=names cargo check -Z unstable-options -Z check-cfg=values -cargo check -Z unstable-options -Z check-cfg=features,names,values +cargo check -Z unstable-options -Z check-cfg=cfgs +cargo check -Z unstable-options -Z check-cfg=features,names,values,cfgs ``` -Or for `output`: +Those values can also be set in the `[unstable]` section of `.cargo/config.toml`: + +```toml +[unstable] +check-cfg = ["cfgs", "features"] +``` + +#### Usage of `output` ```rust,no_run // build.rs @@ -1218,6 +1227,23 @@ println!("cargo:rustc-check-cfg=names(foo, bar)"); cargo check -Z unstable-options -Z check-cfg=output ``` +#### Usage of `cfgs` + +In `Cargo.toml`: + +```toml +[cfgs] +"use_libc" = [] # means `--check-cfg=values('use_libc')` +"loom" = ["yes", "no", "auto"] # means `--check-cfg=values('feature', "yes", "no", "auto")` + +[cfgs."unknow_values"] # means `--check-cfg=names('unknow_values')` +values = false # aka values are unknown (so everything is accepted) +``` + +``` +cargo check -Z unstable-options -Z check-cfg=cfgs +``` + ### `cargo:rustc-check-cfg=CHECK_CFG` The `rustc-check-cfg` instruction tells Cargo to pass the given value to the diff --git a/tests/testsuite/check_cfg.rs b/tests/testsuite/check_cfg.rs index 8d2baed9c71..d44571b6f6c 100644 --- a/tests/testsuite/check_cfg.rs +++ b/tests/testsuite/check_cfg.rs @@ -582,7 +582,265 @@ fn config_invalid() { p.cargo("build") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains("error: unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs") + .with_stderr_contains("error: unstable check-cfg only takes `features`, `names`, `values`, `output` or `cfgs` as valid inputs") + .with_status(101) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_simple() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + use_a = [] + net = ["y", "m"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustc" => "values" of "use_a")) + .with_stderr_contains(x!("rustc" => "values" of "net" with "y" "m")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_simple_with_config() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + use_a = [] + net = ["y", "m"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + r#" + [unstable] + check-cfg = ["cfgs"] + "#, + ) + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustc" => "values" of "use_a")) + .with_stderr_contains(x!("rustc" => "values" of "net" with "y" "m")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_simple_with_doc() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + use_a = [] + net = ["y", "m"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("doc -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustdoc" => "values" of "use_a")) + .with_stderr_contains(x!("rustdoc" => "values" of "net" with "y" "m")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_values() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs."net"] + values = false + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustc" => "names" of "net")) + .with_stderr_does_not_contain(x!("rustc" => "values" of "net")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_values_true() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs."net"] + values = true + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustc" => "values" of "net")) + .with_stderr_does_not_contain(x!("rustc" => "names" of "net")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_complex() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + use_a = [] + net = ["y", "m"] + + [cfgs."io"] + values = false + + [cfgs."rs"] + values = true + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains(x!("rustc" => "values" of "use_a")) + .with_stderr_contains(x!("rustc" => "values" of "rs")) + .with_stderr_contains(x!("rustc" => "values" of "net" with "y" "m")) + .with_stderr_contains(x!("rustc" => "names" of "io")) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_empty() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v -Zcheck-cfg=cfgs") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_does_not_contain("--check-cfg") + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_feature_gate() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs] + use_a = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains("error: failed to parse manifest [..]") + .with_stderr_contains("[..] `[cfgs]` section requires `-Zcheck-cfg=cfgs`") + .with_status(101) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_invalid() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs."net"] + values = "ho" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains("error: failed to parse manifest [..]") + .with_status(101) + .run(); +} + +#[cargo_test(nightly, reason = "--check-cfg is unstable")] +fn cfgs_invalid2() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [cfgs."net"] + values = ["y", "m"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["check-cfg"]) + .with_stderr_contains("error: failed to parse manifest [..]") .with_status(101) .run(); }