target_spec/platform.rs
1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{Error, Triple};
5use std::{borrow::Cow, collections::BTreeSet, ops::Deref};
6
7// This is generated by the build script.
8include!(concat!(env!("OUT_DIR"), "/build_target.rs"));
9
10/// A platform to evaluate target specifications against.
11///
12/// # Standard and custom platforms
13///
14/// `target-spec` recognizes two kinds of platforms:
15///
16/// * **Standard platforms:** These platforms are only specified by their triple string. For
17/// example, the platform `x86_64-unknown-linux-gnu` is a standard platform since it is recognized
18/// by Rust as a tier 1 platform.
19///
20/// All [builtin platforms](https://doc.rust-lang.org/nightly/rustc/platform-support.html) are
21/// standard platforms.
22///
23/// By default, if a platform isn't builtin, target-spec attempts to heuristically determine the
24/// characteristics of the platform based on the triple string. (Use the
25/// [`new_strict`](Self::new_strict) constructor to disable this.)
26///
27/// * **Custom platforms:** These platforms are specified via a
28/// triple string and either a JSON file in the format
29/// [defined by
30/// Rust](https://docs.rust-embedded.org/embedonomicon/custom-target.html),
31/// or via `rustc --print=cfg` output. Custom platforms are
32/// used for targets not recognized by Rust.
33#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
34#[must_use]
35pub struct Platform {
36 triple: Triple,
37 target_features: TargetFeatures,
38 flags: BTreeSet<Cow<'static, str>>,
39}
40
41impl Platform {
42 /// Creates a new standard `Platform` from the given triple and target features.
43 ///
44 /// Returns an error if this platform wasn't known to `target-spec`.
45 pub fn new(
46 triple_str: impl Into<Cow<'static, str>>,
47 target_features: TargetFeatures,
48 ) -> Result<Self, Error> {
49 let triple = Triple::new(triple_str.into()).map_err(Error::UnknownPlatformTriple)?;
50 Ok(Self::from_triple(triple, target_features))
51 }
52
53 /// Creates a new standard `Platform` from the given triple and target features.
54 ///
55 /// This constructor only consults the builtin platform table, and does not attempt to
56 /// heuristically determine the platform's characteristics based on the triple string.
57 pub fn new_strict(
58 triple_str: impl Into<Cow<'static, str>>,
59 target_features: TargetFeatures,
60 ) -> Result<Self, Error> {
61 let triple = Triple::new_strict(triple_str.into()).map_err(Error::UnknownPlatformTriple)?;
62 Ok(Self::from_triple(triple, target_features))
63 }
64
65 /// Creates a new standard `Platform` from `rustc -vV` output and the given
66 /// target features.
67 pub fn from_rustc_version_verbose(
68 output: impl AsRef<[u8]>,
69 target_features: TargetFeatures,
70 ) -> Result<Self, Error> {
71 let triple = Triple::from_rustc_version_verbose(output.as_ref())?;
72 Ok(Self::from_triple(triple, target_features))
73 }
74
75 /// Previous name for [`Self::build_target`], renamed to clarify what
76 /// `current` means.
77 ///
78 /// This method is deprecated and will be removed in a future version.
79 #[deprecated(
80 since = "3.4.0",
81 note = "this method has been renamed to `build_target`"
82 )]
83 #[inline]
84 pub fn current() -> Result<Self, Error> {
85 Self::build_target()
86 }
87
88 /// Returns the target platform, as detected at build time.
89 ///
90 /// In case of cross-compilation, this will be the **target platform**, not
91 /// the host platform.
92 ///
93 /// Some target platforms are runtime cross-compatible, e.g.
94 /// `x86_64-unknown-linux-musl` binaries can be run on
95 /// `x86_64-unknown-linux-gnu`. In that case, this function returns
96 /// `x86_64-unknown-linux-musl`. To obtain `x86_64-unknown-linux-gnu`, run
97 /// `${RUSTC:-rustc} -vV` and pass it into [`Self::from_rustc_version_verbose`].
98 ///
99 /// This is currently always a standard platform, and will return an error if the current
100 /// platform was unknown to this version of `target-spec`.
101 ///
102 /// # Notes
103 ///
104 /// In the future, this constructor may also support custom platforms. This will not be
105 /// considered a breaking change.
106 pub fn build_target() -> Result<Self, Error> {
107 let triple = Triple::new(BUILD_TARGET).map_err(Error::UnknownPlatformTriple)?;
108 let target_features = TargetFeatures::features(BUILD_TARGET_FEATURES.iter().copied());
109 Ok(Self {
110 triple,
111 target_features,
112 flags: BTreeSet::new(),
113 })
114 }
115
116 /// Creates a new standard platform from a `Triple` and target features.
117 pub fn from_triple(triple: Triple, target_features: TargetFeatures) -> Self {
118 Self {
119 triple,
120 target_features,
121 flags: BTreeSet::new(),
122 }
123 }
124
125 /// Creates a new custom `Platform` from the given triple, platform, and target features.
126 #[cfg(feature = "custom")]
127 pub fn new_custom(
128 triple_str: impl Into<Cow<'static, str>>,
129 json: &str,
130 target_features: TargetFeatures,
131 ) -> Result<Self, Error> {
132 let triple = Triple::new_custom(triple_str, json).map_err(Error::CustomPlatformCreate)?;
133 Ok(Self {
134 triple,
135 target_features,
136 flags: BTreeSet::new(),
137 })
138 }
139
140 /// Creates a new custom `Platform` from the given triple,
141 /// `rustc --print=cfg` output, and target features.
142 #[cfg(feature = "custom-cfg")]
143 pub fn new_custom_cfg(
144 triple_str: impl Into<Cow<'static, str>>,
145 cfg_text: &str,
146 target_features: TargetFeatures,
147 ) -> Result<Self, Error> {
148 let triple =
149 Triple::new_custom_cfg(triple_str, cfg_text).map_err(Error::CustomPlatformCreate)?;
150 Ok(Self {
151 triple,
152 target_features,
153 flags: BTreeSet::new(),
154 })
155 }
156
157 /// Adds a set of flags to accept.
158 ///
159 /// A flag is a single token like the `foo` in `cfg(not(foo))`.
160 ///
161 /// A default `cargo build` will always evaluate flags to false, but custom wrappers may cause
162 /// some flags to evaluate to true. For example, as of version 0.6, `cargo web build` will cause
163 /// `cargo_web` to evaluate to true.
164 pub fn add_flags(&mut self, flags: impl IntoIterator<Item = impl Into<Cow<'static, str>>>) {
165 self.flags.extend(flags.into_iter().map(|s| s.into()));
166 }
167
168 /// Returns the target triple string for this platform.
169 pub fn triple_str(&self) -> &str {
170 self.triple.as_str()
171 }
172
173 /// Returns the set of flags enabled for this platform.
174 pub fn flags(&self) -> impl ExactSizeIterator<Item = &str> {
175 self.flags.iter().map(|flag| flag.deref())
176 }
177
178 /// Returns true if this flag was set with `add_flags`.
179 pub fn has_flag(&self, flag: impl AsRef<str>) -> bool {
180 self.flags.contains(flag.as_ref())
181 }
182
183 /// Returns true if this is a standard platform.
184 ///
185 /// A standard platform can be either builtin, or heuristically determined.
186 ///
187 /// # Examples
188 ///
189 /// ```
190 /// use target_spec::{Platform, TargetFeatures};
191 ///
192 /// // x86_64-unknown-linux-gnu is Linux x86_64.
193 /// let platform = Platform::new("x86_64-unknown-linux-gnu", TargetFeatures::Unknown).unwrap();
194 /// assert!(platform.is_standard());
195 /// ```
196 pub fn is_standard(&self) -> bool {
197 self.triple.is_standard()
198 }
199
200 /// Returns true if this is a builtin platform.
201 ///
202 /// All builtin platforms are standard, but not all standard platforms are builtin.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use target_spec::{Platform, TargetFeatures};
208 ///
209 /// // x86_64-unknown-linux-gnu is Linux x86_64, which is a Rust tier 1 platform.
210 /// let platform = Platform::new("x86_64-unknown-linux-gnu", TargetFeatures::Unknown).unwrap();
211 /// assert!(platform.is_builtin());
212 /// ```
213 pub fn is_builtin(&self) -> bool {
214 self.triple.is_builtin()
215 }
216
217 /// Returns true if this is a heuristically determined platform.
218 ///
219 /// All heuristically determined platforms are standard, but most of the time, standard
220 /// platforms are builtin.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use target_spec::{Platform, TargetFeatures};
226 ///
227 /// // armv5te-apple-darwin is not a real platform, but target-spec can heuristically
228 /// // guess at its characteristics.
229 /// let platform = Platform::new("armv5te-apple-darwin", TargetFeatures::Unknown).unwrap();
230 /// assert!(platform.is_heuristic());
231 /// ```
232 pub fn is_heuristic(&self) -> bool {
233 self.triple.is_heuristic()
234 }
235
236 /// Returns true if this is a custom platform.
237 ///
238 /// This is always available, but if neither the `custom` nor
239 /// `custom-cfg` feature is turned on, this always returns
240 /// false.
241 pub fn is_custom(&self) -> bool {
242 self.triple.is_custom()
243 }
244
245 /// Returns the underlying [`Triple`].
246 pub fn triple(&self) -> &Triple {
247 &self.triple
248 }
249
250 /// Returns the set of target features for this platform.
251 pub fn target_features(&self) -> &TargetFeatures {
252 &self.target_features
253 }
254
255 #[cfg(feature = "summaries")]
256 pub(crate) fn custom_json(&self) -> Option<&str> {
257 self.triple.custom_json()
258 }
259
260 #[cfg(feature = "summaries")]
261 pub(crate) fn custom_cfg_text(&self) -> Option<&str> {
262 self.triple.custom_cfg_text()
263 }
264}
265
266/// A set of target features to match.
267#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
268#[non_exhaustive]
269pub enum TargetFeatures {
270 /// The target features are unknown.
271 Unknown,
272 /// Only match the specified features.
273 Features(BTreeSet<Cow<'static, str>>),
274 /// Match all features.
275 All,
276}
277
278impl TargetFeatures {
279 /// Creates a new `TargetFeatures` which matches some features.
280 pub fn features(features: impl IntoIterator<Item = impl Into<Cow<'static, str>>>) -> Self {
281 TargetFeatures::Features(features.into_iter().map(|s| s.into()).collect())
282 }
283
284 /// Creates a new `TargetFeatures` which doesn't match any features.
285 pub fn none() -> Self {
286 TargetFeatures::Features(BTreeSet::new())
287 }
288
289 /// Returns `Some(true)` if this feature is a match, `Some(false)` if it isn't, and `None` if
290 /// the set of target features is unknown.
291 pub fn matches(&self, feature: &str) -> Option<bool> {
292 match self {
293 TargetFeatures::Unknown => None,
294 TargetFeatures::Features(features) => Some(features.contains(feature)),
295 TargetFeatures::All => Some(true),
296 }
297 }
298}
299
300#[cfg(all(test, feature = "custom-cfg"))]
301mod custom_cfg_tests {
302 use crate::TargetSpecExpression;
303
304 use super::*;
305
306 const LINUX_CFG: &str = "\
307 panic=\"unwind\"\n\
308 target_arch=\"x86_64\"\n\
309 target_endian=\"little\"\n\
310 target_env=\"gnu\"\n\
311 target_family=\"unix\"\n\
312 target_feature=\"fxsr\"\n\
313 target_feature=\"sse\"\n\
314 target_feature=\"sse2\"\n\
315 target_os=\"linux\"\n\
316 target_pointer_width=\"64\"\n\
317 target_vendor=\"unknown\"\n";
318
319 #[test]
320 fn new_custom_cfg_basic() {
321 let platform =
322 Platform::new_custom_cfg("my-custom-linux", LINUX_CFG, TargetFeatures::Unknown)
323 .expect("parsed successfully");
324
325 assert!(platform.is_custom());
326 assert!(!platform.is_standard());
327 assert!(!platform.is_builtin());
328 assert_eq!(platform.triple_str(), "my-custom-linux");
329 assert_eq!(*platform.target_features(), TargetFeatures::Unknown);
330 }
331
332 #[test]
333 fn new_custom_cfg_evaluates_expressions() {
334 let platform =
335 Platform::new_custom_cfg("my-custom-linux", LINUX_CFG, TargetFeatures::Unknown)
336 .expect("parsed successfully");
337
338 // Evaluate cfg expressions against the custom
339 // platform.
340 let spec =
341 TargetSpecExpression::new("cfg(target_os = \"linux\")").expect("valid expression");
342 assert_eq!(
343 spec.eval(&platform),
344 Some(true),
345 "target_os = linux matches",
346 );
347
348 let spec =
349 TargetSpecExpression::new("cfg(target_os = \"windows\")").expect("valid expression");
350 assert_eq!(
351 spec.eval(&platform),
352 Some(false),
353 "target_os = windows does not match",
354 );
355
356 let spec = TargetSpecExpression::new("cfg(unix)").expect("valid expression");
357 assert_eq!(spec.eval(&platform), Some(true), "unix family matches",);
358 }
359
360 #[test]
361 fn new_custom_cfg_with_features() {
362 let platform = Platform::new_custom_cfg("my-custom-linux", LINUX_CFG, TargetFeatures::All)
363 .expect("parsed successfully");
364
365 assert_eq!(
366 platform.target_features().matches("avx512"),
367 Some(true),
368 "All matches any feature",
369 );
370 assert_eq!(*platform.target_features(), TargetFeatures::All);
371 }
372
373 #[test]
374 fn new_custom_cfg_with_no_features() {
375 let platform =
376 Platform::new_custom_cfg("my-custom-linux", LINUX_CFG, TargetFeatures::none())
377 .expect("parsed successfully");
378
379 assert_eq!(
380 platform.target_features().matches("sse2"),
381 Some(false),
382 "sse2 not matched with empty features",
383 );
384 }
385}