Skip to content
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

Extract Platform to a separate crate. #7375

Merged
merged 1 commit into from
Sep 20, 2019
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ path = "src/cargo/lib.rs"
[dependencies]
atty = "0.2"
bytesize = "1.0"
cargo-platform = { path = "crates/cargo-platform", version = "0.1" }
crates-io = { path = "crates/crates-io", version = "0.28" }
crossbeam-utils = "0.6"
crypto-hash = "0.3.1"
Expand Down
2 changes: 2 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
displayName: "Check rustfmt (crates-io)"
- bash: cd crates/resolver-tests && cargo fmt --all -- --check
displayName: "Check rustfmt (resolver-tests)"
- bash: cd crates/cargo-platform && cargo fmt --all -- --check
displayName: "Check rustfmt (cargo-platform)"
variables:
TOOLCHAIN: stable

Expand Down
3 changes: 3 additions & 0 deletions ci/azure-test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ steps:

- bash: cargo test -p cargo-test-support
displayName: "cargo test -p cargo-test-support"

- bash: cargo test -p cargo-platform
displayName: "cargo test -p cargo-platform"
13 changes: 13 additions & 0 deletions crates/cargo-platform/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "cargo-platform"
version = "0.1.0"
authors = ["The Cargo Project Developers"]
edition = "2018"
license = "MIT OR Apache-2.0"
homepage = "https://github.com/rust-lang/cargo"
repository = "https://github.com/rust-lang/cargo"
documentation = "https://docs.rs/cargo-platform"
description = "Cargo's representation of a target platform."

[dependencies]
serde = { version = "1.0.82", features = ['derive'] }
55 changes: 55 additions & 0 deletions crates/cargo-platform/examples/matches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! This example demonstrates how to filter a Platform based on the current
//! host target.

use cargo_platform::{Cfg, Platform};
use std::process::Command;
use std::str::FromStr;

static EXAMPLES: &[&str] = &[
"cfg(windows)",
"cfg(unix)",
"cfg(target_os=\"macos\")",
"cfg(target_os=\"linux\")",
"cfg(any(target_arch=\"x86\", target_arch=\"x86_64\"))",
];

fn main() {
let target = get_target();
let cfgs = get_cfgs();
println!("host target={} cfgs:", target);
for cfg in &cfgs {
println!(" {}", cfg);
}
let mut examples: Vec<&str> = EXAMPLES.iter().copied().collect();
examples.push(target.as_str());
for example in examples {
let p = Platform::from_str(example).unwrap();
println!("{:?} matches: {:?}", example, p.matches(&target, &cfgs));
}
}

fn get_target() -> String {
let output = Command::new("rustc")
.arg("-Vv")
.output()
.expect("rustc failed to run");
let stdout = String::from_utf8(output.stdout).unwrap();
for line in stdout.lines() {
if line.starts_with("host: ") {
return String::from(&line[6..]);
}
}
panic!("Failed to find host: {}", stdout);
}

fn get_cfgs() -> Vec<Cfg> {
let output = Command::new("rustc")
.arg("--print=cfg")
.output()
.expect("rustc failed to run");
let stdout = String::from_utf8(output.stdout).unwrap();
stdout
.lines()
.map(|line| Cfg::from_str(line).unwrap())
.collect()
}
147 changes: 94 additions & 53 deletions src/cargo/util/cfg.rs → crates/cargo-platform/src/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
use crate::error::{ParseError, ParseErrorKind::*};
use std::fmt;
use std::iter;
use std::str::{self, FromStr};

use crate::util::CargoResult;

#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
pub enum Cfg {
Name(String),
KeyPair(String, String),
}

/// A cfg expression.
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
pub enum CfgExpr {
Not(Box<CfgExpr>),
Expand All @@ -18,6 +12,15 @@ pub enum CfgExpr {
Value(Cfg),
}

/// A cfg value.
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
pub enum Cfg {
/// A named cfg value, like `unix`.
Name(String),
/// A key/value cfg pair, like `target_os = "linux"`.
KeyPair(String, String),
}

#[derive(PartialEq)]
enum Token<'a> {
LeftParen,
Expand All @@ -28,23 +31,27 @@ enum Token<'a> {
String(&'a str),
}

#[derive(Clone)]
struct Tokenizer<'a> {
s: iter::Peekable<str::CharIndices<'a>>,
orig: &'a str,
}

struct Parser<'a> {
t: iter::Peekable<Tokenizer<'a>>,
t: Tokenizer<'a>,
}

