Skip to content

Add cfg_version support #15533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
6 changes: 5 additions & 1 deletion crates/cargo-platform/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion crates/cargo-platform/examples/matches.rs
Original file line number Diff line number Diff line change
@@ -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))
);
}
}

82 changes: 76 additions & 6 deletions crates/cargo-platform/src/cfg.rs
Original file line number Diff line number Diff line change
@@ -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,13 +16,59 @@ 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<u64>,
}

impl CfgRustVersion {
pub fn parse(version: &str) -> Option<Self> {
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 {
/// A named cfg value, like `unix`.
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(),
5 changes: 5 additions & 0 deletions crates/cargo-platform/src/error.rs
Original file line number Diff line number Diff line change
@@ -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\")`"
),
}
}
}
8 changes: 5 additions & 3 deletions crates/cargo-platform/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(..) => {}
},
}
}
226 changes: 163 additions & 63 deletions crates/cargo-platform/tests/test_cfg.rs
Original file line number Diff line number Diff line change
@@ -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<T>(s: &str, expected: T)
where
T: FromStr + PartialEq + fmt::Debug,
@@ -57,23 +75,17 @@ where
assert_eq!(c, expected);
}

fn bad<T>(s: &str, err: &str)
#[track_caller]
fn bad<T>(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::<Cfg>("", "but cfg expression ended");
bad::<Cfg>(" ", "but cfg expression ended");
bad::<Cfg>("\t", "unexpected character");
bad::<Cfg>("7", "unexpected character");
bad::<Cfg>("=", "expected identifier");
bad::<Cfg>(",", "expected identifier");
bad::<Cfg>("(", "expected identifier");
bad::<Cfg>("foo (", "unexpected content `(` found after cfg expression");
bad::<Cfg>("bar =", "expected a string");
bad::<Cfg>("bar = \"", "unterminated string");
bad::<Cfg>(
"foo, bar",
"unexpected content `, bar` found after cfg expression",
"",
str![
"failed to parse `` as a cfg expression: expected identifier, but cfg expression ended"
],
);
bad::<Cfg>(" ", str!["failed to parse ` ` as a cfg expression: expected identifier, but cfg expression ended"]);
bad::<Cfg>("\t", str!["failed to parse ` ` as a cfg expression: unexpected character ` ` in cfg, expected parens, a comma, an identifier, or a string"]);
bad::<Cfg>("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::<Cfg>(
"=",
str!["failed to parse `=` as a cfg expression: expected identifier, found `=`"],
);
bad::<Cfg>(
",",
str!["failed to parse `,` as a cfg expression: expected identifier, found `,`"],
);
bad::<Cfg>(
"(",
str!["failed to parse `(` as a cfg expression: expected identifier, found `(`"],
);
bad::<Cfg>(
"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::<Cfg>(
"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::<Cfg>(
"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::<Cfg>(
"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::<Cfg>(
"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::<Cfg>(
"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::<Cfg>(
"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::<Cfg>("r# foo", "unexpected character");
bad::<Cfg>("r #foo", "unexpected content");
bad::<Cfg>("r#\"foo\"", "unexpected character");
bad::<Cfg>("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::<CfgExpr>(" ", "but cfg expression ended");
bad::<CfgExpr>(" all", "expected `(`");
bad::<CfgExpr>("all(a", "expected `)`");
bad::<CfgExpr>("not", "expected `(`");
bad::<CfgExpr>("not(a", "expected `)`");
bad::<CfgExpr>("a = ", "expected a string");
bad::<CfgExpr>("all(not())", "expected identifier");
bad::<CfgExpr>(" ", str!["failed to parse ` ` as a cfg expression: expected start of a cfg expression, but cfg expression ended"]);
bad::<CfgExpr>(
" all",
str!["failed to parse ` all` as a cfg expression: expected `(`, but cfg expression ended"],
);
bad::<CfgExpr>(
"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::<CfgExpr>(
"not",
str!["failed to parse `not` as a cfg expression: expected `(`, but cfg expression ended"],
);
bad::<CfgExpr>(
"not(a",
str!["failed to parse `not(a` as a cfg expression: expected `)`, but cfg expression ended"],
);
bad::<CfgExpr>("a = ", str!["failed to parse `a = ` as a cfg expression: expected a string, but cfg expression ended"]);
bad::<CfgExpr>(
"all(not())",
str!["failed to parse `all(not())` as a cfg expression: expected identifier, found `)`"],
);
bad::<CfgExpr>("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]
21 changes: 16 additions & 5 deletions src/cargo/core/compiler/build_context/target_info.rs
Original file line number Diff line number Diff line change
@@ -157,8 +157,15 @@ impl TargetInfo {
rustc: &Rustc,
kind: CompileKind,
) -> CargoResult<TargetInfo> {
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<Vec<String>> {
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<Option<Vec<String>>> {
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.
8 changes: 6 additions & 2 deletions src/cargo/core/compiler/compilation.rs
Original file line number Diff line number Diff line change
@@ -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<O
.target_cfgs()?
.iter()
.filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
.filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
.filter(|(key, _linker)| {
CfgExpr::matches_key(key, target_cfg, &bcx.target_data.rustc.version)
});
let matching_linker = cfgs.next();
if let Some((key, linker)) = cfgs.next() {
anyhow::bail!(
1 change: 1 addition & 0 deletions src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
@@ -416,6 +416,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, 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 {
141 changes: 141 additions & 0 deletions tests/testsuite/cfg.rs
Original file line number Diff line number Diff line change
@@ -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();
}
Comment on lines +821 to +840
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we had a way to override the rust version so we could test with 1.2345 (the version we always use for "far future" in tests)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use RUSTC_FORCE_RUSTC_VERSION?


#[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
Comment on lines +949 to +956
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't match on the exact output from rustc because if they change the output it would prevent this test from passing. I think something like the following should be close enough:

Suggested change
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
error[E0463]: can't find crate for `b`
...
[ERROR] could not compile `foo` (lib) due to 1 previous error

"#]])
.run();
}