#
cargo/core/compiler/
output_depinfo.rs

    1//! dep-info files for external build system integration.
    2//! See [`output_depinfo`] for more.
    3
    4use cargo_util::paths::normalize_path;
    5use std::collections::{BTreeSet, HashSet};
    6use std::io::{BufWriter, Write};
    7use std::path::{Path, PathBuf};
    8
    9use super::{BuildRunner, FileFlavor, Unit, fingerprint};
    10use crate::util::{CargoResult, internal};
    11use cargo_util::paths;
    12use tracing::debug;
    13
    14/// Basically just normalizes a given path and converts it to a string.
    15fn render_filename<P: AsRef<Path>>(path: P, basedir: Option<&str>) -> CargoResult<String> {
    16    fn wrap_path(path: &Path) -> CargoResult<String> {
    17        path.to_str()
    18            .ok_or_else(|| internal(format!("path `{:?}` not utf-8", path)))
    19            .map(|f| f.replace(" ", "\\ "))
    20    }
    21
    22    let path = path.as_ref();
    23    if let Some(basedir) = basedir {
    24        let norm_path = normalize_path(path);
    25        let norm_basedir = normalize_path(basedir.as_ref());
    26        match norm_path.strip_prefix(norm_basedir) {
    27            Ok(relpath) => wrap_path(relpath),
    28            _ => wrap_path(path),
    29        }
    30    } else {
    31        wrap_path(path)
    32    }
    33}
    34
    35/// Collects all dependencies of the `unit` for the output dep info file.
    36///
    37/// Dependencies will be stored in `deps`, including:
    38///
    39/// * dependencies from [fingerprint dep-info]
    40/// * paths from `rerun-if-changed` build script instruction
    41/// * ...and traverse transitive dependencies recursively
    42///
    43/// [fingerprint dep-info]: super::fingerprint#fingerprint-dep-info-files
    44fn add_deps_for_unit(
    45    deps: &mut BTreeSet<PathBuf>,
    46    build_runner: &mut BuildRunner<'_, '_>,
    47    unit: &Unit,
    48    visited: &mut HashSet<Unit>,
    49) -> CargoResult<()> {
    50    if !visited.insert(unit.clone()) {
    51        return Ok(());
    52    }
    53
    54    // units representing the execution of a build script don't actually
    55    // generate a dep info file, so we just keep on going below
    56    if !unit.mode.is_run_custom_build() {
    57        // Add dependencies from rustc dep-info output (stored in fingerprint directory)
    58        let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
    59        if let Some(paths) = fingerprint::parse_dep_info(
    60            unit.pkg.root(),
    61            build_runner.files().host_build_root(),
    62            &dep_info_loc,
    63        )? {
    64            for path in paths.files.into_keys() {
    65                deps.insert(path);
    66            }
    67        } else {
    68            debug!(
    69                "can't find dep_info for {:?} {}",
    70                unit.pkg.package_id(),
    71                unit.target
    72            );
    73            return Err(internal("dep_info missing"));
    74        }
    75    }
    76
    77    // Add rerun-if-changed dependencies
    78    if let Some(metadata_vec) = build_runner.find_build_script_metadatas(unit) {
    79        for metadata in metadata_vec {
    80            if let Some(output) = build_runner
    81                .build_script_outputs
    82                .lock()
    83                .unwrap()
    84                .get(metadata)
    85            {
    86                for path in &output.rerun_if_changed {
    87                    // The paths we have saved from the unit are of arbitrary relativeness and may be
    88                    // relative to the crate root of the dependency.
    89                    let path = unit.pkg.root().join(path);
    90                    deps.insert(path);
    91                }
    92            }
    93        }
    94    }
    95
    96    // Recursively traverse all transitive dependencies
    97    let unit_deps = Vec::from(build_runner.unit_deps(unit)); // Create vec due to mutable borrow.
    98    for dep in unit_deps {
    99        if dep.unit.is_local() {
    100            add_deps_for_unit(deps, build_runner, &dep.unit, visited)?;
    101        }
    102    }
    103    Ok(())
    104}
    105
    106/// Save a `.d` dep-info file for the given unit. This is the third kind of
    107/// dep-info mentioned in [`fingerprint`] module.
    108///
    109/// Argument `unit` is expected to be the root unit, which will be uplifted.
    110///
    111/// Cargo emits its own dep-info files in the output directory. This is
    112/// only done for every "uplifted" artifact. These are intended to be used
    113/// with external build systems so that they can detect if Cargo needs to be
    114/// re-executed.
    115///
    116/// It includes all the entries from the `rustc` dep-info file, and extends it
    117/// with any `rerun-if-changed` entries from build scripts. It also includes
    118/// sources from any path dependencies. Registry dependencies are not included
    119/// under the assumption that changes to them can be detected via changes to
    120/// `Cargo.lock`.
    121///
    122/// [`fingerprint`]: super::fingerprint#dep-info-files
    123pub fn output_depinfo(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<()> {
    124    let bcx = build_runner.bcx;
    125    let mut deps = BTreeSet::new();
    126    let mut visited = HashSet::new();
    127    let success = add_deps_for_unit(&mut deps, build_runner, unit, &mut visited).is_ok();
    128    let basedir_string;
    129    let basedir = match bcx.gctx.build_config()?.dep_info_basedir.clone() {
    130        Some(value) => {
    131            basedir_string = value
    132                .resolve_path(bcx.gctx)
    133                .as_os_str()
    134                .to_str()
    135                .ok_or_else(|| anyhow::format_err!("build.dep-info-basedir path not utf-8"))?
    136                .to_string();
    137            Some(basedir_string.as_str())
    138        }
    139        None => None,
    140    };
    141    let deps = deps
    142        .iter()
    143        .map(|f| render_filename(f, basedir))
    144        .collect::<CargoResult<Vec<_>>>()?;
    145
    146    for output in build_runner.outputs(unit)?.iter().filter(|o| {
    147        !matches!(
    148            o.flavor,
    149            FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
    150        )
    151    }) {
    152        if let Some(ref link_dst) = output.hardlink {
    153            let output_path = link_dst.with_extension("d");
    154            if success {
    155                let target_fn = render_filename(link_dst, basedir)?;
    156
    157                // If nothing changed don't recreate the file which could alter
    158                // its mtime
    159                if let Ok(previous) = fingerprint::parse_rustc_dep_info(&output_path) {
    160                    if previous
    161                        .files
    162                        .iter()
    163                        .map(|(path, _checksum)| path)
    164                        .eq(deps.iter().map(Path::new))
    165                    {
    166                        continue;
    167                    }
    168                }
    169
    170                // Otherwise write it all out
    171                let mut outfile = BufWriter::new(paths::create(output_path)?);
    172                write!(outfile, "{}:", target_fn)?;
    173                for dep in &deps {
    174                    write!(outfile, " {}", dep)?;
    175                }
    176                writeln!(outfile)?;
    177
    178            // dep-info generation failed, so delete output file. This will
    179            // usually cause the build system to always rerun the build
    180            // rule, which is correct if inefficient.
    181            } else if output_path.exists() {
    182                paths::remove_file(output_path)?;
    183            }
    184        }
    185    }
    186    Ok(())
    187}