Skip to main content

rust_toolchain/
target.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4use std::{fmt, slice};
5
6/// A target platform
7///
8/// Commonly represented as a [`target triple`]. A target triple consists of three (or four) components: the
9/// architecture component, the vendor component, the operating system component and optionally
10/// a fourth component representing the environment (e.g. gnu or msvc).
11///
12/// # Reading materials
13///
14/// - [`RFC 0131: target specification`]
15/// - [`rustup concepts: toolchains`]
16/// - [`rustup component history`]
17/// - [`rustc platform support`]
18///
19/// [`target triple`]: https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md#detailed-design
20/// [`RFC 0131: target specification`]: https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md#detailed-design
21/// [`rustup concepts: toolchains`]: https://rust-lang.github.io/rustup/concepts/toolchains.html
22/// [`rustup component history`]: https://rust-lang.github.io/rustup-components-history/
23/// [`rustc platform support`]: https://doc.rust-lang.org/rustc/platform-support.html
24#[derive(Clone, Debug, Eq, Hash, PartialEq)]
25pub struct Target {
26    target: target_lexicon::Triple,
27}
28
29impl Target {
30    /// Create a new `Target` instance which represents the `host` platform.
31    ///
32    /// The platform on which this library is compiled, will be the `host` platform.
33    pub const fn host() -> Self {
34        Self {
35            target: target_lexicon::HOST,
36        }
37    }
38
39    /// Create a new `Target` instance from a [`target triple`].
40    ///
41    /// * See also: [Rustc platform support](https://doc.rust-lang.org/rustc/platform-support.html)
42    ///
43    /// [`target triple`]: https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md#detailed-design
44    pub fn try_from_target_triple(triple: &str) -> Result<Self, ParseError> {
45        let platform = target_lexicon::Triple::from_str(triple).map_err(ParseError::from)?;
46
47        Ok(Self { target: platform })
48    }
49
50    /// Create a new `Target` instance from a [`target triple`], defaults to
51    /// `unknown-unknown-unknown` if the give triple is not recognized.
52    ///
53    /// * See also: [Rustc platform support](https://doc.rust-lang.org/rustc/platform-support.html)
54    ///
55    /// [`target triple`]: https://github.com/rust-lang/rfcs/blob/master/text/0131-target-specification.md#detailed-design
56    pub fn from_target_triple_or_unknown(triple: &str) -> Self {
57        let platform = target_lexicon::Triple::from_str(triple)
58            .unwrap_or_else(|_| target_lexicon::Triple::unknown());
59
60        Self { target: platform }
61    }
62}
63
64impl fmt::Display for Target {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "{}", self.target)
67    }
68}
69
70/// A set of unique [`Target`]s, ordered by target triple.
71///
72/// The targets are held behind an [`Arc`], so cloning a set, and with it cloning
73/// the [`Toolchain`] which holds it, does not copy its targets.
74///
75/// Two toolchains which support the same targets can share a single set. As you can imagine,
76/// this happens a lot in Rust distribution land, so it's well worth vs a regular BTreeSet ;).
77///
78/// [`Toolchain`]: crate::Toolchain
79#[derive(Clone, Debug, Eq)]
80pub struct TargetSet {
81    targets: Arc<[Target]>,
82}
83
84impl TargetSet {
85    /// The amount of targets in the set.
86    pub fn len(&self) -> usize {
87        self.targets.len()
88    }
89
90    /// Returns true if the set holds no targets, and false otherwise.
91    pub fn is_empty(&self) -> bool {
92        self.targets.is_empty()
93    }
94
95    /// Returns true if the given target is a member of the set.
96    pub fn contains(&self, target: &Target) -> bool {
97        self.targets.contains(target)
98    }
99
100    /// Iterate over the targets of the set.
101    pub fn iter(&self) -> slice::Iter<'_, Target> {
102        self.targets.iter()
103    }
104
105    /// The targets of the set, as a slice ordered by target triple.
106    pub fn as_slice(&self) -> &[Target] {
107        &self.targets
108    }
109}
110
111impl Default for TargetSet {
112    fn default() -> Self {
113        Self {
114            targets: Vec::new().into(),
115        }
116    }
117}
118
119impl PartialEq for TargetSet {
120    fn eq(&self, other: &Self) -> bool {
121        Arc::ptr_eq(&self.targets, &other.targets) || self.targets == other.targets
122    }
123}
124
125impl FromIterator<Target> for TargetSet {
126    fn from_iter<I: IntoIterator<Item = Target>>(iter: I) -> Self {
127        // A target is identified by its triple, which is also what the set is ordered by
128        let unique = iter
129            .into_iter()
130            .map(|target| (target.to_string(), target))
131            .collect::<BTreeMap<_, _>>();
132
133        Self {
134            targets: unique.into_values().collect(),
135        }
136    }
137}
138
139impl From<Vec<Target>> for TargetSet {
140    fn from(targets: Vec<Target>) -> Self {
141        targets.into_iter().collect()
142    }
143}
144
145impl<'a> IntoIterator for &'a TargetSet {
146    type Item = &'a Target;
147    type IntoIter = slice::Iter<'a, Target>;
148
149    fn into_iter(self) -> Self::IntoIter {
150        self.targets.iter()
151    }
152}
153
154/// Errors which may occur while parsing a [`Target`].
155#[derive(Debug, thiserror::Error)]
156#[non_exhaustive]
157pub enum ParseError {
158    #[error("Unknown architecture `{0}`")]
159    Architecture(String),
160    #[error("Unknown vendor `{0}`")]
161    Vendor(String),
162    #[error("Unknown operating system `{0}`")]
163    OperatingSystem(String),
164    #[error("Unknown environment `{0}`")]
165    Environment(String),
166    #[error("Unknown binary format `{0}`")]
167    BinaryFormat(String),
168    #[error("Unknown field `{0}`")]
169    Field(String),
170}
171
172impl From<target_lexicon::ParseError> for ParseError {
173    fn from(value: target_lexicon::ParseError) -> Self {
174        match value {
175            target_lexicon::ParseError::UnrecognizedArchitecture(v) => ParseError::Architecture(v),
176            target_lexicon::ParseError::UnrecognizedVendor(v) => ParseError::Vendor(v),
177            target_lexicon::ParseError::UnrecognizedOperatingSystem(v) => {
178                ParseError::OperatingSystem(v)
179            }
180            target_lexicon::ParseError::UnrecognizedEnvironment(v) => ParseError::Environment(v),
181            target_lexicon::ParseError::UnrecognizedBinaryFormat(v) => ParseError::BinaryFormat(v),
182            target_lexicon::ParseError::UnrecognizedField(v) => ParseError::Field(v),
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn create_platform() {
193        let this_platform = Target::host();
194
195        let expected = Target {
196            target: target_lexicon::HOST,
197        };
198
199        assert_eq!(this_platform, expected);
200    }
201
202    #[test]
203    fn to_string() {
204        let target = Target::try_from_target_triple("x86_64-unknown-linux-gnu").unwrap();
205
206        assert_eq!(target.to_string(), "x86_64-unknown-linux-gnu");
207    }
208
209    mod target_set {
210        use super::*;
211        use std::ptr;
212
213        fn target(triple: &str) -> Target {
214            Target::from_target_triple_or_unknown(triple)
215        }
216
217        #[test]
218        fn an_empty_set() {
219            let set = TargetSet::default();
220
221            assert!(set.is_empty());
222            assert_eq!(set.len(), 0);
223        }
224
225        #[test]
226        fn a_set_is_ordered_by_target_triple() {
227            let set = [
228                target("x86_64-unknown-linux-gnu"),
229                target("aarch64-apple-darwin"),
230            ]
231            .into_iter()
232            .collect::<TargetSet>();
233
234            let triples = set.iter().map(|t| t.to_string()).collect::<Vec<_>>();
235
236            assert_eq!(
237                triples,
238                ["aarch64-apple-darwin", "x86_64-unknown-linux-gnu"]
239            );
240        }
241
242        #[test]
243        fn a_set_holds_every_target_once() {
244            let set = vec![
245                target("x86_64-unknown-linux-gnu"),
246                target("x86_64-unknown-linux-gnu"),
247            ]
248            .into_iter()
249            .collect::<TargetSet>();
250
251            assert_eq!(set.len(), 1);
252            assert!(set.contains(&target("x86_64-unknown-linux-gnu")));
253        }
254
255        #[test]
256        fn a_set_is_equal_regardless_of_the_order_it_was_built_in() {
257            let left = [
258                target("wasm32-unknown-unknown"),
259                target("aarch64-apple-darwin"),
260            ]
261            .into_iter()
262            .collect::<TargetSet>();
263            let right = [
264                target("aarch64-apple-darwin"),
265                target("wasm32-unknown-unknown"),
266            ]
267            .into_iter()
268            .collect::<TargetSet>();
269
270            assert_eq!(left, right);
271        }
272
273        #[test]
274        fn a_clone_shares_the_targets_of_the_set_it_was_cloned_from() {
275            let set = [target("aarch64-apple-darwin")]
276                .into_iter()
277                .collect::<TargetSet>();
278            let clone = set.clone();
279
280            assert!(ptr::eq(set.as_slice(), clone.as_slice()));
281        }
282    }
283}