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