Skip to main content

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