1use std::collections::{BTreeSet, HashMap};
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7use cargo_platform::CfgExpr;
8use cargo_util::{ProcessBuilder, paths};
9
10use crate::core::Package;
11use crate::core::compiler::BuildContext;
12use crate::core::compiler::RustdocFingerprint;
13use crate::core::compiler::apply_env_config;
14use crate::core::compiler::{CompileKind, Unit, UnitHash};
15use crate::util::{CargoResult, GlobalContext};
16
17#[derive(Debug)]
19enum ToolKind {
20 Rustc,
22 Rustdoc,
24 HostProcess,
26 TargetProcess,
28}
29
30impl ToolKind {
31 fn is_rustc_tool(&self) -> bool {
32 matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
33 }
34}
35
36pub struct Doctest {
38 pub unit: Unit,
40 pub args: Vec<OsString>,
42 pub unstable_opts: bool,
44 pub linker: Option<PathBuf>,
46 pub script_metas: Option<Vec<UnitHash>>,
50
51 pub env: HashMap<String, OsString>,
53}
54
55pub struct UnitOutput {
57 pub unit: Unit,
59 pub path: PathBuf,
61 pub script_metas: Option<Vec<UnitHash>>,
65
66 pub env: HashMap<String, OsString>,
68}
69
70pub struct Compilation<'gctx> {
72 pub tests: Vec<UnitOutput>,
74
75 pub binaries: Vec<UnitOutput>,
77
78 pub cdylibs: Vec<UnitOutput>,
80
81 pub root_crate_names: Vec<String>,
83
84 pub native_dirs: BTreeSet<PathBuf>,
91
92 pub root_output: HashMap<CompileKind, PathBuf>,
94
95 pub deps_output: HashMap<CompileKind, PathBuf>,
98
99 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
101
102 pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
108
109 pub to_doc_test: Vec<Doctest>,
111
112 pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>,
116
117 pub host: String,
119
120 gctx: &'gctx GlobalContext,
121
122 rustc_process: ProcessBuilder,
124 rustc_workspace_wrapper_process: ProcessBuilder,
126 primary_rustc_process: Option<ProcessBuilder>,
129
130 target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
131 target_linkers: HashMap<CompileKind, Option<PathBuf>>,
133
134 pub lint_warning_count: usize,
136}
137
138impl<'gctx> Compilation<'gctx> {
139 pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
140 let rustc_process = bcx.rustc().process();
141 let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
142 let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
143 Ok(Compilation {
144 native_dirs: BTreeSet::new(),
145 root_output: HashMap::new(),
146 deps_output: HashMap::new(),
147 sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
148 tests: Vec::new(),
149 binaries: Vec::new(),
150 cdylibs: Vec::new(),
151 root_crate_names: Vec::new(),
152 extra_env: HashMap::new(),
153 to_doc_test: Vec::new(),
154 rustdoc_fingerprints: None,
155 gctx: bcx.gctx,
156 host: bcx.host_triple().to_string(),
157 rustc_process,
158 rustc_workspace_wrapper_process,
159 primary_rustc_process,
160 target_runners: bcx
161 .build_config
162 .requested_kinds
163 .iter()
164 .chain(Some(&CompileKind::Host))
165 .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
166 .collect::<CargoResult<HashMap<_, _>>>()?,
167 target_linkers: bcx
168 .build_config
169 .requested_kinds
170 .iter()
171 .chain(Some(&CompileKind::Host))
172 .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
173 .collect::<CargoResult<HashMap<_, _>>>()?,
174 lint_warning_count: 0,
175 })
176 }
177
178 pub fn rustc_process(
186 &self,
187 unit: &Unit,
188 is_primary: bool,
189 is_workspace: bool,
190 ) -> CargoResult<ProcessBuilder> {
191 let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
192 self.primary_rustc_process.clone().unwrap()
193 } else if is_workspace {
194 self.rustc_workspace_wrapper_process.clone()
195 } else {
196 self.rustc_process.clone()
197 };
198 if self.gctx.extra_verbose() {
199 rustc.display_env_vars();
200 }
201 let cmd = fill_rustc_tool_env(rustc, unit);
202 self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
203 }
204
205 pub fn rustdoc_process(
207 &self,
208 unit: &Unit,
209 script_metas: Option<&Vec<UnitHash>>,
210 ) -> CargoResult<ProcessBuilder> {
211 let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
212 if self.gctx.extra_verbose() {
213 rustdoc.display_env_vars();
214 }
215 let cmd = fill_rustc_tool_env(rustdoc, unit);
216 let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
217 cmd.retry_with_argfile(true);
218 unit.target.edition().cmd_edition_arg(&mut cmd);
219
220 for crate_type in unit.target.rustc_crate_types() {
221 cmd.arg("--crate-type").arg(crate_type.as_str());
222 }
223
224 Ok(cmd)
225 }
226
227 pub fn host_process<T: AsRef<OsStr>>(
234 &self,
235 cmd: T,
236 pkg: &Package,
237 ) -> CargoResult<ProcessBuilder> {
238 let builder = if !self.gctx.target_applies_to_host()?
241 && let Some((runner, args)) = self.target_runner(CompileKind::Host)
242 {
243 let mut builder = ProcessBuilder::new(runner);
244 builder.args(args);
245 builder.arg(cmd);
246 builder
247 } else {
248 ProcessBuilder::new(cmd)
249 };
250 self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess)
251 }
252
253 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
254 self.target_runners.get(&kind).and_then(|x| x.as_ref())
255 }
256
257 pub fn target_linker(&self, kind: CompileKind) -> Option<PathBuf> {
259 self.target_linkers.get(&kind).and_then(|x| x.clone())
260 }
261
262 pub fn target_process<T: AsRef<OsStr>>(
270 &self,
271 cmd: T,
272 kind: CompileKind,
273 pkg: &Package,
274 script_metas: Option<&Vec<UnitHash>>,
275 ) -> CargoResult<ProcessBuilder> {
276 let builder = if let Some((runner, args)) = self.target_runner(kind) {
277 let mut builder = ProcessBuilder::new(runner);
278 builder.args(args);
279 builder.arg(cmd);
280 builder
281 } else {
282 ProcessBuilder::new(cmd)
283 };
284 let tool_kind = ToolKind::TargetProcess;
285 let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
286
287 if let Some(client) = self.gctx.jobserver_from_env() {
288 builder.inherit_jobserver(client);
289 }
290
291 Ok(builder)
292 }
293
294 fn fill_env(
300 &self,
301 mut cmd: ProcessBuilder,
302 pkg: &Package,
303 script_metas: Option<&Vec<UnitHash>>,
304 kind: CompileKind,
305 tool_kind: ToolKind,
306 ) -> CargoResult<ProcessBuilder> {
307 let mut search_path = Vec::new();
308 if tool_kind.is_rustc_tool() {
309 if matches!(tool_kind, ToolKind::Rustdoc) {
310 search_path.extend(super::filter_dynamic_search_path(
317 self.native_dirs.iter(),
318 &self.root_output[&CompileKind::Host],
319 ));
320 }
321 search_path.push(self.deps_output[&CompileKind::Host].clone());
322 } else {
323 if let Some(path) = self.root_output.get(&kind) {
324 search_path.extend(super::filter_dynamic_search_path(
325 self.native_dirs.iter(),
326 path,
327 ));
328 search_path.push(path.clone());
329 }
330 search_path.push(self.deps_output[&kind].clone());
331 if self.gctx.cli_unstable().build_std.is_none() ||
336 pkg.proc_macro()
338 {
339 search_path.push(self.sysroot_target_libdir[&kind].clone());
340 }
341 }
342
343 let dylib_path = paths::dylib_path();
344 let dylib_path_is_empty = dylib_path.is_empty();
345 if dylib_path.starts_with(&search_path) {
346 search_path = dylib_path;
347 } else {
348 search_path.extend(dylib_path.into_iter());
349 }
350 if cfg!(target_os = "macos") && dylib_path_is_empty {
351 if let Some(home) = self.gctx.get_env_os("HOME") {
355 search_path.push(PathBuf::from(home).join("lib"));
356 }
357 search_path.push(PathBuf::from("/usr/local/lib"));
358 search_path.push(PathBuf::from("/usr/lib"));
359 }
360 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
361
362 cmd.env(paths::dylib_path_envvar(), &search_path);
363 if let Some(meta_vec) = script_metas {
364 for meta in meta_vec {
365 if let Some(env) = self.extra_env.get(meta) {
366 for (k, v) in env {
367 cmd.env(k, v);
368 }
369 }
370 }
371 }
372
373 let cargo_exe = self.gctx.cargo_exe()?;
374 cmd.env(crate::CARGO_ENV, cargo_exe);
375
376 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
381 .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
382 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
383 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
384 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
385 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
386 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
387 .env("CARGO_PKG_NAME", &*pkg.name());
388
389 for (key, value) in pkg.manifest().metadata().env_vars() {
390 cmd.env(key, value.as_ref());
391 }
392
393 cmd.cwd(pkg.root());
394
395 apply_env_config(self.gctx, &mut cmd)?;
396
397 Ok(cmd)
398 }
399}
400
401fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
404 if unit.target.is_executable() {
405 let name = unit
406 .target
407 .binary_filename()
408 .unwrap_or(unit.target.name().to_string());
409
410 cmd.env("CARGO_BIN_NAME", name);
411 }
412 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
413 cmd
414}
415
416fn get_sysroot_target_libdir(
417 bcx: &BuildContext<'_, '_>,
418) -> CargoResult<HashMap<CompileKind, PathBuf>> {
419 bcx.all_kinds
420 .iter()
421 .map(|&kind| {
422 let Some(info) = bcx.target_data.get_info(kind) else {
423 let target = match kind {
424 CompileKind::Host => "host".to_owned(),
425 CompileKind::Target(s) => s.short_name().to_owned(),
426 };
427
428 let dependency = bcx
429 .unit_graph
430 .iter()
431 .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
432 .unwrap();
433
434 anyhow::bail!(
435 "could not find specification for target `{target}`.\n \
436 Dependency `{dependency}` requires to build for target `{target}`."
437 )
438 };
439
440 Ok((kind, info.sysroot_target_libdir.clone()))
441 })
442 .collect()
443}
444
445fn target_runner(
446 bcx: &BuildContext<'_, '_>,
447 kind: CompileKind,
448) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
449 if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() {
452 let path = runner.val.path.clone().resolve_program(bcx.gctx);
453 return Ok(Some((path, runner.val.args.clone())));
454 }
455
456 let target_cfg = bcx.target_data.info(kind).cfg();
458 let mut cfgs = bcx
459 .gctx
460 .target_cfgs()?
461 .iter()
462 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
463 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
464 let matching_runner = cfgs.next();
465 if let Some((key, runner)) = cfgs.next() {
466 anyhow::bail!(
467 "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
468 first match `{}` located in {}\n\
469 second match `{}` located in {}",
470 matching_runner.unwrap().0,
471 matching_runner.unwrap().1.definition,
472 key,
473 runner.definition
474 );
475 }
476 Ok(matching_runner.map(|(_k, runner)| {
477 (
478 runner.val.path.clone().resolve_program(bcx.gctx),
479 runner.val.args.clone(),
480 )
481 }))
482}
483
484fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
486 if let Some(path) = bcx
488 .target_data
489 .target_config(kind)
490 .linker
491 .as_ref()
492 .map(|l| l.val.clone().resolve_program(bcx.gctx))
493 {
494 return Ok(Some(path));
495 }
496
497 let target_cfg = bcx.target_data.info(kind).cfg();
499 let mut cfgs = bcx
500 .gctx
501 .target_cfgs()?
502 .iter()
503 .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
504 .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
505 let matching_linker = cfgs.next();
506 if let Some((key, linker)) = cfgs.next() {
507 anyhow::bail!(
508 "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
509 first match `{}` located in {}\n\
510 second match `{}` located in {}",
511 matching_linker.unwrap().0,
512 matching_linker.unwrap().1.definition,
513 key,
514 linker.definition
515 );
516 }
517 Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
518}