1use std::collections::{BTreeSet, HashMap};
4use std::ffi::{OsStr, OsString};
5use std::path::Path;
6use std::path::PathBuf;
7
8use cargo_platform::CfgExpr;
9use cargo_util::{ProcessBuilder, paths};
10
11use crate::core::Package;
12use crate::core::compiler::BuildContext;
13use crate::core::compiler::CompileTarget;
14use crate::core::compiler::RustdocFingerprint;
15use crate::core::compiler::apply_env_config;
16use crate::core::compiler::{CompileKind, Unit, UnitHash};
17use crate::util::{CargoResult, GlobalContext};
18
19#[derive(Debug)]
21enum ToolKind {
22 Rustc,
24 Rustdoc,
26 HostProcess,
28 TargetProcess,
30}
31
32impl ToolKind {
33 fn is_rustc_tool(&self) -> bool {
34 matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
35 }
36}
37
38pub struct Doctest {
40 pub unit: Unit,
42 pub args: Vec<OsString>,
44 pub unstable_opts: bool,
46 pub linker: Option<PathBuf>,
48 pub script_metas: Option<Vec<UnitHash>>,
52
53 pub env: HashMap<String, OsString>,
55}
56
57pub struct UnitOutput {
59 pub unit: Unit,
61 pub path: PathBuf,
63 pub script_metas: Option<Vec<UnitHash>>,
67
68 pub env: HashMap<String, OsString>,
70}
71
72pub struct Compilation<'gctx> {
74 pub tests: Vec<UnitOutput>,
76
77 pub binaries: Vec<UnitOutput>,
79
80 pub cdylibs: Vec<UnitOutput>,
82
83 pub root_crate_names: Vec<String>,
85
86 pub native_dirs: BTreeSet<PathBuf>,
93
94 pub root_output: HashMap<CompileKind, PathBuf>,
96
97 pub deps_output: HashMap<CompileKind, PathBuf>,
100
101 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
103
104 pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
110
111 pub to_doc_test: Vec<Doctest>,
113
114 pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>,
118
119 pub host: String,
121
122 gctx: &'gctx GlobalContext,
123
124 rustc_process: ProcessBuilder,
126 rustc_workspace_wrapper_process: ProcessBuilder,
128 primary_rustc_process: Option<ProcessBuilder>,
131
132 runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
134 linkers: HashMap<CompileKind, Option<PathBuf>>,
136
137 pub lint_warning_count: usize,
139}
140
141impl<'gctx> Compilation<'gctx> {
142 pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
143 let rustc_process = bcx.rustc().process();
144 let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
145 let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
146 let host = bcx.host_triple().to_string();
147
148 let insert_explicit_host_runner = !bcx.gctx.target_applies_to_host()?
153 && bcx
154 .build_config
155 .requested_kinds
156 .iter()
157 .any(CompileKind::is_host);
158 let mut runners = bcx
159 .build_config
160 .requested_kinds
161 .iter()
162 .chain(Some(&CompileKind::Host))
163 .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
164 .collect::<CargoResult<HashMap<_, _>>>()?;
165 if insert_explicit_host_runner {
166 let kind = explicit_host_kind(&host);
167 runners.insert(kind, target_runner(bcx, kind)?);
168 }
169
170 let mut linkers = bcx
171 .build_config
172 .requested_kinds
173 .iter()
174 .chain(Some(&CompileKind::Host))
175 .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
176 .collect::<CargoResult<HashMap<_, _>>>()?;
177 if insert_explicit_host_runner {
178 let kind = explicit_host_kind(&host);
179 linkers.insert(kind, target_linker(bcx, kind)?);
180 }
181 Ok(Compilation {
182 native_dirs: BTreeSet::new(),
183 root_output: HashMap::new(),
184 deps_output: HashMap::new(),
185 sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
186 tests: Vec::new(),
187 binaries: Vec::new(),
188 cdylibs: Vec::new(),
189 root_crate_names: Vec::new(),
190 extra_env: HashMap::new(),
191 to_doc_test: Vec::new(),
192 rustdoc_fingerprints: None,
193 gctx: bcx.gctx,
194 host,
195 rustc_process,
196 rustc_workspace_wrapper_process,
197 primary_rustc_process,
198 runners,
199 linkers,
200 lint_warning_count: 0,
201 })
202 }
203
204 pub fn rustc_process(
212 &self,
213 unit: &Unit,
214 is_primary: bool,
215 is_workspace: bool,
216 ) -> CargoResult<ProcessBuilder> {
217 let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
218 self.primary_rustc_process.clone().unwrap()
219 } else if is_workspace {
220 self.rustc_workspace_wrapper_process.clone()
221 } else {
222 self.rustc_process.clone()
223 };
224 if self.gctx.extra_verbose() {
225 rustc.display_env_vars();
226 }
227 let cmd = fill_rustc_tool_env(rustc, unit);
228 self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
229 }
230
231 pub fn rustdoc_process(
233 &self,
234 unit: &Unit,
235 script_metas: Option<&Vec<UnitHash>>,
236 ) -> CargoResult<ProcessBuilder> {
237 let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
238 if self.gctx.extra_verbose() {
239 rustdoc.display_env_vars();
240 }
241 let cmd = fill_rustc_tool_env(rustdoc, unit);
242 let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
243 cmd.retry_with_argfile(true);
244 unit.target.edition().cmd_edition_arg(&mut cmd);
245
246 for crate_type in unit.target.rustc_crate_types() {
247 cmd.arg("--crate-type").arg(crate_type.as_str());
248 }
249
250 Ok(cmd)
251 }
252
253 pub fn host_process<T: AsRef<OsStr>>(
260 &self,
261 cmd: T,
262 pkg: &Package,
263 ) -> CargoResult<ProcessBuilder> {
264 let builder = if !self.gctx.target_applies_to_host()?
267 && let Some((runner, args)) = self
268 .runners
269 .get(&CompileKind::Host)
270 .and_then(|x| x.as_ref())
271 {
272 let mut builder = ProcessBuilder::new(runner);
273 builder.args(args);
274 builder.arg(cmd);
275 builder
276 } else {
277 ProcessBuilder::new(cmd)
278 };
279 self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess)
280 }
281
282 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
283 let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
284 let kind = if !target_applies_to_host && kind.is_host() {
285 explicit_host_kind(&self.host)
288 } else {
289 kind
290 };
291 self.runners.get(&kind).and_then(|x| x.as_ref())
292 }
293
294 pub fn host_linker(&self) -> Option<&Path> {
296 self.linkers
297 .get(&CompileKind::Host)
298 .and_then(|x| x.as_ref())
299 .map(|x| x.as_path())
300 }
301
302 pub fn target_linker(&self, kind: CompileKind) -> Option<&Path> {
304 let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
305 let kind = if !target_applies_to_host && kind.is_host() {
306 explicit_host_kind(&self.host)
309 } else {
310 kind
311 };
312 self.linkers
313 .get(&kind)
314 .and_then(|x| x.as_ref())
315 .map(|x| x.as_path())
316 }
317
318 pub fn target_process<T: AsRef<OsStr>>(
326 &self,
327 cmd: T,
328 kind: CompileKind,
329 pkg: &Package,
330 script_metas: Option<&Vec<UnitHash>>,
331 ) -> CargoResult<ProcessBuilder> {
332 let builder = if let Some((runner, args)) = self.target_runner(kind) {
333 let mut builder = ProcessBuilder::new(runner);
334 builder.args(args);
335 builder.arg(cmd);
336 builder
337 } else {
338 ProcessBuilder::new(cmd)
339 };
340 let tool_kind = ToolKind::TargetProcess;
341 let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
342
343 if let Some(client) = self.gctx.jobserver_from_env() {
344 builder.inherit_jobserver(client);
345 }
346
347 Ok(builder)
348 }
349
350 fn fill_env(
356 &self,
357 mut cmd: ProcessBuilder,
358 pkg: &Package,
359 script_metas: Option<&Vec<UnitHash>>,
360 kind: CompileKind,
361 tool_kind: ToolKind,
362 ) -> CargoResult<ProcessBuilder> {
363 let mut search_path = Vec::new();
364 if tool_kind.is_rustc_tool() {
365 if matches!(tool_kind, ToolKind::Rustdoc) {
366 search_path.extend(super::filter_dynamic_search_path(
373 self.native_dirs.iter(),
374 &self.root_output[&CompileKind::Host],
375 ));
376 }
377 search_path.push(self.deps_output[&CompileKind::Host].clone());
378 } else {
379 if let Some(path) = self.root_output.get(&kind) {
380 search_path.extend(super::filter_dynamic_search_path(
381 self.native_dirs.iter(),
382 path,
383 ));
384 search_path.push(path.clone());
385 }
386 search_path.push(self.deps_output[&kind].clone());
387 if self.gctx.cli_unstable().build_std.is_none() ||
392 pkg.proc_macro()
394 {
395 search_path.push(self.sysroot_target_libdir[&kind].clone());
396 }
397 }
398
399 let dylib_path = paths::dylib_path();
400 let dylib_path_is_empty = dylib_path.is_empty();
401 if dylib_path.starts_with(&search_path) {
402 search_path = dylib_path;
403 } else {
404 search_path.extend(dylib_path.into_iter());
405 }
406 if cfg!(target_os = "macos") && dylib_path_is_empty {
407 if let Some(home) = self.gctx.get_env_os("HOME") {
411 search_path.push(PathBuf::from(home).join("lib"));
412 }
413 search_path.push(PathBuf::from("/usr/local/lib"));
414 search_path.push(PathBuf::from("/usr/lib"));
415 }
416 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
417
418 cmd.env(paths::dylib_path_envvar(), &search_path);
419 if let Some(meta_vec) = script_metas {
420 for meta in meta_vec {
421 if let Some(env) = self.extra_env.get(meta) {
422 for (k, v) in env {
423 cmd.env(k, v);
424 }
425 }
426 }
427 }
428
429 let cargo_exe = self.gctx.cargo_exe()?;
430 cmd.env(crate::CARGO_ENV, cargo_exe);
431
432 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
437 .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
438 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
439 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
440 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
441 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
442 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
443 .env("CARGO_PKG_NAME", &*pkg.name());
444
445 for (key, value) in pkg.manifest().metadata().env_vars() {
446 cmd.env(key, value.as_ref());
447 }
448
449 cmd.cwd(pkg.root());
450
451 apply_env_config(self.gctx, &mut cmd)?;
452
453 Ok(cmd)
454 }
455}
456
457fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
460 if unit.target.is_executable() {
461 let name = unit
462 .target
463 .binary_filename()
464 .unwrap_or(unit.target.name().to_string());
465
466 cmd.env("CARGO_BIN_NAME", name);
467 }
468 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
469 cmd
470}
471
472fn get_sysroot_target_libdir(
473 bcx: &BuildContext<'_, '_>,
474) -> CargoResult<HashMap<CompileKind, PathBuf>> {
475 bcx.all_kinds
476 .iter()
477 .map(|&kind| {
478 let Some(info) = bcx.target_data.get_info(kind) else {
479 let target = match kind {
480 CompileKind::Host => "host".to_owned(),
481 CompileKind::Target(s) => s.short_name().to_owned(),
482 };
483
484 let dependency = bcx
485 .unit_graph
486 .iter()
487 .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
488 .unwrap();
489
490 anyhow::bail!(
491 "could not find specification for target `{target}`.\n \
492 Dependency `{dependency}` requires to build for target `{target}`."
493 )
494 };
495
496 Ok((kind, info.sysroot_target_libdir.clone()))
497 })
498 .collect()
499}
500
501fn target_runner(
502 bcx: &BuildContext<'_, '_>,
503 kind: CompileKind,
504) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
505 if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() {
506 let path = runner.val.path.clone().resolve_program(bcx.gctx);
507 return Ok(Some((path, runner.val.args.clone())));
508 }
509
510 let target_cfg = bcx.target_data.info(kind).cfg();
512 let mut cfgs = bcx
513 .gctx
514 .target_cfgs()?
515 .iter()
516 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
517 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
518 let matching_runner = cfgs.next();
519 if let Some((key, runner)) = cfgs.next() {
520 anyhow::bail!(
521 "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
522 first match `{}` located in {}\n\
523 second match `{}` located in {}",
524 matching_runner.unwrap().0,
525 matching_runner.unwrap().1.definition,
526 key,
527 runner.definition
528 );
529 }
530 Ok(matching_runner.map(|(_k, runner)| {
531 (
532 runner.val.path.clone().resolve_program(bcx.gctx),
533 runner.val.args.clone(),
534 )
535 }))
536}
537
538fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
540 if let Some(path) = bcx
542 .target_data
543 .target_config(kind)
544 .linker
545 .as_ref()
546 .map(|l| l.val.clone().resolve_program(bcx.gctx))
547 {
548 return Ok(Some(path));
549 }
550
551 let target_cfg = bcx.target_data.info(kind).cfg();
553 let mut cfgs = bcx
554 .gctx
555 .target_cfgs()?
556 .iter()
557 .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
558 .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
559 let matching_linker = cfgs.next();
560 if let Some((key, linker)) = cfgs.next() {
561 anyhow::bail!(
562 "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
563 first match `{}` located in {}\n\
564 second match `{}` located in {}",
565 matching_linker.unwrap().0,
566 matching_linker.unwrap().1.definition,
567 key,
568 linker.definition
569 );
570 }
571 Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
572}
573
574fn explicit_host_kind(host: &str) -> CompileKind {
575 let target = CompileTarget::new(host, false).expect("must be a host tuple");
576 CompileKind::Target(target)
577}