Skip to content

Commit c8f9de9

Browse files
mo8itepage
andcommitted
feat: Implement cfg_version support
Co-authored-by: Ed Page <[email protected]>
1 parent d849068 commit c8f9de9

File tree

8 files changed

+155
-80
lines changed

8 files changed

+155
-80
lines changed

crates/cargo-platform/src/cfg.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::{ParseError, ParseErrorKind::*};
2+
use std::cmp::Ordering;
23
use std::fmt;
34
use std::hash::{Hash, Hasher};
45
use std::iter;
@@ -15,13 +16,59 @@ pub enum CfgExpr {
1516
False,
1617
}
1718

19+
// Major version restricted to `1`.
20+
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
21+
pub struct CfgRustVersion {
22+
pub minor: u64,
23+
pub patch: Option<u64>,
24+
}
25+
26+
impl CfgRustVersion {
27+
pub fn parse(version: &str) -> Option<Self> {
28+
let minor_patch = version.strip_prefix("1.")?;
29+
let (minor, patch) = match minor_patch.split_once('.') {
30+
Some((minor, patch)) => (minor.parse().ok()?, Some(patch.parse().ok()?)),
31+
None => (minor_patch.parse().ok()?, None),
32+
};
33+
Some(Self { minor, patch })
34+
}
35+
36+
pub fn matches(&self, rustc_version: &semver::Version) -> bool {
37+
match self.minor.cmp(&rustc_version.minor) {
38+
Ordering::Less => true,
39+
Ordering::Equal => match self.patch {
40+
Some(patch) => {
41+
if rustc_version.pre.as_str() == "nightly" {
42+
false
43+
} else {
44+
patch <= rustc_version.patch
45+
}
46+
}
47+
None => true,
48+
},
49+
Ordering::Greater => false,
50+
}
51+
}
52+
}
53+
54+
impl fmt::Display for CfgRustVersion {
55+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56+
match self.patch {
57+
Some(patch) => write!(f, "version(\"1.{}.{patch}\")", self.minor),
58+
None => write!(f, "version(\"1.{}\")", self.minor),
59+
}
60+
}
61+
}
62+
1863
/// A cfg value.
1964
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
2065
pub enum Cfg {
2166
/// A named cfg value, like `unix`.
2267
Name(Ident),
2368
/// A key/value cfg pair, like `target_os = "linux"`.
2469
KeyPair(Ident, String),
70+
/// A Rust version cfg value, like `version("1.23.4")` or `version("1.23")`.
71+
Version(CfgRustVersion),
2572
}
2673

2774
/// A identifier
@@ -124,6 +171,7 @@ impl fmt::Display for Cfg {
124171
match *self {
125172
Cfg::Name(ref s) => s.fmt(f),
126173
Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v),
174+
Cfg::Version(ref cfg_rust_version) => cfg_rust_version.fmt(f),
127175
}
128176
}
129177
}
@@ -148,6 +196,9 @@ impl CfgExpr {
148196
CfgExpr::Not(ref e) => !e.matches(cfg, rustc_version),
149197
CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg, rustc_version)),
150198
CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg, rustc_version)),
199+
CfgExpr::Value(Cfg::Version(ref cfg_rust_version)) => {
200+
cfg_rust_version.matches(rustc_version)
201+
}
151202
CfgExpr::Value(ref e) => cfg.contains(e),
152203
CfgExpr::True => true,
153204
CfgExpr::False => false,
@@ -275,6 +326,25 @@ impl<'a> Parser<'a> {
275326
},
276327
val.to_string(),
277328
)
329+
} else if name == "version" {
330+
self.eat(&Token::LeftParen)?;
331+
let token = self
332+
.t
333+
.next()
334+
.ok_or_else(|| ParseError::new(self.t.orig, InvalidVersion))??;
335+
let Token::String(version_str) = token else {
336+
return Err(ParseError::new(
337+
self.t.orig,
338+
UnexpectedToken {
339+
expected: "a string",
340+
found: token.classify(),
341+
},
342+
));
343+
};
344+
self.eat(&Token::RightParen)?;
345+
let version = CfgRustVersion::parse(version_str)
346+
.ok_or_else(|| ParseError::new(self.t.orig, InvalidVersion))?;
347+
Cfg::Version(version)
278348
} else {
279349
Cfg::Name(Ident {
280350
name: name.to_string(),

crates/cargo-platform/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub enum ParseErrorKind {
1818
IncompleteExpr(&'static str),
1919
UnterminatedExpression(String),
2020
InvalidTarget(String),
21+
InvalidVersion,
2122
}
2223

2324
impl fmt::Display for ParseError {
@@ -51,6 +52,10 @@ impl fmt::Display for ParseErrorKind {
5152
write!(f, "unexpected content `{}` found after cfg expression", s)
5253
}
5354
InvalidTarget(s) => write!(f, "invalid target specifier: {}", s),
55+
InvalidVersion => write!(
56+
f,
57+
"invalid Rust cfg version, expected format `version(\"1.23.4\")` or `version(\"1.23\")`"
58+
),
5459
}
5560
}
5661
}

crates/cargo-platform/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod cfg;
1818
mod error;
1919

2020
use cfg::KEYWORDS;
21-
pub use cfg::{Cfg, CfgExpr, Ident};
21+
pub use cfg::{Cfg, CfgExpr, CfgRustVersion, Ident};
2222
pub use error::{ParseError, ParseErrorKind};
2323

2424
/// Platform definition.
@@ -97,6 +97,7 @@ impl Platform {
9797
https://doc.rust-lang.org/cargo/reference/features.html"
9898
))
9999
},
100+
Cfg::Version(..) => {},
100101
}
101102
CfgExpr::True | CfgExpr::False => {},
102103
}
@@ -130,6 +131,7 @@ impl Platform {
130131
));
131132
}
132133
}
134+
Cfg::Version(..) => {}
133135
},
134136
}
135137
}

