1 | //! Platform definition used by Cargo. |
2 | //! |
3 | //! This defines a [`Platform`] type which is used in Cargo to specify a target platform. |
4 | //! There are two kinds, a named target like `x86_64-apple-darwin`, and a "cfg expression" |
5 | //! like `cfg(any(target_os = "macos", target_os = "ios"))`. |
6 | //! |
7 | //! See `examples/matches.rs` for an example of how to match against a `Platform`. |
8 | //! |
9 | //! [`Platform`]: enum.Platform.html |
10 | |
11 | use std::fmt; |
12 | use std::str::FromStr; |
13 | |
14 | mod cfg; |
15 | mod error; |
16 | |
17 | pub use cfg::{Cfg, CfgExpr}; |
18 | pub use error::{ParseError, ParseErrorKind}; |
19 | |
20 | /// Platform definition. |
21 | #[derive (Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] |
22 | pub enum Platform { |
23 | /// A named platform, like `x86_64-apple-darwin`. |
24 | Name(String), |
25 | /// A cfg expression, like `cfg(windows)`. |
26 | Cfg(CfgExpr), |
27 | } |
28 | |
29 | impl Platform { |
30 | /// Returns whether the Platform matches the given target and cfg. |
31 | /// |
32 | /// The named target and cfg values should be obtained from `rustc`. |
33 | pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool { |
34 | match *self { |
35 | Platform::Name(ref p) => p == name, |
36 | Platform::Cfg(ref p) => p.matches(cfg), |
37 | } |
38 | } |
39 | |
40 | fn validate_named_platform(name: &str) -> Result<(), ParseError> { |
41 | if let Some(ch) = name |
42 | .chars() |
43 | .find(|&c| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.' )) |
44 | { |
45 | if name.chars().any(|c| c == '(' ) { |
46 | return Err(ParseError::new( |
47 | name, |
48 | ParseErrorKind::InvalidTarget( |
49 | "unexpected `(` character, cfg expressions must start with `cfg(`" |
50 | .to_string(), |
51 | ), |
52 | )); |
53 | } |
54 | return Err(ParseError::new( |
55 | name, |
56 | ParseErrorKind::InvalidTarget(format!( |
57 | "unexpected character {} in target name" , |
58 | ch |
59 | )), |
60 | )); |
61 | } |
62 | Ok(()) |
63 | } |
64 | |
65 | pub fn check_cfg_attributes(&self, warnings: &mut Vec<String>) { |
66 | fn check_cfg_expr(expr: &CfgExpr, warnings: &mut Vec<String>) { |
67 | match *expr { |
68 | CfgExpr::Not(ref e) => check_cfg_expr(e, warnings), |
69 | CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { |
70 | for e in e { |
71 | check_cfg_expr(e, warnings); |
72 | } |
73 | } |
74 | CfgExpr::Value(ref e) => match e { |
75 | Cfg::Name(name) => match name.as_str() { |
76 | "test" | "debug_assertions" | "proc_macro" => |
77 | warnings.push(format!( |
78 | "Found ` {}` in `target.'cfg(...)'.dependencies`. \ |
79 | This value is not supported for selecting dependencies \ |
80 | and will not work as expected. \ |
81 | To learn more visit \ |
82 | https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies" , |
83 | name |
84 | )), |
85 | _ => (), |
86 | }, |
87 | Cfg::KeyPair(name, _) => if name.as_str() == "feature" { |
88 | warnings.push(String::from( |
89 | "Found `feature = ...` in `target.'cfg(...)'.dependencies`. \ |
90 | This key is not supported for selecting dependencies \ |
91 | and will not work as expected. \ |
92 | Use the [features] section instead: \ |
93 | https://doc.rust-lang.org/cargo/reference/features.html" |
94 | )) |
95 | }, |
96 | } |
97 | } |
98 | } |
99 | |
100 | if let Platform::Cfg(cfg) = self { |
101 | check_cfg_expr(cfg, warnings); |
102 | } |
103 | } |
104 | } |
105 | |
106 | impl serde::Serialize for Platform { |
107 | fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> |
108 | where |
109 | S: serde::Serializer, |
110 | { |
111 | self.to_string().serialize(serializer:s) |
112 | } |
113 | } |
114 | |
115 | impl<'de> serde::Deserialize<'de> for Platform { |
116 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
117 | where |
118 | D: serde::Deserializer<'de>, |
119 | { |
120 | let s: String = String::deserialize(deserializer)?; |
121 | FromStr::from_str(&s).map_err(op:serde::de::Error::custom) |
122 | } |
123 | } |
124 | |
125 | impl FromStr for Platform { |
126 | type Err = ParseError; |
127 | |
128 | fn from_str(s: &str) -> Result<Platform, ParseError> { |
129 | if s.starts_with("cfg(" ) && s.ends_with(')' ) { |
130 | let s: &str = &s[4..s.len() - 1]; |
131 | s.parse().map(op:Platform::Cfg) |
132 | } else { |
133 | Platform::validate_named_platform(name:s)?; |
134 | Ok(Platform::Name(s.to_string())) |
135 | } |
136 | } |
137 | } |
138 | |
139 | impl fmt::Display for Platform { |
140 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
141 | match *self { |
142 | Platform::Name(ref n: &String) => n.fmt(f), |
143 | Platform::Cfg(ref e: &CfgExpr) => write!(f, "cfg( {})" , e), |
144 | } |
145 | } |
146 | } |
147 | |