Skip to content

Commit a3ff382

Browse files
committed
Auto merge of #9404 - ehuss:rustdoc-fingerprint-remove-dir, r=alexcrichton
Some changes to rustdoc fingerprint checking. #8640 introduced a check which deletes the `doc` directory if cargo detects it has stale contents from a different toolchain version. Rustdoc has some shared files (js and css for example) that can get corrupted between versions. Unfortunately that caused some problems with rustbuild which does a few unusual things. Rustbuild will: * Create the `doc` directory before running `cargo doc` and places a `.stamp` file inside it. * Creates symlinks of the `doc` directory so that they can be shared across different target directories (in particular, between rustc and rustdoc). In order to address these issues, this PR does several things: * Adds `-Z skip-rustdoc-fingerprint` to disable the `doc` clearing behavior. * Don't delete the `doc` directory if the rustdoc fingerprint is missing. This is intended to help with the scenario where the user creates a `doc` directory ahead of time with pre-existing contents before the first build. The downside is that cargo will not be able to protect against switching from pre-1.53 to post-1.53. * Don't delete the `doc` directory itself (just its contents). This should help if the user created the `doc` directory as a symlink to somewhere else. * Don't delete hidden files in the `doc` directory. This isn't something that rustdoc creates. Only the `-Z` change is needed for rustbuild. The others I figured I'd include just to be on the safe side in case there are other users doing unusual things (and I had already written them thinking they would work for rustbuild). Hopefully the rustbuild `.stamp` mechanism will be enough protection there. Fixes #9336
2 parents d1baf0d + c373867 commit a3ff382

File tree

3 files changed

+136
-57
lines changed

3 files changed

+136
-57
lines changed

src/cargo/core/compiler/build_context/target_info.rs

+66-44
Original file line numberDiff line numberDiff line change
@@ -771,27 +771,6 @@ pub struct RustDocFingerprint {
771771
}
772772

