1 | use std::{env, str::FromStr}; |
2 | |
3 | use crate::{ |
4 | target::{llvm, TargetInfo}, |
5 | utilities::OnceLock, |
6 | Error, ErrorKind, |
7 | }; |
8 | |
9 | #[derive (Debug)] |
10 | struct 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 | llvm_target: Box<str>, |
18 | } |
19 | |
20 | impl 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 llvm_target = match fallback_target { |
84 | Some(ft) => ft.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 | llvm_target: llvm_target.into_boxed_str(), |
96 | }) |
97 | } |
98 | } |
99 | |
100 | /// Parser for [`TargetInfo`], contains cached information. |
101 | #[derive (Default, Debug)] |
102 | pub(crate) struct TargetInfoParser(OnceLock<Result<TargetInfoParserInner, Error>>); |
103 | |
104 | impl 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 | llvm_target, |
118 | }) => Ok(TargetInfo { |
119 | full_arch, |
120 | arch, |
121 | vendor, |
122 | os, |
123 | env, |
124 | abi, |
125 | llvm_target, |
126 | }), |
127 | Err(e) => Err(e.clone()), |
128 | } |
129 | } |
130 | } |
131 | |