target_spec/
custom.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Parse custom platforms.
5
6use std::borrow::Cow;
7
8use cfg_expr::targets::{
9    Abi, Arch, Env, Families, Family, HasAtomic, HasAtomics, Os, TargetInfo, Triple, Vendor,
10};
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
14#[serde(rename_all = "kebab-case")]
15pub(crate) struct TargetDefinition {
16    // TODO: it would be nice to use target-spec-json for this, but that has a
17    // few limitations as of v0.1:
18    //
19    // * target-pointer-width is a string, not an integer.
20    // * Os and Env deserialized to enums, but we would really like them to be strings.
21    //
22    // ---
23    arch: String,
24    #[serde(rename = "target-pointer-width", with = "target_pointer_width")]
25    pointer_width: u8,
26
27    // These parameters are not used by target-spec but are mandatory in Target, so we require them
28    // here. https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.Target.html
29    #[allow(dead_code)]
30    llvm_target: String,
31    #[allow(dead_code)]
32    data_layout: String,
33
34    // These are optional parameters used by target-spec.
35    #[serde(default)]
36    os: Option<String>,
37    #[serde(default)]
38    abi: Option<String>,
39    #[serde(default)]
40    env: Option<String>,
41    #[serde(default)]
42    vendor: Option<String>,
43    #[serde(default)]
44    target_family: Vec<String>,
45    #[serde(default)]
46    target_endian: Endian,
47    #[serde(default)]
48    min_atomic_width: Option<u16>,
49    #[serde(default)]
50    max_atomic_width: Option<u16>,
51    #[serde(default)]
52    panic_strategy: PanicStrategy,
53}
54
55impl TargetDefinition {
56    pub(crate) fn into_target_info(self, triple: Cow<'static, str>) -> TargetInfo {
57        // Per https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_target/spec/mod.rs.html,
58        // the default value for min_atomic_width is 8.
59        let min_atomic_width = self.min_atomic_width.unwrap_or(8);
60        // The default max atomic width is the pointer width.
61        let max_atomic_width = self.max_atomic_width.unwrap_or(self.pointer_width as u16);
62
63        let mut has_atomics = Vec::new();
64        // atomic_width should always be a power of two, but rather than checking that we just
65        // start counting up from 8.
66        let mut atomic_width = 8;
67        while atomic_width <= max_atomic_width {
68            if atomic_width < min_atomic_width {
69                atomic_width *= 2;
70                continue;
71            }
72            has_atomics.push(HasAtomic::IntegerSize(atomic_width));
73            if atomic_width == self.pointer_width as u16 {
74                has_atomics.push(HasAtomic::Pointer);
75            }
76            atomic_width *= 2;
77        }
78
79        TargetInfo {
80            triple: Triple::new(triple),
81            os: self.os.map(Os::new),
82            abi: self.abi.map(Abi::new),
83            arch: Arch::new(self.arch),
84            env: self.env.map(Env::new),
85            vendor: self.vendor.map(Vendor::new),
86            families: Families::new(self.target_family.into_iter().map(Family::new)),
87            pointer_width: self.pointer_width,
88            endian: self.target_endian.to_cfg_expr(),
89            has_atomics: HasAtomics::new(has_atomics),
90            panic: self.panic_strategy.to_cfg_expr(),
91        }
92    }
93}
94
95mod target_pointer_width {
96    use serde::{de::Error, Deserialize, Deserializer, Serializer};
97
98    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<u8, D::Error>
99    where
100        D: Deserializer<'de>,
101    {
102        // Pointer width is specified as a string.
103        let string = String::deserialize(deserializer)?;
104        string
105            .parse::<u8>()
106            .map_err(|error| D::Error::custom(format!("error parsing as integer: {error}")))
107    }
108
109    pub(super) fn serialize<S>(value: &u8, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: Serializer,
112    {
113        serializer.serialize_str(&value.to_string())
114    }
115}
116
117#[derive(
118    Copy, Clone, Debug, Deserialize, Serialize, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
119)]
120#[serde(rename_all = "kebab-case")]
121enum Endian {
122    #[default]
123    Little,
124    Big,
125}
126
127impl Endian {
128    fn to_cfg_expr(self) -> cfg_expr::targets::Endian {
129        match self {
130            Self::Little => cfg_expr::targets::Endian::little,
131            Self::Big => cfg_expr::targets::Endian::big,
132        }
133    }
134}
135
136#[derive(
137    Copy, Clone, Debug, Deserialize, Serialize, Default, Eq, Hash, Ord, PartialEq, PartialOrd,
138)]
139#[serde(rename_all = "kebab-case")]
140enum PanicStrategy {
141    #[default]
142    Unwind,
143    Abort,
144}
145
146impl PanicStrategy {
147    fn to_cfg_expr(self) -> cfg_expr::targets::Panic {
148        match self {
149            Self::Unwind => cfg_expr::targets::Panic::unwind,
150            Self::Abort => cfg_expr::targets::Panic::abort,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use std::{collections::BTreeMap, process::Command};
159
160    #[derive(Deserialize)]
161    #[serde(transparent)]
162    struct AllTargets(BTreeMap<String, TargetDefinition>);
163
164    #[test]
165    fn test_all_builtin_specs_recognized() {
166        let rustc_bin: String = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_owned());
167        let output = Command::new(rustc_bin)
168            // Used for -Zunstable-options. This is test-only code so it doesn't matter.
169            .env("RUSTC_BOOTSTRAP", "1")
170            .args(["-Z", "unstable-options", "--print", "all-target-specs-json"])
171            .output()
172            .expect("rustc command succeeded");
173        assert!(output.status.success(), "rustc command succeeded");
174
175        let all_targets: AllTargets = serde_json::from_slice(&output.stdout)
176            .expect("deserializing all-target-specs-json succeeded");
177        for (triple, target_def) in all_targets.0 {
178            eprintln!("*** testing {triple}");
179            // Just make sure this doesn't panic. (If this becomes fallible in the future, then this
180            // shouldn't return an error either.)
181            target_def.clone().into_target_info(triple.clone().into());
182            let json =
183                serde_json::to_string(&target_def).expect("target def serialized successfully");
184            eprintln!("* minified json: {json}");
185            let target_def_2 = serde_json::from_str(&json).expect("target def 2 deserialized");
186            assert_eq!(target_def, target_def_2, "matches");
187
188            // Do some spot checks for things like big-endian targets.
189            if triple.starts_with("powerpc-") || triple.starts_with("powerpc64-") {
190                assert_eq!(
191                    target_def.target_endian,
192                    Endian::Big,
193                    "powerpc is big-endian"
194                );
195            }
196            if triple.contains("-linux") {
197                assert!(
198                    target_def.target_family.contains(&"unix".to_owned()),
199                    "linux target_family should contain unix (was {:#?})",
200                    target_def.target_family,
201                );
202            }
203        }
204    }
205}