Skip to content

Commit 05e32af

Browse files
authored
fix(fingerprint): clean doc dirs for only requested targets (#16331)
### What does this PR try to resolve? Previously if rustc version mismatches, Cargo removes all `doc` directories including target platforms that are not part of the build. This PR makes it `--target` aware and stops excessive cleanup, by putting `.rustdoc_fingerprint.json` in each target platform directory. ### How to test and review this PR? I'd like to reuse this file to track doc parts directories in <#16309>, and noticed that this file is for the entire workspace rather than per-target, hich is not compatible with mergeable cross-crate info JSONs. For concurrent safety, `build-dir` should be locked already since Cargo locks every intent except check (see <#16307>). This file is touched only under `UserIntent::Doc`.
2 parents bd97934 + 03abcfb commit 05e32af

File tree

7 files changed

+224
-182
lines changed

7 files changed

+224
-182
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ use crate::util::logger::BuildLogger;
1313
use std::collections::{HashMap, HashSet};
1414

1515
mod target_info;
16-
pub use self::target_info::{
17-
FileFlavor, FileType, RustDocFingerprint, RustcTargetData, TargetInfo,
18-
};
16+
pub use self::target_info::FileFlavor;
17+
pub use self::target_info::FileType;
18+
pub use self::target_info::RustcTargetData;
19+
pub use self::target_info::TargetInfo;
1920

2021
/// The build context, containing complete information needed for a build task
2122
/// before it gets started.

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

Lines changed: 9 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@
77
//! * [`RustcTargetData::info`] to get a [`TargetInfo`] for an in-depth query.
88
//! * [`TargetInfo::rustc_outputs`] to get a list of supported file types.
99
10+
use crate::core::compiler::CompileKind;
11+
use crate::core::compiler::CompileMode;
12+
use crate::core::compiler::CompileTarget;
13+
use crate::core::compiler::CrateType;
1014
use crate::core::compiler::apply_env_config;
11-
use crate::core::compiler::{BuildRunner, CompileKind, CompileMode, CompileTarget, CrateType};
1215
use crate::core::{Dependency, Package, Target, TargetKind, Workspace};
1316
use crate::util::context::{GlobalContext, StringList, TargetConfig};
1417
use crate::util::interning::InternedString;
1518
use crate::util::{CargoResult, Rustc};
19+
1620
use anyhow::Context as _;
1721
use cargo_platform::{Cfg, CfgExpr};
18-
use cargo_util::{ProcessBuilder, paths};
19-
use serde::{Deserialize, Serialize};
22+
use cargo_util::ProcessBuilder;
23+
use serde::Deserialize;
24+
2025
use std::cell::RefCell;
2126
use std::collections::hash_map::{Entry, HashMap};
22-
use std::path::{Path, PathBuf};
27+
use std::path::PathBuf;
2328
use std::rc::Rc;
2429
use std::str::{self, FromStr};
2530

@@ -1110,113 +1115,3 @@ impl<'gctx> RustcTargetData<'gctx> {
11101115
&self.requested_kinds
11111116
}
11121117
}
1113-
1114-
/// Structure used to deal with Rustdoc fingerprinting
1115-
#[derive(Debug, Serialize, Deserialize)]
1116-
pub struct RustDocFingerprint {
1117-
pub rustc_vv: String,
1118-
}
1119-
1120-
impl RustDocFingerprint {
1121-
/// This function checks whether the latest version of `Rustc` used to compile this
1122-
/// `Workspace`'s docs was the same as the one is currently being used in this `cargo doc`
1123-
/// call.
1124-
///
1125-
/// In case it's not, it takes care of removing the `doc/` folder as well as overwriting
1126-
/// the rustdoc fingerprint info in order to guarantee that we won't end up with mixed
1127-
/// versions of the `js/html/css` files that `rustdoc` autogenerates which do not have
1128-
/// any versioning.
1129-
pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> {
1130-
if build_runner
1131-
.bcx
1132-
.gctx
1133-
.cli_unstable()
1134-
.skip_rustdoc_fingerprint
1135-
{
1136-
return Ok(());
1137-
}
1138-
let actual_rustdoc_target_data = RustDocFingerprint {
1139-
rustc_vv: build_runner.bcx.rustc().verbose_version.clone(),
1140-
};
1141-
1142-
let fingerprint_path = build_runner
1143-
.files()
1144-
.host_build_root()
1145-
.join(".rustdoc_fingerprint.json");
1146-
let write_fingerprint = || -> CargoResult<()> {
1147-
paths::write(
1148-
&fingerprint_path,
1149-
serde_json::to_string(&actual_rustdoc_target_data)?,
1150-
)
1151-
};
1152-
let Ok(rustdoc_data) = paths::read(&fingerprint_path) else {
1153-
// If the fingerprint does not exist, do not clear out the doc
1154-
// directories. Otherwise this ran into problems where projects
1155-
// like bootstrap were creating the doc directory before running
1156-
// `cargo doc` in a way that deleting it would break it.
1157-
return write_fingerprint();
1158-
};
1159-
match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
1160-
Ok(fingerprint) => {
1161-
if fingerprint.rustc_vv == actual_rustdoc_target_data.rustc_vv {
1162-
return Ok(());
1163-
} else {
1164-
tracing::debug!(
1165-
"doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
1166-
fingerprint.rustc_vv,
1167-
actual_rustdoc_target_data.rustc_vv
1168-
);
1169-
}
1170-
}
1171-
Err(e) => {
1172-
tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
1173-
}
1174-
};
1175-
// Fingerprint does not match, delete the doc directories and write a new fingerprint.
1176-
tracing::debug!(
1177-
"fingerprint {:?} mismatch, clearing doc directories",
1178-
fingerprint_path
1179-
);
1180-
build_runner
1181-
.bcx
1182-
.all_kinds
1183-
.iter()
1184-
.map(|kind| {
1185-
build_runner
1186-
.files()
1187-
.layout(*kind)
1188-
.artifact_dir()
1189-
.expect("artifact-dir was not locked")
1190-
.doc()
1191-
})
1192-
.filter(|path| path.exists())
1193-
.try_for_each(|path| clean_doc(path))?;
1194-
write_fingerprint()?;
1195-
return Ok(());
1196-
1197-
fn clean_doc(path: &Path) -> CargoResult<()> {
1198-
let entries = path
1199-
.read_dir()
1200-
.with_context(|| format!("failed to read directory `{}`", path.display()))?;
1201-
for entry in entries {
1202-
let entry = entry?;
1203-
// Don't remove hidden files. Rustdoc does not create them,
1204-
// but the user might have.
1205-
if entry
1206-
.file_name()
1207-
.to_str()
1208-
.map_or(false, |name| name.starts_with('.'))
1209-
{
1210-
continue;
1211-
}
1212-
let path = entry.path();
1213-
if entry.file_type()?.is_dir() {
1214-
paths::remove_dir_all(path)?;
1215-
} else {
1216-
paths::remove_file(path)?;
1217-
}
1218-
}
1219-
Ok(())
1220-
}
1221-
}
1222-
}

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,9 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
174174
self.check_collisions()?;
175175
self.compute_metadata_for_doc_units();
176176

