Skip to main content

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
95                    if value.as_str() == "host-tuple" {
96                        let host_triple = env!("RUST_HOST_TARGET");
97                        Ok(CompileKind::Target(CompileTarget::new(
98                            host_triple,
99                            gctx.cli_unstable().json_target_spec,
100                        )?))
101                    } else {
102                        Ok(CompileKind::Target(CompileTarget::new(
103                            value.as_str(),
104                            gctx.cli_unstable().json_target_spec,
105                        )?))
106                    }
107                })
108                // First collect into a set to deduplicate any `--target` passed
109                // more than once...
110                .collect::<CargoResult<BTreeSet<_>>>()?
111                // ... then generate a flat list for everything else to use.
112                .into_iter()
113                .collect();
114
115            Ok(deduplicated_targets)
116        };
117
118        if !targets.is_empty() {
119            return dedup(targets);
120        }
121
122        let kinds = match (fallback, &gctx.build_config()?.target) {
123            (_, None) | (CompileKindFallback::JustHost, _) => Ok(vec![CompileKind::Host]),
124            (CompileKindFallback::BuildConfig, Some(build_target_config)) => {
125                dedup(&build_target_config.values(gctx.cwd())?)
126            }
127        };
128
129        kinds
130    }
131
132    /// Hash used for fingerprinting.
133    ///
134    /// Metadata hashing uses the normal Hash trait, which does not
135    /// differentiate on `.json` file contents. The fingerprint hash does
136    /// check the contents.
137    pub fn fingerprint_hash(&self) -> u64 {
138        match self {
139            CompileKind::Host => 0,
140            CompileKind::Target(target) => target.fingerprint_hash(),
141        }
142    }
143
144    /// Adds the `--target` flag to the given [`ProcessBuilder`] if this is a
145    /// non-host build.
146    pub fn add_target_arg(&self, builder: &mut ProcessBuilder) {
147        if let CompileKind::Target(target) = self {
148            builder.arg("--target").arg(target.rustc_target());
149            if matches!(target, CompileTarget::Json { .. }) {
150                builder.arg("-Zunstable-options");
151            }
152        }
153    }
154}
155
156impl serde::ser::Serialize for CompileKind {
157    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
158    where
159        S: serde::ser::Serializer,
160    {
161        match self {
162            CompileKind::Host => None::<&str>.serialize(s),
163            CompileKind::Target(t) => Some(t.rustc_target()).serialize(s),
164        }
165    }
166}
167
168/// Abstraction for the representation of a compilation target that Cargo has.
169///
170/// Compilation targets are one of two things right now:
171///
172/// 1. A raw target string, like `x86_64-unknown-linux-gnu`.
173/// 2. The path to a JSON file, such as `/path/to/my-target.json`.
174///
175/// Raw target strings are typically dictated by `rustc` itself and represent
176/// built-in targets. Custom JSON files are somewhat unstable, but supported
177/// here in Cargo. Note that for JSON target files this `CompileTarget` stores a
178/// full canonicalized path to the target.
179///
180/// The main reason for this existence is to handle JSON target files where when
181/// we call rustc we pass full paths but when we use it for Cargo's purposes
182/// like naming directories or looking up configuration keys we only check the
183/// file stem of JSON target files. For built-in rustc targets this is just an
184/// uninterpreted string basically.
185#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)]
186pub enum CompileTarget {
187    Tuple(InternedString),
188    Json {
189        short: InternedString,
190        path: InternedString,
191    },
192}
193
194impl CompileTarget {
195    pub fn new(name: &str, unstable_json: bool) -> CargoResult<CompileTarget> {
196        let name = name.trim();
197        if name.is_empty() {
198            bail!("target was empty");
199        }
200        if !name.ends_with(".json") {
201            return Ok(CompileTarget::Tuple(name.into()));
202        }
203
204        if !unstable_json {
205            bail!("`.json` target specs require -Zjson-target-spec");
206        }
207
208        // If `name` ends in `.json` then it's likely a custom target
209        // specification. Canonicalize the path to ensure that different builds
210        // with different paths always produce the same result.
211        let p = try_canonicalize(Path::new(name))
212            .with_context(|| format!("target path `{name}` is not a valid file"))?;
213        let path = p
214            .to_str()
215            .ok_or_else(|| anyhow::format_err!("target path `{name}` is not valid unicode"))?
216            .into();
217        let short = p.file_stem().unwrap().to_str().unwrap().into();
218        Ok(CompileTarget::Json { short, path })
219    }
220
221    /// Returns the full unqualified name of this target, suitable for passing
222    /// to `rustc` directly.
223    ///
224    /// Typically this is pretty much the same as `short_name`, but for the case
225    /// of JSON target files this will be a full canonicalized path name for the
226    /// current filesystem.
227    pub fn rustc_target(&self) -> InternedString {
228        match self {
229            CompileTarget::Tuple(name) => *name,
230            CompileTarget::Json { path, .. } => *path,
231        }
232    }
233
234    /// Returns a "short" version of the target name suitable for usage within
235    /// Cargo for configuration and such.
236    ///
237    /// This is typically the same as `rustc_target`, or the full name, but for
238    /// JSON target files this returns just the file stem (e.g. `foo` out of
239    /// `foo.json`) instead of the full path.
240    pub fn short_name(&self) -> &str {
241        match self {
242            CompileTarget::Tuple(name) => name,
243            CompileTarget::Json { short, .. } => short,
244        }
245    }
246
247    /// See [`CompileKind::fingerprint_hash`].
248    pub fn fingerprint_hash(&self) -> u64 {
249        let mut hasher = StableHasher::new();
250        match self {
251            CompileTarget::Tuple(name) => name.hash(&mut hasher),
252            CompileTarget::Json { path, .. } => {
253                // This may have some performance concerns, since it is called
254                // fairly often. If that ever seems worth fixing, consider
255                // embedding this in `CompileTarget`.
256                match fs::read_to_string(path) {
257                    Ok(contents) => contents.hash(&mut hasher),
258                    Err(_) => path.hash(&mut hasher),
259                }
260            }
261        }
262        Hasher::finish(&hasher)
263    }
264}