1use std::fmt;
10use std::str::FromStr;
11
12use target_lexicon::{Architecture, BinaryFormat, OperatingSystem, Triple};
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct Target {
22 triple: Triple,
23}
24
25impl Target {
26 pub fn host() -> Self {
28 Self {
29 triple: Triple::host(),
30 }
31 }
32
33 pub fn from_triple(triple: Triple) -> Self {
35 Self { triple }
36 }
37
38 pub fn triple(&self) -> &Triple {
40 &self.triple
41 }
42
43 pub fn triple_string(&self) -> String {
46 self.triple.to_string()
47 }
48
49 pub fn arch(&self) -> Arch {
51 Arch::from_lexicon(self.triple.architecture)
52 }
53
54 pub fn os(&self) -> Os {
56 Os::from_lexicon(self.triple.operating_system)
57 }
58
59 pub fn is_elf(&self) -> bool {
61 self.triple.binary_format == BinaryFormat::Elf
62 }
63
64 pub fn is_macho(&self) -> bool {
66 self.triple.binary_format == BinaryFormat::Macho
67 }
68
69 pub fn all() -> Vec<Target> {
73 BLESSED_TRIPLES
74 .iter()
75 .map(|s| Target::from_str(s).expect("blessed triple must parse"))
76 .collect()
77 }
78
79 pub fn is_blessed(&self) -> bool {
81 Self::all().iter().any(|t| t == self)
82 }
83
84 pub fn all_names() -> String {
86 Self::all()
87 .iter()
88 .map(|t| t.to_string())
89 .collect::<Vec<_>>()
90 .join(", ")
91 }
92}
93
94const BLESSED_TRIPLES: &[&str] = &[
96 "x86_64-unknown-linux-gnu",
97 "aarch64-unknown-linux-gnu",
98 "aarch64-apple-darwin",
99];
100
101impl FromStr for Target {
102 type Err = TargetParseError;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 let normalized = match s {
110 "x86_64-linux" | "x86-64-linux" => "x86_64-unknown-linux-gnu",
111 "aarch64-linux" | "arm64-linux" => "aarch64-unknown-linux-gnu",
112 "aarch64-macos" | "arm64-macos" => "aarch64-apple-darwin",
113 other => other,
114 };
115 let triple = Triple::from_str(normalized).map_err(|e| TargetParseError {
116 input: s.to_string(),
117 message: e.to_string(),
118 })?;
119 Ok(Target { triple })
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct TargetParseError {
126 pub input: String,
127 pub message: String,
128}
129
130impl fmt::Display for TargetParseError {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "invalid target '{}': {}", self.input, self.message)
133 }
134}
135
136impl std::error::Error for TargetParseError {}
137
138impl fmt::Display for Target {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "{}", self.triple)
141 }
142}
143
144impl Default for Target {
145 fn default() -> Self {
146 Self::host()
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum Arch {
153 X86,
154 X86_64,
155 Arm,
156 Aarch64,
157 Riscv32,
158 Riscv64,
159 Wasm32,
160 Wasm64,
161 Unknown,
164}
165
166impl Arch {
167 fn from_lexicon(a: Architecture) -> Self {
168 match a {
169 Architecture::X86_32(_) => Arch::X86,
170 Architecture::X86_64 | Architecture::X86_64h => Arch::X86_64,
171 Architecture::Arm(_) => Arch::Arm,
172 Architecture::Aarch64(_) => Arch::Aarch64,
173 Architecture::Riscv32(_) => Arch::Riscv32,
174 Architecture::Riscv64(_) => Arch::Riscv64,
175 Architecture::Wasm32 => Arch::Wasm32,
176 Architecture::Wasm64 => Arch::Wasm64,
177 _ => Arch::Unknown,
178 }
179 }
180}
181
182impl fmt::Display for Arch {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 let s = match self {
185 Arch::X86 => "x86",
186 Arch::X86_64 => "x86-64",
187 Arch::Arm => "arm",
188 Arch::Aarch64 => "aarch64",
189 Arch::Riscv32 => "riscv32",
190 Arch::Riscv64 => "riscv64",
191 Arch::Wasm32 => "wasm32",
192 Arch::Wasm64 => "wasm64",
193 Arch::Unknown => "unknown",
194 };
195 f.write_str(s)
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
201pub enum Os {
202 Linux,
203 Macos,
204 Windows,
205 Freestanding,
207 Wasi,
209 Unknown,
211}
212
213impl Os {
214 fn from_lexicon(o: OperatingSystem) -> Self {
215 match o {
216 OperatingSystem::Linux => Os::Linux,
217 OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) | OperatingSystem::IOS(_) => {
218 Os::Macos
221 }
222 OperatingSystem::Windows => Os::Windows,
223 OperatingSystem::Wasi | OperatingSystem::WasiP1 | OperatingSystem::WasiP2 => Os::Wasi,
224 OperatingSystem::None_ | OperatingSystem::Unknown => Os::Freestanding,
225 _ => Os::Unknown,
226 }
227 }
228}
229
230impl fmt::Display for Os {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 let s = match self {
233 Os::Linux => "linux",
234 Os::Macos => "macos",
235 Os::Windows => "windows",
236 Os::Freestanding => "freestanding",
237 Os::Wasi => "wasi",
238 Os::Unknown => "unknown",
239 };
240 f.write_str(s)
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 fn t(s: &str) -> Target {
249 s.parse().unwrap()
250 }
251
252 #[test]
253 fn parses_canonical_triples() {
254 assert_eq!(t("x86_64-unknown-linux-gnu").arch(), Arch::X86_64);
255 assert_eq!(t("x86_64-unknown-linux-gnu").os(), Os::Linux);
256 assert_eq!(t("aarch64-unknown-linux-gnu").arch(), Arch::Aarch64);
257 assert_eq!(t("aarch64-unknown-linux-gnu").os(), Os::Linux);
258 assert_eq!(t("aarch64-apple-darwin").arch(), Arch::Aarch64);
259 assert_eq!(t("aarch64-apple-darwin").os(), Os::Macos);
260 }
261
262 #[test]
263 fn parses_short_aliases() {
264 assert_eq!(t("x86_64-linux"), t("x86_64-unknown-linux-gnu"));
265 assert_eq!(t("x86-64-linux"), t("x86_64-unknown-linux-gnu"));
266 assert_eq!(t("aarch64-linux"), t("aarch64-unknown-linux-gnu"));
267 assert_eq!(t("arm64-linux"), t("aarch64-unknown-linux-gnu"));
268 assert_eq!(t("aarch64-macos"), t("aarch64-apple-darwin"));
269 assert_eq!(t("arm64-macos"), t("aarch64-apple-darwin"));
270 }
271
272 #[test]
273 fn parses_extended_triples() {
274 assert_eq!(t("riscv64gc-unknown-linux-gnu").arch(), Arch::Riscv64);
276 assert_eq!(t("riscv32imc-unknown-none-elf").arch(), Arch::Riscv32);
277 assert_eq!(t("wasm32-unknown-unknown").arch(), Arch::Wasm32);
278 assert_eq!(t("x86_64-pc-windows-msvc").os(), Os::Windows);
279 assert_eq!(t("aarch64-unknown-none").os(), Os::Freestanding);
280 assert_eq!(t("wasm32-wasi").os(), Os::Wasi);
281 }
282
283 #[test]
284 fn rejects_garbage() {
285 assert!("not-a-triple-at-all".parse::<Target>().is_err());
286 }
287
288 #[test]
289 fn binary_format() {
290 assert!(t("x86_64-unknown-linux-gnu").is_elf());
291 assert!(t("aarch64-unknown-linux-gnu").is_elf());
292 assert!(!t("aarch64-unknown-linux-gnu").is_macho());
293 assert!(t("aarch64-apple-darwin").is_macho());
294 assert!(!t("aarch64-apple-darwin").is_elf());
295 }
296
297 #[test]
298 fn default_is_host() {
299 assert_eq!(Target::default(), Target::host());
300 }
301
302 #[test]
303 fn blessed_targets_round_trip() {
304 for target in Target::all() {
305 let s = target.to_string();
306 let parsed: Target = s.parse().expect("Display output should re-parse");
307 assert_eq!(target, parsed);
308 assert!(target.is_blessed());
309 }
310 }
311
312 #[test]
313 fn unblessed_target_is_not_blessed() {
314 let t = "wasm32-wasi".parse::<Target>().unwrap();
315 assert!(!t.is_blessed());
316 }
317
318 #[test]
319 fn arch_display_matches_legacy() {
320 assert_eq!(Arch::X86_64.to_string(), "x86-64");
321 assert_eq!(Arch::Aarch64.to_string(), "aarch64");
322 }
323
324 #[test]
325 fn os_display_matches_legacy() {
326 assert_eq!(Os::Linux.to_string(), "linux");
327 assert_eq!(Os::Macos.to_string(), "macos");
328 }
329}