cargo/core/compiler/build_runner/
mod.rs

1//! [`BuildRunner`] is the mutable state used during the build process.
2
3use std::collections::{HashMap, HashSet};
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex};
6
7use crate::core::PackageId;
8use crate::core::compiler::compilation::{self, UnitOutput};
9use crate::core::compiler::locking::LockManager;
10use crate::core::compiler::{self, Unit, UserIntent, artifact};
11use crate::util::cache_lock::CacheLockMode;
12use crate::util::errors::CargoResult;
13use annotate_snippets::{Level, Message};
14use anyhow::{Context as _, bail};
15use cargo_util::paths;
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::{BuildContext, Compilation, CompileKind, CompileMode, Executor, FileFlavor};
28
29mod compilation_files;
30use self::compilation_files::CompilationFiles;
31pub use self::compilation_files::{Metadata, OutputFile, UnitHash};
32
33/// Collection of all the stuff that is needed to perform a build.
34///
35/// Different from the [`BuildContext`], `Context` is a _mutable_ state used
36/// throughout the entire build process. Everything is coordinated through this.
37///
38/// [`BuildContext`]: crate::core::compiler::BuildContext
39pub struct BuildRunner<'a, 'gctx> {
40    /// Mostly static information about the build task.
41    pub bcx: &'a BuildContext<'a, 'gctx>,
42    /// A large collection of information about the result of the entire compilation.
43    pub compilation: Compilation<'gctx>,
44    /// Output from build scripts, updated after each build script runs.
45    pub build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
46    /// Dependencies (like rerun-if-changed) declared by a build script.
47    /// This is *only* populated from the output from previous runs.
48    /// If the build script hasn't ever been run, then it must be run.
49    pub build_explicit_deps: HashMap<Unit, BuildDeps>,
50    /// Fingerprints used to detect if a unit is out-of-date.
51    pub fingerprints: HashMap<Unit, Arc<Fingerprint>>,
52    /// Cache of file mtimes to reduce filesystem hits.
53    pub mtime_cache: HashMap<PathBuf, FileTime>,
54    /// Cache of file checksums to reduce filesystem reads.
55    pub checksum_cache: HashMap<PathBuf, Checksum>,
56    /// A set used to track which units have been compiled.
57    /// A unit may appear in the job graph multiple times as a dependency of
58    /// multiple packages, but it only needs to run once.
59    pub compiled: HashSet<Unit>,
60    /// Linking information for each `Unit`.
61    /// See `build_map` for details.
62    pub build_scripts: HashMap<Unit, Arc<BuildScripts>>,
63    /// Job server client to manage concurrency with other processes.
64    pub jobserver: Client,
65    /// "Primary" packages are the ones the user selected on the command-line
66    /// with `-p` flags. If no flags are specified, then it is the defaults
67    /// based on the current directory and the default workspace members.
68    primary_packages: HashSet<PackageId>,
69    /// An abstraction of the files and directories that will be generated by
70    /// the compilation. This is `None` until after `unit_dependencies` has
71    /// been computed.
72    files: Option<CompilationFiles<'a, 'gctx>>,
73
74    /// A set of units which are compiling rlibs and are expected to produce
75    /// metadata files in addition to the rlib itself.
76    rmeta_required: HashSet<Unit>,
77
78    /// Map of the LTO-status of each unit. This indicates what sort of
79    /// compilation is happening (only object, only bitcode, both, etc), and is
80    /// precalculated early on.
81    pub lto: HashMap<Unit, Lto>,
82
83    /// Map of Doc/Docscrape units to metadata for their -Cmetadata flag.
84    /// See `Context::find_metadata_units` for more details.
85    pub metadata_for_doc_units: HashMap<Unit, Metadata>,
86
87    /// Set of metadata of Docscrape units that fail before completion, e.g.
88    /// because the target has a type error. This is in an Arc<Mutex<..>>
89    /// because it is continuously updated as the job progresses.
90    pub failed_scrape_units: Arc<Mutex<HashSet<UnitHash>>>,
91
92    /// Manages locks for build units when fine grain locking is enabled.
93    pub lock_manager: Arc<LockManager>,
94}
95
96impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
97    pub fn new(bcx: &'a BuildContext<'a, 'gctx>) -> CargoResult<Self> {
98        // Load up the jobserver that we'll use to manage our parallelism. This
99        // is the same as the GNU make implementation of a jobserver, and
100        // intentionally so! It's hoped that we can interact with GNU make and
101        // all share the same jobserver.
102        //
103        // Note that if we don't have a jobserver in our environment then we
104        // create our own, and we create it with `n` tokens, but immediately
105        // acquire one, because one token is ourself, a running process.
106        let jobserver = match bcx.gctx.jobserver_from_env() {
107            Some(c) => c.clone(),
108            None => {
109                let client =
110                    Client::new(bcx.jobs() as usize).context("failed to create jobserver")?;
111                client.acquire_raw()?;
112                client
113            }
114        };
115
116        Ok(Self {
117            bcx,
118            compilation: Compilation::new(bcx)?,
119            build_script_outputs: Arc::new(Mutex::new(BuildScriptOutputs::default())),
120            fingerprints: HashMap::new(),
121            mtime_cache: HashMap::new(),
122            checksum_cache: HashMap::new(),
123            compiled: HashSet::new(),
124            build_scripts: HashMap::new(),
125            build_explicit_deps: HashMap::new(),
126            jobserver,
127            primary_packages: HashSet::new(),
128            files: None,
129            rmeta_required: HashSet::new(),
130            lto: HashMap::new(),
131            metadata_for_doc_units: HashMap::new(),
132            failed_scrape_units: Arc::new(Mutex::new(HashSet::new())),
133            lock_manager: Arc::new(LockManager::new()),
134        })
135    }
136
137    /// Dry-run the compilation without actually running it.
138    ///
139    /// This is expected to collect information like the location of output artifacts.
140    /// Please keep in sync with non-compilation part in [`BuildRunner::compile`].
141    pub fn dry_run(mut self) -> CargoResult<Compilation<'gctx>> {
142        let _lock = self
143            .bcx
144            .gctx
145            .acquire_package_cache_lock(CacheLockMode::Shared)?;
146        self.lto = super::lto::generate(self.bcx)?;
147        self.prepare_units()?;
148        self.prepare()?;
149        self.check_collisions()?;
150
151        for unit in &self.bcx.roots {
152            self.collect_tests_and_executables(unit)?;
153        }
154
155        Ok(self.compilation)
156    }
157
158    /// Starts compilation, waits for it to finish, and returns information
159    /// about the result of compilation.
160    ///
161    /// See [`ops::cargo_compile`] for a higher-level view of the compile process.
162    ///
163    /// [`ops::cargo_compile`]: crate::ops::cargo_compile
164    #[tracing::instrument(skip_all)]
165    pub fn compile(mut self, exec: &Arc<dyn Executor>) -> CargoResult<Compilation<'gctx>> {
166        // A shared lock is held during the duration of the build since rustc
167        // needs to read from the `src` cache, and we don't want other
168        // commands modifying the `src` cache while it is running.
169        let _lock = self
170            .bcx
171            .gctx
172            .acquire_package_cache_lock(CacheLockMode::Shared)?;
173        let mut queue = JobQueue::new(self.bcx);
174        self.lto = super::lto::generate(self.bcx)?;
175        self.prepare_units()?;
176        self.prepare()?;
177        custom_build::build_map(&mut self)?;
178        self.check_collisions()?;
179        self.compute_metadata_for_doc_units();
180
181        // We need to make sure that if there were any previous docs already compiled,
182        // they were compiled with the same Rustc version that we're currently using.
183        // See the function doc comment for more.
184        if self.bcx.build_config.intent.is_doc() {
185            RustdocFingerprint::check_rustdoc_fingerprint(&self)?
186        }
187
188        for unit in &self.bcx.roots {
189            let force_rebuild = self.bcx.build_config.force_rebuild;
190            super::compile(&mut self, &mut queue, unit, exec, force_rebuild)?;
191        }
192
193        // Now that we've got the full job queue and we've done all our
194        // fingerprint analysis to determine what to run, bust all the memoized
195        // fingerprint hashes to ensure that during the build they all get the
196        // most up-to-date values. In theory we only need to bust hashes that
197        // transitively depend on a dirty build script, but it shouldn't matter
198        // that much for performance anyway.
199        for fingerprint in self.fingerprints.values() {
200            fingerprint.clear_memoized();
201        }
202
203        // Now that we've figured out everything that we're going to do, do it!
204        queue.execute(&mut self)?;
205
206        // Add `OUT_DIR` to env vars if unit has a build script.
207        let units_with_build_script = &self
208            .bcx
209            .roots
210            .iter()
211            .filter(|unit| self.build_scripts.contains_key(unit))
212            .dedup_by(|x, y| x.pkg.package_id() == y.pkg.package_id())
213            .collect::<Vec<_>>();
214        for unit in units_with_build_script {
215            for dep in &self.bcx.unit_graph[unit] {
216                if dep.unit.mode.is_run_custom_build() {
217                    let out_dir = self
218                        .files()
219                        .build_script_out_dir(&dep.unit)
220                        .display()
221                        .to_string();
222                    let script_meta = self.get_run_build_script_metadata(&dep.unit);
223                    self.compilation
224                        .extra_env
225                        .entry(script_meta)
226                        .or_insert_with(Vec::new)
227                        .push(("OUT_DIR".to_string(), out_dir));
228                }
229            }
230        }
231
232        self.collect_doc_merge_info()?;
233
234        // Collect the result of the build into `self.compilation`.
235        for unit in &self.bcx.roots {
236            self.collect_tests_and_executables(unit)?;
237
238            // Collect information for `rustdoc --test`.
239            if unit.mode.is_doc_test() {
240                let mut unstable_opts = false;
241                let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
242                args.extend(compiler::lib_search_paths(&self, unit)?);
243                args.extend(compiler::lto_args(&self, unit));
244                args.extend(compiler::features_args(unit));
245                args.extend(compiler::check_cfg_args(unit));
246
247                let script_metas = self.find_build_script_metadatas(unit);
248                if let Some(meta_vec) = script_metas.clone() {
249                    for meta in meta_vec {
250                        if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
251                            for cfg in &output.cfgs {
252                                args.push("--cfg".into());
253                                args.push(cfg.into());
254                            }
255
256                            for check_cfg in &output.check_cfgs {
257                                args.push("--check-cfg".into());
258                                args.push(check_cfg.into());
259                            }
260
261                            for (lt, arg) in &output.linker_args {
262                                if lt.applies_to(&unit.target, unit.mode) {
263                                    args.push("-C".into());
264                                    args.push(format!("link-arg={}", arg).into());
265                                }
266                            }
267                        }
268                    }
269                }
270                args.extend(unit.rustdocflags.iter().map(Into::into));
271
272                use super::MessageFormat;
273                let format = match self.bcx.build_config.message_format {
274                    MessageFormat::Short => "short",
275                    MessageFormat::Human => "human",
276                    MessageFormat::Json { .. } => "json",
277                };
278                args.push("--error-format".into());
279                args.push(format.into());
280
281                self.compilation.to_doc_test.push(compilation::Doctest {
282                    unit: unit.clone(),
283                    args,
284                    unstable_opts,
285                    linker: self.compilation.target_linker(unit.kind).clone(),
286                    script_metas,
287                    env: artifact::get_env(&self, unit, self.unit_deps(unit))?,
288                });
289            }
290
291            super::output_depinfo(&mut self, unit)?;
292        }
293
294        for (script_meta, output) in self.build_script_outputs.lock().unwrap().iter() {
295            self.compilation
296                .extra_env
297                .entry(*script_meta)
298                .or_insert_with(Vec::new)
299                .extend(output.env.iter().cloned());
300
301            for dir in output.library_paths.iter() {
302                self.compilation
303                    .native_dirs
304                    .insert(dir.clone().into_path_buf());
305            }
306        }
307        Ok(self.compilation)
308    }
309
310    fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> {
311        for output in self.outputs(unit)?.iter() {
312            if matches!(
313                output.flavor,
314                FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
315            ) {
316                continue;
317            }
318
319            let bindst = output.bin_dst();
320
321            if unit.mode == CompileMode::Test {
322                self.compilation
323                    .tests
324                    .push(self.unit_output(unit, &output.path)?);
325            } else if unit.target.is_executable() {
326                self.compilation
327                    .binaries
328                    .push(self.unit_output(unit, bindst)?);
329            } else if unit.target.is_cdylib()
330                && !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit)
331            {
332                self.compilation
333                    .cdylibs
334                    .push(self.unit_output(unit, bindst)?);
335            }
336        }
337        Ok(())
338    }
339
340    fn collect_doc_merge_info(&mut self) -> CargoResult<()> {
341        if !self.bcx.gctx.cli_unstable().rustdoc_mergeable_info {
342            return Ok(());
343        }
344
345        if !self.bcx.build_config.intent.is_doc() {
346            return Ok(());
347        }
348
349        if self.bcx.build_config.intent.wants_doc_json_output() {
350            // rustdoc JSON output doesn't support merge (yet?)
351            return Ok(());
352        }
353
354        let mut doc_parts_map: HashMap<_, Vec<_>> = HashMap::new();
355
356        let unit_iter = if self.bcx.build_config.intent.wants_deps_docs() {
357            itertools::Either::Left(self.bcx.unit_graph.keys())
358        } else {
359            itertools::Either::Right(self.bcx.roots.iter())
360        };
361
362        for unit in unit_iter {
363            if !unit.mode.is_doc() {
364                continue;
365            }
366            // Assumption: one `rustdoc` call generates only one cross-crate info JSON.
367            let outputs = self.outputs(unit)?;
368
369            let Some(doc_parts) = outputs
370                .iter()
371                .find(|o| matches!(o.flavor, FileFlavor::DocParts))
372            else {
373                continue;
374            };
375
376            doc_parts_map
377                .entry(unit.kind)
378                .or_default()
379                .push(doc_parts.path.to_owned());
380        }
381
382        self.compilation.rustdoc_fingerprints = Some(
383            doc_parts_map
384                .into_iter()
385                .map(|(kind, doc_parts)| (kind, RustdocFingerprint::new(self, kind, doc_parts)))
386                .collect(),
387        );
388
389        Ok(())
390    }
391
392    /// Returns the executable for the specified unit (if any).
393    pub fn get_executable(&mut self, unit: &Unit) -> CargoResult<Option<PathBuf>> {
394        let is_binary = unit.target.is_executable();
395        let is_test = unit.mode.is_any_test();
396        if !unit.mode.generates_executable() || !(is_binary || is_test) {
397            return Ok(None);
398        }
399        Ok(self
400            .outputs(unit)?
401            .iter()
402            .find(|o| o.flavor == FileFlavor::Normal)
403            .map(|output| output.bin_dst().clone()))
404    }
405
406    #[tracing::instrument(skip_all)]
407    pub fn prepare_units(&mut self) -> CargoResult<()> {
408        let dest = self.bcx.profiles.get_dir_name();
409        // We try to only lock the artifact-dir if we need to.
410        // For example, `cargo check` does not write any files to the artifact-dir so we don't need
411        // to lock it.
412        let must_take_artifact_dir_lock = match self.bcx.build_config.intent {
413            UserIntent::Check { .. } => {
414                // Generally cargo check does not need to take the artifact-dir lock but there is
415                // one exception: If check has `--timings` we still need to lock artifact-dir since
416                // we will output the report files.
417                self.bcx.build_config.timing_report
418            }
419            UserIntent::Build
420            | UserIntent::Test
421            | UserIntent::Doc { .. }
422            | UserIntent::Doctest
423            | UserIntent::Bench => true,
424        };
425        let host_layout =
426            Layout::new(self.bcx.ws, None, &dest, must_take_artifact_dir_lock, false)?;
427        let mut targets = HashMap::new();
428        for kind in self.bcx.all_kinds.iter() {
429            if let CompileKind::Target(target) = *kind {
430                let layout = Layout::new(
431                    self.bcx.ws,
432                    Some(target),
433                    &dest,
434                    must_take_artifact_dir_lock,
435                    false,
436                )?;
437                targets.insert(target, layout);
438            }
439        }
440        self.primary_packages
441            .extend(self.bcx.roots.iter().map(|u| u.pkg.package_id()));
442        self.compilation
443            .root_crate_names
444            .extend(self.bcx.roots.iter().map(|u| u.target.crate_name()));
445
446        self.record_units_requiring_metadata();
447
448        let files = CompilationFiles::new(self, host_layout, targets);
449        self.files = Some(files);
450        Ok(())
451    }
452
453    /// Prepare this context, ensuring that all filesystem directories are in
454    /// place.
455    #[tracing::instrument(skip_all)]
456    pub fn prepare(&mut self) -> CargoResult<()> {
457        self.files
458            .as_mut()
459            .unwrap()
460            .host
461            .prepare()
462            .context("couldn't prepare build directories")?;
463        for target in self.files.as_mut().unwrap().target.values_mut() {
464            target
465                .prepare()
466                .context("couldn't prepare build directories")?;
467        }
468
469        let files = self.files.as_ref().unwrap();
470        for &kind in self.bcx.all_kinds.iter() {
471            let layout = files.layout(kind);
472            if let Some(artifact_dir) = layout.artifact_dir() {
473                self.compilation
474                    .root_output
475                    .insert(kind, artifact_dir.dest().to_path_buf());
476            }
477            if self.bcx.gctx.cli_unstable().build_dir_new_layout {
478                for (unit, _) in self.bcx.unit_graph.iter() {
479                    let dep_dir = self.files().deps_dir(unit);
480                    paths::create_dir_all(&dep_dir)?;
481                    self.compilation.deps_output.insert(kind, dep_dir);
482                }
483            } else {
484                self.compilation
485                    .deps_output
486                    .insert(kind, layout.build_dir().legacy_deps().to_path_buf());
487            }
488        }
489        Ok(())
490    }
491
492    pub fn files(&self) -> &CompilationFiles<'a, 'gctx> {
493        self.files.as_ref().unwrap()
494    }
495
496    /// Returns the filenames that the given unit will generate.
497    pub fn outputs(&self, unit: &Unit) -> CargoResult<Arc<Vec<OutputFile>>> {
498        self.files.as_ref().unwrap().outputs(unit, self.bcx)
499    }
500
501    /// Direct dependencies for the given unit.
502    pub fn unit_deps(&self, unit: &Unit) -> &[UnitDep] {
503        &self.bcx.unit_graph[unit]
504    }
505
506    /// Returns the `RunCustomBuild` Units associated with the given Unit.
507    ///
508    /// If the package does not have a build script, this returns None.
509    pub fn find_build_script_units(&self, unit: &Unit) -> Option<Vec<Unit>> {
510        if unit.mode.is_run_custom_build() {
511            return Some(vec![unit.clone()]);
512        }
513
514        let build_script_units: Vec<Unit> = self.bcx.unit_graph[unit]
515            .iter()
516            .filter(|unit_dep| {
517                unit_dep.unit.mode.is_run_custom_build()
518                    && unit_dep.unit.pkg.package_id() == unit.pkg.package_id()
519            })
520            .map(|unit_dep| unit_dep.unit.clone())
521            .collect();
522        if build_script_units.is_empty() {
523            None
524        } else {
525            Some(build_script_units)
526        }
527    }
528
529    /// Returns the metadata hash for the `RunCustomBuild` Unit associated with
530    /// the given unit.
531    ///
532    /// If the package does not have a build script, this returns None.
533    pub fn find_build_script_metadatas(&self, unit: &Unit) -> Option<Vec<UnitHash>> {
534        self.find_build_script_units(unit).map(|units| {
535            units
536                .iter()
537                .map(|u| self.get_run_build_script_metadata(u))
538                .collect()
539        })
540    }
541
542    /// Returns the metadata hash for a `RunCustomBuild` unit.
543    pub fn get_run_build_script_metadata(&self, unit: &Unit) -> UnitHash {
544        assert!(unit.mode.is_run_custom_build());
545        self.files().metadata(unit).unit_id()
546    }
547
548    /// Returns the list of SBOM output file paths for a given [`Unit`].
549    pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
550        Ok(self
551            .outputs(unit)?
552            .iter()
553            .filter(|o| o.flavor == FileFlavor::Sbom)
554            .map(|o| o.path.clone())
555            .collect())
556    }
557
558    pub fn is_primary_package(&self, unit: &Unit) -> bool {
559        self.primary_packages.contains(&unit.pkg.package_id())
560    }
561
562    /// Returns a [`UnitOutput`] which represents some information about the
563    /// output of a unit.
564    pub fn unit_output(&self, unit: &Unit, path: &Path) -> CargoResult<UnitOutput> {
565        let script_metas = self.find_build_script_metadatas(unit);
566        let env = artifact::get_env(&self, unit, self.unit_deps(unit))?;
567        Ok(UnitOutput {
568            unit: unit.clone(),
569            path: path.to_path_buf(),
570            script_metas,
571            env,
572        })
573    }
574
575    /// Check if any output file name collision happens.
576    /// See <https://github.com/rust-lang/cargo/issues/6313> for more.
577    #[tracing::instrument(skip_all)]
578    fn check_collisions(&self) -> CargoResult<()> {
579        let mut output_collisions = HashMap::new();
580        let describe_collision = |unit: &Unit, other_unit: &Unit| -> String {
581            format!(
582                "the {} target `{}` in package `{}` has the same output filename as the {} target `{}` in package `{}`",
583                unit.target.kind().description(),
584                unit.target.name(),
585                unit.pkg.package_id(),
586                other_unit.target.kind().description(),
587                other_unit.target.name(),
588                other_unit.pkg.package_id(),
589            )
590        };
591        let suggestion = [
592            Level::NOTE.message("this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>"),
593            Level::HELP.message("consider changing their names to be unique or compiling them separately")
594        ];
595        let rustdoc_suggestion = [
596            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>")
597        ];
598        let report_collision = |unit: &Unit,
599                                other_unit: &Unit,
600                                path: &PathBuf,
601                                messages: &[Message<'_>]|
602         -> CargoResult<()> {
603            if unit.target.name() == other_unit.target.name() {
604                self.bcx.gctx.shell().print_report(
605                    &[Level::WARNING
606                        .secondary_title(format!("output filename collision at {}", path.display()))
607                        .elements(
608                            [Level::NOTE.message(describe_collision(unit, other_unit))]
609                                .into_iter()
610                                .chain(messages.iter().cloned()),
611                        )],
612                    false,
613                )
614            } else {
615                self.bcx.gctx.shell().print_report(
616                    &[Level::WARNING
617                        .secondary_title(format!("output filename collision at {}", path.display()))
618                        .elements([
619                            Level::NOTE.message(describe_collision(unit, other_unit)),
620                            Level::NOTE.message("if this looks unexpected, it may be a bug in Cargo. Please file a bug \
621                                report at https://github.com/rust-lang/cargo/issues/ with as much information as you \
622                                can provide."),
623                            Level::NOTE.message(format!("cargo {} running on `{}` target `{}`",
624                                crate::version(), self.bcx.host_triple(), self.bcx.target_data.short_name(&unit.kind))),
625                            Level::NOTE.message(format!("first unit: {unit:?}")),
626                            Level::NOTE.message(format!("second unit: {other_unit:?}")),
627                        ])],
628                    false,
629                )
630            }
631        };
632
633        fn doc_collision_error(unit: &Unit, other_unit: &Unit) -> CargoResult<()> {
634            bail!(
635                "document output filename collision\n\
636                 The {} `{}` in package `{}` has the same name as the {} `{}` in package `{}`.\n\
637                 Only one may be documented at once since they output to the same path.\n\
638                 Consider documenting only one, renaming one, \
639                 or marking one with `doc = false` in Cargo.toml.",
640                unit.target.kind().description(),
641                unit.target.name(),
642                unit.pkg,
643                other_unit.target.kind().description(),
644                other_unit.target.name(),
645                other_unit.pkg,
646            );
647        }
648
649        let mut keys = self
650            .bcx
651            .unit_graph
652            .keys()
653            .filter(|unit| !unit.mode.is_run_custom_build())
654            .collect::<Vec<_>>();
655        // Sort for consistent error messages.
656        keys.sort_unstable();
657        // These are kept separate to retain compatibility with older
658        // versions, which generated an error when there was a duplicate lib
659        // or bin (but the old code did not check bin<->lib collisions). To
660        // retain backwards compatibility, this only generates an error for
661        // duplicate libs or duplicate bins (but not both). Ideally this
662        // shouldn't be here, but since there isn't a complete workaround,
663        // yet, this retains the old behavior.
664        let mut doc_libs = HashMap::new();
665        let mut doc_bins = HashMap::new();
666        for unit in keys {
667            if unit.mode.is_doc() && self.is_primary_package(unit) {
668                // These situations have been an error since before 1.0, so it
669                // is not a warning like the other situations.
670                if unit.target.is_lib() {
671                    if let Some(prev) = doc_libs.insert((unit.target.crate_name(), unit.kind), unit)
672                    {
673                        doc_collision_error(unit, prev)?;
674                    }
675                } else if let Some(prev) =
676                    doc_bins.insert((unit.target.crate_name(), unit.kind), unit)
677                {
678                    doc_collision_error(unit, prev)?;
679                }
680            }
681            for output in self.outputs(unit)?.iter() {
682                if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) {
683                    if unit.mode.is_doc() {
684                        // See https://github.com/rust-lang/rust/issues/56169
685                        // and https://github.com/rust-lang/rust/issues/61378
686                        report_collision(unit, other_unit, &output.path, &rustdoc_suggestion)?;
687                    } else {
688                        report_collision(unit, other_unit, &output.path, &suggestion)?;
689                    }
690                }
691                if let Some(hardlink) = output.hardlink.as_ref() {
692                    if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit) {
693                        report_collision(unit, other_unit, hardlink, &suggestion)?;
694                    }
695                }
696                if let Some(ref export_path) = output.export_path {
697                    if let Some(other_unit) = output_collisions.insert(export_path.clone(), unit) {
698                        self.bcx.gctx.shell().print_report(
699                            &[Level::WARNING
700                                .secondary_title(format!(
701                                    "`--artifact-dir` filename collision at {}",
702                                    export_path.display()
703                                ))
704                                .elements(
705                                    [Level::NOTE.message(describe_collision(unit, other_unit))]
706                                        .into_iter()
707                                        .chain(suggestion.iter().cloned()),
708                                )],
709                            false,
710                        )?;
711                    }
712                }
713            }
714        }
715        Ok(())
716    }
717
718    /// Records the list of units which are required to emit metadata.
719    ///
720    /// Units which depend only on the metadata of others requires the others to
721    /// actually produce metadata, so we'll record that here.
722    fn record_units_requiring_metadata(&mut self) {
723        for (key, deps) in self.bcx.unit_graph.iter() {
724            for dep in deps {
725                if self.only_requires_rmeta(key, &dep.unit) {
726                    self.rmeta_required.insert(dep.unit.clone());
727                }
728            }
729        }
730    }
731
732    /// Returns whether when `parent` depends on `dep` if it only requires the
733    /// metadata file from `dep`.
734    pub fn only_requires_rmeta(&self, parent: &Unit, dep: &Unit) -> bool {
735        // We're only a candidate for requiring an `rmeta` file if we
736        // ourselves are building an rlib,
737        !parent.requires_upstream_objects()
738            && parent.mode == CompileMode::Build
739            // Our dependency must also be built as an rlib, otherwise the
740            // object code must be useful in some fashion
741            && !dep.requires_upstream_objects()
742            && dep.mode == CompileMode::Build
743    }
744
745    /// Returns whether when `unit` is built whether it should emit metadata as
746    /// well because some compilations rely on that.
747    pub fn rmeta_required(&self, unit: &Unit) -> bool {
748        self.rmeta_required.contains(unit)
749    }
750
751    /// Finds metadata for Doc/Docscrape units.
752    ///
753    /// rustdoc needs a -Cmetadata flag in order to recognize StableCrateIds that refer to
754    /// items in the crate being documented. The -Cmetadata flag used by reverse-dependencies
755    /// will be the metadata of the Cargo unit that generated the current library's rmeta file,
756    /// which should be a Check unit.
757    ///
758    /// If the current crate has reverse-dependencies, such a Check unit should exist, and so
759    /// we use that crate's metadata. If not, we use the crate's Doc unit so at least examples
760    /// scraped from the current crate can be used when documenting the current crate.
761    #[tracing::instrument(skip_all)]
762    pub fn compute_metadata_for_doc_units(&mut self) {
763        for unit in self.bcx.unit_graph.keys() {
764            if !unit.mode.is_doc() && !unit.mode.is_doc_scrape() {
765                continue;
766            }
767
768            let matching_units = self
769                .bcx
770                .unit_graph
771                .keys()
772                .filter(|other| {
773                    unit.pkg == other.pkg
774                        && unit.target == other.target
775                        && !other.mode.is_doc_scrape()
776                })
777                .collect::<Vec<_>>();
778            let metadata_unit = matching_units
779                .iter()
780                .find(|other| other.mode.is_check())
781                .or_else(|| matching_units.iter().find(|other| other.mode.is_doc()))
782                .unwrap_or(&unit);
783            self.metadata_for_doc_units
784                .insert(unit.clone(), self.files().metadata(metadata_unit));
785        }
786    }
787}