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         // Apply any environment variables from the config
349         for (key, value) in self.config.env_config()?.iter() {
350             // never override a value that has already been set by cargo
351             if cmd.get_envs().contains_key(key) {
352                 continue;
353             }
354 
355             if value.is_force() || env::var_os(key).is_none() {
356                 cmd.env(key, value.resolve(self.config));
357             }
358         }
359 
360         Ok(cmd)
361     }
362 }
363 
364 /// Prepares a rustc_tool process with additional environment variables
365 /// that are only relevant in a context that has a unit
fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder366 fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
367     if unit.target.is_bin() {
368         let name = unit
369             .target
370             .binary_filename()
371             .unwrap_or(unit.target.name().to_string());
372 
373         cmd.env("CARGO_BIN_NAME", name);
374     }
375     cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
376     cmd
377 }
378 
target_runner( bcx: &BuildContext<'_, '_>, kind: CompileKind, ) -> CargoResult<Option<(PathBuf, Vec<String>)>>379 fn target_runner(
380     bcx: &BuildContext<'_, '_>,
381     kind: CompileKind,
382 ) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
383     let target = bcx.target_data.short_name(&kind);
384 
385     // try target.{}.runner
386     let key = format!("target.{}.runner", target);
387 
388     if let Some(v) = bcx.config.get::<Option<config::PathAndArgs>>(&key)? {
389         let path = v.path.resolve_program(bcx.config);
390         return Ok(Some((path, v.args)));
391     }
392 
393     // try target.'cfg(...)'.runner
394     let target_cfg = bcx.target_data.info(kind).cfg();
395     let mut cfgs = bcx
396         .config
397         .target_cfgs()?
398         .iter()
399         .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
400         .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
401     let matching_runner = cfgs.next();
402     if let Some((key, runner)) = cfgs.next() {
403         anyhow::bail!(
404             "several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`\n\
405              first match `{}` located in {}\n\
406              second match `{}` located in {}",
407             matching_runner.unwrap().0,
408             matching_runner.unwrap().1.definition,
409             key,
410             runner.definition
411         );
412     }
413     Ok(matching_runner.map(|(_k, runner)| {
414         (
415             runner.val.path.clone().resolve_program(bcx.config),
416             runner.val.args.clone(),
417         )
418     }))
419 }
420