1 use std::collections::{BTreeSet, HashMap};
2 use std::env;
3 use std::ffi::{OsStr, OsString};
4 use std::path::PathBuf;
5
6 use cargo_platform::CfgExpr;
7 use cargo_util::{paths, ProcessBuilder};
8
9 use super::BuildContext;
10 use crate::core::compiler::{CompileKind, Metadata, Unit};
11 use crate::core::Package;
12 use crate::util::{config, CargoResult, Config};
13
14 /// Structure with enough information to run `rustdoc --test`.
15 pub struct Doctest {
16 /// What's being doctested
17 pub unit: Unit,
18 /// Arguments needed to pass to rustdoc to run this test.
19 pub args: Vec<OsString>,
20 /// Whether or not -Zunstable-options is needed.
21 pub unstable_opts: bool,
22 /// The -Clinker value to use.
23 pub linker: Option<PathBuf>,
24 /// The script metadata, if this unit's package has a build script.
25 ///
26 /// This is used for indexing [`Compilation::extra_env`].
27 pub script_meta: Option<Metadata>,
28 }
29
30 /// Information about the output of a unit.
31 #[derive(Ord, PartialOrd, Eq, PartialEq)]
32 pub struct UnitOutput {
33 /// The unit that generated this output.
34 pub unit: Unit,
35 /// Path to the unit's primary output (an executable or cdylib).
36 pub path: PathBuf,
37 /// The script metadata, if this unit's package has a build script.
38 ///
39 /// This is used for indexing [`Compilation::extra_env`].
40 pub script_meta: Option<Metadata>,
41 }
42
43 /// A structure returning the result of a compilation.
44 pub struct Compilation<'cfg> {
45 /// An array of all tests created during this compilation.
46 pub tests: Vec<UnitOutput>,
47
48 /// An array of all binaries created.
49 pub binaries: Vec<UnitOutput>,
50
51 /// An array of all cdylibs created.
52 pub cdylibs: Vec<UnitOutput>,
53
54 /// The crate names of the root units specified on the command-line.
55 pub root_crate_names: Vec<String>,
56
57 /// All directories for the output of native build commands.
58 ///
59 /// This is currently used to drive some entries which are added to the
60 /// LD_LIBRARY_PATH as appropriate.
61 ///
62 /// The order should be deterministic.
63 pub native_dirs: BTreeSet<PathBuf>,
64
65 /// Root output directory (for the local package's artifacts)
66 pub root_output: HashMap<CompileKind, PathBuf>,
67
68 /// Output directory for rust dependencies.
69 /// May be for the host or for a specific target.
70 pub deps_output: HashMap<CompileKind, PathBuf>,
71
72 /// The path to the host libdir for the compiler used
73 sysroot_host_libdir: PathBuf,
74
75 /// The path to libstd for each target
76 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
77
78 /// Extra environment variables that were passed to compilations and should
79 /// be passed to future invocations of programs.
80 ///
81 /// The key is the build script metadata for uniquely identifying the
82 /// `RunCustomBuild` unit that generated these env vars.
83 pub extra_env: HashMap<Metadata, Vec<(String, String)>>,
84
85 /// Libraries to test with rustdoc.
86 pub to_doc_test: Vec<Doctest>,
87
88 /// The target host triple.
89 pub host: String,
90
91 config: &'cfg Config,
92
93 /// Rustc process to be used by default
94 rustc_process: ProcessBuilder,
95 /// Rustc process to be used for workspace crates instead of rustc_process
96 rustc_workspace_wrapper_process: ProcessBuilder,
97 /// Optional rustc process to be used for primary crates instead of either rustc_process or
98 /// rustc_workspace_wrapper_process
99 primary_rustc_process: Option<ProcessBuilder>,
100
101 target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
102 }
103
104 impl<'cfg> Compilation<'cfg> {
new<'a>(bcx: &BuildContext<'a, 'cfg>) -> CargoResult<Compilation<'cfg>>105 pub fn new<'a>(bcx: &BuildContext<'a, 'cfg>) -> CargoResult<Compilation<'cfg>> {
106 let mut rustc = bcx.rustc().process();
107 let mut primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
108 let mut rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
109
110 if bcx.config.extra_verbose() {
111 rustc.display_env_vars();
112 rustc_workspace_wrapper_process.display_env_vars();
113
114 if let Some(rustc) = primary_rustc_process.as_mut() {
115 rustc.display_env_vars();
116 }
117 }
118
119 Ok(Compilation {
120 // TODO: deprecated; remove.
121 native_dirs: BTreeSet::new(),
122 root_output: HashMap::new(),
123 deps_output: HashMap::new(),
124 sysroot_host_libdir: bcx
125 .target_data
126 .info(CompileKind::Host)
127 .sysroot_host_libdir
128 .clone(),
129 sysroot_target_libdir: bcx
130 .all_kinds
131 .iter()
132 .map(|&kind| {
133 (
134 kind,
135 bcx.target_data.info(kind).sysroot_target_libdir.clone(),
136 )
137 })
138 .collect(),
139 tests: Vec::new(),
140 binaries: Vec::new(),
141 cdylibs: Vec::new(),
142 root_crate_names: Vec::new(),
143 extra_env: HashMap::new(),
144 to_doc_test: Vec::new(),
145 config: bcx.config,
146 host: bcx.host_triple().to_string(),
147 rustc_process: rustc,
148 rustc_workspace_wrapper_process,
149 primary_rustc_process,
150 target_runners: bcx
151 .build_config
152 .requested_kinds
153 .iter()
154 .chain(Some(&CompileKind::Host))
155 .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
156 .collect::<CargoResult<HashMap<_, _>>>()?,
157 })
158 }
159
160 /// Returns a [`ProcessBuilder`] for running `rustc`.
161 ///
162 /// `is_primary` is true if this is a "primary package", which means it
163 /// was selected by the user on the command-line (such as with a `-p`
164 /// flag), see [`crate::core::compiler::Context::primary_packages`].
165 ///
166 /// `is_workspace` is true if this is a workspace member.
rustc_process( &self, unit: &Unit, is_primary: bool, is_workspace: bool, ) -> CargoResult<ProcessBuilder>167 pub fn rustc_process(
168 &self,
169 unit: &Unit,
170 is_primary: bool,
171 is_workspace: bool,
172 ) -> CargoResult<ProcessBuilder> {
173 let rustc = if is_primary && self.primary_rustc_process.is_some() {
174 self.primary_rustc_process.clone().unwrap()
175 } else if is_workspace {
176 self.rustc_workspace_wrapper_process.clone()
177 } else {
178 self.rustc_process.clone()
179 };
180
181 let cmd = fill_rustc_tool_env(rustc, unit);
182 self.fill_env(cmd, &unit.pkg, None, unit.kind, true)
183 }
184
185 /// Returns a [`ProcessBuilder`] for running `rustdoc`.
rustdoc_process( &self, unit: &Unit, script_meta: Option<Metadata>, ) -> CargoResult<ProcessBuilder>186 pub fn rustdoc_process(
187 &self,
188 unit: &Unit,
189 script_meta: Option<Metadata>,
190 ) -> CargoResult<ProcessBuilder> {
191 let rustdoc = ProcessBuilder::new(&*self.config.rustdoc()?);
192 let cmd = fill_rustc_tool_env(rustdoc, unit);
193 let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?;
194 unit.target.edition().cmd_edition_arg(&mut p);
195
196 for crate_type in unit.target.rustc_crate_types() {
197 p.arg("--crate-type").arg(crate_type.as_str());
198 }
199
200 Ok(p)
201 }
202
203 /// Returns a [`ProcessBuilder`] appropriate for running a process for the
204 /// host platform.
205 ///
206 /// This is currently only used for running build scripts. If you use this
207 /// for anything else, please be extra careful on how environment
208 /// variables are set!
host_process<T: AsRef<OsStr>>( &self, cmd: T, pkg: &Package, ) -> CargoResult<ProcessBuilder>209 pub fn host_process<T: AsRef<OsStr>>(
210 &self,
211 cmd: T,
212 pkg: &Package,
213 ) -> CargoResult<ProcessBuilder> {
214 self.fill_env(
215 ProcessBuilder::new(cmd),
216 pkg,
217 None,
218 CompileKind::Host,
219 false,
220 )
221 }
222
target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)>223 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
224 self.target_runners.get(&kind).and_then(|x| x.as_ref())
225 }
226
227 /// Returns a [`ProcessBuilder`] appropriate for running a process for the
228 /// target platform. This is typically used for `cargo run` and `cargo
229 /// test`.
230 ///
231 /// `script_meta` is the metadata for the `RunCustomBuild` unit that this
232 /// unit used for its build script. Use `None` if the package did not have
233 /// a build script.
target_process<T: AsRef<OsStr>>( &self, cmd: T, kind: CompileKind, pkg: &Package, script_meta: Option<Metadata>, ) -> CargoResult<ProcessBuilder>234 pub fn target_process<T: AsRef<OsStr>>(
235 &self,
236 cmd: T,
237 kind: CompileKind,
238 pkg: &Package,
239 script_meta: Option<Metadata>,
240 ) -> CargoResult<ProcessBuilder> {
241 let builder = if let Some((runner, args)) = self.target_runner(kind) {
242 let mut builder = ProcessBuilder::new(runner);
243 builder.args(args);
244 builder.arg(cmd);
245 builder
246 } else {
247 ProcessBuilder::new(cmd)
248 };
249 self.fill_env(builder, pkg, script_meta, kind, false)
250 }
251
252 /// Prepares a new process with an appropriate environment to run against
253 /// the artifacts produced by the build process.
254 ///
255 /// The package argument is also used to configure environment variables as
256 /// well as the working directory of the child process.
fill_env( &self, mut cmd: ProcessBuilder, pkg: &Package, script_meta: Option<Metadata>, kind: CompileKind, is_rustc_tool: bool, ) -> CargoResult<ProcessBuilder>257 fn fill_env(
258 &self,
259 mut cmd: ProcessBuilder,
260 pkg: &Package,
261 script_meta: Option<Metadata>,
262 kind: CompileKind,
263 is_rustc_tool: bool,
264 ) -> CargoResult<ProcessBuilder> {
265 let mut search_path = Vec::new();
266 if is_rustc_tool {
267 search_path.push(self.deps_output[&CompileKind::Host].clone());
268 search_path.push(self.sysroot_host_libdir.clone());
269 } else {
270 search_path.extend(super::filter_dynamic_search_path(
271 self.native_dirs.iter(),
272 &self.root_output[&kind],
273 ));
274 search_path.push(self.deps_output[&kind].clone());
275 search_path.push(self.root_output[&kind].clone());
276 // For build-std, we don't want to accidentally pull in any shared
277 // libs from the sysroot that ships with rustc. This may not be
278 // required (at least I cannot craft a situation where it
279 // matters), but is here to be safe.
280 if self.config.cli_unstable().build_std.is_none() {
281 search_path.push(self.sysroot_target_libdir[&kind].clone());
282 }
283 }
284
285 let dylib_path = paths::dylib_path();
286 let dylib_path_is_empty = dylib_path.is_empty();
287 search_path.extend(dylib_path.into_iter());
288 if cfg!(target_os = "macos") && dylib_path_is_empty {
289 // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't
290 // set or set to an empty string. Since Cargo is explicitly setting
291 // the value, make sure the defaults still work.
292 if let Some(home) = env::var_os("HOME") {
293 search_path.push(PathBuf::from(home).join("lib"));
294 }
295 search_path.push(PathBuf::from("/usr/local/lib"));
296 search_path.push(PathBuf::from("/usr/lib"));
297 }
298 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
299
300 cmd.env(paths::dylib_path_envvar(), &search_path);
301 if let Some(meta) = script_meta {
302 if let Some(env) = self.extra_env.get(&meta) {
303 for (k, v) in env {
304 cmd.env(k, v);
305 }
306 }
307 }
308
309 let metadata = pkg.manifest().metadata();
310
311 let cargo_exe = self.config.cargo_exe()?;
312 cmd.env(crate::CARGO_ENV, cargo_exe);
313
314 // When adding new environment variables depending on
315 // crate properties which might require rebuild upon change
316 // consider adding the corresponding properties to the hash
317 // in BuildContext::target_metadata()
318 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
319 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
320 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
321 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
322 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
323 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
324 .env("CARGO_PKG_NAME", &*pkg.name())
325 .env(
326 "CARGO_PKG_DESCRIPTION",
327 metadata.description.as_ref().unwrap_or(&String::new()),
328 )
329 .env(
330 "CARGO_PKG_HOMEPAGE",
331 metadata.homepage.as_ref().unwrap_or(&String::new()),
332 )
333 .env(
334 "CARGO_PKG_REPOSITORY",
335 metadata.repository.as_ref().unwrap_or(&String::new()),
336 )
337 .env(
338 "CARGO_PKG_LICENSE",
339 metadata.license.as_ref().unwrap_or(&String::new()),
340 )
341 .env(
342 "CARGO_PKG_LICENSE_FILE",
343 metadata.license_file.as_ref().unwrap_or(&String::new()),
344 )
345 .env("CARGO_PKG_AUTHORS", &pkg.authors().join(":"))
346 .cwd(pkg.root());
347
348 if self.config.cli_unstable().configurable_env {
349 // Apply any environment variables from the config
350 for (key, value) in self.config.env_config()?.iter() {
351 if value.is_force() || cmd.get_env(key).is_none() {
352 cmd.env(key, value.resolve(self.config));
353 }
354 }
355 }
356
357 Ok(cmd)
358 }
359 }
360
361 /// Prepares a rustc_tool process with additional environment variables
362 /// that are only relevant in a context that has a unit
fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder363 fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
364 if unit.target.is_bin() {
365 cmd.env("CARGO_BIN_NAME", unit.target.name());
366 }
367 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
368 cmd
369 }
370
target_runner( bcx: &BuildContext<'_, '_>, kind: CompileKind, ) -> CargoResult<Option<(PathBuf, Vec<String>)>>371 fn target_runner(
372 bcx: &BuildContext<'_, '_>,
373 kind: CompileKind,
374 ) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
375 let target = bcx.target_data.short_name(&kind);
376
377 // try target.{}.runner
378 let key = format!("target.{}.runner", target);
379
380 if let Some(v) = bcx.config.get::<Option<config::PathAndArgs>>(&key)? {
381 let path = v.path.resolve_program(bcx.config);
382 return Ok(Some((path, v.args)));
383 }
384
385 // try target.'cfg(...)'.runner
386 let target_cfg = bcx.target_data.info(kind).cfg();
387 let mut cfgs = bcx
388 .config
389 .target_cfgs()?
390 .iter()
391 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
392 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
393 let matching_runner = cfgs.next();
394 if let Some((key, runner)) = cfgs.next() {
395 anyhow::bail!(
396 "several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`\n\
397 first match `{}` located in {}\n\
398 second match `{}` located in {}",
399 matching_runner.unwrap().0,
400 matching_runner.unwrap().1.definition,
401 key,
402 runner.definition
403 );
404 }
405 Ok(matching_runner.map(|(_k, runner)| {
406 (
407 runner.val.path.clone().resolve_program(bcx.config),
408 runner.val.args.clone(),
409 )
410 }))
411 }
412