rustdoc: clean up and further test mergeable CCI

This fixes a limitation that prevented me from adding it to Cargo.
The rust compiler-docs bundle is built by running multiple `cargo`
commands in succession, and I want to support that, so I'm stuck
putting the doc parts all in one directory, so that subsequent
`cargo` runs can pick up the previous runs' data. It's less clean,
but it should still be usable in hermetic build environments if
you give every crate its own directory (which you needed to do
before, oddly enough).
This commit is contained in:
Michael Howell 2025-10-28 15:51:03 -07:00
parent 24be917c03
commit 0786642d61
7 changed files with 92 additions and 17 deletions

View file

@ -975,15 +975,16 @@ fn parse_extern_html_roots(
Ok(externs)
}
/// Path directly to crate-info file.
/// Path directly to crate-info directory.
///
/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
/// For example, `/home/user/project/target/doc.parts`.
/// Each crate has its info stored in a file called `CRATENAME.json`.
#[derive(Clone, Debug)]
pub(crate) struct PathToParts(pub(crate) PathBuf);
impl PathToParts {
fn from_flag(path: String) -> Result<PathToParts, String> {
let mut path = PathBuf::from(path);
let path = PathBuf::from(path);
// check here is for diagnostics
if path.exists() && !path.is_dir() {
Err(format!(
@ -992,20 +993,22 @@ impl PathToParts {
))
} else {
// if it doesn't exist, we'll create it. worry about that in write_shared
path.push("crate-info");
Ok(PathToParts(path))
}
}
}
/// Reports error if --include-parts-dir / crate-info is not a file
/// Reports error if --include-parts-dir is not a directory
fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
let mut ret = Vec::new();
for p in m.opt_strs("include-parts-dir") {
let p = PathToParts::from_flag(p)?;
// this is just for diagnostic
if !p.0.is_file() {
return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
if !p.0.is_dir() {
return Err(format!(
"--include-parts-dir expected {} to be a directory",
p.0.display()
));
}
ret.push(p);
}

View file

@ -14,7 +14,7 @@
//! or contains "invocation-specific".
use std::cell::RefCell;
use std::ffi::OsString;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{self, Write as _};
use std::iter::once;
@ -83,9 +83,11 @@ pub(crate) fn write_shared(
};
if let Some(parts_out_dir) = &opt.parts_out_dir {
create_parents(&parts_out_dir.0)?;
let mut parts_out_file = parts_out_dir.0.clone();
parts_out_file.push(&format!("{crate_name}.json"));
create_parents(&parts_out_file)?;
try_err!(
fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
&parts_out_dir.0
);
}
@ -237,13 +239,25 @@ impl CrateInfo {
pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
parts_paths
.iter()
.map(|parts_path| {
let path = &parts_path.0;
let parts = try_err!(fs::read(path), &path);
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
Ok::<_, Error>(parts)
.fold(Ok(Vec::new()), |acc, parts_path| {
let mut acc = acc?;
let dir = &parts_path.0;
acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
.filter_map(|file| {
let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
let file = try_err!(file, dir.as_path());
if file.path().extension() != Some(OsStr::new("json")) {
return Ok(None);
}
let parts = try_err!(fs::read(file.path()), file.path());
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
Ok(Some(parts))
};
to_crate_info(file).transpose()
})
.collect::<Result<Vec<CrateInfo>, Error>>()?);
Ok(acc)
})
.collect::<Result<Vec<CrateInfo>, Error>>()
}
}

View file

@ -0,0 +1,4 @@
//@ hasraw crates.js 'dep1'
//@ hasraw search.index/name/*.js 'Dep1'
//@ has dep1/index.html
pub struct Dep1;

View file

@ -0,0 +1,4 @@
//@ hasraw crates.js 'dep1'
//@ hasraw search.index/name/*.js 'Dep1'
//@ has dep2/index.html
pub struct Dep2;

View file

@ -0,0 +1,4 @@
//@ !hasraw crates.js 'dep_missing'
//@ !hasraw search.index/name/*.js 'DepMissing'
//@ has dep_missing/index.html
pub struct DepMissing;

View file

@ -0,0 +1,46 @@
// Running --merge=finalize without an input crate root should not trigger ICE.
// Issue: https://github.com/rust-lang/rust/issues/146646
//@ needs-target-std
use run_make_support::{htmldocck, path, rustdoc};
fn main() {
let out_dir = path("out");
let merged_dir = path("merged");
let parts_out_dir = path("parts");
rustdoc()
.input("dep1.rs")
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
.arg("--merge=none")
.run();
assert!(parts_out_dir.join("dep1.json").exists());
rustdoc()
.input("dep2.rs")
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
.arg("--merge=none")
.run();
assert!(parts_out_dir.join("dep2.json").exists());
// dep_missing is different, because --parts-out-dir is not supplied
rustdoc().input("dep_missing.rs").out_dir(&out_dir).run();
assert!(parts_out_dir.join("dep2.json").exists());
let output = rustdoc()
.arg("-Zunstable-options")
.out_dir(&out_dir)
.arg(format!("--include-parts-dir={}", parts_out_dir.display()))
.arg("--merge=finalize")
.run();
output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug.");
htmldocck().arg(&out_dir).arg("dep1.rs").run();
htmldocck().arg(&out_dir).arg("dep2.rs").run();
htmldocck().arg(out_dir).arg("dep_missing.rs").run();
}

View file

@ -16,7 +16,7 @@ fn main() {
.arg(format!("--parts-out-dir={}", parts_out_dir.display()))
.arg("--merge=none")
.run();
assert!(parts_out_dir.join("crate-info").exists());
assert!(parts_out_dir.join("sierra.json").exists());
let output = rustdoc()
.arg("-Zunstable-options")