Skip to content

Commit 7af0425

Browse files
committed
feat(fingerprint): integrate rustdoc dep-info files
This leverages the unstable `--emit=depinfo` option from rustdoc, so that rustdoc invocation rebuild can be better tracked without traversing the entire directory. Some design decisions: * Rustdoc's depinfo doesn't and shouldn't emit to `target/doc`, as the directory is considered part of the final artifact directory. In regard to that, we specify the dep-info output path to the fingerprint directory of rustdoc invocation. It looks like this `target/debug/.fingerprint/serde-12d29d32b3b8b38f/doc-lib-serde.d`. * We also start supporting `-Zchecksum-freshness` as a side effect. Could make it a separate PR if desired. * `-Zbinary-dep-depinfo` is not enabled along with this, since doc generations don't really require any binary dependencies.
1 parent 9ba555f commit 7af0425

File tree

3 files changed

+83
-11
lines changed

3 files changed

+83
-11
lines changed

src/cargo/core/compiler/fingerprint/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1494,7 +1494,9 @@ fn calculate_normal(
14941494

14951495
// Afterwards calculate our own fingerprint information.
14961496
let build_root = build_root(build_runner);
1497-
let local = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
1497+
let is_any_doc_gen = unit.mode.is_doc() || unit.mode.is_doc_scrape();
1498+
let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
1499+
let local = if is_any_doc_gen && !rustdoc_depinfo_enabled {
14981500
// rustdoc does not have dep-info files.
14991501
let fingerprint = pkg_fingerprint(build_runner.bcx, &unit.pkg).with_context(|| {
15001502
format!(

src/cargo/core/compiler/mod.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,20 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu
763763
add_error_format_and_color(build_runner, &mut rustdoc);
764764
add_allow_features(build_runner, &mut rustdoc);
765765

766+
// TODO: create a dedicated unstable flag
767+
if build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo {
768+
// invocation-specific is required for keeping the original rustdoc emission
769+
let mut arg = OsString::from("--emit=invocation-specific,dep-info=");
770+
arg.push(rustdoc_dep_info_loc(build_runner, unit));
771+
rustdoc.arg(arg);
772+
773+
if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
774+
rustdoc.arg("-Z").arg("checksum-hash-algorithm=blake3");
775+
}
776+
777+
rustdoc.arg("-Zunstable-options");
778+
}
779+
766780
if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
767781
trim_paths_args_rustdoc(&mut rustdoc, build_runner, unit, trim_paths)?;
768782
}
@@ -838,6 +852,20 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
838852
let package_id = unit.pkg.package_id();
839853
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
840854
let target = Target::clone(&unit.target);
855+
856+
let rustdoc_dep_info_loc = rustdoc_dep_info_loc(build_runner, unit);
857+
let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
858+
let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
859+
let pkg_root = unit.pkg.root().to_path_buf();
860+
let cwd = rustdoc
861+
.get_cwd()
862+
.unwrap_or_else(|| build_runner.bcx.gctx.cwd())
863+
.to_path_buf();
864+
let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
865+
let is_local = unit.is_local();
866+
let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
867+
let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
868+
841869
let mut output_options = OutputOptions::new(build_runner, unit);
842870
let script_metadata = build_runner.find_build_script_metadata(unit);
843871
let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) {
@@ -903,6 +931,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
903931
paths::remove_dir_all(crate_dir)?;
904932
}
905933
state.running(&rustdoc);
934+
let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
906935

907936
let result = rustdoc
908937
.exec_with_streaming(
@@ -930,6 +959,29 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
930959
return Err(e);
931960
}
932961

962+
if rustdoc_depinfo_enabled && rustdoc_dep_info_loc.exists() {
963+
fingerprint::translate_dep_info(
964+
&rustdoc_dep_info_loc,
965+
&dep_info_loc,
966+
&cwd,
967+
&pkg_root,
968+
&build_dir,
969+
&rustdoc,
970+
// Should we track source file for doc gen?
971+
is_local,
972+
&env_config,
973+
)
974+
.with_context(|| {
975+
internal(format_args!(
976+
"could not parse/generate dep info at: {}",
977+
rustdoc_dep_info_loc.display()
978+
))
979+
})?;
980+
// This mtime shift allows Cargo to detect if a source file was
981+
// modified in the middle of the build.
982+
paths::set_file_time_no_err(dep_info_loc, timestamp);
983+
}
984+
933985
Ok(())
934986
}))
935987
}
@@ -2012,3 +2064,10 @@ fn scrape_output_path(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoR
20122064
.outputs(unit)
20132065
.map(|outputs| outputs[0].path.clone())
20142066
}
2067+
2068+
/// Gets the dep-info file emitted by rustdoc.
2069+
fn rustdoc_dep_info_loc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> PathBuf {
2070+
let mut loc = build_runner.files().fingerprint_file_path(unit, "");
2071+
loc.set_extension("d");
2072+
loc
2073+
}

