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}