crates/cargo-platform/tests/test_cfg.rs

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::fmt;
22
use std::str::FromStr;
33

4-
use cargo_platform::{Cfg, CfgExpr, Ident, Platform};
4+
use cargo_platform::{Cfg, CfgExpr, CfgRustVersion, Ident, Platform};
5+
use semver::{BuildMetadata, Prerelease};
56
use snapbox::assert_data_eq;
67
use snapbox::prelude::*;
78
use snapbox::str;
@@ -37,6 +38,18 @@ macro_rules! c {
3738
$e.to_string(),
3839
)
3940
};
41+
(version($minor:literal)) => {
42+
Cfg::Version(CfgRustVersion {
43+
minor: $minor,
44+
patch: None,
45+
})
46+
};
47+
(version($minor:literal, $patch:literal)) => {
48+
Cfg::Version(CfgRustVersion {
49+
minor: $minor,
50+
patch: Some($patch),
51+
})
52+
};
4053
}
4154

4255
macro_rules! e {
@@ -88,42 +101,12 @@ fn cfg_syntax() {
88101
good(" foo=\"3\" ", c!(foo = "3"));
89102
good("foo = \"3 e\"", c!(foo = "3 e"));
90103
good(" r#foo = \"3 e\"", c!(r # foo = "3 e"));
91-
bad::<Cfg>(
92-
"version(\"1.23.4\")",
93-
str![[
94-
r#"failed to parse `version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"#
95-
]],
96-
);
97-
bad::<Cfg>(
98-
"version(\"1.23\")",
99-
str![[
100-
r#"failed to parse `version("1.23")` as a cfg expression: unexpected content `("1.23")` found after cfg expression"#
101-
]],
102-
);
103-
bad::<Cfg>(
104-
"version(\"1.234.56\")",
105-
str![[
106-
r#"failed to parse `version("1.234.56")` as a cfg expression: unexpected content `("1.234.56")` found after cfg expression"#
107-
]],
108-
);
109-
bad::<Cfg>(
110-
" version(\"1.23.4\")",
111-
str![[
112-
r#"failed to parse ` version("1.23.4")` as a cfg expression: unexpected content `("1.23.4")` found after cfg expression"#
113-
]],
114-
);
115-
bad::<Cfg>(
116-
"version(\"1.23.4\") ",
117-
str![[
118-
r#"failed to parse `version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"#
119-
]],
120-
);
121-
bad::<Cfg>(
122-
" version(\"1.23.4\") ",
123-
str![[
124-
r#"failed to parse ` version("1.23.4") ` as a cfg expression: unexpected content `("1.23.4") ` found after cfg expression"#
125-
]],
126-
);
104+
good("version(\"1.23.4\")", c!(version(23, 4)));
105+
good("version(\"1.23\")", c!(version(23)));
106+
good("version(\"1.234.56\")", c!(version(234, 56)));
107+
good(" version(\"1.23.4\")", c!(version(23, 4)));
108+
good("version(\"1.23.4\") ", c!(version(23, 4)));
109+
good(" version(\"1.23.4\") ", c!(version(23, 4)));
127110
good("version = \"1.23.4\"", c!(version = "1.23.4"));
128111
}
129112

@@ -153,43 +136,43 @@ fn cfg_syntax_bad() {
153136
bad::<Cfg>(
154137
"version(\"1\")",
155138
str![[
156-
r#"failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression"#
139+
r#"failed to parse `version("1")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"#
157140
]],
158141
);
159142
bad::<Cfg>(
160143
"version(\"1.\")",
161144
str![[
162-
r#"failed to parse `version("1.")` as a cfg expression: unexpected content `("1.")` found after cfg expression"#
145+
r#"failed to parse `version("1.")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"#
163146
]],
164147
);
165148
bad::<Cfg>(
166149
"version(\"1.2.\")",
167150
str![[
168-
r#"failed to parse `version("1.2.")` as a cfg expression: unexpected content `("1.2.")` found after cfg expression"#
151+
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")`"#
169152
]],
170153
);
171154
bad::<Cfg>(
172155
"version(\"1.2.3.\")",
173156
str![[
174-
r#"failed to parse `version("1.2.3.")` as a cfg expression: unexpected content `("1.2.3.")` found after cfg expression"#
157+
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")`"#
175158
]],
176159
);
177160
bad::<Cfg>(
178161
"version(\"1.2.3-stable\")",
179162
str![[
180-
r#"failed to parse `version("1.2.3-stable")` as a cfg expression: unexpected content `("1.2.3-stable")` found after cfg expression"#
163+
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")`"#
181164
]],
182165
);
183166
bad::<Cfg>(
184167
"version(\"2.3\")",
185168
str![[
186-
r#"failed to parse `version("2.3")` as a cfg expression: unexpected content `("2.3")` found after cfg expression"#
169+
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")`"#
187170
]],
188171
);
189172
bad::<Cfg>(
190173
"version(\"0.99.9\")",
191174
str![[
192-
r#"failed to parse `version("0.99.9")` as a cfg expression: unexpected content `("0.99.9")` found after cfg expression"#
175+
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")`"#
193176
]],
194177
);
195178
}
@@ -214,12 +197,7 @@ fn cfg_expr() {
214197
good("all(a, )", e!(all(a)));
215198
good("not(a = \"b\")", e!(not(a = "b")));
216199
good("not(all(a))", e!(not(all(a))));
217-
bad::<Cfg>(
218-
"not(version(\"1.23.4\"))",
219-
str![[
220-
r#"failed to parse `not(version("1.23.4"))` as a cfg expression: unexpected content `(version("1.23.4"))` found after cfg expression"#
221-
]],
222-
);
200+
good("not(version(\"1.23.4\"))", e!(not(version(23, 4))));
223201
}
224202

225203
#[test]
@@ -279,6 +257,28 @@ fn cfg_matches() {
279257
assert!(!e!(not(bar)).matches(&[c!(bar)], &v87));
280258
assert!(!e!(not(bar)).matches(&[c!(baz), c!(bar)], &v87));
281259
assert!(!e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo)], &v87));
260+
261+
assert!(e!(version(87)).matches(&[], &v87));
262+
assert!(e!(version(87, 0)).matches(&[], &v87));
263+
assert!(e!(version(86)).matches(&[], &v87));
264+
assert!(e!(version(86, 1)).matches(&[], &v87));
265+
assert!(!e!(version(87, 1)).matches(&[], &v87));
266+
assert!(!e!(version(88)).matches(&[], &v87));
267+
assert!(!e!(version(88, 1)).matches(&[], &v87));
268+
assert!(e!(not(version(88))).matches(&[], &v87));
269+
assert!(e!(not(version(88, 1))).matches(&[], &v87));
270+
271+
let v89_nightly = semver::Version {
272+
major: 1,
273+
minor: 89,
274+
patch: 0,
275+
pre: Prerelease::new("nightly").unwrap(),
276+
build: BuildMetadata::EMPTY,
277+
};
278+
assert!(e!(version(89)).matches(&[], &v89_nightly));
279+
assert!(!e!(version(89, 0)).matches(&[], &v89_nightly));
280+
assert!(e!(version(88)).matches(&[], &v89_nightly));
281+
assert!(e!(version(88, 0)).matches(&[], &v89_nightly));
282282
}
283283

284284
#[test]

0 commit comments

Comments
 (0)