1use cargo_util::collections::HashMap;
4use std::collections::BTreeSet;
5use std::ffi::{OsStr, OsString};
6use std::path::Path;
7use std::path::PathBuf;
8use std::rc::Rc;
9
10use cargo_platform::CfgExpr;
11use cargo_util::{ProcessBuilder, paths};
12
13use crate::compiler::BuildContext;
14use crate::compiler::CompileTarget;
15use crate::compiler::RustdocFingerprint;
16use crate::compiler::apply_env_config;
17use crate::compiler::{CompileKind, Unit, UnitHash};
18use crate::context::ConfigRelativePathResolveExt;
19use crate::util::{CargoResult, GlobalContext};
20use crate::workspace::Package;
21
22#[derive(Debug)]
24enum ToolKind {
25 Rustc,
27 Rustdoc,
29 HostProcess,
31 TargetProcess,
33}
34
35impl ToolKind {
36 fn is_rustc_tool(&self) -> bool {
37 matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
38 }
39}
40
41pub struct Doctest {
43 pub unit: Unit,
45 pub args: Vec<OsString>,
47 pub unstable_opts: bool,
49 pub linker: Option<PathBuf>,
51 pub script_metas: Option<Vec<UnitHash>>,
55
56 pub env: HashMap<String, OsString>,
58}
59
60pub struct UnitOutput {
62 pub unit: Unit,
64 pub path: PathBuf,
66 pub script_metas: Option<Vec<UnitHash>>,
70
71 pub env: HashMap<String, OsString>,
73}
74
75pub struct Compilation<'gctx> {
77 pub tests: Vec<UnitOutput>,
79
80 pub binaries: Vec<UnitOutput>,
82
83 pub cdylibs: Vec<UnitOutput>,
85
86 pub root_crate_names: Vec<String>,
88
89 pub native_dirs: BTreeSet<PathBuf>,
96
97 pub root_output: HashMap<CompileKind, PathBuf>,
99
100 pub deps_output: HashMap<CompileKind, BTreeSet<PathBuf>>,
103
104 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
106
107 pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
113
114 pub to_doc_test: Vec<Doctest>,
116
117 pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>,
121
122 pub rustdocflags: HashMap<CompileKind, Rc<[String]>>,
124
125 pub host: String,
127
128 gctx: &'gctx GlobalContext,
129
130 rustc_process: ProcessBuilder,
132 rustc_workspace_wrapper_process: ProcessBuilder,
134 primary_rustc_process: Option<ProcessBuilder>,
137
138 runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
140 linkers: HashMap<CompileKind, Option<PathBuf>>,
142
143 pub lint_warning_count: usize,
145}
146
147impl<'gctx> Compilation<'gctx> {
148 pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
149 let rustc_process = bcx.rustc().process();
150 let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
151 let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
152 let host = bcx.host_triple().to_string();
153 let sysroot_target_libdir = get_sysroot_target_libdir(bcx)?;
154 let rustdocflags = bcx
155 .all_kinds
156 .iter()
157 .map(|&kind| (kind, bcx.target_data.info(kind).rustdocflags.clone()))
158 .collect();
159
160 let insert_explicit_host_runner = !bcx.gctx.target_applies_to_host()?
165 && bcx
166 .build_config
167 .requested_kinds
168 .iter()
169 .any(CompileKind::is_host);
170 let mut runners = bcx
171 .build_config
172 .requested_kinds
173 .iter()
174 .chain(Some(&CompileKind::Host))
175 .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
176 .collect::<CargoResult<HashMap<_, _>>>()?;
177 if insert_explicit_host_runner {
178 let kind = explicit_host_kind(&host);
179 runners.insert(kind, target_runner(bcx, kind)?);
180 }
181
182 let mut linkers = bcx
183 .build_config
184 .requested_kinds
185 .iter()
186 .chain(Some(&CompileKind::Host))
187 .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
188 .collect::<CargoResult<HashMap<_, _>>>()?;
189 if insert_explicit_host_runner {
190 let kind = explicit_host_kind(&host);
191 linkers.insert(kind, target_linker(bcx, kind)?);
192 }
193 Ok(Compilation {
194 native_dirs: BTreeSet::new(),
195 root_output: HashMap::default(),
196 deps_output: HashMap::default(),
197 sysroot_target_libdir,
198 tests: Vec::new(),
199 binaries: Vec::new(),
200 cdylibs: Vec::new(),
201 root_crate_names: Vec::new(),
202 extra_env: HashMap::default(),
203 to_doc_test: Vec::new(),
204 rustdoc_fingerprints: None,
205 rustdocflags,
206 gctx: bcx.gctx,
207 host,
208 rustc_process,
209 rustc_workspace_wrapper_process,
210 primary_rustc_process,
211 runners,
212 linkers,
213 lint_warning_count: 0,
214 })
215 }
216
217 pub fn rustc_process(
225 &self,
226 unit: &Unit,
227 is_primary: bool,
228 is_workspace: bool,
229 ) -> CargoResult<ProcessBuilder> {
230 let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
231 self.primary_rustc_process.clone().unwrap()
232 } else if is_workspace {
233 self.rustc_workspace_wrapper_process.clone()
234 } else {
235 self.rustc_process.clone()
236 };
237 if self.gctx.extra_verbose() {
238 rustc.display_env_vars();
239 }
240 let cmd = fill_rustc_tool_env(rustc, unit);
241 self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
242 }
243
244 pub fn rustdoc_process(
246 &self,
247 unit: &Unit,
248 script_metas: Option<&Vec<UnitHash>>,
249 ) -> CargoResult<ProcessBuilder> {
250 let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
251 if self.gctx.extra_verbose() {
252 rustdoc.display_env_vars();
253 }
254 let cmd = fill_rustc_tool_env(rustdoc, unit);
255 let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
256 cmd.retry_with_argfile(true);
257 unit.target.edition().cmd_edition_arg(&mut cmd);
258
259 for crate_type in unit.target.rustc_crate_types() {
260 cmd.arg("--crate-type").arg(crate_type.as_str());
261 }
262
263 Ok(cmd)
264 }
265
266 pub fn host_process<T: AsRef<OsStr>>(
273 &self,
274 cmd: T,
275 pkg: &Package,
276 ) -> CargoResult<ProcessBuilder> {
277 let builder = if !self.gctx.target_applies_to_host()?
280 && let Some((runner, args)) = self
281 .runners
282 .get(&CompileKind::Host)
283 .and_then(|x| x.as_ref())
284 {
285 let mut builder = ProcessBuilder::new(runner);
286 builder.args(args);
287 builder.arg(cmd);
288 builder
289 } else {
290 ProcessBuilder::new(cmd)
291 };
292 self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess)
293 }
294
295 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
296 let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
297 let kind = if !target_applies_to_host && kind.is_host() {
298 explicit_host_kind(&self.host)
301 } else {
302 kind
303 };
304 self.runners.get(&kind).and_then(|x| x.as_ref())
305 }
306
307 pub fn host_linker(&self) -> Option<&Path> {
309 self.linkers
310 .get(&CompileKind::Host)
311 .and_then(|x| x.as_ref())
312 .map(|x| x.as_path())
313 }
314
315 pub fn target_linker(&self, kind: CompileKind) -> Option<&Path> {
317 let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
318 let kind = if !target_applies_to_host && kind.is_host() {
319 explicit_host_kind(&self.host)
322 } else {
323 kind
324 };
325 self.linkers
326 .get(&kind)
327 .and_then(|x| x.as_ref())
328 .map(|x| x.as_path())
329 }
330
331 pub fn target_process<T: AsRef<OsStr>>(
339 &self,
340 cmd: T,
341 kind: CompileKind,
342 pkg: &Package,
343 script_metas: Option<&Vec<UnitHash>>,
344 ) -> CargoResult<ProcessBuilder> {
345 let builder = if let Some((runner, args)) = self.target_runner(kind) {
346 let mut builder = ProcessBuilder::new(runner);
347 builder.args(args);
348 builder.arg(cmd);
349 builder
350 } else {
351 ProcessBuilder::new(cmd)
352 };
353 let tool_kind = ToolKind::TargetProcess;
354 let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
355
356 if let Some(client) = self.gctx.jobserver_from_env() {
357 builder.inherit_jobserver(client);
358 }
359
360 Ok(builder)
361 }
362
363 fn fill_env(
369 &self,
370 mut cmd: ProcessBuilder,
371 pkg: &Package,
372 script_metas: Option<&Vec<UnitHash>>,
373 kind: CompileKind,
374 tool_kind: ToolKind,
375 ) -> CargoResult<ProcessBuilder> {
376 let mut search_path = Vec::new();
377 if tool_kind.is_rustc_tool() {
378 if matches!(tool_kind, ToolKind::Rustdoc) {
379 search_path.extend(super::filter_dynamic_search_path(
386 self.native_dirs.iter(),
387 &self.root_output[&CompileKind::Host],
388 ));
389 }
390 if let Some(paths) = self.deps_output.get(&CompileKind::Host) {
391 search_path.extend(paths.clone());
392 }
393 } else {
394 if let Some(path) = self.root_output.get(&kind) {
395 search_path.extend(super::filter_dynamic_search_path(
396 self.native_dirs.iter(),
397 path,
398 ));
399 search_path.push(path.clone());
400 }
401 if let Some(paths) = self.deps_output.get(&kind) {
402 search_path.extend(paths.clone());
403 }
404 if self.gctx.cli_unstable().build_std.is_none() ||
409 pkg.proc_macro()
411 {
412 search_path.push(self.sysroot_target_libdir[&kind].clone());
413 }
414 }
415
416 let dylib_path = paths::dylib_path();
417 let dylib_path_is_empty = dylib_path.is_empty();
418 if dylib_path.starts_with(&search_path) {
419 search_path = dylib_path;
420 } else {
421 search_path.extend(dylib_path.into_iter());
422 }
423 if cfg!(target_os = "macos") && dylib_path_is_empty {
424 if let Some(home) = self.gctx.get_env_os("HOME") {
428 search_path.push(PathBuf::from(home).join("lib"));
429 }
430 search_path.push(PathBuf::from("/usr/local/lib"));
431 search_path.push(PathBuf::from("/usr/lib"));
432 }
433 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
434
435 cmd.env(paths::dylib_path_envvar(), &search_path);
436 if let Some(meta_vec) = script_metas {
437 for meta in meta_vec {
438 if let Some(env) = self.extra_env.get(meta) {
439 for (k, v) in env {
440 if k == "OUT_DIR" && !tool_kind.is_rustc_tool() {
441 continue;
442 }
443 cmd.env(k, v);
444 }
445 }
446 }
447 }
448
449 let cargo_exe = self.gctx.cargo_exe()?;
450 cmd.env(crate::CARGO_ENV, cargo_exe);
451
452 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
457 .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
458 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
459 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
460 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
461 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
462 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
463 .env("CARGO_PKG_NAME", &*pkg.name());
464
465 for (key, value) in pkg.manifest().metadata().env_vars() {
466 cmd.env(key, value.as_ref());
467 }
468
469 cmd.cwd(pkg.root());
470
471 apply_env_config(self.gctx, &mut cmd)?;
472
473 Ok(cmd)
474 }
475}
476
477fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
480 if unit.target.is_executable() {
481 let name = unit
482 .target
483 .binary_filename()
484 .unwrap_or(unit.target.name().to_string());
485
486 cmd.env("CARGO_BIN_NAME", name);
487 }
488 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
489 cmd
490}
491
492fn get_sysroot_target_libdir(
493 bcx: &BuildContext<'_, '_>,
494) -> CargoResult<HashMap<CompileKind, PathBuf>> {
495 bcx.all_kinds
496 .iter()
497 .map(|&kind| {
498 let Some(info) = bcx.target_data.get_info(kind) else {
499 let target = match kind {
500 CompileKind::Host => "host".to_owned(),
501 CompileKind::Target(s) => s.short_name().to_owned(),
502 };
503
504 let dependency = bcx
505 .unit_graph
506 .iter()
507 .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
508 .unwrap();
509
510 anyhow::bail!(
511 "could not find specification for target `{target}`.\n \
512 Dependency `{dependency}` requires to build for target `{target}`."
513 )
514 };
515
516 Ok((kind, info.sysroot_target_libdir.clone()))
517 })
518 .collect()
519}
520
521fn target_runner(
522 bcx: &BuildContext<'_, '_>,
523 kind: CompileKind,
524) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
525 if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() {
526 let path = runner.val.path.clone().resolve_program(bcx.gctx);
527 return Ok(Some((path, runner.val.args.clone())));
528 }
529
530 if kind.is_host() && !bcx.gctx.target_applies_to_host()? {
534 return Ok(None);
535 }
536
537 let target_cfg = bcx.target_data.info(kind).cfg();
539 let mut cfgs = bcx
540 .gctx
541 .target_cfgs()?
542 .iter()
543 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
544 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
545 let matching_runner = cfgs.next();
546 if let Some((key, runner)) = cfgs.next() {
547 anyhow::bail!(
548 "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
549 first match `{}` located in {}\n\
550 second match `{}` located in {}",
551 matching_runner.unwrap().0,
552 matching_runner.unwrap().1.definition,
553 key,
554 runner.definition
555 );
556 }
557 Ok(matching_runner.map(|(_k, runner)| {
558 (
559 runner.val.path.clone().resolve_program(bcx.gctx),
560 runner.val.args.clone(),
561 )
562 }))
563}
564
565fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
567 if let Some(path) = bcx
569 .target_data
570 .target_config(kind)
571 .linker
572 .as_ref()
573 .map(|l| l.val.clone().resolve_program(bcx.gctx))
574 {
575 return Ok(Some(path));
576 }
577
578 if kind.is_host() && !bcx.gctx.target_applies_to_host()? {
582 return Ok(None);
583 }
584
585 let target_cfg = bcx.target_data.info(kind).cfg();
587 let mut cfgs = bcx
588 .gctx
589 .target_cfgs()?
590 .iter()
591 .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
592 .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
593 let matching_linker = cfgs.next();
594 if let Some((key, linker)) = cfgs.next() {
595 anyhow::bail!(
596 "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
597 first match `{}` located in {}\n\
598 second match `{}` located in {}",
599 matching_linker.unwrap().0,
600 matching_linker.unwrap().1.definition,
601 key,
602 linker.definition
603 );
604 }
605 Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
606}
607
608fn explicit_host_kind(host: &str) -> CompileKind {
609 let target = CompileTarget::new(host, false).expect("must be a host tuple");
610 CompileKind::Target(target)
611}