impl FromStr for Cfg {
type Err = failure::Error;
type Err = ParseError;

fn from_str(s: &str) -> CargoResult<Cfg> {
fn from_str(s: &str) -> Result<Cfg, Self::Err> {
let mut p = Parser::new(s);
let e = p.cfg()?;
if p.t.next().is_some() {
failure::bail!("malformed cfg value or key/value pair: `{}`", s)
if let Some(rest) = p.rest() {
return Err(ParseError::new(
p.t.orig,
UnterminatedExpression(rest.to_string()),
));
}
Ok(e)
}
Expand Down Expand Up @@ -85,16 +92,16 @@ impl CfgExpr {
}

impl FromStr for CfgExpr {
type Err = failure::Error;
type Err = ParseError;

fn from_str(s: &str) -> CargoResult<CfgExpr> {
fn from_str(s: &str) -> Result<CfgExpr, Self::Err> {
let mut p = Parser::new(s);
let e = p.expr()?;
if p.t.next().is_some() {
failure::bail!(
"can only have one cfg-expression, consider using all() or \
any() explicitly"
)
if let Some(rest) = p.rest() {
return Err(ParseError::new(
p.t.orig,
UnterminatedExpression(rest.to_string()),
));
}
Ok(e)
}
Expand Down Expand Up @@ -131,14 +138,13 @@ impl<'a> Parser<'a> {
t: Tokenizer {
s: s.char_indices().peekable(),
orig: s,
}
.peekable(),
},
}
}

fn expr(&mut self) -> CargoResult<CfgExpr> {
match self.t.peek() {
Some(&Ok(Token::Ident(op @ "all"))) | Some(&Ok(Token::Ident(op @ "any"))) => {
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
match self.peek() {
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
self.t.next();
let mut e = Vec::new();
self.eat(&Token::LeftParen)?;
Expand All @@ -155,67 +161,108 @@ impl<'a> Parser<'a> {
Ok(CfgExpr::Any(e))
}
}
Some(&Ok(Token::Ident("not"))) => {
Some(Ok(Token::Ident("not"))) => {
self.t.next();
self.eat(&Token::LeftParen)?;
let e = self.expr()?;
self.eat(&Token::RightParen)?;
Ok(CfgExpr::Not(Box::new(e)))
}
Some(&Ok(..)) => self.cfg().map(CfgExpr::Value),
Some(&Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
None => failure::bail!(
"expected start of a cfg expression, \
found nothing"
),
Some(Ok(..)) => self.cfg().map(CfgExpr::Value),
Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
None => Err(ParseError::new(
self.t.orig,
IncompleteExpr("start of a cfg expression"),
)),
}
}

fn cfg(&mut self) -> CargoResult<Cfg> {
fn cfg(&mut self) -> Result<Cfg, ParseError> {
match self.t.next() {
Some(Ok(Token::Ident(name))) => {
let e = if self.r#try(&Token::Equals) {
let val = match self.t.next() {
Some(Ok(Token::String(s))) => s,
Some(Ok(t)) => failure::bail!("expected a string, found {}", t.classify()),
Some(Ok(t)) => {
return Err(ParseError::new(
self.t.orig,
UnexpectedToken {
expected: "a string",
found: t.classify(),
},
))
}
Some(Err(e)) => return Err(e),
None => failure::bail!("expected a string, found nothing"),
None => {
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
}
};
Cfg::KeyPair(name.to_string(), val.to_string())
} else {
Cfg::Name(name.to_string())
};
Ok(e)
}
Some(Ok(t)) => failure::bail!("expected identifier, found {}", t.classify()),
Some(Ok(t)) => Err(ParseError::new(
self.t.orig,
UnexpectedToken {
expected: "identifier",
found: t.classify(),
},
)),
Some(Err(e)) => Err(e),
None => failure::bail!("expected identifier, found nothing"),
None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))),
}
}

fn peek(&mut self) -> Option<Result<Token<'a>, ParseError>> {
self.t.clone().next()
}

fn r#try(&mut self, token: &Token<'a>) -> bool {
match self.t.peek() {
Some(&Ok(ref t)) if token == t => {}
match self.peek() {
Some(Ok(ref t)) if token == t => {}
_ => return false,
}
self.t.next();
true
}

fn eat(&mut self, token: &Token<'a>) -> CargoResult<()> {
fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> {
match self.t.next() {
Some(Ok(ref t)) if token == t => Ok(()),
Some(Ok(t)) => failure::bail!("expected {}, found {}", token.classify(), t.classify()),
Some(Ok(t)) => Err(ParseError::new(
self.t.orig,
UnexpectedToken {
expected: token.classify(),
found: t.classify(),
},
)),
Some(Err(e)) => Err(e),
None => failure::bail!("expected {}, but cfg expr ended", token.classify()),
None => Err(ParseError::new(
self.t.orig,
IncompleteExpr(token.classify()),
)),
}
}

/// Returns the rest of the input from the current location.
fn rest(&self) -> Option<&str> {
let mut s = self.t.s.clone();
loop {
match s.next() {
Some((_, ' ')) => {}
Some((start, _ch)) => return Some(&self.t.orig[start..]),
None => return None,
}
}
}
}

impl<'a> Iterator for Tokenizer<'a> {
type Item = CargoResult<Token<'a>>;
type Item = Result<Token<'a>, ParseError>;

fn next(&mut self) -> Option<CargoResult<Token<'a>>> {
fn next(&mut self) -> Option<Result<Token<'a>, ParseError>> {
loop {
match self.s.next() {
Some((_, ' ')) => {}
Expand All @@ -229,7 +276,7 @@ impl<'a> Iterator for Tokenizer<'a> {
return Some(Ok(Token::String(&self.orig[start + 1..end])));
}
}
return Some(Err(failure::format_err!("unterminated string in cfg")));
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
}
Some((start, ch)) if is_ident_start(ch) => {
while let Some(&(end, ch)) = self.s.peek() {
Expand All @@ -242,13 +289,7 @@ impl<'a> Iterator for Tokenizer<'a> {
return Some(Ok(Token::Ident(&self.orig[start..])));
}
Some((_, ch)) => {
return Some(Err(failure::format_err!(
"unexpected character in \
cfg `{}`, expected parens, \
a comma, an identifier, or \
a string",
ch
)));
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));
}
None => return None,
}
Expand All @@ -265,7 +306,7 @@ fn is_ident_rest(ch: char) -> bool {
}

impl<'a> Token<'a> {
fn classify(&self) -> &str {
fn classify(&self) -> &'static str {
match *self {
Token::LeftParen => "`(`",
Token::RightParen => "`)`",
Expand Down
Loading