From 9f416d7e3bf925ed90660f934356371cffa9962e Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 20 May 2025 15:07:46 -0500 Subject: [PATCH 1/5] chore(platform): Bump to 0.4.0 Co-authored-by: Ed Page --- 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 1a67d0f6bdf..54d5f39806c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ "cargo-credential-libsecret", "cargo-credential-macos-keychain", "cargo-credential-wincred", - "cargo-platform 0.3.0", + "cargo-platform 0.4.0", "cargo-test-support", "cargo-util", "cargo-util-schemas", @@ -441,7 +441,7 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.3.0" +version = "0.4.0" dependencies = [ "serde", ] @@ -3450,7 +3450,7 @@ name = "resolver-tests" version = "0.0.0" dependencies = [ "cargo", - "cargo-platform 0.3.0", + "cargo-platform 0.4.0", "cargo-util", "cargo-util-schemas", "proptest", diff --git a/Cargo.toml b/Cargo.toml index 4c178d5ab79..898c75f9d54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ cargo-credential = { version = "0.4.2", path = "credential/cargo-credential" } cargo-credential-libsecret = { version = "0.4.15", path = "credential/cargo-credential-libsecret" } cargo-credential-macos-keychain = { version = "0.4.15", path = "credential/cargo-credential-macos-keychain" } cargo-credential-wincred = { version = "0.4.15", path = "credential/cargo-credential-wincred" } -cargo-platform = { path = "crates/cargo-platform", version = "0.3.0" } +cargo-platform = { path = "crates/cargo-platform", version = "0.4.0" } cargo-test-macro = { version = "0.4.4", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.7.5", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.22", path = "crates/cargo-util" } diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index baa75741232..078d89eb4df 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-platform" -version = "0.3.0" +version = "0.4.0" edition.workspace = true license.workspace = true rust-version.workspace = true From b1d153e0841f95890fb8e1f599b966cff37ccd42 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 20 May 2025 15:18:43 -0500 Subject: [PATCH 2/5] test(platform): Switch to snapbox --- Cargo.lock | 1 + crates/cargo-platform/Cargo.toml | 3 + crates/cargo-platform/tests/test_cfg.rs | 98 +++++++++++++++---------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54d5f39806c..b7d0ad07770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,7 @@ name = "cargo-platform" version = "0.4.0" dependencies = [ "serde", + "snapbox", ] [[package]] diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index 078d89eb4df..23bbd533873 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -12,5 +12,8 @@ description = "Cargo's representation of a target platform." [dependencies] serde.workspace = true +[dev-dependencies] +snapbox.workspace = true + [lints] workspace = true diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs index 59cb27d54f4..6399488de9b 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -1,7 +1,11 @@ -use cargo_platform::{Cfg, CfgExpr, Ident, Platform}; use std::fmt; use std::str::FromStr; +use cargo_platform::{Cfg, CfgExpr, Ident, Platform}; +use snapbox::assert_data_eq; +use snapbox::prelude::*; +use snapbox::str; + macro_rules! c { ($a:ident) => { Cfg::Name(Ident { @@ -57,23 +61,17 @@ where assert_eq!(c, expected); } -fn bad(s: &str, err: &str) +#[track_caller] +fn bad(input: &str, expected: impl IntoData) where T: FromStr + fmt::Display, T::Err: fmt::Display, { - let e = match T::from_str(s) { - Ok(cfg) => panic!("expected `{}` to not parse but got {}", s, cfg), + let actual = match T::from_str(input) { + Ok(cfg) => panic!("expected `{input}` to not parse but got {cfg}"), Err(e) => e.to_string(), }; - assert!( - e.contains(err), - "when parsing `{}`,\n\"{}\" not contained \ - inside: {}", - s, - err, - e - ); + assert_data_eq!(actual, expected.raw()); } #[test] @@ -93,24 +91,38 @@ fn cfg_syntax() { #[test] fn cfg_syntax_bad() { - bad::("", "but cfg expression ended"); - bad::(" ", "but cfg expression ended"); - bad::("\t", "unexpected character"); - bad::("7", "unexpected character"); - bad::("=", "expected identifier"); - bad::(",", "expected identifier"); - bad::("(", "expected identifier"); - bad::("foo (", "unexpected content `(` found after cfg expression"); - bad::("bar =", "expected a string"); - bad::("bar = \"", "unterminated string"); bad::( - "foo, bar", - "unexpected content `, bar` found after cfg expression", + "", + str![ + "failed to parse `` as a cfg expression: expected identifier, but cfg expression ended" + ], + ); + bad::(" ", str!["failed to parse ` ` as a cfg expression: expected identifier, but cfg expression ended"]); + bad::("\t", str!["failed to parse ` ` as a cfg expression: unexpected character ` ` in cfg, expected parens, a comma, an identifier, or a string"]); + bad::("7", str!["failed to parse `7` as a cfg expression: unexpected character `7` in cfg, expected parens, a comma, an identifier, or a string"]); + bad::( + "=", + str!["failed to parse `=` as a cfg expression: expected identifier, found `=`"], + ); + bad::( + ",", + str!["failed to parse `,` as a cfg expression: expected identifier, found `,`"], + ); + bad::( + "(", + str!["failed to parse `(` as a cfg expression: expected identifier, found `(`"], ); - bad::("r# foo", "unexpected character"); - bad::("r #foo", "unexpected content"); - bad::("r#\"foo\"", "unexpected character"); - bad::("foo = r#\"\"", "unexpected character"); + bad::("foo (", str!["failed to parse `foo (` as a cfg expression: unexpected content `(` found after cfg expression"]); + bad::("bar =", str!["failed to parse `bar =` as a cfg expression: expected a string, but cfg expression ended"]); + bad::( + "bar = \"", + str![[r#"failed to parse `bar = "` as a cfg expression: unterminated string in cfg"#]], + ); + bad::("foo, bar", str!["failed to parse `foo, bar` as a cfg expression: unexpected content `, bar` found after cfg expression"]); + bad::("r# foo", str!["failed to parse `r# foo` as a cfg expression: unexpected character ` ` in cfg, expected parens, a comma, an identifier, or a string"]); + bad::("r #foo", str!["failed to parse `r #foo` as a cfg expression: unexpected content `#foo` found after cfg expression"]); + bad::("r#\"foo\"", str![[r#"failed to parse `r#"foo"` as a cfg expression: unexpected character `"` in cfg, expected parens, a comma, an identifier, or a string"#]]); + bad::("foo = r#\"\"", str![[r#"failed to parse `foo = r#""` as a cfg expression: unexpected character `"` in cfg, expected parens, a comma, an identifier, or a string"#]]); } #[test] @@ -137,17 +149,29 @@ fn cfg_expr() { #[test] fn cfg_expr_bad() { - bad::(" ", "but cfg expression ended"); - bad::(" all", "expected `(`"); - bad::("all(a", "expected `)`"); - bad::("not", "expected `(`"); - bad::("not(a", "expected `)`"); - bad::("a = ", "expected a string"); - bad::("all(not())", "expected identifier"); + bad::(" ", str!["failed to parse ` ` as a cfg expression: expected start of a cfg expression, but cfg expression ended"]); + bad::( + " all", + str!["failed to parse ` all` as a cfg expression: expected `(`, but cfg expression ended"], + ); + bad::( + "all(a", + str!["failed to parse `all(a` as a cfg expression: expected `)`, but cfg expression ended"], + ); + bad::( + "not", + str!["failed to parse `not` as a cfg expression: expected `(`, but cfg expression ended"], + ); + bad::( + "not(a", + str!["failed to parse `not(a` as a cfg expression: expected `)`, but cfg expression ended"], + ); + bad::("a = ", str!["failed to parse `a = ` as a cfg expression: expected a string, but cfg expression ended"]); bad::( - "foo(a)", - "unexpected content `(a)` found after cfg expression", + "all(not())", + str!["failed to parse `all(not())` as a cfg expression: expected identifier, found `)`"], ); + bad::("foo(a)", str!["failed to parse `foo(a)` as a cfg expression: unexpected content `(a)` found after cfg expression"]); } #[test] From bd6b647fa5a8f1575d75272b684e0488a1ff2ee3 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 20 May 2025 15:23:45 -0500 Subject: [PATCH 3/5] test: Show current behavior for cfg_version Co-authored-by: Ed Page --- crates/cargo-platform/tests/test_cfg.rs | 93 ++++++++++++-- tests/testsuite/cfg.rs | 159 ++++++++++++++++++++++++ 2 files changed, 243 insertions(+), 9 deletions(-) diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs index 6399488de9b..9c05ba99a61 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -49,6 +49,7 @@ macro_rules! e { ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); } +#[track_caller] fn good(s: &str, expected: T) where T: FromStr + PartialEq + fmt::Debug, @@ -87,6 +88,43 @@ fn cfg_syntax() { good(" foo=\"3\" ", c!(foo = "3")); good("foo = \"3 e\"", c!(foo = "3 e")); good(" r#foo = \"3 e\"", c!(r # foo = "3 e")); + bad::( + "version(\"1.23.4\")", + str![[ + r#"failed to parse `version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.23\")", + str![[ + r#"failed to parse `version("1.23")` as a cfg expression: unexpected content `("1.23")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.234.56\")", + str![[ + r#"failed to parse `version("1.234.56")` as a cfg expression: unexpected content `("1.234.56")` found after cfg expression"# + ]], + ); + bad::( + " version(\"1.23.4\")", + str![[ + r#"failed to parse ` version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.23.4\") ", + str![[ + r#"failed to parse `version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"# + ]], + ); + bad::( + " version(\"1.23.4\") ", + str![[ + r#"failed to parse ` version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"# + ]], + ); + good("version = \"1.23.4\"", c!(version = "1.23.4")); } #[test] @@ -112,17 +150,48 @@ fn cfg_syntax_bad() { "(", str!["failed to parse `(` as a cfg expression: expected identifier, found `(`"], ); - bad::("foo (", str!["failed to parse `foo (` as a cfg expression: unexpected content `(` found after cfg expression"]); - bad::("bar =", str!["failed to parse `bar =` as a cfg expression: expected a string, but cfg expression ended"]); bad::( - "bar = \"", - str![[r#"failed to parse `bar = "` as a cfg expression: unterminated string in cfg"#]], + "version(\"1\")", + str![[ + r#"failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.\")", + str![[ + r#"failed to parse `version("1.")` as a cfg expression: unexpected content `("1.")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.2.\")", + str![[ + r#"failed to parse `version("1.2.")` as a cfg expression: unexpected content `("1.2.")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.2.3.\")", + str![[ + r#"failed to parse `version("1.2.3.")` as a cfg expression: unexpected content `("1.2.3.")` found after cfg expression"# + ]], + ); + bad::( + "version(\"1.2.3-stable\")", + str![[ + r#"failed to parse `version("1.2.3-stable")` as a cfg expression: unexpected content `("1.2.3-stable")` found after cfg expression"# + ]], + ); + bad::( + "version(\"2.3\")", + str![[ + r#"failed to parse `version("2.3")` as a cfg expression: unexpected content `("2.3")` found after cfg expression"# + ]], + ); + bad::( + "version(\"0.99.9\")", + str![[ + r#"failed to parse `version("0.99.9")` as a cfg expression: unexpected content `("0.99.9")` found after cfg expression"# + ]], ); - bad::("foo, bar", str!["failed to parse `foo, bar` as a cfg expression: unexpected content `, bar` found after cfg expression"]); - bad::("r# foo", str!["failed to parse `r# foo` as a cfg expression: unexpected character ` ` in cfg, expected parens, a comma, an identifier, or a string"]); - bad::("r #foo", str!["failed to parse `r #foo` as a cfg expression: unexpected content `#foo` found after cfg expression"]); - bad::("r#\"foo\"", str![[r#"failed to parse `r#"foo"` as a cfg expression: unexpected character `"` in cfg, expected parens, a comma, an identifier, or a string"#]]); - bad::("foo = r#\"\"", str![[r#"failed to parse `foo = r#""` as a cfg expression: unexpected character `"` in cfg, expected parens, a comma, an identifier, or a string"#]]); } #[test] @@ -145,6 +214,12 @@ fn cfg_expr() { good("all(a, )", e!(all(a))); good("not(a = \"b\")", e!(not(a = "b"))); good("not(all(a))", e!(not(all(a)))); + bad::( + "not(version(\"1.23.4\"))", + str![[ + r#"failed to parse `not(version("1.23.4"))` as a cfg expression: unexpected content `(version("1.23.4"))` found after cfg expression"# + ]], + ); } #[test] diff --git a/tests/testsuite/cfg.rs b/tests/testsuite/cfg.rs index 4e7832ad56e..c2c73df79ef 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -817,3 +817,162 @@ fn cfg_booleans_rustflags_no_effect() { "#]]) .run(); } + +#[cargo_test] +fn cfg_version() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + edition = "2015" + + [target.'cfg(version("1.87.0"))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + p.cargo("check -v") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + failed to parse `version("1.87.0")` as a cfg expression: unexpected content `("1.87.0")` found after cfg expression + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_version_short() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + edition = "2015" + + [target.'cfg(version("1.87"))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + p.cargo("check -v") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + failed to parse `version("1.87")` as a cfg expression: unexpected content `("1.87")` found after cfg expression + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_bad_version() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2015" + + [target.'cfg(version("1"))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_bad_version2() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2015" + + [target.'cfg(version(1.87.0))'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + failed to parse `version(1.87.0)` as a cfg expression: unexpected content `(1.87.0)` found after cfg expression + +"#]]) + .run(); +} + +#[cargo_test] +fn cfg_bad_version3() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2015" + + [target.'cfg(version = "1.87.0")'.dependencies] + b = { path = 'b' } + "#, + ) + .file("src/lib.rs", "extern crate b;") + .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[CHECKING] foo v0.0.0 ([ROOT]/foo) +error[E0463]: can't find crate for `b` + --> src/lib.rs:1:1 + | +1 | extern crate b; + | ^^^^^^^^^^^^^^^ can't find crate + +For more information about this error, try `rustc --explain E0463`. +[ERROR] could not compile `foo` (lib) due to 1 previous error + +"#]]) + .run(); +} From 84c3bfd079369a13f84b408c2361f1e047ef38ce Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 20 May 2025 15:28:40 -0500 Subject: [PATCH 4/5] feat(platform)!: Require the rust version to do matching Co-authored-by: Ed Page --- Cargo.lock | 1 + crates/cargo-platform/Cargo.toml | 1 + crates/cargo-platform/examples/matches.rs | 6 ++- crates/cargo-platform/src/cfg.rs | 12 ++--- crates/cargo-platform/src/lib.rs | 4 +- crates/cargo-platform/tests/test_cfg.rs | 53 ++++++++++++----------- 6 files changed, 42 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7d0ad07770..40591baabb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ name = "cargo-platform" version = "0.4.0" dependencies = [ + "semver", "serde", "snapbox", ] diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index 23bbd533873..d1ec5463b6e 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -10,6 +10,7 @@ documentation = "https://docs.rs/cargo-platform" description = "Cargo's representation of a target platform." [dependencies] +semver.workspace = true serde.workspace = true [dev-dependencies] diff --git a/crates/cargo-platform/examples/matches.rs b/crates/cargo-platform/examples/matches.rs index 11318a7df55..0ff40743fec 100644 --- a/crates/cargo-platform/examples/matches.rs +++ b/crates/cargo-platform/examples/matches.rs @@ -26,7 +26,11 @@ fn main() { examples.push(target.as_str()); for example in examples { let p = Platform::from_str(example).unwrap(); - println!("{:?} matches: {:?}", example, p.matches(&target, &cfgs)); + println!( + "{:?} matches: {:?}", + example, + p.matches(&target, &cfgs, &semver::Version::new(0, 0, 0)) + ); } } diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index 5928e4c2d6c..f4e8a0191f3 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -130,24 +130,24 @@ impl fmt::Display for Cfg { impl CfgExpr { /// Utility function to check if the key, "cfg(..)" matches the `target_cfg` - pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool { + pub fn matches_key(key: &str, target_cfg: &[Cfg], rustc_version: &semver::Version) -> bool { if key.starts_with("cfg(") && key.ends_with(')') { let cfg = &key[4..key.len() - 1]; CfgExpr::from_str(cfg) .ok() - .map(|ce| ce.matches(target_cfg)) + .map(|ce| ce.matches(target_cfg, rustc_version)) .unwrap_or(false) } else { false } } - pub fn matches(&self, cfg: &[Cfg]) -> bool { + pub fn matches(&self, cfg: &[Cfg], rustc_version: &semver::Version) -> bool { match *self { - CfgExpr::Not(ref e) => !e.matches(cfg), - CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)), - CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)), + CfgExpr::Not(ref e) => !e.matches(cfg, rustc_version), + CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg, rustc_version)), + CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg, rustc_version)), CfgExpr::Value(ref e) => cfg.contains(e), CfgExpr::True => true, CfgExpr::False => false, diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs index 96b2485775a..447218ce63f 100644 --- a/crates/cargo-platform/src/lib.rs +++ b/crates/cargo-platform/src/lib.rs @@ -34,10 +34,10 @@ impl Platform { /// Returns whether the Platform matches the given target and cfg. /// /// The named target and cfg values should be obtained from `rustc`. - pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool { + pub fn matches(&self, name: &str, cfg: &[Cfg], rustc_version: &semver::Version) -> bool { match *self { Platform::Name(ref p) => p == name, - Platform::Cfg(ref p) => p.matches(cfg), + Platform::Cfg(ref p) => p.matches(cfg, rustc_version), } } diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs index 9c05ba99a61..5835bd567fa 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -251,33 +251,34 @@ fn cfg_expr_bad() { #[test] fn cfg_matches() { - assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)])); - assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)])); - assert!(e!(any(foo, bar)).matches(&[c!(bar)])); - assert!(e!(any(foo, bar)).matches(&[c!(foo)])); - assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); - assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)])); - assert!(e!(not(foo)).matches(&[c!(bar)])); - assert!(e!(not(foo)).matches(&[])); - assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)])); - assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)])); - assert!(e!(foo).matches(&[c!(r # foo)])); - assert!(e!(r # foo).matches(&[c!(foo)])); - assert!(e!(r # foo).matches(&[c!(r # foo)])); + let v87 = semver::Version::new(1, 87, 0); + assert!(e!(foo).matches(&[c!(bar), c!(foo), c!(baz)], &v87)); + assert!(e!(any(foo)).matches(&[c!(bar), c!(foo), c!(baz)], &v87)); + assert!(e!(any(foo, bar)).matches(&[c!(bar)], &v87)); + assert!(e!(any(foo, bar)).matches(&[c!(foo)], &v87)); + assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)], &v87)); + assert!(e!(all(foo, bar)).matches(&[c!(foo), c!(bar)], &v87)); + assert!(e!(not(foo)).matches(&[c!(bar)], &v87)); + assert!(e!(not(foo)).matches(&[], &v87)); + assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)], &v87)); + assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)], &v87)); + assert!(e!(foo).matches(&[c!(r # foo)], &v87)); + assert!(e!(r # foo).matches(&[c!(foo)], &v87)); + assert!(e!(r # foo).matches(&[c!(r # foo)], &v87)); - assert!(!e!(foo).matches(&[])); - assert!(!e!(foo).matches(&[c!(bar)])); - assert!(!e!(foo).matches(&[c!(fo)])); - assert!(!e!(any(foo)).matches(&[])); - assert!(!e!(any(foo)).matches(&[c!(bar)])); - assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)])); - assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)])); - assert!(!e!(all(foo, bar)).matches(&[c!(bar)])); - assert!(!e!(all(foo, bar)).matches(&[c!(foo)])); - assert!(!e!(all(foo, bar)).matches(&[])); - assert!(!e!(not(bar)).matches(&[c!(bar)])); - assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)])); - assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)])); + assert!(!e!(foo).matches(&[], &v87)); + assert!(!e!(foo).matches(&[c!(bar)], &v87)); + assert!(!e!(foo).matches(&[c!(fo)], &v87)); + assert!(!e!(any(foo)).matches(&[], &v87)); + assert!(!e!(any(foo)).matches(&[c!(bar)], &v87)); + assert!(!e!(any(foo)).matches(&[c!(bar), c!(baz)], &v87)); + assert!(!e!(all(foo)).matches(&[c!(bar), c!(baz)], &v87)); + assert!(!e!(all(foo, bar)).matches(&[c!(bar)], &v87)); + assert!(!e!(all(foo, bar)).matches(&[c!(foo)], &v87)); + assert!(!e!(all(foo, bar)).matches(&[], &v87)); + assert!(!e!(not(bar)).matches(&[c!(bar)], &v87)); + assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)], &v87)); + assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)], &v87)); } #[test] From a99580b948abff2177029654c38b6cd8d34db80a Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 20 May 2025 15:30:04 -0500 Subject: [PATCH 5/5] feat: Implement cfg_version support Co-authored-by: Ed Page --- crates/cargo-platform/src/cfg.rs | 70 ++++++++++++ crates/cargo-platform/src/error.rs | 5 + crates/cargo-platform/src/lib.rs | 4 +- crates/cargo-platform/tests/test_cfg.rs | 100 +++++++++--------- .../compiler/build_context/target_info.rs | 21 +++- src/cargo/core/compiler/compilation.rs | 8 +- src/cargo/core/compiler/custom_build.rs | 1 + tests/testsuite/cfg.rs | 26 +---- 8 files changed, 155 insertions(+), 80 deletions(-) diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index f4e8a0191f3..f697e005248 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -1,4 +1,5 @@ use crate::error::{ParseError, ParseErrorKind::*}; +use std::cmp::Ordering; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter; @@ -15,6 +16,50 @@ pub enum CfgExpr { False, } +// Major version restricted to `1`. +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] +pub struct CfgRustVersion { + pub minor: u64, + pub patch: Option, +} + +impl CfgRustVersion { + pub fn parse(version: &str) -> Option { + let minor_patch = version.strip_prefix("1.")?; + let (minor, patch) = match minor_patch.split_once('.') { + Some((minor, patch)) => (minor.parse().ok()?, Some(patch.parse().ok()?)), + None => (minor_patch.parse().ok()?, None), + }; + Some(Self { minor, patch }) + } + + pub fn matches(&self, rustc_version: &semver::Version) -> bool { + match self.minor.cmp(&rustc_version.minor) { + Ordering::Less => true, + Ordering::Equal => match self.patch { + Some(patch) => { + if rustc_version.pre.as_str() == "nightly" { + false + } else { + patch <= rustc_version.patch + } + } + None => true, + }, + Ordering::Greater => false, + } + } +} + +impl fmt::Display for CfgRustVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.patch { + Some(patch) => write!(f, "version(\"1.{}.{patch}\")", self.minor), + None => write!(f, "version(\"1.{}\")", self.minor), + } + } +} + /// A cfg value. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum Cfg { @@ -22,6 +67,8 @@ pub enum Cfg { Name(Ident), /// A key/value cfg pair, like `target_os = "linux"`. KeyPair(Ident, String), + /// A Rust version cfg value, like `version("1.23.4")` or `version("1.23")`. + Version(CfgRustVersion), } /// A identifier @@ -124,6 +171,7 @@ impl fmt::Display for Cfg { match *self { Cfg::Name(ref s) => s.fmt(f), Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v), + Cfg::Version(ref cfg_rust_version) => cfg_rust_version.fmt(f), } } } @@ -148,6 +196,9 @@ impl CfgExpr { CfgExpr::Not(ref e) => !e.matches(cfg, rustc_version), CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg, rustc_version)), CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg, rustc_version)), + CfgExpr::Value(Cfg::Version(ref cfg_rust_version)) => { + cfg_rust_version.matches(rustc_version) + } CfgExpr::Value(ref e) => cfg.contains(e), CfgExpr::True => true, CfgExpr::False => false, @@ -275,6 +326,25 @@ impl<'a> Parser<'a> { }, val.to_string(), ) + } else if name == "version" { + self.eat(&Token::LeftParen)?; + let token = self + .t + .next() + .ok_or_else(|| ParseError::new(self.t.orig, InvalidVersion))??; + let Token::String(version_str) = token else { + return Err(ParseError::new( + self.t.orig, + UnexpectedToken { + expected: "a string", + found: token.classify(), + }, + )); + }; + self.eat(&Token::RightParen)?; + let version = CfgRustVersion::parse(version_str) + .ok_or_else(|| ParseError::new(self.t.orig, InvalidVersion))?; + Cfg::Version(version) } else { Cfg::Name(Ident { name: name.to_string(), diff --git a/crates/cargo-platform/src/error.rs b/crates/cargo-platform/src/error.rs index 2d5b315f9e7..e279f7cd3b7 100644 --- a/crates/cargo-platform/src/error.rs +++ b/crates/cargo-platform/src/error.rs @@ -18,6 +18,7 @@ pub enum ParseErrorKind { IncompleteExpr(&'static str), UnterminatedExpression(String), InvalidTarget(String), + InvalidVersion, } impl fmt::Display for ParseError { @@ -51,6 +52,10 @@ impl fmt::Display for ParseErrorKind { write!(f, "unexpected content `{}` found after cfg expression", s) } InvalidTarget(s) => write!(f, "invalid target specifier: {}", s), + InvalidVersion => write!( + f, + "invalid Rust cfg version, expected format `version(\"1.23.4\")` or `version(\"1.23\")`" + ), } } } diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs index 447218ce63f..52493feba86 100644 --- a/crates/cargo-platform/src/lib.rs +++ b/crates/cargo-platform/src/lib.rs @@ -18,7 +18,7 @@ mod cfg; mod error; use cfg::KEYWORDS; -pub use cfg::{Cfg, CfgExpr, Ident}; +pub use cfg::{Cfg, CfgExpr, CfgRustVersion, Ident}; pub use error::{ParseError, ParseErrorKind}; /// Platform definition. @@ -97,6 +97,7 @@ impl Platform { https://doc.rust-lang.org/cargo/reference/features.html" )) }, + Cfg::Version(..) => {}, } CfgExpr::True | CfgExpr::False => {}, } @@ -130,6 +131,7 @@ impl Platform { )); } } + Cfg::Version(..) => {} }, } } diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs index 5835bd567fa..25c345a6101 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -1,7 +1,8 @@ use std::fmt; use std::str::FromStr; -use cargo_platform::{Cfg, CfgExpr, Ident, Platform}; +use cargo_platform::{Cfg, CfgExpr, CfgRustVersion, Ident, Platform}; +use semver::{BuildMetadata, Prerelease}; use snapbox::assert_data_eq; use snapbox::prelude::*; use snapbox::str; @@ -37,6 +38,18 @@ macro_rules! c { $e.to_string(), ) }; + (version($minor:literal)) => { + Cfg::Version(CfgRustVersion { + minor: $minor, + patch: None, + }) + }; + (version($minor:literal, $patch:literal)) => { + Cfg::Version(CfgRustVersion { + minor: $minor, + patch: Some($patch), + }) + }; } macro_rules! e { @@ -88,42 +101,12 @@ fn cfg_syntax() { good(" foo=\"3\" ", c!(foo = "3")); good("foo = \"3 e\"", c!(foo = "3 e")); good(" r#foo = \"3 e\"", c!(r # foo = "3 e")); - bad::( - "version(\"1.23.4\")", - str![[ - r#"failed to parse `version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"# - ]], - ); - bad::( - "version(\"1.23\")", - str![[ - r#"failed to parse `version("1.23")` as a cfg expression: unexpected content `("1.23")` found after cfg expression"# - ]], - ); - bad::( - "version(\"1.234.56\")", - str![[ - r#"failed to parse `version("1.234.56")` as a cfg expression: unexpected content `("1.234.56")` found after cfg expression"# - ]], - ); - bad::( - " version(\"1.23.4\")", - str![[ - r#"failed to parse ` version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"# - ]], - ); - bad::( - "version(\"1.23.4\") ", - str![[ - r#"failed to parse `version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"# - ]], - ); - bad::( - " version(\"1.23.4\") ", - str![[ - r#"failed to parse ` version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"# - ]], - ); + good("version(\"1.23.4\")", c!(version(23, 4))); + good("version(\"1.23\")", c!(version(23))); + good("version(\"1.234.56\")", c!(version(234, 56))); + good(" version(\"1.23.4\")", c!(version(23, 4))); + good("version(\"1.23.4\") ", c!(version(23, 4))); + good(" version(\"1.23.4\") ", c!(version(23, 4))); good("version = \"1.23.4\"", c!(version = "1.23.4")); } @@ -153,43 +136,43 @@ fn cfg_syntax_bad() { bad::( "version(\"1\")", str![[ - r#"failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression"# + r#"failed to parse `version("1")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"1.\")", str![[ - r#"failed to parse `version("1.")` as a cfg expression: unexpected content `("1.")` found after cfg expression"# + r#"failed to parse `version("1.")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"1.2.\")", str![[ - r#"failed to parse `version("1.2.")` as a cfg expression: unexpected content `("1.2.")` found after cfg expression"# + r#"failed to parse `version("1.2.")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"1.2.3.\")", str![[ - r#"failed to parse `version("1.2.3.")` as a cfg expression: unexpected content `("1.2.3.")` found after cfg expression"# + r#"failed to parse `version("1.2.3.")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"1.2.3-stable\")", str![[ - r#"failed to parse `version("1.2.3-stable")` as a cfg expression: unexpected content `("1.2.3-stable")` found after cfg expression"# + r#"failed to parse `version("1.2.3-stable")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"2.3\")", str![[ - r#"failed to parse `version("2.3")` as a cfg expression: unexpected content `("2.3")` found after cfg expression"# + r#"failed to parse `version("2.3")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); bad::( "version(\"0.99.9\")", str![[ - r#"failed to parse `version("0.99.9")` as a cfg expression: unexpected content `("0.99.9")` found after cfg expression"# + r#"failed to parse `version("0.99.9")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# ]], ); } @@ -214,12 +197,7 @@ fn cfg_expr() { good("all(a, )", e!(all(a))); good("not(a = \"b\")", e!(not(a = "b"))); good("not(all(a))", e!(not(all(a)))); - bad::( - "not(version(\"1.23.4\"))", - str![[ - r#"failed to parse `not(version("1.23.4"))` as a cfg expression: unexpected content `(version("1.23.4"))` found after cfg expression"# - ]], - ); + good("not(version(\"1.23.4\"))", e!(not(version(23, 4)))); } #[test] @@ -279,6 +257,28 @@ fn cfg_matches() { assert!(!e!(not(bar)).matches(&[c!(bar)], &v87)); assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)], &v87)); assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)], &v87)); + + assert!(e!(version(87)).matches(&[], &v87)); + assert!(e!(version(87, 0)).matches(&[], &v87)); + assert!(e!(version(86)).matches(&[], &v87)); + assert!(e!(version(86, 1)).matches(&[], &v87)); + assert!(!e!(version(87, 1)).matches(&[], &v87)); + assert!(!e!(version(88)).matches(&[], &v87)); + assert!(!e!(version(88, 1)).matches(&[], &v87)); + assert!(e!(not(version(88))).matches(&[], &v87)); + assert!(e!(not(version(88, 1))).matches(&[], &v87)); + + let v89_nightly = semver::Version { + major: 1, + minor: 89, + patch: 0, + pre: Prerelease::new("nightly").unwrap(), + build: BuildMetadata::EMPTY, + }; + assert!(e!(version(89)).matches(&[], &v89_nightly)); + assert!(!e!(version(89, 0)).matches(&[], &v89_nightly)); + assert!(e!(version(88)).matches(&[], &v89_nightly)); + assert!(e!(version(88, 0)).matches(&[], &v89_nightly)); } #[test] diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index c2fcad6b7b3..0279741649d 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -157,8 +157,15 @@ impl TargetInfo { rustc: &Rustc, kind: CompileKind, ) -> CargoResult { - let mut rustflags = - extra_args(gctx, requested_kinds, &rustc.host, None, kind, Flags::Rust)?; + let mut rustflags = extra_args( + gctx, + requested_kinds, + &rustc.host, + None, + kind, + Flags::Rust, + &rustc.version, + )?; let mut turn = 0; loop { let extra_fingerprint = kind.fingerprint_hash(); @@ -281,6 +288,7 @@ impl TargetInfo { Some(&cfg), kind, Flags::Rust, + &rustc.version, )?; // Tricky: `RUSTFLAGS` defines the set of active `cfg` flags, active @@ -353,6 +361,7 @@ impl TargetInfo { Some(&cfg), kind, Flags::Rustdoc, + &rustc.version, )? .into(), cfg, @@ -774,6 +783,7 @@ fn extra_args( target_cfg: Option<&[Cfg]>, kind: CompileKind, flags: Flags, + rustc_version: &semver::Version, ) -> CargoResult> { let target_applies_to_host = gctx.target_applies_to_host()?; @@ -802,7 +812,7 @@ fn extra_args( if let Some(rustflags) = rustflags_from_env(gctx, flags) { Ok(rustflags) } else if let Some(rustflags) = - rustflags_from_target(gctx, host_triple, target_cfg, kind, flags)? + rustflags_from_target(gctx, host_triple, target_cfg, kind, flags, rustc_version)? { Ok(rustflags) } else if let Some(rustflags) = rustflags_from_build(gctx, flags)? { @@ -846,6 +856,7 @@ fn rustflags_from_target( target_cfg: Option<&[Cfg]>, kind: CompileKind, flag: Flags, + rustc_version: &semver::Version, ) -> CargoResult>> { let mut rustflags = Vec::new(); @@ -872,7 +883,7 @@ fn rustflags_from_target( Flags::Rustdoc => None, } }) - .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg)) + .filter(|(key, _rustflags)| CfgExpr::matches_key(key, target_cfg, rustc_version)) .for_each(|(_key, cfg_rustflags)| { rustflags.extend(cfg_rustflags.as_slice().iter().cloned()); }); @@ -1060,7 +1071,7 @@ impl<'gctx> RustcTargetData<'gctx> { return true; }; let name = self.short_name(&kind); - platform.matches(name, self.cfg(kind)) + platform.matches(name, self.cfg(kind), &self.rustc.version) } /// Gets the list of `cfg`s printed out from the compiler for the specified kind. diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index c30343ef16c..39e2fcee5f9 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -444,7 +444,9 @@ fn target_runner( .target_cfgs()? .iter() .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner))) - .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg)); + .filter(|(key, _runner)| { + CfgExpr::matches_key(key, target_cfg, &bcx.target_data.rustc.version) + }); let matching_runner = cfgs.next(); if let Some((key, runner)) = cfgs.next() { anyhow::bail!( @@ -485,7 +487,9 @@ fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult, unit: &Unit) -> CargoResul let values = cfg_map.entry(k.as_str()).or_default(); values.push(v.as_str()); } + Cfg::Version(..) => {} } } for (k, v) in cfg_map { diff --git a/tests/testsuite/cfg.rs b/tests/testsuite/cfg.rs index c2c73df79ef..a182caee63e 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -836,16 +836,7 @@ fn cfg_version() { .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "") .build(); - p.cargo("check -v") - .with_status(101) - .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - failed to parse `version("1.87.0")` as a cfg expression: unexpected content `("1.87.0")` found after cfg expression - -"#]]) - .run(); + p.cargo("check -v").run(); } #[cargo_test] @@ -866,16 +857,7 @@ fn cfg_version_short() { .file("b/Cargo.toml", &basic_manifest("b", "0.0.1")) .file("b/src/lib.rs", "") .build(); - p.cargo("check -v") - .with_status(101) - .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - failed to parse `version("1.87")` as a cfg expression: unexpected content `("1.87")` found after cfg expression - -"#]]) - .run(); + p.cargo("check -v").run(); } #[cargo_test] @@ -903,7 +885,7 @@ fn cfg_bad_version() { [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: - failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression + failed to parse `version("1")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")` "#]]) .run(); @@ -934,7 +916,7 @@ fn cfg_bad_version2() { [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: - failed to parse `version(1.87.0)` as a cfg expression: unexpected content `(1.87.0)` found after cfg expression + failed to parse `version(1.87.0)` as a cfg expression: unexpected character `1` in cfg, expected parens, a comma, an identifier, or a string "#]]) .run();