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