diff --git a/Cargo.lock b/Cargo.lock index 1a67d0f6bdf..40591baabb4 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,9 +441,11 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.3.0" +version = "0.4.0" dependencies = [ + "semver", "serde", + "snapbox", ] [[package]] @@ -3450,7 +3452,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..d1ec5463b6e 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 @@ -10,7 +10,11 @@ documentation = "https://docs.rs/cargo-platform" description = "Cargo's representation of a target platform." [dependencies] +semver.workspace = true serde.workspace = true +[dev-dependencies] +snapbox.workspace = true + [lints] workspace = true 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..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,30 +171,34 @@ 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), } } } 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(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 96b2485775a..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. @@ -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), } } @@ -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 59cb27d54f4..25c345a6101 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -1,7 +1,12 @@ -use cargo_platform::{Cfg, CfgExpr, Ident, Platform}; use std::fmt; use std::str::FromStr; +use cargo_platform::{Cfg, CfgExpr, CfgRustVersion, Ident, Platform}; +use semver::{BuildMetadata, Prerelease}; +use snapbox::assert_data_eq; +use snapbox::prelude::*; +use snapbox::str; + macro_rules! c { ($a:ident) => { Cfg::Name(Ident { @@ -33,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 { @@ -45,6 +62,7 @@ macro_rules! e { ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); } +#[track_caller] fn good(s: &str, expected: T) where T: FromStr + PartialEq + fmt::Debug, @@ -57,23 +75,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] @@ -89,28 +101,80 @@ 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")); + 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")); } #[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::( + "version(\"1\")", + str![[ + 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: 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: 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: 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: 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: 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: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"# + ]], ); - bad::("r# foo", "unexpected character"); - bad::("r #foo", "unexpected content"); - bad::("r#\"foo\"", "unexpected character"); - bad::("foo = r#\"\"", "unexpected character"); } #[test] @@ -133,52 +197,88 @@ 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)))); + good("not(version(\"1.23.4\"))", e!(not(version(23, 4)))); } #[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::( - "foo(a)", - "unexpected content `(a)` found after cfg expression", + "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::( + "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] 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(&[], &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)); - 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!(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 4e7832ad56e..a182caee63e 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -817,3 +817,144 @@ 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").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").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: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")` + +"#]]) + .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 character `1` in cfg, expected parens, a comma, an identifier, or a string + +"#]]) + .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(); +}