cc/target/
parser.rs

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        // `TARGET` must be present.
18        //
19        // No need to emit `rerun-if-env-changed` for this,
20        // as it is controlled by Cargo itself.
21        #[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        // Parse the full architecture name from the target name.
30        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            // No need to emit `rerun-if-env-changed` for these,
37            // as they are controlled by Cargo itself.
38            #[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        // Prefer to use `CARGO_ENV_*` if set, since these contain the most
52        // correct information relative to the current `rustc`, and makes it
53        // possible to support custom target JSON specs unknown to `rustc`.
54        //
55        // NOTE: If the user is using an older `rustc`, that data may be older
56        // than our pre-generated data, but we still prefer Cargo's view of
57        // the world, since at least `cc` won't differ from `rustc` in that
58        // case.
59        //
60        // These may not be set in case the user depended on being able to
61        // just set `TARGET` outside of build scripts; in those cases, fall
62        // back back to data from the known set of target names instead.
63        //
64        // See discussion in #1225 for further details.
65        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        // `target_abi` was stabilized in Rust 1.78, which is higher than our
72        // MSRV, so it may not always be available; In that case, fall back to
73        // `""`, which is _probably_ correct for unknown target names.
74        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/// Parser for [`TargetInfo`], contains cached information.
89#[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
118/// Parse the full architecture in the target name into the simpler
119/// `cfg(target_arch = "...")` that `rustc` exposes.
120fn parse_arch(full_arch: &str) -> Option<&str> {
121    // NOTE: Some of these don't necessarily match an existing target in
122    // `rustc`. They're parsed anyhow to be as forward-compatible as possible,
123    // while still being correct.
124    //
125    // See also:
126    // https://docs.rs/cfg-expr/0.18.0/cfg_expr/targets/index.html
127    // https://docs.rs/target-lexicon/0.13.2/target_lexicon/enum.Architecture.html
128    // https://gcc.gnu.org/onlinedocs/gcc/Submodel-Options.html
129    // `clang -print-targets`
130    Some(match full_arch {
131        arch if arch.starts_with("mipsisa32r6") => "mips32r6", // mipsisa32r6 | mipsisa32r6el
132        arch if arch.starts_with("mipsisa64r6") => "mips64r6", // mipsisa64r6 | mipsisa64r6el
133
134        arch if arch.starts_with("mips64") => "mips64", // mips64 | mips64el
135        arch if arch.starts_with("mips") => "mips",     // mips | mipsel
136
137        arch if arch.starts_with("loongarch64") => "loongarch64",
138        arch if arch.starts_with("loongarch32") => "loongarch32",
139
140        arch if arch.starts_with("powerpc64") => "powerpc64", // powerpc64 | powerpc64le
141        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", // x86_64 | x86_64h
146        arch if arch.starts_with("i") && arch.ends_with("86") => "x86", // i386 | i586 | i686
147
148        "arm64ec" => "arm64ec", // https://github.com/rust-lang/rust/issues/131172
149        arch if arch.starts_with("aarch64") => "aarch64", // arm64e | arm64_32
150        arch if arch.starts_with("arm64") => "aarch64", // aarch64 | aarch64_be
151
152        arch if arch.starts_with("arm") => "arm", // arm | armv7s | armeb | ...
153        arch if arch.starts_with("thumb") => "arm", // thumbv4t | thumbv7a | thumbv8m | ...
154
155        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", // wasm32 | wasm32v1
160        "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", // bpfeb | bpfel
166
167        // https://github.com/bytecodealliance/wasmtime/tree/v30.0.1/pulley
168        arch if arch.starts_with("pulley64") => "pulley64",
169        arch if arch.starts_with("pulley32") => "pulley32",
170
171        // https://github.com/Clever-ISA/Clever-ISA
172        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
191/// Parse environment and ABI from the last component of the target name.
192fn parse_envabi(last_component: &str) -> Option<(&str, &str)> {
193    let (env, abi) = match last_component {
194        // Combined environment and ABI
195
196        // gnullvm | gnueabi | gnueabihf | gnuabiv2 | gnuabi64 | gnuspe | gnux32 | gnu_ilp32
197        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        // musl | musleabi | musleabihf | muslabi64 | muslspe
203        env_and_abi if env_and_abi.starts_with("musl") => {
204            ("musl", env_and_abi.strip_prefix("musl").unwrap())
205        }
206        // uclibc | uclibceabi | uclibceabihf
207        env_and_abi if env_and_abi.starts_with("uclibc") => {
208            ("uclibc", env_and_abi.strip_prefix("uclibc").unwrap())
209        }
210        // newlib | newlibeabihf
211        env_and_abi if env_and_abi.starts_with("newlib") => {
212            ("newlib", env_and_abi.strip_prefix("newlib").unwrap())
213        }
214
215        // Environments
216        "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        // ABIs
227        "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        // Badly named targets, ELF is already known from target OS.
238        // Probably too late to fix now though.
239        "elf" => ("", ""),
240        // Undesirable to expose to user code (yet):
241        // https://github.com/rust-lang/rust/pull/131166#issuecomment-2389541917
242        "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        // FIXME(madsmtm): This target should be renamed, cannot be parsed
252        // with the means we do below (since `none` must not be interpreted
253        // as an env/ABI).
254        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        // Insist that the target name contains at least a valid architecture.
268        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        // Newer target names have begun omitting the vendor, so the only
280        // component we know must be there is the OS name.
281        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            // Two components; format is `arch-os`.
290            [os] => ("unknown", *os, "", ""),
291            // The three-component case is a bit tricky to handle, it could
292            // either have the format `arch-vendor-os` or `arch-os-env+abi`.
293            [vendor_or_os, os_or_envabi] => {
294                // We differentiate between these by checking if the last
295                // component is an env/ABI; if it isn't, then it's probably
296                // an OS instead.
297                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            // Four components; format is `arch-vendor-os-env+abi`.
304            [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        // Part of the architecture name is carried over into the ABI.
322        match full_arch {
323            // https://github.com/rust-lang/compiler-team/issues/830
324            arch if arch.starts_with("riscv32e") => {
325                abi = "ilp32e";
326            }
327            _ => {}
328        }
329
330        // Various environment/ABIs are determined based on OS name.
331        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        // Extra overrides for badly named targets.
340        match target {
341            // Actually simulator targets.
342            "i386-apple-ios" | "x86_64-apple-ios" | "x86_64-apple-tvos" => {
343                abi = "sim";
344            }
345            // Name should've contained `muslabi64`.
346            "mips64-openwrt-linux-musl" => {
347                abi = "abi64";
348            }
349            // Specifies abi even though not in name.
350            "armv6-unknown-freebsd" | "armv6k-nintendo-3ds" | "armv7-unknown-freebsd" => {
351                abi = "eabihf";
352            }
353            // Specifies abi even though not in name.
354            "armv7-unknown-linux-ohos" | "armv7-unknown-trusty" => {
355                abi = "eabi";
356            }
357            _ => {}
358        }
359
360        let os = match os {
361            // Horizon is the common/internal OS name for 3DS and the Switch.
362            "3ds" | "switch" => "horizon",
363            // FIXME(madsmtm): macOS targets are badly named.
364            "darwin" => "macos",
365
366            // WASI targets contain the preview version in them too. Should've
367            // been `wasi-p1`/`wasi-p2`, but that's probably too late now.
368            os if os.starts_with("wasi") => {
369                env = os.strip_prefix("wasi").unwrap();
370                "wasi"
371            }
372            // FIXME(madsmtm): Badly named targets `*-linux-androideabi`,
373            // should be `*-android-eabi`.
374            "androideabi" => {
375                abi = "eabi";
376                "android"
377            }
378
379            os => os,
380        };
381
382        let vendor = match vendor {
383            // esp, esp32, esp32s2 etc.
384            vendor if vendor.starts_with("esp") => "espressif",
385            // FIXME(madsmtm): Badly named targets `*-linux-android*`,
386            // "linux" makes no sense as the vendor name.
387            "linux" if os == "android" || os == "androideabi" => "unknown",
388            // FIXME(madsmtm): Fix in `rustc` after
389            // https://github.com/rust-lang/compiler-team/issues/850.
390            "wali" => "unknown",
391            "lynx" => "unknown",
392            // Some Linux distributions set their name as the target vendor,
393            // so we have to assume that it can be an arbitary string.
394            vendor => vendor,
395        };
396
397        // Intentionally also marked as an ABI:
398        // https://github.com/rust-lang/rust/pull/86922
399        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 tier 1 targets.
441    #[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            // Check that they parse.
457            let _ = TargetInfo::from_rustc_target(target).unwrap();
458        }
459    }
460
461    // Various custom target names not (or no longer) known by `rustc`.
462    #[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            // Check that they parse.
487            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        // Cannot determine full architecture from cfgs.
493        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            // Not set in older Rust versions
502            abi: "",
503        };
504
505        for cfg in cfgs.lines() {
506            if let Some((name, value)) = cfg.split_once('=') {
507                // Remove whitespace, if `rustc` decided to insert any.
508                let name = name.trim();
509                let value = value.trim();
510
511                // Remove quotes around value.
512                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                // Skip cfgs like `debug_assertions` and `unix`.
525            }
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    // Used in .github/workflows/test-rustc-targets.yml
538    #[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}