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::apply_env_config;
13use crate::core::compiler::{CompileKind, Unit, UnitHash};
14use crate::util::{CargoResult, GlobalContext, context};
15
16#[derive(Debug)]
18enum ToolKind {
19 Rustc,
21 Rustdoc,
23 HostProcess,
25 TargetProcess,
27}
28
29impl ToolKind {
30 fn is_rustc_tool(&self) -> bool {
31 matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
32 }
33}
34
35pub struct Doctest {
37 pub unit: Unit,
39 pub args: Vec<OsString>,
41 pub unstable_opts: bool,
43 pub linker: Option<PathBuf>,
45 pub script_metas: Option<Vec<UnitHash>>,
49
50 pub env: HashMap<String, OsString>,
52}
53
54#[derive(Ord, PartialOrd, Eq, PartialEq)]
56pub struct UnitOutput {
57 pub unit: Unit,
59 pub path: PathBuf,
61 pub script_metas: Option<Vec<UnitHash>>,
65}
66
67pub struct Compilation<'gctx> {
69 pub tests: Vec<UnitOutput>,
71
72 pub binaries: Vec<UnitOutput>,
74
75 pub cdylibs: Vec<UnitOutput>,
77
78 pub root_crate_names: Vec<String>,
80
81 pub native_dirs: BTreeSet<PathBuf>,
88
89 pub root_output: HashMap<CompileKind, PathBuf>,
91
92 pub deps_output: HashMap<CompileKind, PathBuf>,
95
96 sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
98
99 pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
105
106 pub to_doc_test: Vec<Doctest>,
108
109 pub host: String,
111
112 gctx: &'gctx GlobalContext,
113
114 rustc_process: ProcessBuilder,
116 rustc_workspace_wrapper_process: ProcessBuilder,
118 primary_rustc_process: Option<ProcessBuilder>,
121
122 target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
123 target_linkers: HashMap<CompileKind, Option<PathBuf>>,
125
126 pub lint_warning_count: usize,
128}
129
130impl<'gctx> Compilation<'gctx> {
131 pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
132 let rustc_process = bcx.rustc().process();
133 let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
134 let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
135 Ok(Compilation {
136 native_dirs: BTreeSet::new(),
137 root_output: HashMap::new(),
138 deps_output: HashMap::new(),
139 sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
140 tests: Vec::new(),
141 binaries: Vec::new(),
142 cdylibs: Vec::new(),
143 root_crate_names: Vec::new(),
144 extra_env: HashMap::new(),
145 to_doc_test: Vec::new(),
146 gctx: bcx.gctx,
147 host: bcx.host_triple().to_string(),
148 rustc_process,
149 rustc_workspace_wrapper_process,
150 primary_rustc_process,
151 target_runners: bcx
152 .build_config
153 .requested_kinds
154 .iter()
155 .chain(Some(&CompileKind::Host))
156 .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
157 .collect::<CargoResult<HashMap<_, _>>>()?,
158 target_linkers: bcx
159 .build_config
160 .requested_kinds
161 .iter()
162 .chain(Some(&CompileKind::Host))
163 .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
164 .collect::<CargoResult<HashMap<_, _>>>()?,
165 lint_warning_count: 0,
166 })
167 }
168
169 pub fn rustc_process(
177 &self,
178 unit: &Unit,
179 is_primary: bool,
180 is_workspace: bool,
181 ) -> CargoResult<ProcessBuilder> {
182 let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
183 self.primary_rustc_process.clone().unwrap()
184 } else if is_workspace {
185 self.rustc_workspace_wrapper_process.clone()
186 } else {
187 self.rustc_process.clone()
188 };
189 if self.gctx.extra_verbose() {
190 rustc.display_env_vars();
191 }
192 let cmd = fill_rustc_tool_env(rustc, unit);
193 self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
194 }
195
196 pub fn rustdoc_process(
198 &self,
199 unit: &Unit,
200 script_metas: Option<&Vec<UnitHash>>,
201 ) -> CargoResult<ProcessBuilder> {
202 let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
203 if self.gctx.extra_verbose() {
204 rustdoc.display_env_vars();
205 }
206 let cmd = fill_rustc_tool_env(rustdoc, unit);
207 let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
208 cmd.retry_with_argfile(true);
209 unit.target.edition().cmd_edition_arg(&mut cmd);
210
211 for crate_type in unit.target.rustc_crate_types() {
212 cmd.arg("--crate-type").arg(crate_type.as_str());
213 }
214
215 Ok(cmd)
216 }
217
218 pub fn host_process<T: AsRef<OsStr>>(
225 &self,
226 cmd: T,
227 pkg: &Package,
228 ) -> CargoResult<ProcessBuilder> {
229 self.fill_env(
230 ProcessBuilder::new(cmd),
231 pkg,
232 None,
233 CompileKind::Host,
234 ToolKind::HostProcess,
235 )
236 }
237
238 pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
239 self.target_runners.get(&kind).and_then(|x| x.as_ref())
240 }
241
242 pub fn target_linker(&self, kind: CompileKind) -> Option<PathBuf> {
244 self.target_linkers.get(&kind).and_then(|x| x.clone())
245 }
246
247 pub fn target_process<T: AsRef<OsStr>>(
255 &self,
256 cmd: T,
257 kind: CompileKind,
258 pkg: &Package,
259 script_metas: Option<&Vec<UnitHash>>,
260 ) -> CargoResult<ProcessBuilder> {
261 let builder = if let Some((runner, args)) = self.target_runner(kind) {
262 let mut builder = ProcessBuilder::new(runner);
263 builder.args(args);
264 builder.arg(cmd);
265 builder
266 } else {
267 ProcessBuilder::new(cmd)
268 };
269 let tool_kind = ToolKind::TargetProcess;
270 let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
271
272 if let Some(client) = self.gctx.jobserver_from_env() {
273 builder.inherit_jobserver(client);
274 }
275
276 Ok(builder)
277 }
278
279 fn fill_env(
285 &self,
286 mut cmd: ProcessBuilder,
287 pkg: &Package,
288 script_metas: Option<&Vec<UnitHash>>,
289 kind: CompileKind,
290 tool_kind: ToolKind,
291 ) -> CargoResult<ProcessBuilder> {
292 let mut search_path = Vec::new();
293 if tool_kind.is_rustc_tool() {
294 if matches!(tool_kind, ToolKind::Rustdoc) {
295 search_path.extend(super::filter_dynamic_search_path(
302 self.native_dirs.iter(),
303 &self.root_output[&CompileKind::Host],
304 ));
305 }
306 search_path.push(self.deps_output[&CompileKind::Host].clone());
307 } else {
308 if let Some(path) = self.root_output.get(&kind) {
309 search_path.extend(super::filter_dynamic_search_path(
310 self.native_dirs.iter(),
311 path,
312 ));
313 search_path.push(path.clone());
314 }
315 search_path.push(self.deps_output[&kind].clone());
316 if self.gctx.cli_unstable().build_std.is_none() ||
321 pkg.proc_macro()
323 {
324 search_path.push(self.sysroot_target_libdir[&kind].clone());
325 }
326 }
327
328 let dylib_path = paths::dylib_path();
329 let dylib_path_is_empty = dylib_path.is_empty();
330 if dylib_path.starts_with(&search_path) {
331 search_path = dylib_path;
332 } else {
333 search_path.extend(dylib_path.into_iter());
334 }
335 if cfg!(target_os = "macos") && dylib_path_is_empty {
336 if let Some(home) = self.gctx.get_env_os("HOME") {
340 search_path.push(PathBuf::from(home).join("lib"));
341 }
342 search_path.push(PathBuf::from("/usr/local/lib"));
343 search_path.push(PathBuf::from("/usr/lib"));
344 }
345 let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
346
347 cmd.env(paths::dylib_path_envvar(), &search_path);
348 if let Some(meta_vec) = script_metas {
349 for meta in meta_vec {
350 if let Some(env) = self.extra_env.get(meta) {
351 for (k, v) in env {
352 cmd.env(k, v);
353 }
354 }
355 }
356 }
357
358 let cargo_exe = self.gctx.cargo_exe()?;
359 cmd.env(crate::CARGO_ENV, cargo_exe);
360
361 cmd.env("CARGO_MANIFEST_DIR", pkg.root())
366 .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
367 .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
368 .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
369 .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
370 .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
371 .env("CARGO_PKG_VERSION", &pkg.version().to_string())
372 .env("CARGO_PKG_NAME", &*pkg.name());
373
374 for (key, value) in pkg.manifest().metadata().env_vars() {
375 cmd.env(key, value.as_ref());
376 }
377
378 cmd.cwd(pkg.root());
379
380 apply_env_config(self.gctx, &mut cmd)?;
381
382 Ok(cmd)
383 }
384}
385
386fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
389 if unit.target.is_executable() {
390 let name = unit
391 .target
392 .binary_filename()
393 .unwrap_or(unit.target.name().to_string());
394
395 cmd.env("CARGO_BIN_NAME", name);
396 }
397 cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
398 cmd
399}
400
401fn get_sysroot_target_libdir(
402 bcx: &BuildContext<'_, '_>,
403) -> CargoResult<HashMap<CompileKind, PathBuf>> {
404 bcx.all_kinds
405 .iter()
406 .map(|&kind| {
407 let Some(info) = bcx.target_data.get_info(kind) else {
408 let target = match kind {
409 CompileKind::Host => "host".to_owned(),
410 CompileKind::Target(s) => s.short_name().to_owned(),
411 };
412
413 let dependency = bcx
414 .unit_graph
415 .iter()
416 .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
417 .unwrap();
418
419 anyhow::bail!(
420 "could not find specification for target `{target}`.\n \
421 Dependency `{dependency}` requires to build for target `{target}`."
422 )
423 };
424
425 Ok((kind, info.sysroot_target_libdir.clone()))
426 })
427 .collect()
428}
429
430fn target_runner(
431 bcx: &BuildContext<'_, '_>,
432 kind: CompileKind,
433) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
434 let target = bcx.target_data.short_name(&kind);
435
436 let key = format!("target.{}.runner", target);
438
439 if let Some(v) = bcx.gctx.get::<Option<context::PathAndArgs>>(&key)? {
440 let path = v.path.resolve_program(bcx.gctx);
441 return Ok(Some((path, v.args)));
442 }
443
444 let target_cfg = bcx.target_data.info(kind).cfg();
446 let mut cfgs = bcx
447 .gctx
448 .target_cfgs()?
449 .iter()
450 .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
451 .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
452 let matching_runner = cfgs.next();
453 if let Some((key, runner)) = cfgs.next() {
454 anyhow::bail!(
455 "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
456 first match `{}` located in {}\n\
457 second match `{}` located in {}",
458 matching_runner.unwrap().0,
459 matching_runner.unwrap().1.definition,
460 key,
461 runner.definition
462 );
463 }
464 Ok(matching_runner.map(|(_k, runner)| {
465 (
466 runner.val.path.clone().resolve_program(bcx.gctx),
467 runner.val.args.clone(),
468 )
469 }))
470}
471
472fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
474 if let Some(path) = bcx
476 .target_data
477 .target_config(kind)
478 .linker
479 .as_ref()
480 .map(|l| l.val.clone().resolve_program(bcx.gctx))
481 {
482 return Ok(Some(path));
483 }
484
485 let target_cfg = bcx.target_data.info(kind).cfg();
487 let mut cfgs = bcx
488 .gctx
489 .target_cfgs()?
490 .iter()
491 .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
492 .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
493 let matching_linker = cfgs.next();
494 if let Some((key, linker)) = cfgs.next() {
495 anyhow::bail!(
496 "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
497 first match `{}` located in {}\n\
498 second match `{}` located in {}",
499 matching_linker.unwrap().0,
500 matching_linker.unwrap().1.definition,
501 key,
502 linker.definition
503 );
504 }
505 Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
506}