1use crate::util::data_structures::{HashMap, HashSet};
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex};
6
7use crate::compiler::compilation::{self, UnitOutput};
8use crate::compiler::locking::LockManager;
9use crate::compiler::{self, Unit, UserIntent, artifact};
10use crate::util::cache_lock::CacheLockMode;
11use crate::util::errors::CargoResult;
12use crate::workspace::PackageId;
13use anyhow::{Context as _, bail};
14use cargo_util::paths;
15use cargo_util_terminal::report::{Level, Message};
16use filetime::FileTime;
17use itertools::Itertools;
18use jobserver::Client;
19
20use super::RustdocFingerprint;
21use super::custom_build::{self, BuildDeps, BuildScriptOutputs, BuildScripts};
22use super::fingerprint::{Checksum, Fingerprint};
23use super::job_queue::JobQueue;
24use super::layout::Layout;
25use super::lto::Lto;
26use super::unit_graph::UnitDep;
27use super::unused_deps::UnusedDepState;
28use super::{BuildContext, Compilation, CompileKind, CompileMode, Executor, FileFlavor};
29
30mod compilation_files;
31use self::compilation_files::CompilationFiles;
32pub use self::compilation_files::{Metadata, OutputFile, UnitHash};
33
34pub struct BuildRunner<'a, 'gctx> {
41 pub bcx: &'a BuildContext<'a, 'gctx>,
43 pub compilation: Compilation<'gctx>,
45 pub build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
47 pub build_explicit_deps: HashMap<Unit, BuildDeps>,
51 pub fingerprints: HashMap<Unit, Arc<Fingerprint>>,
53 pub mtime_cache: HashMap<PathBuf, FileTime>,
55 pub checksum_cache: HashMap<PathBuf, Checksum>,
57 pub compiled: HashSet<Unit>,
61 pub build_scripts: HashMap<Unit, Arc<BuildScripts>>,
64 pub jobserver: Client,
66 primary_packages: HashSet<PackageId>,
70 files: Option<CompilationFiles<'a, 'gctx>>,
74
75 rmeta_required: HashSet<Unit>,
78
79 pub lto: HashMap<Unit, Lto>,
83
84 pub metadata_for_doc_units: HashMap<Unit, Metadata>,
87
88 pub failed_scrape_units: Arc<Mutex<HashSet<UnitHash>>>,
92
93 pub unused_dep_state: UnusedDepState,
94
95 pub lock_manager: Arc<LockManager>,
97}
98
99impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
100 pub fn new(bcx: &'a BuildContext<'a, 'gctx>) -> CargoResult<Self> {
101 let jobserver = match bcx.gctx.jobserver_from_env() {
110 Some(c) => c.clone(),
111 None => {
112 let client =
113 Client::new(bcx.jobs() as usize).context("failed to create jobserver")?;
114 client.acquire_raw()?;
115 client
116 }
117 };
118
119 Ok(Self {
120 bcx,
121 compilation: Compilation::new(bcx)?,
122 build_script_outputs: Arc::new(Mutex::new(BuildScriptOutputs::default())),
123 fingerprints: HashMap::default(),
124 mtime_cache: HashMap::default(),
125 checksum_cache: HashMap::default(),
126 compiled: HashSet::default(),
127 build_scripts: HashMap::default(),
128 build_explicit_deps: HashMap::default(),
129 jobserver,
130 primary_packages: HashSet::default(),
131 files: None,
132 rmeta_required: HashSet::default(),
133 lto: HashMap::default(),
134 metadata_for_doc_units: HashMap::default(),
135 failed_scrape_units: Arc::new(Mutex::new(HashSet::default())),
136 unused_dep_state: UnusedDepState::new(bcx),
137 lock_manager: Arc::new(LockManager::new()),
138 })
139 }
140
141 pub fn dry_run(mut self) -> CargoResult<Compilation<'gctx>> {
146 let _lock = self
147 .bcx
148 .gctx
149 .acquire_package_cache_lock(CacheLockMode::Shared)?;
150 self.lto = super::lto::generate(self.bcx)?;
151 self.prepare_units()?;
152 self.prepare()?;
153 self.check_collisions()?;
154
155 for unit in &self.bcx.roots {
156 self.collect_tests_and_executables(unit)?;
157 }
158
159 Ok(self.compilation)
160 }
161
162 #[tracing::instrument(skip_all)]
169 pub fn compile(mut self, exec: &Arc<dyn Executor>) -> CargoResult<Compilation<'gctx>> {
170 let _lock = self
174 .bcx
175 .gctx
176 .acquire_package_cache_lock(CacheLockMode::Shared)?;
177 let mut queue = JobQueue::new(self.bcx);
178 self.lto = super::lto::generate(self.bcx)?;
179 self.prepare_units()?;
180 self.prepare()?;
181 custom_build::build_map(&mut self)?;
182 self.check_collisions()?;
183 self.compute_metadata_for_doc_units();
184
185 if self.bcx.build_config.intent.is_doc() {
189 RustdocFingerprint::check_rustdoc_fingerprint(&self)?
190 }
191
192 for unit in &self.bcx.roots {
193 let force_rebuild = self.bcx.build_config.force_rebuild;
194 super::compile(&mut self, &mut queue, unit, exec, force_rebuild)?;
195 }
196
197 for fingerprint in self.fingerprints.values() {
204 fingerprint.clear_memoized();
205 }
206
207 queue.execute(&mut self)?;
209
210 let units_with_build_script = &self
212 .bcx
213 .roots
214 .iter()
215 .filter(|unit| self.build_scripts.contains_key(unit))
216 .dedup_by(|x, y| x.pkg.package_id() == y.pkg.package_id())
217 .collect::<Vec<_>>();
218 for unit in units_with_build_script {
219 for dep in &self.bcx.unit_graph[unit] {
220 if dep.unit.mode.is_run_custom_build() {
221 let out_dir = if self.bcx.gctx.cli_unstable().build_dir_new_layout {
222 self.files().out_dir_new_layout(&dep.unit)
223 } else {
224 self.files().build_script_out_dir(&dep.unit)
225 };
226 let script_meta = self.get_run_build_script_metadata(&dep.unit);
227 self.compilation
228 .extra_env
229 .entry(script_meta)
230 .or_insert_with(Vec::new)
231 .push(("OUT_DIR".to_string(), out_dir.display().to_string()));
232 }
233 }
234 }
235
236 self.collect_doc_merge_info()?;
237
238 for unit in &self.bcx.roots {
240 self.collect_tests_and_executables(unit)?;
241
242 if unit.mode.is_doc_test() {
244 let mut unstable_opts = false;
245 let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
246 args.extend(compiler::lib_search_paths(&self, unit)?);
247 args.extend(compiler::lto_args(&self, unit));
248 args.extend(compiler::features_args(unit));
249 args.extend(compiler::check_cfg_args(unit));
250
251 let script_metas = self.find_build_script_metadatas(unit);
252 if let Some(meta_vec) = script_metas.clone() {
253 for meta in meta_vec {
254 if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
255 for cfg in &output.cfgs {
256 args.push("--cfg".into());
257 args.push(cfg.into());
258 }
259
260 for check_cfg in &output.check_cfgs {
261 args.push("--check-cfg".into());
262 args.push(check_cfg.into());
263 }
264
265 for (lt, arg) in &output.linker_args {
266 if lt.applies_to(&unit.target, unit.mode) {
267 args.push("-C".into());
268 args.push(format!("link-arg={}", arg).into());
269 }
270 }
271 }
272 }
273 }
274 args.extend(unit.rustdocflags.iter().map(Into::into));
275
276 use super::MessageFormat;
277 let format = match self.bcx.build_config.message_format {
278 MessageFormat::Short => "short",
279 MessageFormat::Human => "human",
280 MessageFormat::Json { .. } => "json",
281 };
282 args.push("--error-format".into());
283 args.push(format.into());
284
285 self.compilation.to_doc_test.push(compilation::Doctest {
286 unit: unit.clone(),
287 args,
288 unstable_opts,
289 linker: self
290 .compilation
291 .target_linker(unit.kind)
292 .map(|p| p.to_path_buf()),
293 script_metas,
294 env: artifact::get_env(&self, unit, self.unit_deps(unit))?,
295 });
296 }
297
298 super::output_depinfo(&mut self, unit)?;
299 }
300
301 for (script_meta, output) in self.build_script_outputs.lock().unwrap().iter() {
302 self.compilation
303 .extra_env
304 .entry(*script_meta)
305 .or_insert_with(Vec::new)
306 .extend(output.env.iter().cloned());
307
308 for dir in output.library_paths.iter() {
309 self.compilation
310 .native_dirs
311 .insert(dir.clone().into_path_buf());
312 }
313 }
314 Ok(self.compilation)
315 }
316
317 fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> {
318 for output in self.outputs(unit)?.iter() {
319 if matches!(
320 output.flavor,
321 FileFlavor::DebugInfo
322 | FileFlavor::Auxiliary
323 | FileFlavor::Sbom
324 | FileFlavor::Unremap
325 ) {
326 continue;
327 }
328
329 let bindst = output.bin_dst();
330
331 if unit.mode == CompileMode::Test {
332 self.compilation
333 .tests
334 .push(self.unit_output(unit, &output.path)?);
335 } else if unit.target.is_executable() {
336 self.compilation
337 .binaries
338 .push(self.unit_output(unit, bindst)?);
339 } else if unit.target.is_cdylib()
340 && !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit)
341 {
342 self.compilation
343 .cdylibs
344 .push(self.unit_output(unit, bindst)?);
345 }
346 }
347 Ok(())
348 }
349
350 fn collect_doc_merge_info(&mut self) -> CargoResult<()> {
351 if !self.bcx.gctx.cli_unstable().rustdoc_mergeable_info {
352 return Ok(());
353 }
354
355 if !self.bcx.build_config.intent.is_doc() {
356 return Ok(());
357 }
358
359 if self.bcx.build_config.intent.wants_doc_json_output() {
360 return Ok(());
362 }
363
364 let mut doc_parts_map: HashMap<_, Vec<_>> = HashMap::default();
365
366 let unit_iter = if self.bcx.build_config.intent.wants_deps_docs() {
367 itertools::Either::Left(self.bcx.unit_graph.keys())
368 } else {
369 itertools::Either::Right(self.bcx.roots.iter())
370 };
371
372 for unit in unit_iter {
373 if !unit.mode.is_doc() {
374 continue;
375 }
376 let outputs = self.outputs(unit)?;
378
379 let Some(doc_parts) = outputs
380 .iter()
381 .find(|o| matches!(o.flavor, FileFlavor::DocParts))
382 else {
383 continue;
384 };
385
386 doc_parts_map
387 .entry(unit.kind)
388 .or_default()
389 .push(doc_parts.path.to_owned());
390 }
391
392 self.compilation.rustdoc_fingerprints = Some(
393 doc_parts_map
394 .into_iter()
395 .map(|(kind, doc_parts)| (kind, RustdocFingerprint::new(self, kind, doc_parts)))
396 .collect(),
397 );
398
399 Ok(())
400 }
401
402 pub fn get_executable(&mut self, unit: &Unit) -> CargoResult<Option<PathBuf>> {
404 let is_binary = unit.target.is_executable();
405 let is_test = unit.mode.is_any_test();
406 if !unit.mode.generates_executable() || !(is_binary || is_test) {
407 return Ok(None);
408 }
409 Ok(self
410 .outputs(unit)?
411 .iter()
412 .find(|o| o.flavor == FileFlavor::Normal)
413 .map(|output| output.bin_dst().clone()))
414 }
415
416 #[tracing::instrument(skip_all)]
417 pub fn prepare_units(&mut self) -> CargoResult<()> {
418 let dest = self.bcx.profiles.get_dir_name();
419 let must_take_artifact_dir_lock = match self.bcx.build_config.intent {
423 UserIntent::Check { .. } => {
424 self.bcx.build_config.timing_report
428 }
429 UserIntent::Build
430 | UserIntent::Test
431 | UserIntent::Doc { .. }
432 | UserIntent::Doctest
433 | UserIntent::Bench => true,
434 };
435 let host_layout =
436 Layout::new(self.bcx.ws, None, &dest, must_take_artifact_dir_lock, false)?;
437 let mut targets = HashMap::default();
438 for kind in self.bcx.all_kinds.iter() {
439 if let CompileKind::Target(target) = *kind {
440 let layout = Layout::new(
441 self.bcx.ws,
442 Some(target),
443 &dest,
444 must_take_artifact_dir_lock,
445 false,
446 )?;
447 targets.insert(target, layout);
448 }
449 }
450 self.primary_packages
451 .extend(self.bcx.roots.iter().map(|u| u.pkg.package_id()));
452 self.compilation
453 .root_crate_names
454 .extend(self.bcx.roots.iter().map(|u| u.target.crate_name()));
455
456 self.record_units_requiring_metadata();
457
458 let files = CompilationFiles::new(self, host_layout, targets);
459 self.files = Some(files);
460 Ok(())
461 }
462
463 #[tracing::instrument(skip_all)]
466 pub fn prepare(&mut self) -> CargoResult<()> {
467 self.files
468 .as_mut()
469 .unwrap()
470 .host
471 .prepare()
472 .context("couldn't prepare build directories")?;
473 for target in self.files.as_mut().unwrap().target.values_mut() {
474 target
475 .prepare()
476 .context("couldn't prepare build directories")?;
477 }
478
479 let files = self.files.as_ref().unwrap();
480 for &kind in self.bcx.all_kinds.iter() {
481 let layout = files.layout(kind);
482 if let Some(artifact_dir) = layout.artifact_dir() {
483 self.compilation
484 .root_output
485 .insert(kind, artifact_dir.dest().to_path_buf());
486 }
487 if self.bcx.gctx.cli_unstable().build_dir_new_layout {
488 for (unit, _) in self.bcx.unit_graph.iter() {
489 if kind != unit.kind {
490 continue;
491 }
492 let dep_dir = self.files().deps_dir(unit);
493 paths::create_dir_all(&dep_dir)?;
494 if unit.target.is_dylib() {
495 self.compilation
496 .deps_output
497 .entry(kind)
498 .or_default()
499 .insert(dep_dir);
500 }
501 }
502 } else {
503 self.compilation
504 .deps_output
505 .entry(kind)
506 .or_default()
507 .insert(layout.build_dir().legacy_deps().to_path_buf());
508 }
509 }
510 Ok(())
511 }
512
513 pub fn files(&self) -> &CompilationFiles<'a, 'gctx> {
514 self.files.as_ref().unwrap()
515 }
516
517 pub fn outputs(&self, unit: &Unit) -> CargoResult<Arc<Vec<OutputFile>>> {
519 self.files.as_ref().unwrap().outputs(unit, self.bcx)
520 }
521
522 pub fn unit_deps(&self, unit: &Unit) -> &[UnitDep] {
524 &self.bcx.unit_graph[unit]
525 }
526
527 pub fn find_build_script_units(&self, unit: &Unit) -> Option<Vec<Unit>> {
531 if unit.mode.is_run_custom_build() {
532 return Some(vec![unit.clone()]);
533 }
534
535 let build_script_units: Vec<Unit> = self.bcx.unit_graph[unit]
536 .iter()
537 .filter(|unit_dep| {
538 unit_dep.unit.mode.is_run_custom_build()
539 && unit_dep.unit.pkg.package_id() == unit.pkg.package_id()
540 })
541 .map(|unit_dep| unit_dep.unit.clone())
542 .collect();
543 if build_script_units.is_empty() {
544 None
545 } else {
546 Some(build_script_units)
547 }
548 }
549
550 pub fn find_build_script_metadatas(&self, unit: &Unit) -> Option<Vec<UnitHash>> {
555 self.find_build_script_units(unit).map(|units| {
556 units
557 .iter()
558 .map(|u| self.get_run_build_script_metadata(u))
559 .collect()
560 })
561 }
562
563 pub fn get_run_build_script_metadata(&self, unit: &Unit) -> UnitHash {
565 assert!(unit.mode.is_run_custom_build());
566 self.files().metadata(unit).unit_id()
567 }
568
569 pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
571 Ok(self
572 .outputs(unit)?
573 .iter()
574 .filter(|o| o.flavor == FileFlavor::Sbom)
575 .map(|o| o.path.clone())
576 .collect())
577 }
578
579 pub fn unremap_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
581 Ok(self
582 .outputs(unit)?
583 .iter()
584 .filter(|o| o.flavor == FileFlavor::Unremap)
585 .map(|o| o.path.clone())
586 .collect())
587 }
588
589 pub fn is_primary_package(&self, unit: &Unit) -> bool {
590 self.primary_packages.contains(&unit.pkg.package_id())
591 }
592
593 pub fn unit_output(&self, unit: &Unit, path: &Path) -> CargoResult<UnitOutput> {
596 let script_metas = self.find_build_script_metadatas(unit);
597 let env = artifact::get_env(&self, unit, self.unit_deps(unit))?;
598 Ok(UnitOutput {
599 unit: unit.clone(),
600 path: path.to_path_buf(),
601 script_metas,
602 env,
603 })
604 }
605
606 #[tracing::instrument(skip_all)]
609 fn check_collisions(&self) -> CargoResult<()> {
610 let mut output_collisions = HashMap::default();
611 let describe_collision = |unit: &Unit, other_unit: &Unit| -> String {
612 format!(
613 "the {} target `{}` in package `{}` has the same output filename as the {} target `{}` in package `{}`",
614 unit.target.kind().description(),
615 unit.target.name(),
616 unit.pkg.package_id(),
617 other_unit.target.kind().description(),
618 other_unit.target.name(),
619 other_unit.pkg.package_id(),
620 )
621 };
622 let suggestion = [
623 Level::NOTE.message("this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>"),
624 Level::HELP.message("consider changing their names to be unique or compiling them separately")
625 ];
626 let rustdoc_suggestion = [
627 Level::NOTE.message("this is a known bug where multiple crates with the same name use the same path; see <https://github.com/rust-lang/cargo/issues/6313>")
628 ];
629 let report_collision = |unit: &Unit,
630 other_unit: &Unit,
631 path: &PathBuf,
632 messages: &[Message<'_>]|
633 -> CargoResult<()> {
634 if unit.target.name() == other_unit.target.name() {
635 self.bcx.gctx.shell().print_report(
636 &[Level::WARNING
637 .secondary_title(format!("output filename collision at {}", path.display()))
638 .elements(
639 [Level::NOTE.message(describe_collision(unit, other_unit))]
640 .into_iter()
641 .chain(messages.iter().cloned()),
642 )],
643 false,
644 )
645 } else {
646 self.bcx.gctx.shell().print_report(
647 &[Level::WARNING
648 .secondary_title(format!("output filename collision at {}", path.display()))
649 .elements([
650 Level::NOTE.message(describe_collision(unit, other_unit)),
651 Level::NOTE.message("if this looks unexpected, it may be a bug in Cargo. Please file a bug \
652 report at https://github.com/rust-lang/cargo/issues/ with as much information as you \
653 can provide."),
654 Level::NOTE.message(format!("cargo {} running on `{}` target `{}`",
655 crate::version(), self.bcx.host_triple(), self.bcx.target_data.short_name(&unit.kind))),
656 Level::NOTE.message(format!("first unit: {unit:?}")),
657 Level::NOTE.message(format!("second unit: {other_unit:?}")),
658 ])],
659 false,
660 )
661 }
662 };
663
664 fn doc_collision_error(unit: &Unit, other_unit: &Unit) -> CargoResult<()> {
665 bail!(
666 "document output filename collision\n\
667 The {} `{}` in package `{}` has the same name as the {} `{}` in package `{}`.\n\
668 Only one may be documented at once since they output to the same path.\n\
669 Consider documenting only one, renaming one, \
670 or marking one with `doc = false` in Cargo.toml.",
671 unit.target.kind().description(),
672 unit.target.name(),
673 unit.pkg,
674 other_unit.target.kind().description(),
675 other_unit.target.name(),
676 other_unit.pkg,
677 );
678 }
679
680 let mut keys = self
681 .bcx
682 .unit_graph
683 .keys()
684 .filter(|unit| !unit.mode.is_run_custom_build())
685 .collect::<Vec<_>>();
686 keys.sort_unstable();
688 let mut doc_libs = HashMap::default();
696 let mut doc_bins = HashMap::default();
697 for unit in keys {
698 if unit.mode.is_doc() && self.is_primary_package(unit) {
699 if unit.target.is_lib() {
702 if let Some(prev) = doc_libs.insert((unit.target.crate_name(), unit.kind), unit)
703 {
704 doc_collision_error(unit, prev)?;
705 }
706 } else if let Some(prev) =
707 doc_bins.insert((unit.target.crate_name(), unit.kind), unit)
708 {
709 doc_collision_error(unit, prev)?;
710 }
711 }
712 for output in self.outputs(unit)?.iter() {
713 if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) {
714 if unit.mode.is_doc() {
715 report_collision(unit, other_unit, &output.path, &rustdoc_suggestion)?;
718 } else {
719 report_collision(unit, other_unit, &output.path, &suggestion)?;
720 }
721 }
722 if let Some(hardlink) = output.hardlink.as_ref() {
723 if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit) {
724 report_collision(unit, other_unit, hardlink, &suggestion)?;
725 }
726 }
727 if let Some(ref export_path) = output.export_path {
728 if let Some(other_unit) = output_collisions.insert(export_path.clone(), unit) {
729 self.bcx.gctx.shell().print_report(
730 &[Level::WARNING
731 .secondary_title(format!(
732 "`--artifact-dir` filename collision at {}",
733 export_path.display()
734 ))
735 .elements(
736 [Level::NOTE.message(describe_collision(unit, other_unit))]
737 .into_iter()
738 .chain(suggestion.iter().cloned()),
739 )],
740 false,
741 )?;
742 }
743 }
744 }
745 }
746 Ok(())
747 }
748
749 fn record_units_requiring_metadata(&mut self) {
754 for (key, deps) in self.bcx.unit_graph.iter() {
755 for dep in deps {
756 if self.only_requires_rmeta(key, &dep.unit) {
757 self.rmeta_required.insert(dep.unit.clone());
758 }
759 }
760 }
761 }
762
763 pub fn only_requires_rmeta(&self, parent: &Unit, dep: &Unit) -> bool {
766 !parent.requires_upstream_objects()
769 && parent.mode == CompileMode::Build
770 && !dep.requires_upstream_objects()
773 && dep.mode == CompileMode::Build
774 }
775
776 pub fn rmeta_required(&self, unit: &Unit) -> bool {
779 self.rmeta_required.contains(unit)
780 }
781
782 #[tracing::instrument(skip_all)]
793 pub fn compute_metadata_for_doc_units(&mut self) {
794 for unit in self.bcx.unit_graph.keys() {
795 if !unit.mode.is_doc() && !unit.mode.is_doc_scrape() {
796 continue;
797 }
798
799 let matching_units = self
800 .bcx
801 .unit_graph
802 .keys()
803 .filter(|other| {
804 unit.pkg == other.pkg
805 && unit.target == other.target
806 && !other.mode.is_doc_scrape()
807 })
808 .collect::<Vec<_>>();
809 let metadata_unit = matching_units
810 .iter()
811 .find(|other| other.mode.is_check())
812 .or_else(|| matching_units.iter().find(|other| other.mode.is_doc()))
813 .unwrap_or(&unit);
814 self.metadata_for_doc_units
815 .insert(unit.clone(), self.files().metadata(metadata_unit));
816 }
817 }
818}