From 57c96c194af60c2e6512c82ccbcbbf5f4c93643a Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 5 Sep 2019 09:02:27 -0700 Subject: [PATCH] Extract Platform to a separate crate. --- Cargo.toml | 1 + azure-pipelines.yml | 2 + ci/azure-test-all.yml | 3 + crates/cargo-platform/Cargo.toml | 13 ++ crates/cargo-platform/examples/matches.rs | 55 ++++++ .../util => crates/cargo-platform/src}/cfg.rs | 147 +++++++++------ crates/cargo-platform/src/error.rs | 70 +++++++ crates/cargo-platform/src/lib.rs | 106 +++++++++++ crates/cargo-platform/tests/test_cfg.rs | 178 ++++++++++++++++++ publish.py | 50 +++++ src/cargo/core/compiler/build_context/mod.rs | 3 +- .../compiler/build_context/target_info.rs | 6 +- src/cargo/core/compiler/compilation.rs | 5 +- src/cargo/core/compiler/custom_build.rs | 2 +- src/cargo/core/dependency.rs | 57 +----- src/cargo/util/mod.rs | 2 - src/cargo/util/toml/mod.rs | 3 +- tests/testsuite/cfg.rs | 151 +-------------- 18 files changed, 589 insertions(+), 265 deletions(-) create mode 100644 crates/cargo-platform/Cargo.toml create mode 100644 crates/cargo-platform/examples/matches.rs rename {src/cargo/util => crates/cargo-platform/src}/cfg.rs (64%) create mode 100644 crates/cargo-platform/src/error.rs create mode 100644 crates/cargo-platform/src/lib.rs create mode 100644 crates/cargo-platform/tests/test_cfg.rs create mode 100755 publish.py diff --git a/Cargo.toml b/Cargo.toml index 814d7ccefbc..3e1781600af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 503ef785266..61d5c15699f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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 diff --git a/ci/azure-test-all.yml b/ci/azure-test-all.yml index 4b4cc9c2134..e682274e75e 100644 --- a/ci/azure-test-all.yml +++ b/ci/azure-test-all.yml @@ -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" diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml new file mode 100644 index 00000000000..03278c64303 --- /dev/null +++ b/crates/cargo-platform/Cargo.toml @@ -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'] } diff --git a/crates/cargo-platform/examples/matches.rs b/crates/cargo-platform/examples/matches.rs new file mode 100644 index 00000000000..9ad5d10dd58 --- /dev/null +++ b/crates/cargo-platform/examples/matches.rs @@ -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 { + 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() +} diff --git a/src/cargo/util/cfg.rs b/crates/cargo-platform/src/cfg.rs similarity index 64% rename from src/cargo/util/cfg.rs rename to crates/cargo-platform/src/cfg.rs index 4c4ad232df4..662c7f97bee 100644 --- a/src/cargo/util/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -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), @@ -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, @@ -28,23 +31,27 @@ enum Token<'a> { String(&'a str), } +#[derive(Clone)] struct Tokenizer<'a> { s: iter::Peekable>, orig: &'a str, } struct Parser<'a> { - t: iter::Peekable>, + t: Tokenizer<'a>, } impl FromStr for Cfg { - type Err = failure::Error; + type Err = ParseError; - fn from_str(s: &str) -> CargoResult { + fn from_str(s: &str) -> Result { 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) } @@ -85,16 +92,16 @@ impl CfgExpr { } impl FromStr for CfgExpr { - type Err = failure::Error; + type Err = ParseError; - fn from_str(s: &str) -> CargoResult { + fn from_str(s: &str) -> Result { 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) } @@ -131,14 +138,13 @@ impl<'a> Parser<'a> { t: Tokenizer { s: s.char_indices().peekable(), orig: s, - } - .peekable(), + }, } } - fn expr(&mut self) -> CargoResult { - match self.t.peek() { - Some(&Ok(Token::Ident(op @ "all"))) | Some(&Ok(Token::Ident(op @ "any"))) => { + fn expr(&mut self) -> Result { + 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)?; @@ -155,31 +161,41 @@ 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 { + fn cfg(&mut self) -> Result { 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 { @@ -187,35 +203,66 @@ impl<'a> Parser<'a> { }; 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, 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>; + type Item = Result, ParseError>; - fn next(&mut self) -> Option>> { + fn next(&mut self) -> Option, ParseError>> { loop { match self.s.next() { Some((_, ' ')) => {} @@ -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() { @@ -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, } @@ -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 => "`)`", diff --git a/crates/cargo-platform/src/error.rs b/crates/cargo-platform/src/error.rs new file mode 100644 index 00000000000..19a15a1e132 --- /dev/null +++ b/crates/cargo-platform/src/error.rs @@ -0,0 +1,70 @@ +use std::fmt; + +#[derive(Debug)] +pub struct ParseError { + kind: ParseErrorKind, + orig: String, +} + +#[derive(Debug)] +pub enum ParseErrorKind { + UnterminatedString, + UnexpectedChar(char), + UnexpectedToken { + expected: &'static str, + found: &'static str, + }, + IncompleteExpr(&'static str), + UnterminatedExpression(String), + InvalidTarget(String), + + #[doc(hidden)] + __Nonexhaustive, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "failed to parse `{}` as a cfg expression: {}", + self.orig, self.kind + ) + } +} + +impl fmt::Display for ParseErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ParseErrorKind::*; + match self { + UnterminatedString => write!(f, "unterminated string in cfg"), + UnexpectedChar(ch) => write!( + f, + "unexpected character `{}` in cfg, expected parens, a comma, \ + an identifier, or a string", + ch + ), + UnexpectedToken { expected, found } => { + write!(f, "expected {}, found {}", expected, found) + } + IncompleteExpr(expected) => { + write!(f, "expected {}, but cfg expression ended", expected) + } + UnterminatedExpression(s) => { + write!(f, "unexpected content `{}` found after cfg expression", s) + } + InvalidTarget(s) => write!(f, "invalid target specifier: {}", s), + __Nonexhaustive => unreachable!(), + } + } +} + +impl std::error::Error for ParseError {} + +impl ParseError { + pub fn new(orig: &str, kind: ParseErrorKind) -> ParseError { + ParseError { + kind, + orig: orig.to_string(), + } + } +} diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs new file mode 100644 index 00000000000..cad3fb49efc --- /dev/null +++ b/crates/cargo-platform/src/lib.rs @@ -0,0 +1,106 @@ +//! Platform definition used by Cargo. +//! +//! This defines a [`Platform`] type which is used in Cargo to specify a target platform. +//! There are two kinds, a named target like `x86_64-apple-darwin`, and a "cfg expression" +//! like `cfg(any(target_os = "macos", target_os = "ios"))`. +//! +//! See `examples/matches.rs` for an example of how to match against a `Platform`. +//! +//! [`Platform`]: enum.Platform.html + +use std::fmt; +use std::str::FromStr; + +mod cfg; +mod error; + +pub use cfg::{Cfg, CfgExpr}; +pub use error::{ParseError, ParseErrorKind}; + +/// Platform definition. +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] +pub enum Platform { + /// A named platform, like `x86_64-apple-darwin`. + Name(String), + /// A cfg expression, like `cfg(windows)`. + Cfg(CfgExpr), +} + +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 { + match *self { + Platform::Name(ref p) => p == name, + Platform::Cfg(ref p) => p.matches(cfg), + } + } + + fn validate_named_platform(name: &str) -> Result<(), ParseError> { + if let Some(ch) = name + .chars() + .find(|&c| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.')) + { + if name.chars().any(|c| c == '(') { + return Err(ParseError::new( + name, + ParseErrorKind::InvalidTarget( + "unexpected `(` character, cfg expressions must start with `cfg(`" + .to_string(), + ), + )); + } + return Err(ParseError::new( + name, + ParseErrorKind::InvalidTarget(format!( + "unexpected character {} in target name", + ch + )), + )); + } + Ok(()) + } +} + +impl serde::Serialize for Platform { + fn serialize(&self, s: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Platform { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl FromStr for Platform { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + if s.starts_with("cfg(") && s.ends_with(')') { + let s = &s[4..s.len() - 1]; + s.parse().map(Platform::Cfg) + } else { + Platform::validate_named_platform(s)?; + Ok(Platform::Name(s.to_string())) + } + } +} + +impl fmt::Display for Platform { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Platform::Name(ref n) => n.fmt(f), + Platform::Cfg(ref e) => write!(f, "cfg({})", e), + } + } +} diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs new file mode 100644 index 00000000000..012109185bc --- /dev/null +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -0,0 +1,178 @@ +use cargo_platform::{Cfg, CfgExpr, Platform}; +use std::fmt; +use std::str::FromStr; + +macro_rules! c { + ($a:ident) => { + Cfg::Name(stringify!($a).to_string()) + }; + ($a:ident = $e:expr) => { + Cfg::KeyPair(stringify!($a).to_string(), $e.to_string()) + }; +} + +macro_rules! e { + (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*])); + (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*])); + (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*)))); + (($($t:tt)*)) => (e!($($t)*)); + ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); +} + +fn good(s: &str, expected: T) +where + T: FromStr + PartialEq + fmt::Debug, + T::Err: fmt::Display, +{ + let c = match T::from_str(s) { + Ok(c) => c, + Err(e) => panic!("failed to parse `{}`: {}", s, e), + }; + assert_eq!(c, expected); +} + +fn bad(s: &str, err: &str) +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), + Err(e) => e.to_string(), + }; + assert!( + e.contains(err), + "when parsing `{}`,\n\"{}\" not contained \ + inside: {}", + s, + err, + e + ); +} + +#[test] +fn cfg_syntax() { + good("foo", c!(foo)); + good("_bar", c!(_bar)); + good(" foo", c!(foo)); + good(" foo ", c!(foo)); + good(" foo = \"bar\"", c!(foo = "bar")); + good("foo=\"\"", c!(foo = "")); + good(" foo=\"3\" ", c!(foo = "3")); + good("foo = \"3 e\"", c!(foo = "3 e")); +} + +#[test] +fn cfg_syntax_bad() { + bad::("", "but cfg expression ended"); + bad::(" ", "but cfg expression ended"); + bad::("\t", "unexpected character"); + bad::("7", "unexpected character"); + bad::("=", "expected identifier"); + bad::(",", "expected identifier"); + bad::("(", "expected identifier"); + bad::("foo (", "unexpected content `(` found after cfg expression"); + bad::("bar =", "expected a string"); + bad::("bar = \"", "unterminated string"); + bad::( + "foo, bar", + "unexpected content `, bar` found after cfg expression", + ); +} + +#[test] +fn cfg_expr() { + good("foo", e!(foo)); + good("_bar", e!(_bar)); + good(" foo", e!(foo)); + good(" foo ", e!(foo)); + good(" foo = \"bar\"", e!(foo = "bar")); + good("foo=\"\"", e!(foo = "")); + good(" foo=\"3\" ", e!(foo = "3")); + good("foo = \"3 e\"", e!(foo = "3 e")); + + good("all()", e!(all())); + good("all(a)", e!(all(a))); + good("all(a, b)", e!(all(a, b))); + good("all(a, )", e!(all(a))); + good("not(a = \"b\")", e!(not(a = "b"))); + good("not(all(a))", e!(not(all(a)))); +} + +#[test] +fn cfg_expr_bad() { + bad::(" ", "but cfg expression ended"); + bad::(" all", "expected `(`"); + bad::("all(a", "expected `)`"); + bad::("not", "expected `(`"); + bad::("not(a", "expected `)`"); + bad::("a = ", "expected a string"); + bad::("all(not())", "expected identifier"); + bad::( + "foo(a)", + "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(&[])); + 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)])); +} + +#[test] +fn bad_target_name() { + bad::( + "any(cfg(unix), cfg(windows))", + "failed to parse `any(cfg(unix), cfg(windows))` as a cfg expression: \ + invalid target specifier: unexpected `(` character, \ + cfg expressions must start with `cfg(`", + ); + bad::( + "!foo", + "failed to parse `!foo` as a cfg expression: \ + invalid target specifier: unexpected character ! in target name", + ); +} + +#[test] +fn round_trip_platform() { + fn rt(s: &str) { + let p = Platform::from_str(s).unwrap(); + let s2 = p.to_string(); + let p2 = Platform::from_str(&s2).unwrap(); + assert_eq!(p, p2); + } + rt("x86_64-apple-darwin"); + rt("foo"); + rt("cfg(windows)"); + rt("cfg(target_os = \"windows\")"); + rt( + "cfg(any(all(any(target_os = \"android\", target_os = \"linux\"), \ + any(target_arch = \"aarch64\", target_arch = \"arm\", target_arch = \"powerpc64\", \ + target_arch = \"x86\", target_arch = \"x86_64\")), \ + all(target_os = \"freebsd\", target_arch = \"x86_64\")))", + ); +} diff --git a/publish.py b/publish.py new file mode 100755 index 00000000000..01bdf37cd65 --- /dev/null +++ b/publish.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# This script is used to publish Cargo to crates.io. + +import os +import re +import subprocess +import urllib.request +from urllib.error import HTTPError + + +TO_PUBLISH = [ + 'crates/cargo-platform', + 'crates/crates-io', + '.', +] + + +def already_published(name, version): + try: + urllib.request.urlopen('https://crates.io/api/v1/crates/%s/%s/download' % (name, version)) + except HTTPError as e: + if e.code == 404: + return False + raise + return True + + +def maybe_publish(path): + content = open(os.path.join(path, 'Cargo.toml')).read() + name = re.search('^name = "([^"]+)"', content, re.M).group(1) + version = re.search('^version = "([^"]+)"', content, re.M).group(1) + if already_published(name, version): + print('%s %s is already published, skipping' % (name, version)) + return + subprocess.check_call(['cargo', 'publish', '--no-verify'], cwd=path) + + +def main(): + print('Doing dry run first...') + for path in TO_PUBLISH: + subprocess.check_call(['cargo', 'publish', '--no-verify', '--dry-run'], cwd=path) + print('Starting publish...') + for path in TO_PUBLISH: + maybe_publish(path) + print('Publish complete!') + + +if __name__ == '__main__': + main() diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs index 9cb34e3390e..7333b08c680 100644 --- a/src/cargo/core/compiler/build_context/mod.rs +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::str; +use cargo_platform::Cfg; use log::debug; use crate::core::compiler::unit::UnitInterner; @@ -10,7 +11,7 @@ use crate::core::profiles::Profiles; use crate::core::{Dependency, Workspace}; use crate::core::{PackageId, PackageSet}; use crate::util::errors::CargoResult; -use crate::util::{profile, Cfg, Config, Rustc}; +use crate::util::{profile, Config, Rustc}; mod target_info; pub use self::target_info::{FileFlavor, TargetInfo}; diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 926a1ca9664..25095b3324e 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -6,8 +6,8 @@ use std::str::{self, FromStr}; use crate::core::compiler::Kind; use crate::core::TargetKind; -use crate::util::CfgExpr; -use crate::util::{CargoResult, CargoResultExt, Cfg, Config, ProcessBuilder, Rustc}; +use crate::util::{CargoResult, CargoResultExt, Config, ProcessBuilder, Rustc}; +use cargo_platform::{Cfg, CfgExpr}; /// Information about the platform target gleaned from querying rustc. /// @@ -171,7 +171,7 @@ impl TargetInfo { }; let cfg = lines - .map(Cfg::from_str) + .map(|line| Ok(Cfg::from_str(line)?)) .collect::>>() .chain_err(|| { format!( diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index dcd2e1923bf..1e9b7e52fa7 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -3,13 +3,12 @@ use std::env; use std::ffi::OsStr; use std::path::PathBuf; +use cargo_platform::CfgExpr; use semver::Version; use super::BuildContext; use crate::core::{Edition, InternedString, Package, PackageId, Target}; -use crate::util::{ - self, join_paths, process, rustc::Rustc, CargoResult, CfgExpr, Config, ProcessBuilder, -}; +use crate::util::{self, join_paths, process, rustc::Rustc, CargoResult, Config, ProcessBuilder}; pub struct Doctest { /// The package being doc-tested. diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 93b958c76a6..f727087ccfa 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -1,3 +1,4 @@ +use cargo_platform::Cfg; use std::collections::hash_map::{Entry, HashMap}; use std::collections::{BTreeSet, HashSet}; use std::path::{Path, PathBuf}; @@ -8,7 +9,6 @@ use crate::core::compiler::job_queue::JobState; use crate::core::PackageId; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::machine_message::{self, Message}; -use crate::util::Cfg; use crate::util::{self, internal, paths, profile}; use super::job::{Freshness, Job, Work}; diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index e550c41362e..48b1a2eb2ef 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -1,17 +1,15 @@ -use std::fmt; -use std::rc::Rc; -use std::str::FromStr; - +use cargo_platform::Platform; use log::trace; use semver::ReqParseError; use semver::VersionReq; use serde::ser; use serde::Serialize; +use std::rc::Rc; use crate::core::interning::InternedString; use crate::core::{PackageId, SourceId, Summary}; use crate::util::errors::{CargoResult, CargoResultExt}; -use crate::util::{Cfg, CfgExpr, Config}; +use crate::util::Config; /// Information about a dependency requested by a Cargo manifest. /// Cheap to copy. @@ -48,12 +46,6 @@ struct Inner { platform: Option, } -#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] -pub enum Platform { - Name(String), - Cfg(CfgExpr), -} - #[derive(Serialize)] struct SerializedDependency<'a> { name: &'a str, @@ -459,46 +451,3 @@ impl Dependency { } } } - -impl Platform { - pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool { - match *self { - Platform::Name(ref p) => p == name, - Platform::Cfg(ref p) => p.matches(cfg), - } - } -} - -impl ser::Serialize for Platform { - fn serialize(&self, s: S) -> Result - where - S: ser::Serializer, - { - self.to_string().serialize(s) - } -} - -impl FromStr for Platform { - type Err = failure::Error; - - fn from_str(s: &str) -> CargoResult { - if s.starts_with("cfg(") && s.ends_with(')') { - let s = &s[4..s.len() - 1]; - let p = s.parse().map(Platform::Cfg).chain_err(|| { - failure::format_err!("failed to parse `{}` as a cfg expression", s) - })?; - Ok(p) - } else { - Ok(Platform::Name(s.to_string())) - } - } -} - -impl fmt::Display for Platform { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Platform::Name(ref n) => n.fmt(f), - Platform::Cfg(ref e) => write!(f, "cfg({})", e), - } - } -} diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index b35e1c12958..e2d35e48185 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -1,7 +1,6 @@ use std::time::Duration; pub use self::canonical_url::CanonicalUrl; -pub use self::cfg::{Cfg, CfgExpr}; pub use self::config::{homedir, Config, ConfigValue}; pub use self::dependency_queue::DependencyQueue; pub use self::diagnostic_server::RustfixDiagnosticServer; @@ -30,7 +29,6 @@ pub use self::workspace::{ }; mod canonical_url; -mod cfg; pub mod command_prelude; pub mod config; mod dependency_queue; diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index d66ea182c1c..ff3eedaec6e 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::str; +use cargo_platform::Platform; use failure::bail; use log::{debug, trace}; use semver::{self, VersionReq}; @@ -13,7 +14,7 @@ use serde::ser; use serde::{Deserialize, Serialize}; use url::Url; -use crate::core::dependency::{Kind, Platform}; +use crate::core::dependency::Kind; use crate::core::manifest::{LibKind, ManifestMetadata, TargetSourcePath, Warnings}; use crate::core::profiles::Profiles; use crate::core::{Dependency, InternedString, Manifest, PackageId, Summary, Target}; diff --git a/tests/testsuite/cfg.rs b/tests/testsuite/cfg.rs index abba59e3523..2e8aae19582 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -1,145 +1,7 @@ -use std::fmt; -use std::str::FromStr; - -use cargo::util::{Cfg, CfgExpr}; use cargo_test_support::registry::Package; use cargo_test_support::rustc_host; use cargo_test_support::{basic_manifest, project}; -macro_rules! c { - ($a:ident) => { - Cfg::Name(stringify!($a).to_string()) - }; - ($a:ident = $e:expr) => { - Cfg::KeyPair(stringify!($a).to_string(), $e.to_string()) - }; -} - -macro_rules! e { - (any($($t:tt),*)) => (CfgExpr::Any(vec![$(e!($t)),*])); - (all($($t:tt),*)) => (CfgExpr::All(vec![$(e!($t)),*])); - (not($($t:tt)*)) => (CfgExpr::Not(Box::new(e!($($t)*)))); - (($($t:tt)*)) => (e!($($t)*)); - ($($t:tt)*) => (CfgExpr::Value(c!($($t)*))); -} - -fn good(s: &str, expected: T) -where - T: FromStr + PartialEq + fmt::Debug, - T::Err: fmt::Display, -{ - let c = match T::from_str(s) { - Ok(c) => c, - Err(e) => panic!("failed to parse `{}`: {}", s, e), - }; - assert_eq!(c, expected); -} - -fn bad(s: &str, err: &str) -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), - Err(e) => e.to_string(), - }; - assert!( - e.contains(err), - "when parsing `{}`,\n\"{}\" not contained \ - inside: {}", - s, - err, - e - ); -} - -#[cargo_test] -fn cfg_syntax() { - good("foo", c!(foo)); - good("_bar", c!(_bar)); - good(" foo", c!(foo)); - good(" foo ", c!(foo)); - good(" foo = \"bar\"", c!(foo = "bar")); - good("foo=\"\"", c!(foo = "")); - good(" foo=\"3\" ", c!(foo = "3")); - good("foo = \"3 e\"", c!(foo = "3 e")); -} - -#[cargo_test] -fn cfg_syntax_bad() { - bad::("", "found nothing"); - bad::(" ", "found nothing"); - bad::("\t", "unexpected character"); - bad::("7", "unexpected character"); - bad::("=", "expected identifier"); - bad::(",", "expected identifier"); - bad::("(", "expected identifier"); - bad::("foo (", "malformed cfg value"); - bad::("bar =", "expected a string"); - bad::("bar = \"", "unterminated string"); - bad::("foo, bar", "malformed cfg value"); -} - -#[cargo_test] -fn cfg_expr() { - good("foo", e!(foo)); - good("_bar", e!(_bar)); - good(" foo", e!(foo)); - good(" foo ", e!(foo)); - good(" foo = \"bar\"", e!(foo = "bar")); - good("foo=\"\"", e!(foo = "")); - good(" foo=\"3\" ", e!(foo = "3")); - good("foo = \"3 e\"", e!(foo = "3 e")); - - good("all()", e!(all())); - good("all(a)", e!(all(a))); - good("all(a, b)", e!(all(a, b))); - good("all(a, )", e!(all(a))); - good("not(a = \"b\")", e!(not(a = "b"))); - good("not(all(a))", e!(not(all(a)))); -} - -#[cargo_test] -fn cfg_expr_bad() { - bad::(" ", "found nothing"); - bad::(" all", "expected `(`"); - bad::("all(a", "expected `)`"); - bad::("not", "expected `(`"); - bad::("not(a", "expected `)`"); - bad::("a = ", "expected a string"); - bad::("all(not())", "expected identifier"); - bad::("foo(a)", "consider using all() or any() explicitly"); -} - -#[cargo_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(&[])); - 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)])); -} - #[cargo_test] fn cfg_easy() { let p = project() @@ -311,10 +173,7 @@ fn bad_target_spec() { [ERROR] failed to parse manifest at `[..]` Caused by: - failed to parse `4` as a cfg expression - -Caused by: - unexpected character in cfg `4`, [..] + failed to parse `4` as a cfg expression: unexpected character `4` in cfg, [..] ", ) .run(); @@ -345,10 +204,7 @@ fn bad_target_spec2() { [ERROR] failed to parse manifest at `[..]` Caused by: - failed to parse `bar =` as a cfg expression - -Caused by: - expected a string, found nothing + failed to parse `bar =` as a cfg expression: expected a string, but cfg expression ended ", ) .run(); @@ -576,7 +432,8 @@ command was: `[..]compiler[..]--crate-type [..]` Caused by: - unexpected character in cfg `1`, expected parens, a comma, an identifier, or a string + failed to parse `123` as a cfg expression: unexpected character `1` in cfg, \ + expected parens, a comma, an identifier, or a string ", ) .run();