From 1c2bd8c71e32e1f18b5c97e99e7659e6eeb18365 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 6 Oct 2024 17:05:34 +0200 Subject: [PATCH 1/4] Bump cargo-platform to 0.2.0 in preparation for `cfg()` --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/cargo-platform/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a167643abe..a9c95951a82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ "cargo-credential-libsecret", "cargo-credential-macos-keychain", "cargo-credential-wincred", - "cargo-platform 0.1.9", + "cargo-platform 0.2.0", "cargo-test-support", "cargo-util", "cargo-util-schemas", @@ -444,7 +444,7 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.9" +version = "0.2.0" dependencies = [ "serde", ] @@ -3026,7 +3026,7 @@ name = "resolver-tests" version = "0.0.0" dependencies = [ "cargo", - "cargo-platform 0.1.9", + "cargo-platform 0.2.0", "cargo-util", "cargo-util-schemas", "proptest", diff --git a/Cargo.toml b/Cargo.toml index c2cbff88b5d..ad045fa34ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ cargo-credential = { version = "0.4.2", path = "credential/cargo-credential" } cargo-credential-libsecret = { version = "0.4.7", path = "credential/cargo-credential-libsecret" } cargo-credential-macos-keychain = { version = "0.4.7", path = "credential/cargo-credential-macos-keychain" } cargo-credential-wincred = { version = "0.4.7", path = "credential/cargo-credential-wincred" } -cargo-platform = { path = "crates/cargo-platform", version = "0.1.5" } +cargo-platform = { path = "crates/cargo-platform", version = "0.2.0" } cargo-test-macro = { version = "0.3.0", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.6.0", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.14", path = "crates/cargo-util" } diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index 02dd6da8232..778a7cf6742 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-platform" -version = "0.1.9" +version = "0.2.0" edition.workspace = true license.workspace = true rust-version.workspace = true From 5d32d0d9155bc7ed32c4e2f816841abd50496a5b Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 26 Sep 2024 13:31:41 +0200 Subject: [PATCH 2/4] Add `CfgExpr::walk_expr` utility method --- crates/cargo-platform/src/cfg.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index c3ddb69bc88..cf7826aa0e9 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -89,6 +89,29 @@ impl CfgExpr { CfgExpr::Value(ref e) => cfg.contains(e), } } + + /// Walk over all the `CfgExpr`s of the given `CfgExpr`, recursing into `not(...)`, `all(...)`, + /// `any(...)` and stopping at the first error and returning that error. + pub fn walk_expr(&self, mut f: impl FnMut(&CfgExpr) -> Result<(), E>) -> Result<(), E> { + fn walk_expr_inner( + cfg_expr: &CfgExpr, + f: &mut impl FnMut(&CfgExpr) -> Result<(), E>, + ) -> Result<(), E> { + f(cfg_expr)?; + match *cfg_expr { + CfgExpr::Not(ref e) => walk_expr_inner(e, &mut *f), + CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { + for e in e { + let _ = walk_expr_inner(e, &mut *f)?; + } + Ok(()) + } + CfgExpr::Value(_) => Ok(()), + } + } + + walk_expr_inner(self, &mut f) + } } impl FromStr for CfgExpr { From f8467818ad51ed512cab91b192f2aa7892cd3757 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 4 Oct 2024 14:52:49 +0200 Subject: [PATCH 3/4] Add tests in preparation of boolean literal support in cfgs --- tests/testsuite/cfg.rs | 241 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/tests/testsuite/cfg.rs b/tests/testsuite/cfg.rs index 7e90b51a626..23d9e743c0a 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -521,3 +521,244 @@ error[E0463]: can't find crate for `bar` "#]]) .run(); } + +#[cargo_test] +fn cfg_booleans_gate() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + authors = [] + + [target.'cfg(true)'.dependencies] + b = { path = 'b' } + + [target.'cfg(false)'.dependencies] + c = { path = 'c' } + "#, + ) + .file("src/lib.rs", "") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .file("c/Cargo.toml", &basic_manifest("c", "0.0.1")) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("check") + // FIXME: true/false should error out as they are gated + .with_stderr_data(str![[r#" +[LOCKING] 2 packages to latest compatible versions +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans_gate_config() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + "#, + ) + .file("src/lib.rs", "") + .file( + ".cargo/config.toml", + r#" + [target.'cfg(true)'] + rustflags = [] + "#, + ) + .build(); + + p.cargo("check") + // FIXME: true/false should error out as they are gated + .with_stderr_data(str![[r#" +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + authors = [] + + [target.'cfg(true)'.dependencies] + b = { path = 'b' } + + [target.'cfg(false)'.dependencies] + c = { path = 'c' } + "#, + ) + .file("src/lib.rs", "") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .file("c/Cargo.toml", &basic_manifest("c", "0.0.1")) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("check") + // FIXME: `b` should be compiled + .with_stderr_data(str![[r#" +[LOCKING] 2 packages to latest compatible versions +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans_config() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + "#, + ) + .file("src/lib.rs", "") + .file( + ".cargo/config.toml", + r#" + [target.'cfg(true)'] + rustflags = [] + "#, + ) + .build(); + + p.cargo("check") + .with_stderr_data(str![[r#" +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans_not() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + authors = [] + + [target.'cfg(not(false))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[CHECKING] b v0.0.1 ([ROOT]/foo/b) +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans_combinators() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + authors = [] + + [target.'cfg(all(any(true), not(false), true))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("check") + // FIXME: `b` should be compiled + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_booleans_rustflags_no_effect() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + edition = "2015" + authors = [] + + [target.'cfg(true)'.dependencies] + b = { path = 'b' } + + [target.'cfg(false)'.dependencies] + c = { path = 'c' } + "#, + ) + .file("src/lib.rs", "") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .file("c/Cargo.toml", &basic_manifest("c", "0.0.1")) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("check") + // FIXME: only `b` should be compiled, the rustflags don't take effect + .with_stderr_data(str![[r#" +[LOCKING] 2 packages to latest compatible versions +[CHECKING] b v0.0.1 ([ROOT]/foo/b) +[CHECKING] c v0.0.1 ([ROOT]/foo/c) +[CHECKING] a v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .env("RUSTFLAGS", "--cfg true --cfg false") + .run(); +} From e358842596ccde0837c6bfc1675bcd78909e2e81 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 4 Oct 2024 15:23:06 +0200 Subject: [PATCH 4/4] Add support for boolean literals in target cfgs --- crates/cargo-platform/src/cfg.rs | 14 +++- crates/cargo-platform/src/lib.rs | 1 + src/cargo/core/features.rs | 5 ++ src/cargo/util/context/target.rs | 12 ++++ src/cargo/util/toml/mod.rs | 8 ++- src/doc/src/reference/unstable.md | 25 +++++++ tests/testsuite/cargo/z_help/stdout.term.svg | 68 ++++++++++---------- tests/testsuite/cfg.rs | 41 ++++++++---- 8 files changed, 125 insertions(+), 49 deletions(-) diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index cf7826aa0e9..6f92f236fd8 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -10,6 +10,8 @@ pub enum CfgExpr { All(Vec), Any(Vec), Value(Cfg), + True, + False, } /// A cfg value. @@ -87,6 +89,8 @@ impl CfgExpr { CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)), CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)), CfgExpr::Value(ref e) => cfg.contains(e), + CfgExpr::True => true, + CfgExpr::False => false, } } @@ -106,7 +110,7 @@ impl CfgExpr { } Ok(()) } - CfgExpr::Value(_) => Ok(()), + CfgExpr::Value(_) | CfgExpr::True | CfgExpr::False => Ok(()), } } @@ -137,6 +141,8 @@ impl fmt::Display for CfgExpr { CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)), CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)), CfgExpr::Value(ref e) => write!(f, "{}", e), + CfgExpr::True => write!(f, "true"), + CfgExpr::False => write!(f, "false"), } } } @@ -191,7 +197,11 @@ impl<'a> Parser<'a> { self.eat(&Token::RightParen)?; Ok(CfgExpr::Not(Box::new(e))) } - Some(Ok(..)) => self.cfg().map(CfgExpr::Value), + Some(Ok(..)) => self.cfg().map(|v| match v { + Cfg::Name(n) if n == "true" => CfgExpr::True, + Cfg::Name(n) if n == "false" => CfgExpr::False, + v => CfgExpr::Value(v), + }), Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()), None => Err(ParseError::new( self.t.orig, diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs index 71e9140bae9..ba93f7981ab 100644 --- a/crates/cargo-platform/src/lib.rs +++ b/crates/cargo-platform/src/lib.rs @@ -97,6 +97,7 @@ impl Platform { )) }, } + CfgExpr::True | CfgExpr::False => {}, } } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 8bce51535f8..a33c9ff10e8 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -517,6 +517,9 @@ features! { /// Allow paths that resolve relatively to a base specified in the config. (unstable, path_bases, "", "reference/unstable.html#path-bases"), + + /// Allow boolean literals in `[target.'cfg()']` + (unstable, cfg_boolean_literals, "", "reference/unstable.html#cfg-boolean-literals"), } /// Status and metadata for a single unstable feature. @@ -761,6 +764,7 @@ unstable_cli_options!( build_std: Option> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"), build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), cargo_lints: bool = ("Enable the `[lints.cargo]` table"), + cfg_boolean_literals: bool = ("Allow boolean literals in `[target.'cfg()']`"), checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"), codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"), config_include: bool = ("Enable the `include` key in config files"), @@ -1259,6 +1263,7 @@ impl CliUnstable { } "build-std-features" => self.build_std_features = Some(parse_features(v)), "cargo-lints" => self.cargo_lints = parse_empty(k, v)?, + "cfg-boolean-literals" => self.cfg_boolean_literals = parse_empty(k, v)?, "codegen-backend" => self.codegen_backend = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?, diff --git a/src/cargo/util/context/target.rs b/src/cargo/util/context/target.rs index f306ecc1731..dbd5ad622c8 100644 --- a/src/cargo/util/context/target.rs +++ b/src/cargo/util/context/target.rs @@ -1,6 +1,7 @@ use super::{ConfigKey, ConfigRelativePath, GlobalContext, OptValue, PathAndArgs, StringList, CV}; use crate::core::compiler::{BuildOutput, LinkArgTarget}; use crate::util::CargoResult; +use cargo_platform::{CfgExpr, Platform}; use serde::Deserialize; use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; @@ -53,6 +54,17 @@ pub(super) fn load_target_cfgs( let target: BTreeMap = gctx.get("target")?; tracing::debug!("Got all targets {:#?}", target); for (key, cfg) in target { + // Feature gate `cfg(true)`/`cfg(false)` under `-Zcfg-boolean-literals` + if !gctx.cli_unstable().cfg_boolean_literals { + if let Ok(Platform::Cfg(cfg_expr)) = key.parse() { + cfg_expr.walk_expr(|e| match e { + CfgExpr::True | CfgExpr::False => { + anyhow::bail!("`-Zcfg-boolean-literals` should be used to enable cfg boolean literals in `.cargo/config.toml`") + }, + _ => Ok(()), + })?; + } + } if key.starts_with("cfg(") { // Unfortunately this is not able to display the location of the // unused key. Using config::Value doesn't work. One diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index c0f6df61d90..67469b9b79c 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -8,7 +8,7 @@ use std::str::{self, FromStr}; use crate::core::summary::MissingDependencyError; use crate::AlreadyPrintedError; use anyhow::{anyhow, bail, Context as _}; -use cargo_platform::Platform; +use cargo_platform::{CfgExpr, Platform}; use cargo_util::paths; use cargo_util_schemas::manifest::{ self, PackageName, PathBaseName, TomlDependency, TomlDetailedDependency, TomlManifest, @@ -1314,6 +1314,12 @@ pub fn to_real_manifest( for (name, platform) in original_toml.target.iter().flatten() { let platform_kind: Platform = name.parse()?; platform_kind.check_cfg_attributes(warnings); + if let Platform::Cfg(cfg_expr) = &platform_kind { + cfg_expr.walk_expr(|e| match e { + CfgExpr::True | CfgExpr::False => features.require(Feature::cfg_boolean_literals()), + _ => Ok(()), + })?; + } let platform_kind = Some(platform_kind); validate_dependencies( platform.dependencies.as_ref(), diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 57fc0e03040..3164f3d92e6 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -121,6 +121,7 @@ Each new feature described below should explain how to use it. * [package-workspace](#package-workspace) --- Allows for packaging and publishing multiple crates in a workspace. * [native-completions](#native-completions) --- Move cargo shell completions to native completions. * [warnings](#warnings) --- controls warning behavior; options for allowing or denying warnings. + * [cfg-boolean-literals](#cfg-boolean-literals) --- Allows the use of boolean literals in `[target.'cfg()']` ## allow-features @@ -1764,6 +1765,30 @@ Controls how Cargo handles warnings. Allowed values are: * `warn`: warnings are emitted as warnings (default). * `allow`: warnings are hidden. * `deny`: if warnings are emitted, an error will be raised at the end of the operation and the process will exit with a failure exit code. + +## cfg-boolean-literals + +* Tracking Issue: [#00000](https://github.com/rust-lang/cargo/issues/00000) + +This feature allows the use of boolean literals in `[target.'cfg()']`. + +Those boolean literals always evaluate to true and false respectively, they are **not** subject +to `RUSTFLAGS`. + +For example, in this example the `b` dependencies is always compiled, while `c` is never: + +```toml +cargo-features = ["cfg-boolean-literals"] + +[target.'cfg(true)'.dependencies] +b = { path = 'b' } + +[target.'cfg(false)'.dependencies] +c = { path = 'c' } +``` + +For cfgs in `.cargo/config.toml`, `-Zcfg-boolean-literals` must be used. + # Stabilized and removed features ## Compile progress diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index 2c6b292a5be..d9d2735559f 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,4 +1,4 @@ - +