177-
// We need to make sure that if there were any previous docs
178-
// already compiled, they were compiled with the same Rustc version that we're currently
179-
// using. Otherwise we must remove the `doc/` folder and compile again forcing a rebuild.
180-
//
181-
// This is important because the `.js`/`.html` & `.css` files that are generated by Rustc don't have
182-
// any versioning (See https://github.com/rust-lang/cargo/issues/8461).
183-
// Therefore, we can end up with weird bugs and behaviours if we mix different
184-
// versions of these files.
177+
// We need to make sure that if there were any previous docs already compiled,
178+
// they were compiled with the same Rustc version that we're currently using.
179+
// See the function doc comment for more.
185180
if self.bcx.build_config.intent.is_doc() {
186181
RustDocFingerprint::check_rustdoc_fingerprint(&self)?
187182
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@
371371
372372
mod dep_info;
373373
mod dirty_reason;
374+
mod rustdoc;
374375

375376
use std::collections::hash_map::{Entry, HashMap};
376377
use std::env;
@@ -409,6 +410,7 @@ pub use self::dep_info::parse_dep_info;
409410
pub use self::dep_info::parse_rustc_dep_info;
410411
pub use self::dep_info::translate_dep_info;
411412
pub use self::dirty_reason::DirtyReason;
413+
pub use self::rustdoc::RustDocFingerprint;
412414

413415
/// Determines if a [`Unit`] is up-to-date, and if not prepares necessary work to
414416
/// update the persisted fingerprint.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use std::path::Path;
2+
3+
use anyhow::Context as _;
4+
use cargo_util::paths;
5+
use serde::Deserialize;
6+
use serde::Serialize;
7+
8+
use crate::CargoResult;
9+
use crate::core::compiler::BuildRunner;
10+
use crate::core::compiler::CompileKind;
11+
12+
/// Structure used to deal with Rustdoc fingerprinting
13+
///
14+
/// This is important because the `.js`/`.html` & `.css` files
15+
/// that are generated by Rustc don't have any versioning yet
16+
/// (see <https://github.com/rust-lang/cargo/issues/8461>).
17+
/// Therefore, we can end up with weird bugs and behaviours
18+
/// if we mix different versions of these files.
19+
///
20+
/// We need to make sure that if there were any previous docs already compiled,
21+
/// they were compiled with the same Rustc version that we're currently using.
22+
/// Otherwise we must remove the `doc/` folder and compile again forcing a rebuild.
23+
#[derive(Debug, Serialize, Deserialize)]
24+
pub struct RustDocFingerprint {
25+
/// `rustc -vV` verbose version output.
26+
pub rustc_vv: String,
27+
}
28+
29+
impl RustDocFingerprint {
30+
/// Checks whether the latest version of rustc used to compile this workspace's docs
31+
/// was the same as the one is currently being used in this `cargo doc` call.
32+
///
33+
/// In case it's not,
34+
/// it takes care of removing the `<build-dir>/doc/` folder
35+
/// as well as overwriting the rustdoc fingerprint info.
36+
/// This is to guarantee that we won't end up with mixed versions of the `js/html/css` files
37+
/// which `rustdoc` autogenerates without any versioning.
38+
///
39+
/// Each requested target platform maintains its own fingerprint file.
40+
/// That is, if you run `cargo doc` and then `cargo doc --target wasm32-wasip1`,
41+
/// you will have two separate fingerprint files:
42+
///
43+
/// * `<build-dir>/.rustdoc_fingerprint.json` for host
44+
/// * `<build-dir>/wasm32-wasip1/.rustdoc_fingerprint.json`
45+
pub fn check_rustdoc_fingerprint(build_runner: &BuildRunner<'_, '_>) -> CargoResult<()> {
46+
if build_runner
47+
.bcx
48+
.gctx
49+
.cli_unstable()
50+
.skip_rustdoc_fingerprint
51+
{
52+
return Ok(());
53+
}
54+
let new_fingerprint = RustDocFingerprint {
55+
rustc_vv: build_runner.bcx.rustc().verbose_version.clone(),
56+
};
57+
58+
for kind in &build_runner.bcx.build_config.requested_kinds {
59+
check_fingerprint(build_runner, &new_fingerprint, *kind)?;
60+
}
61+
62+
Ok(())
63+
}
64+
}
65+
66+
/// Checks rustdoc fingerprint file for a given [`CompileKind`].
67+
fn check_fingerprint(
68+
build_runner: &BuildRunner<'_, '_>,
69+
new_fingerprint: &RustDocFingerprint,
70+
kind: CompileKind,
71+
) -> CargoResult<()> {
72+
let fingerprint_path = build_runner
73+
.files()
74+
.layout(kind)
75+
.build_dir()
76+
.root()
77+
.join(".rustdoc_fingerprint.json");
78+
79+
let write_fingerprint = || -> CargoResult<()> {
80+
paths::write(&fingerprint_path, serde_json::to_string(new_fingerprint)?)
81+
};
82+
83+
let Ok(rustdoc_data) = paths::read(&fingerprint_path) else {
84+
// If the fingerprint does not exist, do not clear out the doc
85+
// directories. Otherwise this ran into problems where projects
86+
// like bootstrap were creating the doc directory before running
87+
// `cargo doc` in a way that deleting it would break it.
88+
return write_fingerprint();
89+
};
90+
91+
match serde_json::from_str::<RustDocFingerprint>(&rustdoc_data) {
92+
Ok(on_disk_fingerprint) => {
93+
if on_disk_fingerprint.rustc_vv == new_fingerprint.rustc_vv {
94+
return Ok(());
95+
} else {
96+
tracing::debug!(
97+
"doc fingerprint changed:\noriginal:\n{}\nnew:\n{}",
98+
on_disk_fingerprint.rustc_vv,
99+
new_fingerprint.rustc_vv
100+
);
101+
}
102+
}
103+
Err(e) => {
104+
tracing::debug!("could not deserialize {:?}: {}", fingerprint_path, e);
105+
}
106+
};
107+
// Fingerprint does not match, delete the doc directories and write a new fingerprint.
108+
tracing::debug!(
109+
"fingerprint {:?} mismatch, clearing doc directories",
110+
fingerprint_path
111+
);
112+
let doc_dir = build_runner
113+
.files()
114+
.layout(kind)
115+
.artifact_dir()
116+
.expect("artifact-dir was not locked")
117+
.doc();
118+
if doc_dir.exists() {
119+
clean_doc(doc_dir)?;
120+
}
121+
122+
write_fingerprint()?;
123+
124+
Ok(())
125+
}
126+
127+
fn clean_doc(path: &Path) -> CargoResult<()> {
128+
let entries = path
129+
.read_dir()
130+
.with_context(|| format!("failed to read directory `{}`", path.display()))?;
131+
for entry in entries {
132+
let entry = entry?;
133+
// Don't remove hidden files. Rustdoc does not create them,
134+
// but the user might have.
135+
if entry
136+
.file_name()
137+
.to_str()
138+
.map_or(false, |name| name.starts_with('.'))
139+
{
140+
continue;
141+
}
142+
let path = entry.path();
143+
if entry.file_type()?.is_dir() {
144+
paths::remove_dir_all(path)?;
145+
} else {
146+
paths::remove_file(path)?;
147+
}
148+
}
149+
Ok(())
150+
}

src/cargo/core/compiler/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,19 @@ use tracing::{debug, instrument, trace};
7272

7373
pub use self::build_config::UserIntent;
7474
pub use self::build_config::{BuildConfig, CompileMode, MessageFormat, TimingOutput};
75-
pub use self::build_context::{
76-
BuildContext, FileFlavor, FileType, RustDocFingerprint, RustcTargetData, TargetInfo,
77-
};
75+
pub use self::build_context::BuildContext;
76+
pub use self::build_context::FileFlavor;
77+
pub use self::build_context::FileType;
78+
pub use self::build_context::RustcTargetData;
79+
pub use self::build_context::TargetInfo;
7880
pub use self::build_runner::{BuildRunner, Metadata, UnitHash};
7981
pub use self::compilation::{Compilation, Doctest, UnitOutput};
8082
pub use self::compile_kind::{CompileKind, CompileKindFallback, CompileTarget};
8183
pub use self::crate_type::CrateType;
8284
pub use self::custom_build::LinkArgTarget;
8385
pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts, LibraryPath};
8486
pub(crate) use self::fingerprint::DirtyReason;
87+
pub use self::fingerprint::RustDocFingerprint;
8588
pub use self::job_queue::Freshness;
8689
use self::job_queue::{Job, JobQueue, JobState, Work};
8790
pub(crate) use self::layout::Layout;

0 commit comments

Comments
 (0)