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