Skip to content

Commit 7ab4778

Browse files
committed
Auto merge of #7375 - ehuss:extract-platform, r=alexcrichton
Extract Platform to a separate crate. This moves the `Platform`, `Cfg`, `CfgExpr` types to a new crate named "cargo-platform". The intent here is to give users of `cargo_metadata` a way of parsing and inspecting cargo's platform values. Along the way, I rewrote the error handling to remove `failure`, and to slightly improve the output. I'm having doubts whether or not this is a good idea. As you can see from the `examples/matches.rs` example, it is nontrivial to use this (which also misses cargo's config values and environment variables). I don't know if anyone will actually use this. If this doesn't seem to have value, I would suggest closing it. I've also included a sample script, `publish.py`, for publishing cargo itself. I suspect it will need tweaking, but I figure it would be a start and open for feedback.
2 parents b6c6f68 + 57c96c1 commit 7ab4778

File tree

18 files changed

+589
-265
lines changed

18 files changed

+589
-265
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ path = "src/cargo/lib.rs"
2121
[dependencies]
2222
atty = "0.2"
2323
bytesize = "1.0"
24+
cargo-platform = { path = "crates/cargo-platform", version = "0.1" }
2425
crates-io = { path = "crates/crates-io", version = "0.28" }
2526
crossbeam-utils = "0.6"
2627
crypto-hash = "0.3.1"

azure-pipelines.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ jobs:
6161
displayName: "Check rustfmt (crates-io)"
6262
- bash: cd crates/resolver-tests && cargo fmt --all -- --check
6363
displayName: "Check rustfmt (resolver-tests)"
64+
- bash: cd crates/cargo-platform && cargo fmt --all -- --check
65+
displayName: "Check rustfmt (cargo-platform)"
6466
variables:
6567
TOOLCHAIN: stable
6668

ci/azure-test-all.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ steps:
2929

3030
- bash: cargo test -p cargo-test-support
3131
displayName: "cargo test -p cargo-test-support"
32+
33+
- bash: cargo test -p cargo-platform
34+
displayName: "cargo test -p cargo-platform"

crates/cargo-platform/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "cargo-platform"
3+
version = "0.1.0"
4+
authors = ["The Cargo Project Developers"]
5+
edition = "2018"
6+
license = "MIT OR Apache-2.0"
7+
homepage = "https://github.com/rust-lang/cargo"
8+
repository = "https://github.com/rust-lang/cargo"
9+
documentation = "https://docs.rs/cargo-platform"
10+
description = "Cargo's representation of a target platform."
11+
12+
[dependencies]
13+
serde = { version = "1.0.82", features = ['derive'] }
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! This example demonstrates how to filter a Platform based on the current
2+
//! host target.
3+
4+
use cargo_platform::{Cfg, Platform};
5+
use std::process::Command;
6+
use std::str::FromStr;
7+
8+
static EXAMPLES: &[&str] = &[
9+
"cfg(windows)",
10+
"cfg(unix)",
11+
"cfg(target_os=\"macos\")",
12+
"cfg(target_os=\"linux\")",
13+
"cfg(any(target_arch=\"x86\", target_arch=\"x86_64\"))",
14+
];
15+
16+
fn main() {
17+
let target = get_target();
18+
let cfgs = get_cfgs();
19+
println!("host target={} cfgs:", target);
20+
for cfg in &cfgs {
21+
println!(" {}", cfg);
22+
}
23+
let mut examples: Vec<&str> = EXAMPLES.iter().copied().collect();
24+
examples.push(target.as_str());
25+
for example in examples {
26+
let p = Platform::from_str(example).unwrap();
27+
println!("{:?} matches: {:?}", example, p.matches(&target, &cfgs));
28+
}
29+
}
30+
31+
fn get_target() -> String {
32+
let output = Command::new("rustc")
33+
.arg("-Vv")
34+
.output()
35+
.expect("rustc failed to run");
36+
let stdout = String::from_utf8(output.stdout).unwrap();
37+
for line in stdout.lines() {
38+
if line.starts_with("host: ") {
39+
return String::from(&line[6..]);
40+
}
41+
}
42+
panic!("Failed to find host: {}", stdout);
43+
}
44+
45+
fn get_cfgs() -> Vec<Cfg> {
46+
let output = Command::new("rustc")
47+
.arg("--print=cfg")
48+
.output()
49+
.expect("rustc failed to run");
50+
let stdout = String::from_utf8(output.stdout).unwrap();
51+
stdout
52+
.lines()
53+
.map(|line| Cfg::from_str(line).unwrap())
54+
.collect()
55+
}

src/cargo/util/cfg.rs renamed to crates/cargo-platform/src/cfg.rs

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1+
use crate::error::{ParseError, ParseErrorKind::*};
12
use std::fmt;
23
use std::iter;
34
use std::str::{self, FromStr};
45

5-
use crate::util::CargoResult;
6-
7-
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
8-
pub enum Cfg {
9-
Name(String),
10-
KeyPair(String, String),
11-
}
12-
6+
/// A cfg expression.
137
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
148
pub enum CfgExpr {
159
Not(Box<CfgExpr>),
@@ -18,6 +12,15 @@ pub enum CfgExpr {
1812
Value(Cfg),
1913
}
2014

15+
/// A cfg value.
16+
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
17+
pub enum Cfg {
18+
/// A named cfg value, like `unix`.
19+
Name(String),
20+
/// A key/value cfg pair, like `target_os = "linux"`.
21+
KeyPair(String, String),
22+
}
23+
2124
#[derive(PartialEq)]
2225
enum Token<'a> {
2326
LeftParen,
@@ -28,23 +31,27 @@ enum Token<'a> {
2831
String(&'a str),
2932
}
3033

34+
#[derive(Clone)]
3135
struct Tokenizer<'a> {
3236
s: iter::Peekable<str::CharIndices<'a>>,
3337
orig: &'a str,
3438
}
3539

3640
struct Parser<'a> {
37-
t: iter::Peekable<Tokenizer<'a>>,
41+
t: Tokenizer<'a>,
3842
}
3943