773773
impl RustDocFingerprint {
774-
/// Read the `RustDocFingerprint` info from the fingerprint file.
775-
fn read(cx: &Context<'_, '_>) -> CargoResult<Self> {
776-
let rustdoc_data = paths::read(&cx.files().host_root().join(".rustdoc_fingerprint.json"))?;
777-
serde_json::from_str(&rustdoc_data).map_err(|e| anyhow::anyhow!("{:?}", e))
778-
}
779-
780-
/// Write the `RustDocFingerprint` info into the fingerprint file.
781-
fn write<'a, 'cfg>(&self, cx: &Context<'a, 'cfg>) -> CargoResult<()> {
782-
paths::write(
783-
&cx.files().host_root().join(".rustdoc_fingerprint.json"),
784-
serde_json::to_string(&self)?.as_bytes(),
785-
)
786-
}
787-
788-
fn remove_doc_dirs(doc_dirs: &[&Path]) -> CargoResult<()> {
789-
doc_dirs
790-
.iter()
791-
.filter(|path| path.exists())
792-
.try_for_each(|path| paths::remove_dir_all(&path))
793-
}
794-
795774
/// This function checks whether the latest version of `Rustc` used to compile this
796775
/// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc`
797776
/// call.
@@ -801,38 +780,81 @@ impl RustDocFingerprint {
801780
/// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have
802781
/// any versioning.
803782
pub fn check_rustdoc_fingerprint(cx: &Context<'_, '_>) -> CargoResult<()> {
783+
if cx.bcx.config.cli_unstable().skip_rustdoc_fingerprint {
784+
return Ok(());
785+
}
804786
let actual_rustdoc_target_data = RustDocFingerprint {
805787
rustc_vv: cx.bcx.rustc().verbose_version.clone(),
806788
};
807789

808-
// Collect all of the target doc paths for which the docs need to be compiled for.
809-
let doc_dirs: Vec<&Path> = cx
810-
.bcx
790+
let fingerprint_path = cx.files().host_root().join(".rustdoc_fingerprint.json");
791+
let write_fingerprint = || -> CargoResult<()> {
792+
paths::write(
793+
&fingerprint_path,
794+
serde_json::to_string(&actual_rustdoc_target_data)?,
795+
)
796+
};
797+
let rustdoc_data = match paths::read(&fingerprint_path) {
798+
Ok(rustdoc_data) => rustdoc_data,
799+
// If the fingerprint does not exist, do not clear out the doc
800+
// directories. Otherwise this ran into problems where projects
801+
// like rustbuild were creating the doc directory before running
802+
// `cargo doc` in a way that deleting it would break it.
803+
Err(_) => return write_fingerprint(),
804+
};
805+
match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
806+
Ok(fingerprint) => {
807+
if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv {
808+
return Ok(());
809+
} else {
810+
log::debug!(
811+
"doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
812+
fingerprint.rustc_vv,
813+
actual_rustdoc_target_data.rustc_vv
814+
);
815+
}
816+
}
817+
Err(e) => {
818+
log::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
819+
}
820+
};
821+
// Fingerprint does not match, delete the doc directories and write a new fingerprint.
822+
log::debug!(
823+
"fingerprint {:?} mismatch, clearing doc directories",
824+
fingerprint_path
825+
);
826+
cx.bcx
811827
.all_kinds
812828
.iter()
813829
.map(|kind| cx.files().layout(*kind).doc())
814-
.collect();
815-
816-
// Check wether `.rustdoc_fingerprint.json` exists
817-
match Self::read(cx) {
818-
Ok(fingerprint) => {
819-
// Check if rustc_version matches the one we just used. Otherways,
820-
// remove the `doc` folder to trigger a re-compilation of the docs.
821-
if fingerprint.rustc_vv != actual_rustdoc_target_data.rustc_vv {
822-
Self::remove_doc_dirs(&doc_dirs)?;
823-
actual_rustdoc_target_data.write(cx)?
830+
.filter(|path| path.exists())
831+
.try_for_each(|path| clean_doc(path))?;
832+
write_fingerprint()?;
833+
return Ok(());
834+
835+
fn clean_doc(path: &Path) -> CargoResult<()> {
836+
let entries = path
837+
.read_dir()
838+
.with_context(|| format!("failed to read directory `{}`", path.display()))?;
839+
for entry in entries {
840+
let entry = entry?;
841+
// Don't remove hidden files. Rustdoc does not create them,
842+
// but the user might have.
843+
if entry
844+
.file_name()
845+
.to_str()
846+
.map_or(false, |name| name.starts_with('.'))
847+
{
848+
continue;
849+
}
850+
let path = entry.path();
851+
if entry.file_type()?.is_dir() {
852+
paths::remove_dir_all(path)?;
853+
} else {
854+
paths::remove_file(path)?;
824855
}
825856
}
826-
// If the file does not exist, then we cannot assume that the docs were compiled
827-
// with the actual Rustc instance version. Therefore, we try to remove the
828-
// `doc` directory forcing the recompilation of the docs. If the directory doesn't
829-
// exists neither, we simply do nothing and continue.
830-
Err(_) => {
831-
// We don't care if this succeeds as explained above.
832-
let _ = Self::remove_doc_dirs(&doc_dirs);
833-
actual_rustdoc_target_data.write(cx)?
834-
}
857+
Ok(())
835858
}
836-
Ok(())
837859
}
838860
}

src/cargo/core/features.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,8 @@ macro_rules! unstable_cli_options {
542542
(
543543
$(
544544
$(#[$meta:meta])?
545-
$element: ident: $ty: ty = ($help: expr )
546-
),*
545+
$element: ident: $ty: ty = ($help: expr ),
546+
)*
547547
) => {
548548
/// A parsed representation of all unstable flags that Cargo accepts.
549549
///
@@ -603,7 +603,8 @@ unstable_cli_options!(
603603
terminal_width: Option<Option<usize>> = ("Provide a terminal width to rustc for error truncation"),
604604
timings: Option<Vec<String>> = ("Display concurrency information"),
605605
unstable_options: bool = ("Allow the usage of unstable options"),
606-
weak_dep_features: bool = ("Allow `dep_name?/feature` feature syntax")
606+
weak_dep_features: bool = ("Allow `dep_name?/feature` feature syntax"),
607+
skip_rustdoc_fingerprint: bool = (HIDDEN),
607608
);
608609

609610
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
@@ -813,6 +814,7 @@ impl CliUnstable {
813814
"weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?,
814815
"extra-link-arg" => self.extra_link_arg = parse_empty(k, v)?,
815816
"credential-process" => self.credential_process = parse_empty(k, v)?,
817+
"skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
816818
"compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
817819
"offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,
818820
"cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES),

tests/testsuite/doc.rs

+65-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cargo::core::compiler::RustDocFingerprint;
44
use cargo_test_support::paths::CargoPathExt;
55
use cargo_test_support::registry::Package;
66
use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project};
7-
use cargo_test_support::{is_nightly, rustc_host};
7+
use cargo_test_support::{is_nightly, rustc_host, symlink_supported};
88
use std::fs;
99
use std::str;
1010

@@ -1800,7 +1800,6 @@ LLVM version: 9.0
18001800
);
18011801
}
18021802

1803-
#[cfg(target_os = "linux")]
18041803
#[cargo_test]
18051804
fn doc_fingerprint_respects_target_paths() {
18061805
// Random rustc verbose version
@@ -1831,9 +1830,7 @@ LLVM version: 9.0
18311830
.file("src/lib.rs", "//! These are the docs!")
18321831
.build();
18331832

1834-
dummy_project
1835-
.cargo("doc --target x86_64-unknown-linux-gnu")
1836-
.run();
1833+
dummy_project.cargo("doc --target").arg(rustc_host()).run();
18371834

18381835
let fingerprint: RustDocFingerprint =
18391836
serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json"))
@@ -1863,7 +1860,8 @@ LLVM version: 9.0
18631860
fs::write(
18641861
dummy_project
18651862
.build_dir()
1866-
.join("x86_64-unknown-linux-gnu/doc/bogus_file"),
1863+
.join(rustc_host())
1864+
.join("doc/bogus_file"),
18671865
String::from("This is a bogus file and should be removed!"),
18681866
)
18691867
.expect("Error writing test bogus file");
@@ -1872,13 +1870,12 @@ LLVM version: 9.0
18721870
// of rustc, cargo should remove the entire `/doc` folder (including the fingerprint)
18731871
// and generating another one with the actual version.
18741872
// It should also remove the bogus file we created above.
1875-
dummy_project
1876-
.cargo("doc --target x86_64-unknown-linux-gnu")
1877-
.run();
1873+
dummy_project.cargo("doc --target").arg(rustc_host()).run();
18781874

18791875
assert!(!dummy_project
18801876
.build_dir()
1881-
.join("x86_64-unknown-linux-gnu/doc/bogus_file")
1877+
.join(rustc_host())
1878+
.join("doc/bogus_file")
18821879
.exists());
18831880

18841881
let fingerprint: RustDocFingerprint =
@@ -1892,3 +1889,61 @@ LLVM version: 9.0
18921889
(String::from_utf8_lossy(&output.stdout).as_ref())
18931890
);
18941891
}
1892+
1893+
#[cargo_test]
1894+
fn doc_fingerprint_unusual_behavior() {
1895+
// Checks for some unusual circumstances with clearing the doc directory.
1896+
if !symlink_supported() {
1897+
return;
1898+
}
1899+
let p = project().file("src/lib.rs", "").build();
1900+
p.build_dir().mkdir_p();
1901+
let real_doc = p.root().join("doc");
1902+
real_doc.mkdir_p();
1903+
let build_doc = p.build_dir().join("doc");
1904+
p.symlink(&real_doc, &build_doc);
1905+
fs::write(real_doc.join("somefile"), "test").unwrap();
1906+
fs::write(real_doc.join(".hidden"), "test").unwrap();
1907+
p.cargo("doc").run();
1908+
// Make sure for the first run, it does not delete any files and does not
1909+
// break the symlink.
1910+
assert!(build_doc.join("somefile").exists());
1911+
assert!(real_doc.join("somefile").exists());
1912+
assert!(real_doc.join(".hidden").exists());
1913+
assert!(real_doc.join("foo/index.html").exists());
1914+
// Pretend that the last build was generated by an older version.
1915+
p.change_file(
1916+
"target/.rustdoc_fingerprint.json",
1917+
"{\"rustc_vv\": \"I am old\"}",
1918+
);
1919+
// Change file to trigger a new build.
1920+
p.change_file("src/lib.rs", "// changed");
1921+
p.cargo("doc")
1922+
.with_stderr(
1923+
"[DOCUMENTING] foo [..]\n\
1924+
[FINISHED] [..]",
1925+
)
1926+
.run();
1927+
// This will delete somefile, but not .hidden.
1928+
assert!(!real_doc.join("somefile").exists());
1929+
assert!(real_doc.join(".hidden").exists());
1930+
assert!(real_doc.join("foo/index.html").exists());
1931+
// And also check the -Z flag behavior.
1932+
p.change_file(
1933+
"target/.rustdoc_fingerprint.json",
1934+
"{\"rustc_vv\": \"I am old\"}",
1935+
);
1936+
// Change file to trigger a new build.
1937+
p.change_file("src/lib.rs", "// changed2");
1938+
fs::write(real_doc.join("somefile"), "test").unwrap();
1939+
p.cargo("doc -Z skip-rustdoc-fingerprint")
1940+
.masquerade_as_nightly_cargo()
1941+
.with_stderr(
1942+
"[DOCUMENTING] foo [..]\n\
1943+
[FINISHED] [..]",
1944+
)
1945+
.run();
1946+
// Should not have deleted anything.
1947+
assert!(build_doc.join("somefile").exists());
1948+
assert!(real_doc.join("somefile").exists());
1949+
}

0 commit comments

Comments
 (0)