cc/target/
parser.rs

1use std::{env, str::FromStr};
2
3use crate::{
4    target::{llvm, TargetInfo},
5    utilities::OnceLock,
6    Error, ErrorKind,
7};
8
9#[derive(Debug)]
10struct TargetInfoParserInner {
11    full_arch: Box<str>,
12    arch: Box<str>,
13    vendor: Box<str>,
14    os: Box<str>,
15    env: Box<str>,
16    abi: Box<str>,
17    unversioned_llvm_target: Box<str>,
18}
19
20impl TargetInfoParserInner {
21    fn from_cargo_environment_variables() -> Result<Self, Error> {
22        // `TARGET` must be present.
23        //
24        // No need to emit `rerun-if-env-changed` for this,
25        // as it is controlled by Cargo itself.
26        #[allow(clippy::disallowed_methods)]
27        let target_triple = env::var("TARGET").map_err(|err| {
28            Error::new(
29                ErrorKind::EnvVarNotFound,
30                format!("failed reading TARGET: {err}"),
31            )
32        })?;
33
34        // Parse the full architecture name from the target triple.
35        let (full_arch, _rest) = target_triple.split_once('-').ok_or(Error::new(
36            ErrorKind::InvalidTarget,
37            format!("target `{target_triple}` had an unknown architecture"),
38        ))?;
39
40        let cargo_env = |name, fallback: Option<&str>| -> Result<Box<str>, Error> {
41            // No need to emit `rerun-if-env-changed` for these,
42            // as they are controlled by Cargo itself.
43            #[allow(clippy::disallowed_methods)]
44            match env::var(name) {
45                Ok(var) => Ok(var.into_boxed_str()),
46                Err(err) => match fallback {
47                    Some(fallback) => Ok(fallback.into()),
48                    None => Err(Error::new(
49                        ErrorKind::EnvVarNotFound,
50                        format!("did not find fallback information for target `{target_triple}`, and failed reading {name}: {err}"),
51                    )),
52                },
53            }
54        };
55
56        // Prefer to use `CARGO_ENV_*` if set, since these contain the most
57        // correct information relative to the current `rustc`, and makes it
58        // possible to support custom target JSON specs unknown to `rustc`.
59        //
60        // NOTE: If the user is using an older `rustc`, that data may be older
61        // than our pre-generated data, but we still prefer Cargo's view of
62        // the world, since at least `cc` won't differ from `rustc` in that
63        // case.
64        //
65        // These may not be set in case the user depended on being able to
66        // just set `TARGET` outside of build scripts; in those cases, fall
67        // back back to data from the known set of target triples instead.
68        //
69        // See discussion in #1225 for further details.
70        let fallback_target = TargetInfo::from_str(&target_triple).ok();
71        let ft = fallback_target.as_ref();
72        let arch = cargo_env("CARGO_CFG_TARGET_ARCH", ft.map(|t| t.arch))?;
73        let vendor = cargo_env("CARGO_CFG_TARGET_VENDOR", ft.map(|t| t.vendor))?;
74        let os = cargo_env("CARGO_CFG_TARGET_OS", ft.map(|t| t.os))?;
75        let env = cargo_env("CARGO_CFG_TARGET_ENV", ft.map(|t| t.env))?;
76        // `target_abi` was stabilized in Rust 1.78, which is higher than our
77        // MSRV, so it may not always be available; In that case, fall back to
78        // `""`, which is _probably_ correct for unknown target triples.
79        let abi = cargo_env("CARGO_CFG_TARGET_ABI", ft.map(|t| t.abi))
80            .unwrap_or_else(|_| String::default().into_boxed_str());
81
82        // Prefer `rustc`'s LLVM target triple information.
83        let unversioned_llvm_target = match fallback_target {
84            Some(ft) => ft.unversioned_llvm_target.to_string(),
85            None => llvm::guess_llvm_target_triple(full_arch, &vendor, &os, &env, &abi),
86        };
87
88        Ok(Self {
89            full_arch: full_arch.to_string().into_boxed_str(),
90            arch,
91            vendor,
92            os,
93            env,
94            abi,
95            unversioned_llvm_target: unversioned_llvm_target.into_boxed_str(),
96        })
97    }
98}
99
100/// Parser for [`TargetInfo`], contains cached information.
101#[derive(Default, Debug)]
102pub(crate) struct TargetInfoParser(OnceLock<Result<TargetInfoParserInner, Error>>);
103
104impl TargetInfoParser {
105    pub fn parse_from_cargo_environment_variables(&self) -> Result<TargetInfo<'_>, Error> {
106        match self
107            .0
108            .get_or_init(TargetInfoParserInner::from_cargo_environment_variables)
109        {
110            Ok(TargetInfoParserInner {
111                full_arch,
112                arch,
113                vendor,
114                os,
115                env,
116                abi,
117                unversioned_llvm_target,
118            }) => Ok(TargetInfo {
119                full_arch,
120                arch,
121                vendor,
122                os,
123                env,
124                abi,
125                unversioned_llvm_target,
126            }),
127            Err(e) => Err(e.clone()),
128        }
129    }
130}