Skip to content

Commit 6774cdb

Browse files
committed
feat: Implement cfg_version support
1 parent 21772bb commit 6774cdb

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

@@ -154,43 +137,43 @@ fn cfg_syntax_bad() {
154137
bad::<Cfg>(
155138
"version(\"1\")",
156139
str![[
157-
r#"failed to parse `version("1")` as a cfg expression: unexpected content `("1")` found after cfg expression"#
140+
r#"failed to parse `version("1")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"#
158141
]],
159142
);
160143
bad::<Cfg>(
161144
"version(\"1.\")",
162145
str![[
163-
r#"failed to parse `version("1.")` as a cfg expression: unexpected content `("1.")` found after cfg expression"#
146+
r#"failed to parse `version("1.")` as a cfg expression: invalid Rust cfg version, expected format `version("1.23.4")` or `version("1.23")`"#
164147
]],
165148
);
166149
bad::<Cfg>(
167150
"version(\"1.2.\")",
168151
str![[
169-
r#"failed to parse `version("1.2.")` as a cfg expression: unexpected content `("1.2.")` found after cfg expression"#
152+
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")`"#
170153
]],
171154
);
172155
bad::<Cfg>(
173156
"version(\"1.2.3.\")",
174157
str![[
175-
r#"failed to parse `version("1.2.3.")` as a cfg expression: unexpected content `("1.2.3.")` found after cfg expression"#
158+
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")`"#
176159
]],
177160
);
178161
bad::<Cfg>(
179162
"version(\"1.2.3-stable\")",
180163
str![[
181-
r#"failed to parse `version("1.2.3-stable")` as a cfg expression: unexpected content `("1.2.3-stable")` found after cfg expression"#
164+
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")`"#
182165
]],
183166
);
184167
bad::<Cfg>(
185168
"version(\"2.3\")",
186169
str![[
187-
r#"failed to parse `version("2.3")` as a cfg expression: unexpected content `("2.3")` found after cfg expression"#
170+
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")`"#
188171
]],
189172
);
190173
bad::<Cfg>(
191174
"version(\"0.99.9\")",
192175
str![[
193-
r#"failed to parse `version("0.99.9")` as a cfg expression: unexpected content `("0.99.9")` found after cfg expression"#
176+
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")`"#
194177
]],
195178
);
196179
}
@@ -215,12 +198,7 @@ fn cfg_expr() {
215198
good("all(a, )", e!(all(a)));
216199
good("not(a = \"b\")", e!(not(a = "b")));
217200
good("not(all(a))", e!(not(all(a))));
218-
bad::<Cfg>(
219-
"not(version(\"1.23.4\"))",
220-
str![[
221-
r#"failed to parse `not(version("1.23.4"))` as a cfg expression: unexpected content `(version("1.23.4"))` found after cfg expression"#
222-
]],
223-
);
201+
good("not(version(\"1.23.4\"))", e!(not(version(23, 4))));
224202
}
225203

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

285285
#[test]

0 commit comments

Comments
 (0)