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