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