Skip to main content

cargo/compiler/
output_depinfo.rs

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