blob: 2caec54c43c40eaf28e9d361f62054d9267cd95b [file] [edit]
//! [`BuildRunner`] is the mutable state used during the build process.
use crate::util::data_structures::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::compiler::compilation::{self, UnitOutput};
use crate::compiler::locking::LockManager;
use crate::compiler::{self, Unit, UserIntent, artifact};
use crate::util::cache_lock::CacheLockMode;
use crate::util::errors::CargoResult;
use crate::workspace::PackageId;
use anyhow::{Context as _, bail};
use cargo_util::paths;
use cargo_util_terminal::report::{Level, Message};
use filetime::FileTime;
use itertools::Itertools;
use jobserver::Client;
use super::RustdocFingerprint;
use super::custom_build::{self, BuildDeps, BuildScriptOutputs, BuildScripts};
use super::fingerprint::{Checksum, Fingerprint};
use super::job_queue::JobQueue;
use super::layout::Layout;
use super::lto::Lto;
use super::unit_graph::UnitDep;
use super::unused_deps::UnusedDepState;
use super::{BuildContext, Compilation, CompileKind, CompileMode, Executor, FileFlavor};
mod compilation_files;
use self::compilation_files::CompilationFiles;
pub use self::compilation_files::{Metadata, OutputFile, UnitHash};
/// Collection of all the stuff that is needed to perform a build.
///
/// Different from the [`BuildContext`], `Context` is a _mutable_ state used
/// throughout the entire build process. Everything is coordinated through this.
///
/// [`BuildContext`]: crate::compiler::BuildContext
pub struct BuildRunner<'a, 'gctx> {
/// Mostly static information about the build task.
pub bcx: &'a BuildContext<'a, 'gctx>,
/// A large collection of information about the result of the entire compilation.
pub compilation: Compilation<'gctx>,
/// Output from build scripts, updated after each build script runs.
pub build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
/// Dependencies (like rerun-if-changed) declared by a build script.
/// This is *only* populated from the output from previous runs.
/// If the build script hasn't ever been run, then it must be run.
pub build_explicit_deps: HashMap<Unit, BuildDeps>,
/// Fingerprints used to detect if a unit is out-of-date.
pub fingerprints: HashMap<Unit, Arc<Fingerprint>>,
/// Cache of file mtimes to reduce filesystem hits.
pub mtime_cache: HashMap<PathBuf, FileTime>,
/// Cache of file checksums to reduce filesystem reads.
pub checksum_cache: HashMap<PathBuf, Checksum>,
/// A set used to track which units have been compiled.
/// A unit may appear in the job graph multiple times as a dependency of
/// multiple packages, but it only needs to run once.
pub compiled: HashSet<Unit>,
/// Linking information for each `Unit`.
/// See `build_map` for details.
pub build_scripts: HashMap<Unit, Arc<BuildScripts>>,
/// Job server client to manage concurrency with other processes.
pub jobserver: Client,
/// "Primary" packages are the ones the user selected on the command-line
/// with `-p` flags. If no flags are specified, then it is the defaults
/// based on the current directory and the default workspace members.
primary_packages: HashSet<PackageId>,
/// An abstraction of the files and directories that will be generated by
/// the compilation. This is `None` until after `unit_dependencies` has
/// been computed.
files: Option<CompilationFiles<'a, 'gctx>>,
/// A set of units which are compiling rlibs and are expected to produce
/// metadata files in addition to the rlib itself.
rmeta_required: HashSet<Unit>,
/// Map of the LTO-status of each unit. This indicates what sort of
/// compilation is happening (only object, only bitcode, both, etc), and is
/// precalculated early on.
pub lto: HashMap<Unit, Lto>,
/// Map of Doc/Docscrape units to metadata for their -Cmetadata flag.
/// See `Context::find_metadata_units` for more details.
pub metadata_for_doc_units: HashMap<Unit, Metadata>,
/// Set of metadata of Docscrape units that fail before completion, e.g.
/// because the target has a type error. This is in an Arc<Mutex<..>>
/// because it is continuously updated as the job progresses.
pub failed_scrape_units: Arc<Mutex<HashSet<UnitHash>>>,
pub unused_dep_state: UnusedDepState,
/// Manages locks for build units when fine grain locking is enabled.
pub lock_manager: Arc<LockManager>,
}
impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
pub fn new(bcx: &'a BuildContext<'a, 'gctx>) -> CargoResult<Self> {
// Load up the jobserver that we'll use to manage our parallelism. This
// is the same as the GNU make implementation of a jobserver, and
// intentionally so! It's hoped that we can interact with GNU make and
// all share the same jobserver.
//
// Note that if we don't have a jobserver in our environment then we
// create our own, and we create it with `n` tokens, but immediately
// acquire one, because one token is ourself, a running process.
let jobserver = match bcx.gctx.jobserver_from_env() {
Some(c) => c.clone(),
None => {
let client =
Client::new(bcx.jobs() as usize).context("failed to create jobserver")?;
client.acquire_raw()?;
client
}
};
Ok(Self {
bcx,
compilation: Compilation::new(bcx)?,
build_script_outputs: Arc::new(Mutex::new(BuildScriptOutputs::default())),
fingerprints: HashMap::default(),
mtime_cache: HashMap::default(),
checksum_cache: HashMap::default(),
compiled: HashSet::default(),
build_scripts: HashMap::default(),
build_explicit_deps: HashMap::default(),
jobserver,
primary_packages: HashSet::default(),
files: None,
rmeta_required: HashSet::default(),
lto: HashMap::default(),
metadata_for_doc_units: HashMap::default(),
failed_scrape_units: Arc::new(Mutex::new(HashSet::default())),
unused_dep_state: UnusedDepState::new(bcx),
lock_manager: Arc::new(LockManager::new()),
})
}
/// Dry-run the compilation without actually running it.
///
/// This is expected to collect information like the location of output artifacts.
/// Please keep in sync with non-compilation part in [`BuildRunner::compile`].
pub fn dry_run(mut self) -> CargoResult<Compilation<'gctx>> {
let _lock = self
.bcx
.gctx
.acquire_package_cache_lock(CacheLockMode::Shared)?;
self.lto = super::lto::generate(self.bcx)?;
self.prepare_units()?;
self.prepare()?;
self.check_collisions()?;
for unit in &self.bcx.roots {
self.collect_tests_and_executables(unit)?;
}
Ok(self.compilation)
}
/// Starts compilation, waits for it to finish, and returns information
/// about the result of compilation.
///
/// See [`ops::cargo_compile`] for a higher-level view of the compile process.
///
/// [`ops::cargo_compile`]: crate::ops::cargo_compile
#[tracing::instrument(skip_all)]
pub fn compile(mut self, exec: &Arc<dyn Executor>) -> CargoResult<Compilation<'gctx>> {
// A shared lock is held during the duration of the build since rustc
// needs to read from the `src` cache, and we don't want other
// commands modifying the `src` cache while it is running.
let _lock = self
.bcx
.gctx
.acquire_package_cache_lock(CacheLockMode::Shared)?;
let mut queue = JobQueue::new(self.bcx);
self.lto = super::lto::generate(self.bcx)?;
self.prepare_units()?;
self.prepare()?;
custom_build::build_map(&mut self)?;
self.check_collisions()?;
self.compute_metadata_for_doc_units();
// We need to make sure that if there were any previous docs already compiled,
// they were compiled with the same Rustc version that we're currently using.
// See the function doc comment for more.
if self.bcx.build_config.intent.is_doc() {
RustdocFingerprint::check_rustdoc_fingerprint(&self)?
}
for unit in &self.bcx.roots {
let force_rebuild = self.bcx.build_config.force_rebuild;
super::compile(&mut self, &mut queue, unit, exec, force_rebuild)?;
}
// Now that we've got the full job queue and we've done all our
// fingerprint analysis to determine what to run, bust all the memoized
// fingerprint hashes to ensure that during the build they all get the
// most up-to-date values. In theory we only need to bust hashes that
// transitively depend on a dirty build script, but it shouldn't matter
// that much for performance anyway.
for fingerprint in self.fingerprints.values() {
fingerprint.clear_memoized();
}
// Now that we've figured out everything that we're going to do, do it!
queue.execute(&mut self)?;
// Add `OUT_DIR` to env vars if unit has a build script.
let units_with_build_script = &self
.bcx
.roots
.iter()
.filter(|unit| self.build_scripts.contains_key(unit))
.dedup_by(|x, y| x.pkg.package_id() == y.pkg.package_id())
.collect::<Vec<_>>();
for unit in units_with_build_script {
for dep in &self.bcx.unit_graph[unit] {
if dep.unit.mode.is_run_custom_build() {
let out_dir = if self.bcx.gctx.cli_unstable().build_dir_new_layout {
self.files().out_dir_new_layout(&dep.unit)
} else {
self.files().build_script_out_dir(&dep.unit)
};
let script_meta = self.get_run_build_script_metadata(&dep.unit);
self.compilation
.extra_env
.entry(script_meta)
.or_insert_with(Vec::new)
.push(("OUT_DIR".to_string(), out_dir.display().to_string()));
}
}
}
self.collect_doc_merge_info()?;
// Collect the result of the build into `self.compilation`.
for unit in &self.bcx.roots {
self.collect_tests_and_executables(unit)?;
// Collect information for `rustdoc --test`.
if unit.mode.is_doc_test() {
let mut unstable_opts = false;
let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
args.extend(compiler::lib_search_paths(&self, unit)?);
args.extend(compiler::lto_args(&self, unit));
args.extend(compiler::features_args(unit));
args.extend(compiler::check_cfg_args(unit));
let script_metas = self.find_build_script_metadatas(unit);
if let Some(meta_vec) = script_metas.clone() {
for meta in meta_vec {
if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
for cfg in &output.cfgs {
args.push("--cfg".into());
args.push(cfg.into());
}
for check_cfg in &output.check_cfgs {
args.push("--check-cfg".into());
args.push(check_cfg.into());
}
for (lt, arg) in &output.linker_args {
if lt.applies_to(&unit.target, unit.mode) {
args.push("-C".into());
args.push(format!("link-arg={}", arg).into());
}
}
}
}
}
args.extend(unit.rustdocflags.iter().map(Into::into));
use super::MessageFormat;
let format = match self.bcx.build_config.message_format {
MessageFormat::Short => "short",
MessageFormat::Human => "human",
MessageFormat::Json { .. } => "json",
};
args.push("--error-format".into());
args.push(format.into());
self.compilation.to_doc_test.push(compilation::Doctest {
unit: unit.clone(),
args,
unstable_opts,
linker: self
.compilation
.target_linker(unit.kind)
.map(|p| p.to_path_buf()),
script_metas,
env: artifact::get_env(&self, unit, self.unit_deps(unit))?,
});
}
super::output_depinfo(&mut self, unit)?;
}
for (script_meta, output) in self.build_script_outputs.lock().unwrap().iter() {
self.compilation
.extra_env
.entry(*script_meta)
.or_insert_with(Vec::new)
.extend(output.env.iter().cloned());
for dir in output.library_paths.iter() {
self.compilation
.native_dirs
.insert(dir.clone().into_path_buf());
}
}
Ok(self.compilation)
}
fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> {
for output in self.outputs(unit)?.iter() {
if matches!(
output.flavor,
FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
) {
continue;
}
let bindst = output.bin_dst();
if unit.mode == CompileMode::Test {
self.compilation
.tests
.push(self.unit_output(unit, &output.path)?);
} else if unit.target.is_executable() {
self.compilation
.binaries
.push(self.unit_output(unit, bindst)?);
} else if unit.target.is_cdylib()
&& !self.compilation.cdylibs.iter().any(|uo| uo.unit == *unit)
{
self.compilation
.cdylibs
.push(self.unit_output(unit, bindst)?);
}
}
Ok(())
}
fn collect_doc_merge_info(&mut self) -> CargoResult<()> {
if !self.bcx.gctx.cli_unstable().rustdoc_mergeable_info {
return Ok(());
}
if !self.bcx.build_config.intent.is_doc() {
return Ok(());
}
if self.bcx.build_config.intent.wants_doc_json_output() {
// rustdoc JSON output doesn't support merge (yet?)
return Ok(());
}
let mut doc_parts_map: HashMap<_, Vec<_>> = HashMap::default();
let unit_iter = if self.bcx.build_config.intent.wants_deps_docs() {
itertools::Either::Left(self.bcx.unit_graph.keys())
} else {
itertools::Either::Right(self.bcx.roots.iter())
};
for unit in unit_iter {
if !unit.mode.is_doc() {
continue;
}
// Assumption: one `rustdoc` call generates only one cross-crate info JSON.
let outputs = self.outputs(unit)?;
let Some(doc_parts) = outputs
.iter()
.find(|o| matches!(o.flavor, FileFlavor::DocParts))
else {
continue;
};
doc_parts_map
.entry(unit.kind)
.or_default()
.push(doc_parts.path.to_owned());
}
self.compilation.rustdoc_fingerprints = Some(
doc_parts_map
.into_iter()
.map(|(kind, doc_parts)| (kind, RustdocFingerprint::new(self, kind, doc_parts)))
.collect(),
);
Ok(())
}
/// Returns the executable for the specified unit (if any).
pub fn get_executable(&mut self, unit: &Unit) -> CargoResult<Option<PathBuf>> {
let is_binary = unit.target.is_executable();
let is_test = unit.mode.is_any_test();
if !unit.mode.generates_executable() || !(is_binary || is_test) {
return Ok(None);
}
Ok(self
.outputs(unit)?
.iter()
.find(|o| o.flavor == FileFlavor::Normal)
.map(|output| output.bin_dst().clone()))
}
#[tracing::instrument(skip_all)]
pub fn prepare_units(&mut self) -> CargoResult<()> {
let dest = self.bcx.profiles.get_dir_name();
// We try to only lock the artifact-dir if we need to.
// For example, `cargo check` does not write any files to the artifact-dir so we don't need
// to lock it.
let must_take_artifact_dir_lock = match self.bcx.build_config.intent {
UserIntent::Check { .. } => {
// Generally cargo check does not need to take the artifact-dir lock but there is
// one exception: If check has `--timings` we still need to lock artifact-dir since
// we will output the report files.
self.bcx.build_config.timing_report
}
UserIntent::Build
| UserIntent::Test
| UserIntent::Doc { .. }
| UserIntent::Doctest
| UserIntent::Bench => true,
};
let host_layout =
Layout::new(self.bcx.ws, None, &dest, must_take_artifact_dir_lock, false)?;
let mut targets = HashMap::default();
for kind in self.bcx.all_kinds.iter() {
if let CompileKind::Target(target) = *kind {
let layout = Layout::new(
self.bcx.ws,
Some(target),
&dest,
must_take_artifact_dir_lock,
false,
)?;
targets.insert(target, layout);
}
}
self.primary_packages
.extend(self.bcx.roots.iter().map(|u| u.pkg.package_id()));
self.compilation
.root_crate_names
.extend(self.bcx.roots.iter().map(|u| u.target.crate_name()));
self.record_units_requiring_metadata();
let files = CompilationFiles::new(self, host_layout, targets);
self.files = Some(files);
Ok(())
}
/// Prepare this context, ensuring that all filesystem directories are in
/// place.
#[tracing::instrument(skip_all)]
pub fn prepare(&mut self) -> CargoResult<()> {
self.files
.as_mut()
.unwrap()
.host
.prepare()
.context("couldn't prepare build directories")?;
for target in self.files.as_mut().unwrap().target.values_mut() {
target
.prepare()
.context("couldn't prepare build directories")?;
}
let files = self.files.as_ref().unwrap();
for &kind in self.bcx.all_kinds.iter() {
let layout = files.layout(kind);
if let Some(artifact_dir) = layout.artifact_dir() {
self.compilation
.root_output
.insert(kind, artifact_dir.dest().to_path_buf());
}
if self.bcx.gctx.cli_unstable().build_dir_new_layout {
for (unit, _) in self.bcx.unit_graph.iter() {
if kind != unit.kind {
continue;
}
let dep_dir = self.files().deps_dir(unit);
paths::create_dir_all(&dep_dir)?;
if unit.target.is_dylib() {
self.compilation
.deps_output
.entry(kind)
.or_default()
.insert(dep_dir);
}
}
} else {
self.compilation
.deps_output
.entry(kind)
.or_default()
.insert(layout.build_dir().legacy_deps().to_path_buf());
}
}
Ok(())
}
pub fn files(&self) -> &CompilationFiles<'a, 'gctx> {
self.files.as_ref().unwrap()
}
/// Returns the filenames that the given unit will generate.
pub fn outputs(&self, unit: &Unit) -> CargoResult<Arc<Vec<OutputFile>>> {
self.files.as_ref().unwrap().outputs(unit, self.bcx)
}
/// Direct dependencies for the given unit.
pub fn unit_deps(&self, unit: &Unit) -> &[UnitDep] {
&self.bcx.unit_graph[unit]
}
/// Returns the `RunCustomBuild` Units associated with the given Unit.
///
/// If the package does not have a build script, this returns None.
pub fn find_build_script_units(&self, unit: &Unit) -> Option<Vec<Unit>> {
if unit.mode.is_run_custom_build() {
return Some(vec![unit.clone()]);
}
let build_script_units: Vec<Unit> = self.bcx.unit_graph[unit]
.iter()
.filter(|unit_dep| {
unit_dep.unit.mode.is_run_custom_build()
&& unit_dep.unit.pkg.package_id() == unit.pkg.package_id()
})
.map(|unit_dep| unit_dep.unit.clone())
.collect();
if build_script_units.is_empty() {
None
} else {
Some(build_script_units)
}
}
/// Returns the metadata hash for the `RunCustomBuild` Unit associated with
/// the given unit.
///
/// If the package does not have a build script, this returns None.
pub fn find_build_script_metadatas(&self, unit: &Unit) -> Option<Vec<UnitHash>> {
self.find_build_script_units(unit).map(|units| {
units
.iter()
.map(|u| self.get_run_build_script_metadata(u))
.collect()
})
}
/// Returns the metadata hash for a `RunCustomBuild` unit.
pub fn get_run_build_script_metadata(&self, unit: &Unit) -> UnitHash {
assert!(unit.mode.is_run_custom_build());
self.files().metadata(unit).unit_id()
}
/// Returns the list of SBOM output file paths for a given [`Unit`].
pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
Ok(self
.outputs(unit)?
.iter()
.filter(|o| o.flavor == FileFlavor::Sbom)
.map(|o| o.path.clone())
.collect())
}
pub fn is_primary_package(&self, unit: &Unit) -> bool {
self.primary_packages.contains(&unit.pkg.package_id())
}
/// Returns a [`UnitOutput`] which represents some information about the
/// output of a unit.
pub fn unit_output(&self, unit: &Unit, path: &Path) -> CargoResult<UnitOutput> {
let script_metas = self.find_build_script_metadatas(unit);
let env = artifact::get_env(&self, unit, self.unit_deps(unit))?;
Ok(UnitOutput {
unit: unit.clone(),
path: path.to_path_buf(),
script_metas,
env,
})
}
/// Check if any output file name collision happens.
/// See <https://github.com/rust-lang/cargo/issues/6313> for more.
#[tracing::instrument(skip_all)]
fn check_collisions(&self) -> CargoResult<()> {
let mut output_collisions = HashMap::default();
let describe_collision = |unit: &Unit, other_unit: &Unit| -> String {
format!(
"the {} target `{}` in package `{}` has the same output filename as the {} target `{}` in package `{}`",
unit.target.kind().description(),
unit.target.name(),
unit.pkg.package_id(),
other_unit.target.kind().description(),
other_unit.target.name(),
other_unit.pkg.package_id(),
)
};
let suggestion = [
Level::NOTE.message("this may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>"),
Level::HELP.message("consider changing their names to be unique or compiling them separately")
];
let rustdoc_suggestion = [
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>")
];
let report_collision = |unit: &Unit,
other_unit: &Unit,
path: &PathBuf,
messages: &[Message<'_>]|
-> CargoResult<()> {
if unit.target.name() == other_unit.target.name() {
self.bcx.gctx.shell().print_report(
&[Level::WARNING
.secondary_title(format!("output filename collision at {}", path.display()))
.elements(
[Level::NOTE.message(describe_collision(unit, other_unit))]
.into_iter()
.chain(messages.iter().cloned()),
)],
false,
)
} else {
self.bcx.gctx.shell().print_report(
&[Level::WARNING
.secondary_title(format!("output filename collision at {}", path.display()))
.elements([
Level::NOTE.message(describe_collision(unit, other_unit)),
Level::NOTE.message("if this looks unexpected, it may be a bug in Cargo. Please file a bug \
report at https://github.com/rust-lang/cargo/issues/ with as much information as you \
can provide."),
Level::NOTE.message(format!("cargo {} running on `{}` target `{}`",
crate::version(), self.bcx.host_triple(), self.bcx.target_data.short_name(&unit.kind))),
Level::NOTE.message(format!("first unit: {unit:?}")),
Level::NOTE.message(format!("second unit: {other_unit:?}")),
])],
false,
)
}
};
fn doc_collision_error(unit: &Unit, other_unit: &Unit) -> CargoResult<()> {
bail!(
"document output filename collision\n\
The {} `{}` in package `{}` has the same name as the {} `{}` in package `{}`.\n\
Only one may be documented at once since they output to the same path.\n\
Consider documenting only one, renaming one, \
or marking one with `doc = false` in Cargo.toml.",
unit.target.kind().description(),
unit.target.name(),
unit.pkg,
other_unit.target.kind().description(),
other_unit.target.name(),
other_unit.pkg,
);
}
let mut keys = self
.bcx
.unit_graph
.keys()
.filter(|unit| !unit.mode.is_run_custom_build())
.collect::<Vec<_>>();
// Sort for consistent error messages.
keys.sort_unstable();
// These are kept separate to retain compatibility with older
// versions, which generated an error when there was a duplicate lib
// or bin (but the old code did not check bin<->lib collisions). To
// retain backwards compatibility, this only generates an error for
// duplicate libs or duplicate bins (but not both). Ideally this
// shouldn't be here, but since there isn't a complete workaround,
// yet, this retains the old behavior.
let mut doc_libs = HashMap::default();
let mut doc_bins = HashMap::default();
for unit in keys {
if unit.mode.is_doc() && self.is_primary_package(unit) {
// These situations have been an error since before 1.0, so it
// is not a warning like the other situations.
if unit.target.is_lib() {
if let Some(prev) = doc_libs.insert((unit.target.crate_name(), unit.kind), unit)
{
doc_collision_error(unit, prev)?;
}
} else if let Some(prev) =
doc_bins.insert((unit.target.crate_name(), unit.kind), unit)
{
doc_collision_error(unit, prev)?;
}
}
for output in self.outputs(unit)?.iter() {
if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) {
if unit.mode.is_doc() {
// See https://github.com/rust-lang/rust/issues/56169
// and https://github.com/rust-lang/rust/issues/61378
report_collision(unit, other_unit, &output.path, &rustdoc_suggestion)?;
} else {
report_collision(unit, other_unit, &output.path, &suggestion)?;
}
}
if let Some(hardlink) = output.hardlink.as_ref() {
if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit) {
report_collision(unit, other_unit, hardlink, &suggestion)?;
}
}
if let Some(ref export_path) = output.export_path {
if let Some(other_unit) = output_collisions.insert(export_path.clone(), unit) {
self.bcx.gctx.shell().print_report(
&[Level::WARNING
.secondary_title(format!(
"`--artifact-dir` filename collision at {}",
export_path.display()
))
.elements(
[Level::NOTE.message(describe_collision(unit, other_unit))]
.into_iter()
.chain(suggestion.iter().cloned()),
)],
false,
)?;
}
}
}
}
Ok(())
}
/// Records the list of units which are required to emit metadata.
///
/// Units which depend only on the metadata of others requires the others to
/// actually produce metadata, so we'll record that here.
fn record_units_requiring_metadata(&mut self) {
for (key, deps) in self.bcx.unit_graph.iter() {
for dep in deps {
if self.only_requires_rmeta(key, &dep.unit) {
self.rmeta_required.insert(dep.unit.clone());
}
}
}
}
/// Returns whether when `parent` depends on `dep` if it only requires the
/// metadata file from `dep`.
pub fn only_requires_rmeta(&self, parent: &Unit, dep: &Unit) -> bool {
// We're only a candidate for requiring an `rmeta` file if we
// ourselves are building an rlib,
!parent.requires_upstream_objects()
&& parent.mode == CompileMode::Build
// Our dependency must also be built as an rlib, otherwise the
// object code must be useful in some fashion
&& !dep.requires_upstream_objects()
&& dep.mode == CompileMode::Build
}
/// Returns whether when `unit` is built whether it should emit metadata as
/// well because some compilations rely on that.
pub fn rmeta_required(&self, unit: &Unit) -> bool {
self.rmeta_required.contains(unit)
}
/// Finds metadata for Doc/Docscrape units.
///
/// rustdoc needs a -Cmetadata flag in order to recognize StableCrateIds that refer to
/// items in the crate being documented. The -Cmetadata flag used by reverse-dependencies
/// will be the metadata of the Cargo unit that generated the current library's rmeta file,
/// which should be a Check unit.
///
/// If the current crate has reverse-dependencies, such a Check unit should exist, and so
/// we use that crate's metadata. If not, we use the crate's Doc unit so at least examples
/// scraped from the current crate can be used when documenting the current crate.
#[tracing::instrument(skip_all)]
pub fn compute_metadata_for_doc_units(&mut self) {
for unit in self.bcx.unit_graph.keys() {
if !unit.mode.is_doc() && !unit.mode.is_doc_scrape() {
continue;
}
let matching_units = self
.bcx
.unit_graph
.keys()
.filter(|other| {
unit.pkg == other.pkg
&& unit.target == other.target
&& !other.mode.is_doc_scrape()
})
.collect::<Vec<_>>();
let metadata_unit = matching_units
.iter()
.find(|other| other.mode.is_check())
.or_else(|| matching_units.iter().find(|other| other.mode.is_doc()))
.unwrap_or(&unit);
self.metadata_for_doc_units
.insert(unit.clone(), self.files().metadata(metadata_unit));
}
}
}