cargo/core/compiler/compile_kind.rs
1//! Type definitions for cross-compilation.
2
3use crate::core::Target;
4use crate::util::errors::CargoResult;
5use crate::util::interning::InternedString;
6use crate::util::{GlobalContext, StableHasher, try_canonicalize};
7use anyhow::Context as _;
8use anyhow::bail;
9use cargo_util::ProcessBuilder;
10use serde::Serialize;
11use std::collections::BTreeSet;
12use std::fs;
13use std::hash::{Hash, Hasher};
14use std::path::Path;
15
16/// Indicator for how a unit is being compiled.
17///
18/// This is used primarily for organizing cross compilations vs host
19/// compilations, where cross compilations happen at the request of `--target`
20/// and host compilations happen for things like build scripts and procedural
21/// macros.
22#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
23pub enum CompileKind {
24 /// Attached to a unit that is compiled for the "host" system or otherwise
25 /// is compiled without a `--target` flag. This is used for procedural
26 /// macros and build scripts, or if the `--target` flag isn't passed.
27 Host,
28
29 /// Attached to a unit to be compiled for a particular target. This is used
30 /// for units when the `--target` flag is passed.
31 Target(CompileTarget),
32}
33
34/// Fallback behavior in the
35/// [`CompileKind::from_requested_targets_with_fallback`] function when
36/// no targets are specified.
37pub enum CompileKindFallback {
38 /// The build configuration is consulted to find the default target, such as
39 /// `$CARGO_BUILD_TARGET` or reading `build.target`.
40 BuildConfig,
41
42 /// Only the host should be returned when targets aren't explicitly
43 /// specified. This is used by `cargo metadata` for example where "only
44 /// host" has a special meaning in terms of the returned metadata.
45 JustHost,
46}
47
48impl CompileKind {
49 pub fn is_host(&self) -> bool {
50 matches!(self, CompileKind::Host)
51 }
52
53 pub fn for_target(self, target: &Target) -> CompileKind {
54 // Once we start compiling for the `Host` kind we continue doing so, but
55 // if we are a `Target` kind and then we start compiling for a target
56 // that needs to be on the host we lift ourselves up to `Host`.
57 match self {
58 CompileKind::Host => CompileKind::Host,
59 CompileKind::Target(_) if target.for_host() => CompileKind::Host,
60 CompileKind::Target(n) => CompileKind::Target(n),
61 }
62 }
63
64 /// Creates a new list of `CompileKind` based on the requested list of
65 /// targets.
66 ///
67 /// If no targets are given then this returns a single-element vector with
68 /// `CompileKind::Host`.
69 pub fn from_requested_targets(
70 gctx: &GlobalContext,
71 targets: &[String],
72 ) -> CargoResult<Vec<CompileKind>> {
73 CompileKind::from_requested_targets_with_fallback(
74 gctx,
75 targets,
76 CompileKindFallback::BuildConfig,
77 )
78 }
79
80 /// Same as [`CompileKind::from_requested_targets`] except that if `targets`
81 /// doesn't explicitly mention anything the behavior of what to return is
82 /// controlled by the `fallback` argument.
83 pub fn from_requested_targets_with_fallback(
84 gctx: &GlobalContext,
85 targets: &[String],
86 fallback: CompileKindFallback,
87 ) -> CargoResult<Vec<CompileKind>> {
88 let dedup = |targets: &[String]| {
89 let deduplicated_targets = targets
90 .iter()
91 .map(|value| {
92 // This neatly substitutes the manually-specified `host-tuple` target directive
93 // with the compiling machine's target triple.
94 if value.as_str() == "host-tuple" {
95 let host_triple = env!("RUST_HOST_TARGET");
96 Ok(CompileKind::Target(CompileTarget::new(
97 host_triple,
98 gctx.cli_unstable().json_target_spec,
99 )?))
100 } else {
101 Ok(CompileKind::Target(CompileTarget::new(
102 value.as_str(),
103 gctx.cli_unstable().json_target_spec,
104 )?))
105 }
106 })
107 // First collect into a set to deduplicate any `--target` passed
108 // more than once...
109 .collect::<CargoResult<BTreeSet<_>>>()?
110 // ... then generate a flat list for everything else to use.
111 .into_iter()
112 .collect();
113
114 Ok(deduplicated_targets)
115 };
116
117 if !targets.is_empty() {
118 return dedup(targets);
119 }
120
121 let kinds = match (fallback, &gctx.build_config()?.target) {
122 (_, None) | (CompileKindFallback::JustHost, _) => Ok(vec![CompileKind::Host]),
123 (CompileKindFallback::BuildConfig, Some(build_target_config)) => {
124 dedup(&build_target_config.values(gctx.cwd())?)
125 }
126 };
127
128 kinds
129 }
130
131 /// Hash used for fingerprinting.
132 ///
133 /// Metadata hashing uses the normal Hash trait, which does not
134 /// differentiate on `.json` file contents. The fingerprint hash does
135 /// check the contents.
136 pub fn fingerprint_hash(&self) -> u64 {
137 match self {
138 CompileKind::Host => 0,
139 CompileKind::Target(target) => target.fingerprint_hash(),
140 }
141 }
142
143 /// Adds the `--target` flag to the given [`ProcessBuilder`] if this is a
144 /// non-host build.
145 pub fn add_target_arg(&self, builder: &mut ProcessBuilder) {
146 if let CompileKind::Target(target) = self {
147 builder.arg("--target").arg(target.rustc_target());
148 if matches!(target, CompileTarget::Json { .. }) {
149 builder.arg("-Zunstable-options");
150 }
151 }
152 }
153}
154
155impl serde::ser::Serialize for CompileKind {
156 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
157 where
158 S: serde::ser::Serializer,
159 {
160 match self {
161 CompileKind::Host => None::<&str>.serialize(s),
162 CompileKind::Target(t) => Some(t.rustc_target()).serialize(s),
163 }
164 }
165}
166
167/// Abstraction for the representation of a compilation target that Cargo has.
168///
169/// Compilation targets are one of two things right now:
170///
171/// 1. A raw target string, like `x86_64-unknown-linux-gnu`.
172/// 2. The path to a JSON file, such as `/path/to/my-target.json`.
173///
174/// Raw target strings are typically dictated by `rustc` itself and represent
175/// built-in targets. Custom JSON files are somewhat unstable, but supported
176/// here in Cargo. Note that for JSON target files this `CompileTarget` stores a
177/// full canonicalized path to the target.
178///
179/// The main reason for this existence is to handle JSON target files where when
180/// we call rustc we pass full paths but when we use it for Cargo's purposes
181/// like naming directories or looking up configuration keys we only check the
182/// file stem of JSON target files. For built-in rustc targets this is just an
183/// uninterpreted string basically.
184#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)]
185pub enum CompileTarget {
186 Tuple(InternedString),
187 Json {
188 short: InternedString,
189 path: InternedString,
190 },
191}
192
193impl CompileTarget {
194 pub fn new(name: &str, unstable_json: bool) -> CargoResult<CompileTarget> {
195 let name = name.trim();
196 if name.is_empty() {
197 bail!("target was empty");
198 }
199 if !name.ends_with(".json") {
200 return Ok(CompileTarget::Tuple(name.into()));
201 }
202
203 if !unstable_json {
204 bail!(
205 "`.json` target specs require -Zjson-target-spec to be added to the cargo invocation"
206 );
207 }
208
209 // If `name` ends in `.json` then it's likely a custom target
210 // specification. Canonicalize the path to ensure that different builds
211 // with different paths always produce the same result.
212 let p = try_canonicalize(Path::new(name))
213 .with_context(|| format!("target path `{name}` is not a valid file"))?;
214 let path = p
215 .to_str()
216 .ok_or_else(|| anyhow::format_err!("target path `{name}` is not valid unicode"))?
217 .into();
218 let short = p.file_stem().unwrap().to_str().unwrap().into();
219 Ok(CompileTarget::Json { short, path })
220 }
221
222 /// Returns the full unqualified name of this target, suitable for passing
223 /// to `rustc` directly.
224 ///
225 /// Typically this is pretty much the same as `short_name`, but for the case
226 /// of JSON target files this will be a full canonicalized path name for the
227 /// current filesystem.
228 pub fn rustc_target(&self) -> InternedString {
229 match self {
230 CompileTarget::Tuple(name) => *name,
231 CompileTarget::Json { path, .. } => *path,
232 }
233 }
234
235 /// Returns a "short" version of the target name suitable for usage within
236 /// Cargo for configuration and such.
237 ///
238 /// This is typically the same as `rustc_target`, or the full name, but for
239 /// JSON target files this returns just the file stem (e.g. `foo` out of
240 /// `foo.json`) instead of the full path.
241 pub fn short_name(&self) -> &str {
242 match self {
243 CompileTarget::Tuple(name) => name,
244 CompileTarget::Json { short, .. } => short,
245 }
246 }
247
248 /// See [`CompileKind::fingerprint_hash`].
249 pub fn fingerprint_hash(&self) -> u64 {
250 let mut hasher = StableHasher::new();
251 match self {
252 CompileTarget::Tuple(name) => name.hash(&mut hasher),
253 CompileTarget::Json { path, .. } => {
254 // This may have some performance concerns, since it is called
255 // fairly often. If that ever seems worth fixing, consider
256 // embedding this in `CompileTarget`.
257 match fs::read_to_string(path) {
258 Ok(contents) => contents.hash(&mut hasher),
259 Err(_) => path.hash(&mut hasher),
260 }
261 }
262 }
263 Hasher::finish(&hasher)
264 }
265}