tests/testsuite/doc.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,15 +2941,17 @@ fn rebuild_tracks_include_str() {
29412941
p.cargo("doc --verbose -Zrustdoc-depinfo")
29422942
.masquerade_as_nightly_cargo(&["rustdoc-depinfo"])
29432943
.with_stderr_data(str![[r#"
2944-
[FRESH] foo v0.5.0 ([ROOT]/parent/foo)
2944+
[DIRTY] foo v0.5.0 ([ROOT]/parent/foo): the file `src/../../README` has changed ([TIME_DIFF_AFTER_LAST_BUILD])
2945+
[DOCUMENTING] foo v0.5.0 ([ROOT]/parent/foo)
2946+
[RUNNING] `rustdoc [..]`
29452947
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
29462948
[GENERATED] [ROOT]/parent/foo/target/doc/foo/index.html
29472949
29482950
"#]])
29492951
.run();
29502952

29512953
let doc_html = p.read_file("target/doc/foo/index.html");
2952-
assert!(!doc_html.contains("depinfo-after"));
2954+
assert!(doc_html.contains("depinfo-after"));
29532955
}
29542956

29552957
#[cargo_test(nightly, reason = "`rustdoc --emit` is unstable")]
@@ -2978,15 +2980,17 @@ fn rebuild_tracks_path_attr() {
29782980
p.cargo("doc --verbose -Zrustdoc-depinfo")
29792981
.masquerade_as_nightly_cargo(&["rustdoc-depinfo"])
29802982
.with_stderr_data(str![[r#"
2981-
[FRESH] foo v0.5.0 ([ROOT]/parent/foo)
2983+
[DIRTY] foo v0.5.0 ([ROOT]/parent/foo): the file `src/../../bar.rs` has changed ([TIME_DIFF_AFTER_LAST_BUILD])
2984+
[DOCUMENTING] foo v0.5.0 ([ROOT]/parent/foo)
2985+
[RUNNING] `rustdoc [..]`
29822986
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
29832987
[GENERATED] [ROOT]/parent/foo/target/doc/foo/index.html
29842988
29852989
"#]])
29862990
.run();
29872991

29882992
let doc_html = p.read_file("target/doc/foo/index.html");
2989-
assert!(!doc_html.contains("depinfo-after"));
2993+
assert!(doc_html.contains("depinfo-after"));
29902994
}
29912995

29922996
#[cargo_test(nightly, reason = "`rustdoc --emit` is unstable")]
@@ -3015,15 +3019,17 @@ fn rebuild_tracks_env() {
30153019
.env(env, "# depinfo-after")
30163020
.masquerade_as_nightly_cargo(&["rustdoc-depinfo"])
30173021
.with_stderr_data(str![[r#"
3018-
[FRESH] foo v0.5.0 ([ROOT]/foo)
3022+
[DIRTY] foo v0.5.0 ([ROOT]/foo): the environment variable __RUSTDOC_INJECTED changed
3023+
[DOCUMENTING] foo v0.5.0 ([ROOT]/foo)
3024+
[RUNNING] `rustdoc [..]`
30193025
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
30203026
[GENERATED] [ROOT]/foo/target/doc/foo/index.html
30213027
30223028
"#]])
30233029
.run();
30243030

30253031
let doc_html = p.read_file("target/doc/foo/index.html");
3026-
assert!(!doc_html.contains("depinfo-after"));
3032+
assert!(doc_html.contains("depinfo-after"));
30273033
}
30283034

30293035
#[cargo_test(nightly, reason = "`rustdoc --emit` is unstable")]
@@ -3074,22 +3080,27 @@ fn rebuild_tracks_env_in_dep() {
30743080
p.cargo("doc --verbose -Zrustdoc-depinfo")
30753081
.env(env, "# depinfo-after")
30763082
.masquerade_as_nightly_cargo(&["rustdoc-depinfo"])
3077-
.with_stderr_data(str![[r#"
3083+
.with_stderr_data(
3084+
str![[r#"
3085+
[DIRTY] bar v0.1.0: the environment variable __RUSTDOC_INJECTED changed
3086+
[DOCUMENTING] bar v0.1.0
30783087
[DIRTY] bar v0.1.0: the environment variable __RUSTDOC_INJECTED changed
30793088
[CHECKING] bar v0.1.0
30803089
[RUNNING] `rustc --crate-name bar [..]`
3090+
[RUNNING] `rustdoc [..]--crate-name bar [..]`
30813091
[DIRTY] foo v0.0.0 ([ROOT]/foo): the dependency bar was rebuilt
30823092
[DOCUMENTING] foo v0.0.0 ([ROOT]/foo)
30833093
[RUNNING] `rustdoc [..]--crate-name foo [..]`
30843094
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
30853095
[GENERATED] [ROOT]/foo/target/doc/foo/index.html
30863096
30873097
"#]]
3088-
)
3098+
.unordered(),
3099+
)
30893100
.run();
30903101

30913102
let doc_html = p.read_file("target/doc/bar/index.html");
3092-
assert!(!doc_html.contains("depinfo-after"));
3103+
assert!(doc_html.contains("depinfo-after"));
30933104
}
30943105

30953106
#[cargo_test(
@@ -3123,7 +3134,7 @@ fn rebuild_tracks_checksum() {
31233134
p.cargo("doc --verbose -Zrustdoc-depinfo -Zchecksum-freshness")
31243135
.masquerade_as_nightly_cargo(&["rustdoc-depinfo"])
31253136
.with_stderr_data(str![[r#"
3126-
[DIRTY] foo v0.5.0 ([ROOT]/parent/foo): the precalculated components changed
3137+
[DIRTY] foo v0.5.0 ([ROOT]/parent/foo): file size changed (16 != 15) for `src/../../README`
31273138
[DOCUMENTING] foo v0.5.0 ([ROOT]/parent/foo)
31283139
[RUNNING] `rustdoc [..]`
31293140
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

0 commit comments

Comments
 (0)