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