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 "mlibc" => ("mlibc", ""),
225
226 "abi64" => ("", "abi64"),
228 "abiv2" => ("", "spe"),
229 "eabi" => ("", "eabi"),
230 "eabihf" => ("", "eabihf"),
231 "macabi" => ("", "macabi"),
232 "sim" => ("", "sim"),
233 "softfloat" => ("", "softfloat"),
234 "spe" => ("", "spe"),
235 "x32" => ("", "x32"),
236
237 "elf" => ("", ""),
240 "freestanding" => ("", ""),
243
244 _ => return None,
245 };
246 Some((env, abi))
247}
248
249impl<'a> TargetInfo<'a> {
250 pub(crate) fn from_rustc_target(target: &'a str) -> Result<Self, Error> {
251 if target == "x86_64-unknown-linux-none" {
255 return Ok(Self {
256 full_arch: "x86_64",
257 arch: "x86_64",
258 vendor: "unknown",
259 os: "linux",
260 env: "",
261 abi: "",
262 });
263 }
264
265 let mut components = target.split('-');
266
267 let full_arch = components.next().ok_or(Error::new(
269 ErrorKind::InvalidTarget,
270 "target was empty".to_string(),
271 ))?;
272 let arch = parse_arch(full_arch).ok_or_else(|| {
273 Error::new(
274 ErrorKind::UnknownTarget,
275 format!("target `{target}` had an unknown architecture"),
276 )
277 })?;
278
279 let components: Vec<_> = components.collect();
282 let (vendor, os, mut env, mut abi) = match &*components {
283 [] => {
284 return Err(Error::new(
285 ErrorKind::InvalidTarget,
286 format!("target `{target}` must have at least two components"),
287 ))
288 }
289 [os] => ("unknown", *os, "", ""),
291 [vendor_or_os, os_or_envabi] => {
294 if let Some((env, abi)) = parse_envabi(os_or_envabi) {
298 ("unknown", *vendor_or_os, env, abi)
299 } else {
300 (*vendor_or_os, *os_or_envabi, "", "")
301 }
302 }
303 [vendor, os, envabi] => {
305 let (env, abi) = parse_envabi(envabi).ok_or_else(|| {
306 Error::new(
307 ErrorKind::UnknownTarget,
308 format!("unknown environment/ABI `{envabi}` in target `{target}`"),
309 )
310 })?;
311 (*vendor, *os, env, abi)
312 }
313 _ => {
314 return Err(Error::new(
315 ErrorKind::InvalidTarget,
316 format!("too many components in target `{target}`"),
317 ))
318 }
319 };
320
321 match full_arch {
323 arch if arch.starts_with("riscv32e") => {
325 abi = "ilp32e";
326 }
327 _ => {}
328 }
329
330 match os {
332 "3ds" | "rtems" | "espidf" => env = "newlib",
333 "vxworks" => env = "gnu",
334 "redox" => env = "relibc",
335 "aix" => abi = "vec-extabi",
336 _ => {}
337 }
338
339 match target {
341 "i386-apple-ios" | "x86_64-apple-ios" | "x86_64-apple-tvos" => {
343 abi = "sim";
344 }
345 "mips64-openwrt-linux-musl" => {
347 abi = "abi64";
348 }
349 "armv6-unknown-freebsd" | "armv6k-nintendo-3ds" | "armv7-unknown-freebsd" => {
351 abi = "eabihf";
352 }
353 "armv7-unknown-linux-ohos" | "armv7-unknown-trusty" => {
355 abi = "eabi";
356 }
357 _ => {}
358 }
359
360 let os = match os {
361 "3ds" | "switch" => "horizon",
363 "darwin" => "macos",
365
366 os if os.starts_with("wasi") => {
369 env = os.strip_prefix("wasi").unwrap();
370 "wasi"
371 }
372 "androideabi" => {
375 abi = "eabi";
376 "android"
377 }
378
379 os => os,
380 };
381
382 let vendor = match vendor {
383 vendor if vendor.starts_with("esp") => "espressif",
385 "linux" if os == "android" || os == "androideabi" => "unknown",
388 "wali" => "unknown",
391 "lynx" => "unknown",
392 vendor => vendor,
395 };
396
397 if vendor == "fortanix" {
400 abi = "fortanix";
401 }
402 if vendor == "uwp" {
403 abi = "uwp";
404 }
405 if ["powerpc64-unknown-linux-gnu", "powerpc64-wrs-vxworks"].contains(&target) {
406 abi = "elfv1";
407 }
408 if [
409 "powerpc64-unknown-freebsd",
410 "powerpc64-unknown-linux-musl",
411 "powerpc64-unknown-openbsd",
412 "powerpc64le-unknown-freebsd",
413 "powerpc64le-unknown-linux-gnu",
414 "powerpc64le-unknown-linux-musl",
415 ]
416 .contains(&target)
417 {
418 abi = "elfv2";
419 }
420
421 Ok(Self {
422 full_arch,
423 arch,
424 vendor,
425 os,
426 env,
427 abi,
428 })
429 }
430}
431
432#[cfg(test)]
433#[allow(unexpected_cfgs)]
434mod tests {
435 use std::process::Command;
436
437 use super::TargetInfo;
438 use crate::ErrorKind;
439
440 #[test]
442 fn tier1() {
443 let targets = [
444 "aarch64-unknown-linux-gnu",
445 "aarch64-apple-darwin",
446 "i686-pc-windows-gnu",
447 "i686-pc-windows-msvc",
448 "i686-unknown-linux-gnu",
449 "x86_64-apple-darwin",
450 "x86_64-pc-windows-gnu",
451 "x86_64-pc-windows-msvc",
452 "x86_64-unknown-linux-gnu",
453 ];
454
455 for target in targets {
456 let _ = TargetInfo::from_rustc_target(target).unwrap();
458 }
459 }
460
461 #[test]
463 fn parse_extra() {
464 let targets = [
465 "aarch64-unknown-none-gnu",
466 "aarch64-uwp-windows-gnu",
467 "arm-frc-linux-gnueabi",
468 "arm-unknown-netbsd-eabi",
469 "armv7neon-unknown-linux-gnueabihf",
470 "armv7neon-unknown-linux-musleabihf",
471 "thumbv7-unknown-linux-gnueabihf",
472 "thumbv7-unknown-linux-musleabihf",
473 "armv7-apple-ios",
474 "wasm32-wasi",
475 "x86_64-rumprun-netbsd",
476 "x86_64-unknown-linux",
477 "x86_64-alpine-linux-musl",
478 "x86_64-chimera-linux-musl",
479 "x86_64-foxkit-linux-musl",
480 "arm-poky-linux-gnueabi",
481 "x86_64-unknown-moturus",
482 "x86_64-unknown-managarm-mlibc",
483 ];
484
485 for target in targets {
486 let _ = TargetInfo::from_rustc_target(target).unwrap();
488 }
489 }
490
491 fn target_from_rustc_cfgs<'a>(target: &'a str, cfgs: &'a str) -> TargetInfo<'a> {
492 let (full_arch, _rest) = target.split_once('-').expect("target to have arch");
494
495 let mut target = TargetInfo {
496 full_arch,
497 arch: "invalid-none-set",
498 vendor: "invalid-none-set",
499 os: "invalid-none-set",
500 env: "invalid-none-set",
501 abi: "",
503 };
504
505 for cfg in cfgs.lines() {
506 if let Some((name, value)) = cfg.split_once('=') {
507 let name = name.trim();
509 let value = value.trim();
510
511 let value = value.strip_prefix('"').unwrap_or(value);
513 let value = value.strip_suffix('"').unwrap_or(value);
514
515 match name {
516 "target_arch" => target.arch = value,
517 "target_vendor" => target.vendor = value,
518 "target_os" => target.os = value,
519 "target_env" => target.env = value,
520 "target_abi" => target.abi = value,
521 _ => {}
522 }
523 } else {
524 }
526 }
527
528 target
529 }
530
531 #[test]
532 fn unknown_env_determined_as_unknown() {
533 let err = TargetInfo::from_rustc_target("aarch64-unknown-linux-bogus").unwrap_err();
534 assert!(matches!(err.kind, ErrorKind::UnknownTarget));
535 }
536
537 #[test]
539 #[cfg_attr(
540 not(rustc_target_test),
541 ignore = "must enable explicitly with --cfg=rustc_target_test"
542 )]
543 fn parse_rustc_targets() {
544 let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
545
546 let target_list = Command::new(&rustc)
547 .arg("--print=target-list")
548 .output()
549 .unwrap()
550 .stdout;
551 let target_list = String::from_utf8(target_list).unwrap();
552
553 let mut has_failure = false;
554 for target in target_list.lines() {
555 let cfgs = Command::new(&rustc)
556 .arg("--target")
557 .arg(target)
558 .arg("--print=cfg")
559 .output()
560 .unwrap()
561 .stdout;
562 let cfgs = String::from_utf8(cfgs).unwrap();
563
564 let expected = target_from_rustc_cfgs(target, &cfgs);
565 let actual = TargetInfo::from_rustc_target(target);
566
567 if Some(&expected) != actual.as_ref().ok() {
568 eprintln!("failed comparing {target}:");
569 eprintln!(" expected: Ok({expected:?})");
570 eprintln!(" actual: {actual:?}");
571 eprintln!();
572 has_failure = true;
573 }
574 }
575
576 if has_failure {
577 panic!("failed comparing targets");
578 }
579 }
580}