Skip to content

Commit 44c65aa

Browse files
authored
Rollup merge of #148234 - notriddle:doc-cci, r=GuillaumeGomez
rustdoc: make mergeable crate info more usable Part of #130676 Adds documentation and a feature change aimed at making this usable without breaking backwards compat. cc `@EtomicBomb`
2 parents 45608f8 + 0786642 commit 44c65aa

File tree

8 files changed

+123
-17
lines changed

8 files changed

+123
-17
lines changed

src/doc/rustdoc/src/unstable-features.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,37 @@ themselves marked as unstable. To use any of these options, pass `-Z unstable-op
197197
the flag in question to Rustdoc on the command-line. To do this from Cargo, you can either use the
198198
`RUSTDOCFLAGS` environment variable or the `cargo rustdoc` command.
199199

200+
### `--merge`, `--parts-out-dir`, and `--include-parts-dir`
201+
202+
These options control how rustdoc handles files that combine data from multiple crates.
203+
204+
By default, they act like `--merge=shared` is set, and `--parts-out-dir` and `--include-parts-dir`
205+
are turned off. The `--merge=shared` mode causes rustdoc to load the existing data in the out-dir,
206+
combine the new crate data into it, and write the result. This is very easy to use in scripts that
207+
manually invoke rustdoc, but it's also slow, because it performs O(crates) work on
208+
every crate, meaning it performs O(crates<sup>2</sup>) work.
209+
210+
```console
211+
$ rustdoc crate1.rs --out-dir=doc
212+
$ cat doc/search.index/crateNames/*
213+
rd_("fcrate1")
214+
$ rustdoc crate2.rs --out-dir=doc
215+
$ cat doc/search.index/crateNames/*
216+
rd_("fcrate1fcrate2")
217+
```
218+
219+
To delay shared-data merging until the end of a build, so that you only have to perform O(crates)
220+
work, use `--merge=none` on every crate except the last one, which will use `--merge=finalize`.
221+
222+
```console
223+
$ rustdoc +nightly crate1.rs --merge=none --parts-out-dir=crate1.d -Zunstable-options
224+
$ cat doc/search.index/crateNames/*
225+
cat: 'doc/search.index/crateNames/*': No such file or directory
226+
$ rustdoc +nightly crate2.rs --merge=finalize --include-parts-dir=crate1.d -Zunstable-options
227+
$ cat doc/search.index/crateNames/*
228+
rd_("fcrate1fcrate2")
229+
```
230+
200231
### `--document-hidden-items`: Show items that are `#[doc(hidden)]`
201232
<span id="document-hidden-items"></span>
202233

src/librustdoc/config.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -978,15 +978,16 @@ fn parse_extern_html_roots(
978978
Ok(externs)
979979
}
980980

981-
/// Path directly to crate-info file.
981+
/// Path directly to crate-info directory.
982982
///
983-
/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
983+
/// For example, `/home/user/project/target/doc.parts`.
984+
/// Each crate has its info stored in a file called `CRATENAME.json`.
984985
#[derive(Clone, Debug)]
985986
pub(crate) struct PathToParts(pub(crate) PathBuf);
986987

987988
impl PathToParts {
988989
fn from_flag(path: String) -> Result<PathToParts, String> {
989-
let mut path = PathBuf::from(path);
990+
let path = PathBuf::from(path);
990991
// check here is for diagnostics
991992
if path.exists() && !path.is_dir() {
992993
Err(format!(
@@ -995,20 +996,22 @@ impl PathToParts {
995996
))
996997
} else {
997998
// if it doesn't exist, we'll create it. worry about that in write_shared
998-
path.push("crate-info");
999999
Ok(PathToParts(path))
10001000
}
10011001
}
10021002
}
10031003

1004-
/// Reports error if --include-parts-dir / crate-info is not a file
1004+
/// Reports error if --include-parts-dir is not a directory
10051005
fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
10061006
let mut ret = Vec::new();
10071007
for p in m.opt_strs("include-parts-dir") {
10081008
let p = PathToParts::from_flag(p)?;
10091009
// this is just for diagnostic
1010-
if !p.0.is_file() {
1011-
return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
1010+
if !p.0.is_dir() {
1011+
return Err(format!(
1012+
"--include-parts-dir expected {} to be a directory",
1013+
p.0.display()
1014+
));
10121015
}
10131016
ret.push(p);
10141017
}

src/librustdoc/html/render/write_shared.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! or contains "invocation-specific".
1515
1616
use std::cell::RefCell;
17-
use std::ffi::OsString;
17+
use std::ffi::{OsStr, OsString};
1818
use std::fs::File;
1919
use std::io::{self, Write as _};
2020
use std::iter::once;
@@ -84,9 +84,11 @@ pub(crate) fn write_shared(
8484
};
8585

8686
if let Some(parts_out_dir) = &opt.parts_out_dir {
87-
create_parents(&parts_out_dir.0)?;
87+
let mut parts_out_file = parts_out_dir.0.clone();
88+
parts_out_file.push(&format!("{crate_name}.json"));
89+
create_parents(&parts_out_file)?;
8890
try_err!(
89-
fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
91+
fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
9092
&parts_out_dir.0
9193
);
9294
}
@@ -238,13 +240,25 @@ impl CrateInfo {
238240
pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
239241
parts_paths
240242
.iter()
241-
.map(|parts_path| {
242-
let path = &parts_path.0;
243-
let parts = try_err!(fs::read(path), &path);
244-
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
245-
Ok::<_, Error>(parts)
243+
.fold(Ok(Vec::new()), |acc, parts_path| {
244+
let mut acc = acc?;
245+
let dir = &parts_path.0;
246+
acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
247+
.filter_map(|file| {
248+
let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
249+
let file = try_err!(file, dir.as_path());
250+
if file.path().extension() != Some(OsStr::new("json")) {
251+
return Ok(None);
252+
}
253+
let parts = try_err!(fs::read(file.path()), file.path());
254+
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
255+
Ok(Some(parts))
256+
};
257+
to_crate_info(file).transpose()
258+
})
259+
.collect::<Result<Vec<CrateInfo>, Error>>()?);
260+
Ok(acc)
246261
})
247-
.collect::<Result<Vec<CrateInfo>, Error>>()
248262
}
249263
}
250264

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//@ hasraw crates.js 'dep1'
2+
//@ hasraw search.index/name/*.js 'Dep1'
3+
//@ has dep1/index.html
4+
pub struct Dep1;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//@ hasraw crates.js 'dep1'
2+
//@ hasraw search.index/name/*.js 'Dep1'
3+
//@ has dep2/index.html
4+
pub struct Dep2;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//@ !hasraw crates.js 'dep_missing'
2+
//@ !hasraw search.index/name/*.js 'DepMissing'
3+
//@ has dep_missing/index.html
4+
pub struct DepMissing;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Running --merge=finalize without an input crate root should not trigger ICE.
2+
// Issue: https://github.com/rust-lang/rust/issues/146646
3+
4+
//@ needs-target-std
5+
6+
use run_make_support::{htmldocck, path, rustdoc};
7+
8+
fn main() {
9+
let out_dir = path("out");
10+
let merged_dir = path("merged");
11+
let parts_out_dir = path("parts");
12+
13+
rustdoc()
14+
.input("dep1.rs")
15+
.out_dir(&out_dir)
16+
.arg("-Zunstable-options")
17+
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
18+
.arg("--merge=none")
19+
.run();
20+
assert!(parts_out_dir.join("dep1.json").exists());
21+
22+
rustdoc()
23+
.input("dep2.rs")
24+
.out_dir(&out_dir)
25+
.arg("-Zunstable-options")
26+
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
27+
.arg("--merge=none")
28+
.run();
29+
assert!(parts_out_dir.join("dep2.json").exists());
30+
31+
// dep_missing is different, because --parts-out-dir is not supplied
32+
rustdoc().input("dep_missing.rs").out_dir(&out_dir).run();
33+
assert!(parts_out_dir.join("dep2.json").exists());
34+
35+
let output = rustdoc()
36+
.arg("-Zunstable-options")
37+
.out_dir(&out_dir)
38+
.arg(format!("--include-parts-dir={}", parts_out_dir.display()))
39+
.arg("--merge=finalize")
40+
.run();
41+
output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug.");
42+
43+
htmldocck().arg(&out_dir).arg("dep1.rs").run();
44+
htmldocck().arg(&out_dir).arg("dep2.rs").run();
45+
htmldocck().arg(out_dir).arg("dep_missing.rs").run();
46+
}

tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn main() {
1616
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
1717
.arg("--merge=none")
1818
.run();
19-
assert!(parts_out_dir.join("crate-info").exists());
19+
assert!(parts_out_dir.join("sierra.json").exists());
2020

2121
let output = rustdoc()
2222
.arg("-Zunstable-options")

0 commit comments

Comments
 (0)