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, context};
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
55#[derive(Ord, PartialOrd, Eq, PartialEq)]
57pub struct UnitOutput {
58 pub unit: Unit,
60 pub path: PathBuf,
62 pub script_metas: Option<Vec<UnitHash>>,
66}
67
68pub struct Compilation<'gctx> {
70 pub tests: Vec<UnitOutput>,
72
73 pub binaries: Vec<UnitOutput>,
75
76 pub cdylibs: Vec<UnitOutput>,
78
79 pub root_crate_names: Vec<String>,
81
82 pub native_dirs: BTreeSet<PathBuf>,
89
90 pub root_output: HashMap<CompileKind, PathBuf>,
92
93 pub deps_output: HashMap<CompileKind, PathBuf>,
96
97 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
99
100 pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
106
107 pub to_doc_test: Vec<Doctest>,
109
110 pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>,
114
115 pub host: String,
117
118 gctx: &'gctx GlobalContext,
119
120 rustc_process: ProcessBuilder,
122 rustc_workspace_wrapper_process: ProcessBuilder,
124 primary_rustc_process: Option<ProcessBuilder>,
127
128 target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
129 target_linkers: HashMap<CompileKind, Option<PathBuf>>,
131
132 pub lint_warning_count: usize,
134}
135
136impl<'gctx> Compilation<'gctx> {
137 pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
138 let rustc_process = bcx.rustc().process();
139 let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
140 let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
141 Ok(Compilation {
142 native_dirs: BTreeSet::new(),
143 root_output: HashMap::new(),
144 deps_output: HashMap::new(),
145 sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
146 tests: Vec::new(),
147 binaries: Vec::new(),
148 cdylibs: Vec::new(),
149 root_crate_names: Vec::new(),
150 extra_env: HashMap::new(),
151 to_doc_test: Vec::new(),
152 rustdoc_fingerprints: None,
153 gctx: bcx.gctx,
154 host: bcx.host_triple().to_string(),
155 rustc_process,
156 rustc_workspace_wrapper_process,
157 primary_rustc_process,
158 target_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 target_linkers: bcx
166 .build_config
167 .requested_kinds
168 .iter()
169 .chain(Some(&CompileKind::Host))
170 .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
171 .collect::<CargoResult<HashMap<_, _>>>()?,
172 lint_warning_count: 0,
173 })
174 }
175
176 pub fn rustc_process(
184 &self,
185 unit: &Unit,
186 is_primary: bool,
187 is_workspace: bool,
188 ) -> CargoResult<ProcessBuilder> {
189 let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
190 self.primary_rustc_process.clone().unwrap()
191 } else if is_workspace {
192 self.rustc_workspace_wrapper_process.clone()
193 } else {
194 self.rustc_process.clone()
195 };
196 if self.gctx.extra_verbose() {
197 rustc.display_env_vars();
198 }
199 let cmd = fill_rustc_tool_env(rustc, unit);
200 self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
201 }
202
203 pub fn rustdoc_process(
205 &self,
206 unit: &Unit,
207 script_metas: Option<&Vec<UnitHash>>,
208 ) -> CargoResult<ProcessBuilder> {
209 let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
210 if self.gctx.extra_verbose() {
211 rustdoc.display_env_vars();
212 }
213 let cmd = fill_rustc_tool_env(rustdoc, unit);
214 let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
215 cmd.retry_with_argfile(true);
216 unit.target.edition().cmd_edition_arg(&mut cmd);
217
218 for crate_type in unit.target.rustc_crate_types() {
219 cmd.arg("--crate-type").arg(crate_type.as_str());
220 }
221
222 Ok(cmd)
223 }
224
225 pub fn host_process<T: AsRef<OsStr>>(
232 &self,
233 cmd: T,
234 pkg: &Package,
235 ) -> CargoResult<ProcessBuilder> {
236 self.fill_env(
237 ProcessBuilder::new(cmd),
238 pkg,
239 None,
240 CompileKind::Host,
241 ToolKind::HostProcess,
242 )
243 }
244
245 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
246 self.target_runners.get(&kind).and_then(|x| x.as_ref())
247 }
248
249 pub fn target_linker(&self, kind: CompileKind) -> Option<PathBuf> {
251 self.target_linkers.get(&kind).and_then(|x| x.clone())
252 }
253
254 pub fn target_process<T: AsRef<OsStr>>(
262 &self,
263 cmd: T,
264 kind: CompileKind,
265 pkg: &Package,
266 script_metas: Option<&Vec<UnitHash>>,
267 ) -> CargoResult<ProcessBuilder> {
268 let builder = if let Some((runner, args)) = self.target_runner(kind) {
269 let mut builder = ProcessBuilder::new(runner);
270 builder.args(args);
271 builder.arg(cmd);
272 builder
273 } else {
274 ProcessBuilder::new(cmd)
275 };
276 let tool_kind = ToolKind::TargetProcess;
277 let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
278
279 if let Some(client) = self.gctx.jobserver_from_env() {
280 builder.inherit_jobserver(client);
281 }
282
283 Ok(builder)
284 }
285
286 fn fill_env(
292 &self,
293 mut cmd: ProcessBuilder,
294 pkg: &Package,
295 script_metas: Option<&Vec<UnitHash>>,
296 kind: CompileKind,
297 tool_kind: ToolKind,
298 ) -> CargoResult<ProcessBuilder> {
299 let mut search_path = Vec::new();
300 if tool_kind.is_rustc_tool() {
301 if matches!(tool_kind, ToolKind::Rustdoc) {
302 search_path.extend(super::filter_dynamic_search_path(
309 self.native_dirs.iter(),
310 &self.root_output[&CompileKind::Host],
311 ));
312 }
313 search_path.push(self.deps_output[&CompileKind::Host].clone());
314 } else {
315 if let Some(path) = self.root_output.get(&kind) {
316 search_path.extend(super::filter_dynamic_search_path(
317 self.native_dirs.iter(),
318 path,
319 ));
320 search_path.push(path.clone());
321 }
322 search_path.push(self.deps_output[&kind].clone());
323 if self.gctx.cli_unstable().build_std.is_none() ||
328 pkg.proc_macro()
330 {
331 search_path.push(self.sysroot_target_libdir[&kind].clone());
332 }
333 }
334
335 let dylib_path = paths::dylib_path();
336 let dylib_path_is_empty = dylib_path.is_empty();
337 if dylib_path.starts_with(&search_path) {
338 search_path = dylib_path;
339 } else {
340 search_path.extend(dylib_path.into_iter());
341 }
342 if cfg!(target_os = "macos") && dylib_path_is_empty {
343 if let Some(home) = self.gctx.get_env_os("HOME") {
347 search_path.push(PathBuf::from(home).join("lib"));
348 }
349 search_path.push(PathBuf::from("/usr/local/lib"));
350 search_path.push(PathBuf::from("/usr/lib"));
351 }
352 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
353
354 cmd.env(paths::dylib_path_envvar(), &search_path);
355 if let Some(meta_vec) = script_metas {
356 for meta in meta_vec {
357 if let Some(env) = self.extra_env.get(meta) {
358 for (k, v) in env {
359 cmd.env(k, v);
360 }
361 }
362 }
363 }
364
365 let cargo_exe = self.gctx.cargo_exe()?;
366 cmd.env(crate::CARGO_ENV, cargo_exe);
367
368 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
373 .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
374 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
375 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
376 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
377 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
378 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
379 .env("CARGO_PKG_NAME", &*pkg.name());
380
381 for (key, value) in pkg.manifest().metadata().env_vars() {
382 cmd.env(key, value.as_ref());
383 }
384
385 cmd.cwd(pkg.root());
386
387 apply_env_config(self.gctx, &mut cmd)?;
388
389 Ok(cmd)
390 }
391}
392
393fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
396 if unit.target.is_executable() {
397 let name = unit
398 .target
399 .binary_filename()
400 .unwrap_or(unit.target.name().to_string());
401
402 cmd.env("CARGO_BIN_NAME", name);
403 }
404 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
405 cmd
406}
407
408fn get_sysroot_target_libdir(
409 bcx: &BuildContext<'_, '_>,
410) -> CargoResult<HashMap<CompileKind, PathBuf>> {
411 bcx.all_kinds
412 .iter()
413 .map(|&kind| {
414 let Some(info) = bcx.target_data.get_info(kind) else {
415 let target = match kind {
416 CompileKind::Host => "host".to_owned(),
417 CompileKind::Target(s) => s.short_name().to_owned(),
418 };
419
420 let dependency = bcx
421 .unit_graph
422 .iter()
423 .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
424 .unwrap();
425
426 anyhow::bail!(
427 "could not find specification for target `{target}`.\n \
428 Dependency `{dependency}` requires to build for target `{target}`."
429 )
430 };
431
432 Ok((kind, info.sysroot_target_libdir.clone()))
433 })
434 .collect()
435}
436
437fn target_runner(
438 bcx: &BuildContext<'_, '_>,
439 kind: CompileKind,
440) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
441 let target = bcx.target_data.short_name(&kind);
442
443 let key = format!("target.{}.runner", target);
445
446 if let Some(v) = bcx.gctx.get::<Option<context::PathAndArgs>>(&key)? {
447 let path = v.path.resolve_program(bcx.gctx);
448 return Ok(Some((path, v.args)));
449 }
450
451 let target_cfg = bcx.target_data.info(kind).cfg();
453 let mut cfgs = bcx
454 .gctx
455 .target_cfgs()?
456 .iter()
457 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
458 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
459 let matching_runner = cfgs.next();
460 if let Some((key, runner)) = cfgs.next() {
461 anyhow::bail!(
462 "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
463 first match `{}` located in {}\n\
464 second match `{}` located in {}",
465 matching_runner.unwrap().0,
466 matching_runner.unwrap().1.definition,
467 key,
468 runner.definition
469 );
470 }
471 Ok(matching_runner.map(|(_k, runner)| {
472 (
473 runner.val.path.clone().resolve_program(bcx.gctx),
474 runner.val.args.clone(),
475 )
476 }))
477}
478
479fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
481 if let Some(path) = bcx
483 .target_data
484 .target_config(kind)
485 .linker
486 .as_ref()
487 .map(|l| l.val.clone().resolve_program(bcx.gctx))
488 {
489 return Ok(Some(path));
490 }
491
492 let target_cfg = bcx.target_data.info(kind).cfg();
494 let mut cfgs = bcx
495 .gctx
496 .target_cfgs()?
497 .iter()
498 .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
499 .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
500 let matching_linker = cfgs.next();
501 if let Some((key, linker)) = cfgs.next() {
502 anyhow::bail!(
503 "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
504 first match `{}` located in {}\n\
505 second match `{}` located in {}",
506 matching_linker.unwrap().0,
507 matching_linker.unwrap().1.definition,
508 key,
509 linker.definition
510 );
511 }
512 Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
513}