1use std::env;
2
3use crate::{target::TargetInfo, utilities::OnceLock, Error, ErrorKind};
4
5#[derive(Debug)]
6struct TargetInfoParserInner {
7 full_arch: Box<str>,
8 arch: Box<str>,
9 vendor: Box<str>,
10 os: Box<str>,
11 env: Box<str>,
12 abi: Box<str>,
13}
14
15impl TargetInfoParserInner {
16 fn from_cargo_environment_variables() -> Result<Self, Error> {
17 #[allow(clippy::disallowed_methods)]
22 let target_name = env::var("TARGET").map_err(|err| {
23 Error::new(
24 ErrorKind::EnvVarNotFound,
25 format!("failed reading TARGET: {err}"),
26 )
27 })?;
28
29 let (full_arch, _rest) = target_name.split_once('-').ok_or(Error::new(
31 ErrorKind::InvalidTarget,
32 format!("target `{target_name}` only had a single component (at least two required)"),
33 ))?;
34
35 let cargo_env = |name, fallback: Option<&str>| -> Result<Box<str>, Error> {
36 #[allow(clippy::disallowed_methods)]
39 match env::var(name) {
40 Ok(var) => Ok(var.into_boxed_str()),
41 Err(err) => match fallback {
42 Some(fallback) => Ok(fallback.into()),
43 None => Err(Error::new(
44 ErrorKind::EnvVarNotFound,
45 format!("did not find fallback information for target `{target_name}`, and failed reading {name}: {err}"),
46 )),
47 },
48 }
49 };
50
51 let fallback_target = TargetInfo::from_rustc_target(&target_name).ok();
66 let ft = fallback_target.as_ref();
67 let arch = cargo_env("CARGO_CFG_TARGET_ARCH", ft.map(|t| t.arch))?;
68 let vendor = cargo_env("CARGO_CFG_TARGET_VENDOR", ft.map(|t| t.vendor))?;
69 let os = cargo_env("CARGO_CFG_TARGET_OS", ft.map(|t| t.os))?;
70 let env = cargo_env("CARGO_CFG_TARGET_ENV", ft.map(|t| t.env))?;
71 let abi = cargo_env("CARGO_CFG_TARGET_ABI", ft.map(|t| t.abi))
75 .unwrap_or_else(|_| String::default().into_boxed_str());
76
77 Ok(Self {
78 full_arch: full_arch.to_string().into_boxed_str(),
79 arch,
80 vendor,
81 os,
82 env,
83 abi,
84 })
85 }
86}
87
88#[derive(Default, Debug)]
90pub(crate) struct TargetInfoParser(OnceLock<Result<TargetInfoParserInner, Error>>);
91
92impl TargetInfoParser {
93 pub fn parse_from_cargo_environment_variables(&self) -> Result<TargetInfo<'_>, Error> {
94 match self
95 .0
96 .get_or_init(TargetInfoParserInner::from_cargo_environment_variables)
97 {
98 Ok(TargetInfoParserInner {
99 full_arch,
100 arch,
101 vendor,
102 os,
103 env,
104 abi,
105 }) => Ok(TargetInfo {
106 full_arch,
107 arch,
108 vendor,
109 os,
110 env,
111 abi,
112 }),
113 Err(e) => Err(e.clone()),
114 }
115 }
116}
117
118fn parse_arch(full_arch: &str) -> Option<&str> {
121 Some(match full_arch {
131 arch if arch.starts_with("mipsisa32r6") => "mips32r6", arch if arch.starts_with("mipsisa64r6") => "mips64r6", arch if arch.starts_with("mips64") => "mips64", arch if arch.starts_with("mips") => "mips", arch if arch.starts_with("loongarch64") => "loongarch64",
138 arch if arch.starts_with("loongarch32") => "loongarch32",
139
140 arch if arch.starts_with("powerpc64") => "powerpc64", arch if arch.starts_with("powerpc") => "powerpc",
142 arch if arch.starts_with("ppc64") => "powerpc64",
143 arch if arch.starts_with("ppc") => "powerpc",
144
145 arch if arch.starts_with("x86_64") => "x86_64", arch if arch.starts_with("i") && arch.ends_with("86") => "x86", "arm64ec" => "arm64ec", arch if arch.starts_with("aarch64") => "aarch64", arch if arch.starts_with("arm64") => "aarch64", arch if arch.starts_with("arm") => "arm", arch if arch.starts_with("thumb") => "arm", arch if arch.starts_with("riscv64") => "riscv64",
156 arch if arch.starts_with("riscv32") => "riscv32",
157
158 arch if arch.starts_with("wasm64") => "wasm64",
159 arch if arch.starts_with("wasm32") => "wasm32", "asmjs" => "wasm32",
161
162 arch if arch.starts_with("nvptx64") => "nvptx64",
163 arch if arch.starts_with("nvptx") => "nvptx",
164
165 arch if arch.starts_with("bpf") => "bpf", arch if arch.starts_with("pulley64") => "pulley64",
169 arch if arch.starts_with("pulley32") => "pulley32",
170
171 arch if arch.starts_with("clever") => "clever",
173
174 "sparc" | "sparcv7" | "sparcv8" => "sparc",
175 "sparc64" | "sparcv9" => "sparc64",
176
177 "amdgcn" => "amdgpu",
178 "avr" => "avr",
179 "csky" => "csky",
180 "hexagon" => "hexagon",
181 "m68k" => "m68k",
182 "msp430" => "msp430",
183 "r600" => "r600",
184 "s390x" => "s390x",
185 "xtensa" => "xtensa",
186
187 _ => return None,
188 })
189}
190
191fn parse_envabi(last_component: &str) -> Option<(&str, &str)> {
193 let (env, abi) = match last_component {
194 env_and_abi if env_and_abi.starts_with("gnu") => {
198 let abi = env_and_abi.strip_prefix("gnu").unwrap();
199 let abi = abi.strip_prefix("_").unwrap_or(abi);
200 ("gnu", abi)
201 }
202 env_and_abi if env_and_abi.starts_with("musl") => {
204 ("musl", env_and_abi.strip_prefix("musl").unwrap())
205 }
206 env_and_abi if env_and_abi.starts_with("uclibc") => {
208 ("uclibc", env_and_abi.strip_prefix("uclibc").unwrap())
209 }
210 env_and_abi if env_and_abi.starts_with("newlib") => {
212 ("newlib", env_and_abi.strip_prefix("newlib").unwrap())
213 }
214
215 "msvc" => ("msvc", ""),
217 "ohos" => ("ohos", ""),
218 "qnx700" => ("nto70", ""),
219 "qnx710_iosock" => ("nto71_iosock", ""),
220 "qnx710" => ("nto71", ""),
221 "qnx800" => ("nto80", ""),
222 "sgx" => ("sgx", ""),
223 "threads" => ("threads", ""),
224
225 "abi64" => ("", "abi64"),
227 "abiv2" => ("", "spe"),
228 "eabi" => ("", "eabi"),
229 "eabihf" => ("", "eabihf"),
230 "macabi" => ("", "macabi"),
231 "sim" => ("", "sim"),
232 "softfloat" => ("", "softfloat"),
233 "spe" => ("", "spe"),
234 "x32" => ("", "x32"),
235
236 "elf" => ("", ""),
239 "freestanding" => ("", ""),
242
243 _ => return None,
244 };
245 Some((env, abi))
246}
247
248impl<'a> TargetInfo<'a> {
249 pub(crate) fn from_rustc_target(target: &'a str) -> Result<Self, Error> {
250 if target == "x86_64-unknown-linux-none" {
254 return Ok(Self {
255 full_arch: "x86_64",
256 arch: "x86_64",
257 vendor: "unknown",
258 os: "linux",
259 env: "",
260 abi: "",
261 });
262 }
263
264 let mut components = target.split('-');
265
266 let full_arch = components.next().ok_or(Error::new(
268 ErrorKind::InvalidTarget,
269 "target was empty".to_string(),
270 ))?;
271 let arch = parse_arch(full_arch).ok_or_else(|| {
272 Error::new(
273 ErrorKind::UnknownTarget,
274 format!("target `{target}` had an unknown architecture"),
275 )
276 })?;
277
278 let components: Vec<_> = components.collect();
281 let (vendor, os, mut env, mut abi) = match &*components {
282 [] => {
283 return Err(Error::new(
284 ErrorKind::InvalidTarget,
285 format!("target `{target}` must have at least two components"),
286 ))
287 }
288 [os] => ("unknown", *os, "", ""),
290 [vendor_or_os, os_or_envabi] => {
293 if let Some((env, abi)) = parse_envabi(os_or_envabi) {
297 ("unknown", *vendor_or_os, env, abi)
298 } else {
299 (*vendor_or_os, *os_or_envabi, "", "")
300 }
301 }
302 [vendor, os, envabi] => {
304 let (env, abi) = parse_envabi(envabi).ok_or_else(|| {
305 Error::new(
306 ErrorKind::UnknownTarget,
307 format!("unknown environment/ABI `{envabi}` in target `{target}`"),
308 )
309 })?;
310 (*vendor, *os, env, abi)
311 }
312 _ => {
313 return Err(Error::new(
314 ErrorKind::InvalidTarget,
315 format!("too many components in target `{target}`"),
316 ))
317 }
318 };
319
320 match full_arch {
322 arch if arch.starts_with("riscv32e") => {
324 abi = "ilp32e";
325 }
326 _ => {}
327 }
328
329 match os {
331 "3ds" | "rtems" | "espidf" => env = "newlib",
332 "vxworks" => env = "gnu",
333 "redox" => env = "relibc",
334 "aix" => abi = "vec-extabi",
335 _ => {}
336 }
337
338 match target {
340 "i386-apple-ios" | "x86_64-apple-ios" | "x86_64-apple-tvos" => {
342 abi = "sim";
343 }
344 "mips64-openwrt-linux-musl" => {
346 abi = "abi64";
347 }
348 "armv6-unknown-freebsd" | "armv6k-nintendo-3ds" | "armv7-unknown-freebsd" => {
350 abi = "eabihf";
351 }
352 "armv7-unknown-linux-ohos" | "armv7-unknown-trusty" => {
354 abi = "eabi";
355 }
356 _ => {}
357 }
358
359 let os = match os {
360 "3ds" | "switch" => "horizon",
362 "darwin" => "macos",
364
365 os if os.starts_with("wasi") => {
368 env = os.strip_prefix("wasi").unwrap();
369 "wasi"
370 }
371 "androideabi" => {
374 abi = "eabi";
375 "android"
376 }
377
378 os => os,
379 };
380
381 let vendor = match vendor {
382 vendor if vendor.starts_with("esp") => "espressif",
384 "linux" if os == "android" || os == "androideabi" => "unknown",
387 "wali" => "unknown",
390 vendor => vendor,
393 };
394
395 if vendor == "fortanix" {
398 abi = "fortanix";
399 }
400 if vendor == "uwp" {
401 abi = "uwp";
402 }
403
404 Ok(Self {
405 full_arch,
406 arch,
407 vendor,
408 os,
409 env,
410 abi,
411 })
412 }
413}
414
415#[cfg(test)]
416#[allow(unexpected_cfgs)]
417mod tests {
418 use std::process::Command;
419
420 use super::TargetInfo;
421 use crate::ErrorKind;
422
423 #[test]
425 fn tier1() {
426 let targets = [
427 "aarch64-unknown-linux-gnu",
428 "aarch64-apple-darwin",
429 "i686-pc-windows-gnu",
430 "i686-pc-windows-msvc",
431 "i686-unknown-linux-gnu",
432 "x86_64-apple-darwin",
433 "x86_64-pc-windows-gnu",
434 "x86_64-pc-windows-msvc",
435 "x86_64-unknown-linux-gnu",
436 ];
437
438 for target in targets {
439 let _ = TargetInfo::from_rustc_target(target).unwrap();
441 }
442 }
443
444 #[test]
446 fn parse_extra() {
447 let targets = [
448 "aarch64-unknown-none-gnu",
449 "aarch64-uwp-windows-gnu",
450 "arm-frc-linux-gnueabi",
451 "arm-unknown-netbsd-eabi",
452 "armv7neon-unknown-linux-gnueabihf",
453 "armv7neon-unknown-linux-musleabihf",
454 "thumbv7-unknown-linux-gnueabihf",
455 "thumbv7-unknown-linux-musleabihf",
456 "armv7-apple-ios",
457 "wasm32-wasi",
458 "x86_64-rumprun-netbsd",
459 "x86_64-unknown-linux",
460 "x86_64-alpine-linux-musl",
461 "x86_64-chimera-linux-musl",
462 "x86_64-foxkit-linux-musl",
463 "arm-poky-linux-gnueabi",
464 "x86_64-unknown-moturus",
465 ];
466
467 for target in targets {
468 let _ = TargetInfo::from_rustc_target(target).unwrap();
470 }
471 }
472
473 fn target_from_rustc_cfgs<'a>(target: &'a str, cfgs: &'a str) -> TargetInfo<'a> {
474 let (full_arch, _rest) = target.split_once('-').expect("target to have arch");
476
477 let mut target = TargetInfo {
478 full_arch,
479 arch: "invalid-none-set",
480 vendor: "invalid-none-set",
481 os: "invalid-none-set",
482 env: "invalid-none-set",
483 abi: "",
485 };
486
487 for cfg in cfgs.lines() {
488 if let Some((name, value)) = cfg.split_once('=') {
489 let name = name.trim();
491 let value = value.trim();
492
493 let value = value.strip_prefix('"').unwrap_or(value);
495 let value = value.strip_suffix('"').unwrap_or(value);
496
497 match name {
498 "target_arch" => target.arch = value,
499 "target_vendor" => target.vendor = value,
500 "target_os" => target.os = value,
501 "target_env" => target.env = value,
502 "target_abi" => target.abi = value,
503 _ => {}
504 }
505 } else {
506 }
508 }
509
510 target
511 }
512
513 #[test]
514 fn unknown_env_determined_as_unknown() {
515 let err = TargetInfo::from_rustc_target("aarch64-unknown-linux-bogus").unwrap_err();
516 assert!(matches!(err.kind, ErrorKind::UnknownTarget));
517 }
518
519 #[test]
521 #[cfg_attr(
522 not(rustc_target_test),
523 ignore = "must enable explicitly with --cfg=rustc_target_test"
524 )]
525 fn parse_rustc_targets() {
526 let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
527
528 let target_list = Command::new(&rustc)
529 .arg("--print=target-list")
530 .output()
531 .unwrap()
532 .stdout;
533 let target_list = String::from_utf8(target_list).unwrap();
534
535 let mut has_failure = false;
536 for target in target_list.lines() {
537 let cfgs = Command::new(&rustc)
538 .arg("--target")
539 .arg(target)
540 .arg("--print=cfg")
541 .output()
542 .unwrap()
543 .stdout;
544 let cfgs = String::from_utf8(cfgs).unwrap();
545
546 let expected = target_from_rustc_cfgs(target, &cfgs);
547 let actual = TargetInfo::from_rustc_target(target);
548
549 if Some(&expected) != actual.as_ref().ok() {
550 eprintln!("failed comparing {target}:");
551 eprintln!(" expected: Ok({expected:?})");
552 eprintln!(" actual: {actual:?}");
553 eprintln!();
554 has_failure = true;
555 }
556 }
557
558 if has_failure {
559 panic!("failed comparing targets");
560 }
561 }
562}