4044
impl FromStr for Cfg {
41-
type Err = failure::Error;
45+
type Err = ParseError;
4246

43-
fn from_str(s: &str) -> CargoResult<Cfg> {
47+
fn from_str(s: &str) -> Result<Cfg, Self::Err> {
4448
let mut p = Parser::new(s);
4549
let e = p.cfg()?;
46-
if p.t.next().is_some() {
47-
failure::bail!("malformed cfg value or key/value pair: `{}`", s)
50+
if let Some(rest) = p.rest() {
51+
return Err(ParseError::new(
52+
p.t.orig,
53+
UnterminatedExpression(rest.to_string()),
54+
));
4855
}
4956
Ok(e)
5057
}
@@ -85,16 +92,16 @@ impl CfgExpr {
8592
}
8693

8794
impl FromStr for CfgExpr {
88-
type Err = failure::Error;
95+
type Err = ParseError;
8996

90-
fn from_str(s: &str) -> CargoResult<CfgExpr> {
97+
fn from_str(s: &str) -> Result<CfgExpr, Self::Err> {
9198
let mut p = Parser::new(s);
9299
let e = p.expr()?;
93-
if p.t.next().is_some() {
94-
failure::bail!(
95-
"can only have one cfg-expression, consider using all() or \
96-
any() explicitly"
97-
)
100+
if let Some(rest) = p.rest() {
101+
return Err(ParseError::new(
102+
p.t.orig,
103+
UnterminatedExpression(rest.to_string()),
104+
));
98105
}
99106
Ok(e)
100107
}
@@ -131,14 +138,13 @@ impl<'a> Parser<'a> {
131138
t: Tokenizer {
132139
s: s.char_indices().peekable(),
133140
orig: s,
134-
}
135-
.peekable(),
141+
},
136142
}
137143
}
138144

139-
fn expr(&mut self) -> CargoResult<CfgExpr> {
140-
match self.t.peek() {
141-
Some(&Ok(Token::Ident(op @ "all"))) | Some(&Ok(Token::Ident(op @ "any"))) => {
145+
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
146+
match self.peek() {
147+
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
142148
self.t.next();
143149
let mut e = Vec::new();
144150
self.eat(&Token::LeftParen)?;
@@ -155,67 +161,108 @@ impl<'a> Parser<'a> {
155161
Ok(CfgExpr::Any(e))
156162
}
157163
}
158-
Some(&Ok(Token::Ident("not"))) => {
164+
Some(Ok(Token::Ident("not"))) => {
159165
self.t.next();
160166
self.eat(&Token::LeftParen)?;
161167
let e = self.expr()?;
162168
self.eat(&Token::RightParen)?;
163169
Ok(CfgExpr::Not(Box::new(e)))
164170
}
165-
Some(&Ok(..)) => self.cfg().map(CfgExpr::Value),
166-
Some(&Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
167-
None => failure::bail!(
168-
"expected start of a cfg expression, \
169-
found nothing"
170-
),
171+
Some(Ok(..)) => self.cfg().map(CfgExpr::Value),
172+
Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
173+
None => Err(ParseError::new(
174+
self.t.orig,
175+
IncompleteExpr("start of a cfg expression"),
176+
)),
171177
}
172178
}
173179

174-
fn cfg(&mut self) -> CargoResult<Cfg> {
180+
fn cfg(&mut self) -> Result<Cfg, ParseError> {
175181
match self.t.next() {
176182
Some(Ok(Token::Ident(name))) => {
177183
let e = if self.r#try(&Token::Equals) {
178184
let val = match self.t.next() {
179185
Some(Ok(Token::String(s))) => s,
180-
Some(Ok(t)) => failure::bail!("expected a string, found {}", t.classify()),
186+
Some(Ok(t)) => {
187+
return Err(ParseError::new(
188+
self.t.orig,
189+
UnexpectedToken {
190+
expected: "a string",
191+
found: t.classify(),
192+
},
193+
))
194+
}
181195
Some(Err(e)) => return Err(e),
182-
None => failure::bail!("expected a string, found nothing"),
196+
None => {
197+
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
198+
}
183199
};
184200
Cfg::KeyPair(name.to_string(), val.to_string())
185201
} else {
186202
Cfg::Name(name.to_string())
187203
};
188204
Ok(e)
189205
}
190-
Some(Ok(t)) => failure::bail!("expected identifier, found {}", t.classify()),
206+
Some(Ok(t)) => Err(ParseError::new(
207+
self.t.orig,
208+
UnexpectedToken {
209+
expected: "identifier",
210+
found: t.classify(),
211+
},
212+
)),
191213
Some(Err(e)) => Err(e),
192-
None => failure::bail!("expected identifier, found nothing"),
214+
None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))),
193215
}
194216
}
195217

218+
fn peek(&mut self) -> Option<Result<Token<'a>, ParseError>> {
219+
self.t.clone().next()
220+
}
221+
196222
fn r#try(&mut self, token: &Token<'a>) -> bool {
197-
match self.t.peek() {
198-
Some(&Ok(ref t)) if token == t => {}
223+
match self.peek() {
224+
Some(Ok(ref t)) if token == t => {}
199225
_ => return false,
200226
}
201227
self.t.next();
202228
true
203229
}
204230

205-
fn eat(&mut self, token: &Token<'a>) -> CargoResult<()> {
231+
fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> {
206232
match self.t.next() {
207233
Some(Ok(ref t)) if token == t => Ok(()),
208-
Some(Ok(t)) => failure::bail!("expected {}, found {}", token.classify(), t.classify()),
234+
Some(Ok(t)) => Err(ParseError::new(
235+
self.t.orig,
236+
UnexpectedToken {
237+
expected: token.classify(),
238+
found: t.classify(),
239+
},
240+
)),
209241
Some(Err(e)) => Err(e),
210-
None => failure::bail!("expected {}, but cfg expr ended", token.classify()),
242+
None => Err(ParseError::new(
243+
self.t.orig,
244+
IncompleteExpr(token.classify()),
245+
)),
246+
}
247+
}
248+
249+
/// Returns the rest of the input from the current location.
250+
fn rest(&self) -> Option<&str> {
251+
let mut s = self.t.s.clone();
252+
loop {
253+
match s.next() {
254+
Some((_, ' ')) => {}
255+
Some((start, _ch)) => return Some(&self.t.orig[start..]),
256+
None => return None,
257+
}
211258
}
212259
}
213260
}
214261

215262
impl<'a> Iterator for Tokenizer<'a> {
216-
type Item = CargoResult<Token<'a>>;
263+
type Item = Result<Token<'a>, ParseError>;
217264

218-
fn next(&mut self) -> Option<CargoResult<Token<'a>>> {
265+
fn next(&mut self) -> Option<Result<Token<'a>, ParseError>> {
219266
loop {
220267
match self.s.next() {
221268
Some((_, ' ')) => {}
@@ -229,7 +276,7 @@ impl<'a> Iterator for Tokenizer<'a> {
229276
return Some(Ok(Token::String(&self.orig[start + 1..end])));
230277
}
231278
}
232-
return Some(Err(failure::format_err!("unterminated string in cfg")));
279+
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
233280
}
234281
Some((start, ch)) if is_ident_start(ch) => {
235282
while let Some(&(end, ch)) = self.s.peek() {
@@ -242,13 +289,7 @@ impl<'a> Iterator for Tokenizer<'a> {
242289
return Some(Ok(Token::Ident(&self.orig[start..])));
243290
}
244291
Some((_, ch)) => {
245-
return Some(Err(failure::format_err!(
246-
"unexpected character in \
247-
cfg `{}`, expected parens, \
248-
a comma, an identifier, or \
249-
a string",
250-
ch
251-
)));
292+
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));
252293
}
253294
None => return None,
254295
}
@@ -265,7 +306,7 @@ fn is_ident_rest(ch: char) -> bool {
265306
}
266307

267308
impl<'a> Token<'a> {
268-
fn classify(&self) -> &str {
309+
fn classify(&self) -> &'static str {
269310
match *self {
270311
Token::LeftParen => "`(`",
271312
Token::RightParen => "`)`",

0 commit comments

Comments
 (0)