Skip to main content

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}