Skip to content

Commit 7220445

Browse files
authored
feat: rustdoc depinfo rebuild detection via -Zrustdoc-depinfo (#15359)
### What does this PR try to resolve? 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 made in the current implementation: * 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. ### How should we test and review this PR? The tests added has covered these cases: * target src outside package root, e.g., `lib.path = "../lib.rs"` * `#[doc = include_str!("../outside/pkgroot")]` * `#[path = "../outside/pkgroot"]` * `env!` ### Additional information Fixes #12266 Closes #15205
2 parents 585f66a + a18e604 commit 7220445

File tree

6 files changed

+384
-15
lines changed

6 files changed

+384
-15
lines changed

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

+3-1
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

+58
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,19 @@ 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+
if build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo {
767+
// invocation-specific is required for keeping the original rustdoc emission
768+
let mut arg = OsString::from("--emit=invocation-specific,dep-info=");
769+
arg.push(rustdoc_dep_info_loc(build_runner, unit));
770+
rustdoc.arg(arg);
771+
772+
if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
773+
rustdoc.arg("-Z").arg("checksum-hash-algorithm=blake3");
774+
}
775+
776+
rustdoc.arg("-Zunstable-options");
777+
}
778+
766779
if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
767780
trim_paths_args_rustdoc(&mut rustdoc, build_runner, unit, trim_paths)?;
768781
}
@@ -838,6 +851,20 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
838851
let package_id = unit.pkg.package_id();
839852
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
840853
let target = Target::clone(&unit.target);
854+
855+
let rustdoc_dep_info_loc = rustdoc_dep_info_loc(build_runner, unit);
856+
let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
857+
let build_dir = build_runner.bcx.ws.build_dir().into_path_unlocked();
858+
let pkg_root = unit.pkg.root().to_path_buf();
859+
let cwd = rustdoc
860+
.get_cwd()
861+
.unwrap_or_else(|| build_runner.bcx.gctx.cwd())
862+
.to_path_buf();
863+
let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
864+
let is_local = unit.is_local();
865+
let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
866+
let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
867+
841868
let mut output_options = OutputOptions::new(build_runner, unit);
842869
let script_metadata = build_runner.find_build_script_metadata(unit);
843870
let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) {
@@ -903,6 +930,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
903930
paths::remove_dir_all(crate_dir)?;
904931
}
905932
state.running(&rustdoc);
933+
let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
906934

907935
let result = rustdoc
908936
.exec_with_streaming(
@@ -930,6 +958,29 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
930958
return Err(e);
931959
}
932960

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

src/cargo/core/features.rs

+2
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ unstable_cli_options!(
790790
public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
791791
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
792792
root_dir: Option<PathBuf> = ("Set the root directory relative to which paths are printed (defaults to workspace root)"),
793+
rustdoc_depinfo: bool = ("Use dep-info files in rustdoc rebuild detection"),
793794
rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
794795
rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
795796
sbom: bool = ("Enable the `sbom` option in build config in .cargo/config.toml file"),
@@ -1303,6 +1304,7 @@ impl CliUnstable {
13031304
"trim-paths" => self.trim_paths = parse_empty(k, v)?,
13041305
"publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
13051306
"root-dir" => self.root_dir = v.map(|v| v.into()),
1307+
"rustdoc-depinfo" => self.rustdoc_depinfo = parse_empty(k, v)?,
13061308
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
13071309
"rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
13081310
"sbom" => self.sbom = parse_empty(k, v)?,

src/doc/src/reference/unstable.md

+9
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Each new feature described below should explain how to use it.
9898
* [rustdoc-map](#rustdoc-map) --- Provides mappings for documentation to link to external sites like [docs.rs](https://docs.rs/).
9999
* [scrape-examples](#scrape-examples) --- Shows examples within documentation.
100100
* [output-format](#output-format-for-rustdoc) --- Allows documentation to also be emitted in the experimental [JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types/).
101+
* [rustdoc-depinfo](#rustdoc-depinfo) --- Use dep-info files in rustdoc rebuild detection.
101102
* `Cargo.toml` extensions
102103
* [Profile `rustflags` option](#profile-rustflags-option) --- Passed directly to rustc.
103104
* [codegen-backend](#codegen-backend) --- Select the codegen backend used by rustc.
@@ -1904,6 +1905,14 @@ Requires `-Zunstable-options`.
19041905
See [`cargo package --message-format`](../commands/cargo-package.md#option-cargo-package---message-format)
19051906
for more information.
19061907

1908+
## rustdoc depinfo
1909+
1910+
* Tracking Issue: [#00000](https://github.com/rust-lang/cargo/issues/00000)
1911+
1912+
The `-Z rustdoc-depinfo` flag leverages rustdoc's dep-info files to determine
1913+
whether documentations are required to re-generate. This can be combined with
1914+
`-Z checksum-freshness` to detect checksum changes rather than file mtime.
1915+
19071916
# Stabilized and removed features
19081917

19091918
## Compile progress

tests/testsuite/cargo/z_help/stdout.term.svg

+16-14
Loading

0 commit comments

Comments
 (0)