1use crate::util;
4use anyhow::{Context, Result, ensure};
5use std::{fs, path::Path, process::Command};
6
7pub fn build() -> Result<()> {
8 let root = util::root();
9 let staging = tempfile::tempdir()?;
10 let target = root.join("target/site-rustdoc");
11
12 let status = util::command(&["cargo", "clean", "--doc", "--target-dir"])
15 .arg(&target)
16 .status()
17 .context("cleaning previous rustdoc output")?;
18
19 ensure!(status.success(), "rustdoc cleanup failed");
20
21 let status = util::command(&[
22 "cargo",
23 "doc",
24 "--locked",
25 "--workspace",
26 "--no-deps",
27 "--document-private-items",
28 "--target-dir",
29 ])
30 .arg(&target)
31 .env("CARGO_ENCODED_RUSTDOCFLAGS", "-Dwarnings")
32 .status()
33 .context("building workspace rustdoc")?;
34
35 ensure!(status.success(), "rustdoc build failed");
36
37 stage(&root, staging.path())?;
38
39 let status = util::command(&["mdbook", "build"])
40 .arg(staging.path())
41 .arg("--dest-dir")
42 .arg(root.join("_site"))
43 .status()
44 .context("running mdbook; install it with mise install github:rust-lang/mdBook")?;
45
46 ensure!(status.success(), "mdBook build failed");
47 publish_api(&target.join("doc"), &root.join("_site"))?;
48
49 Ok(())
50}
51
52pub fn publish_api(source: &Path, site: &Path) -> Result<()> {
54 let destination = site.join("api");
55
56 ensure!(
58 source.join("crates.js").is_file(),
59 "missing rustdoc crate index"
60 );
61
62 if destination.exists() {
63 fs::remove_dir_all(&destination)?;
64 }
65
66 for path in util::files(source)? {
67 let target = destination.join(path.strip_prefix(source)?);
68
69 fs::create_dir_all(target.parent().context("rustdoc file parent")?)?;
70 fs::copy(path, target)?;
71 }
72
73 fs::write(
74 destination.join("index.html"),
75 "<!doctype html><html lang=\"en\"><meta charset=\"utf-8\">\
76 <title>Rust API</title>\
77 <meta http-equiv=\"refresh\" content=\"0; url=../docs/rust-api.html\">\
78 <a href=\"../docs/rust-api.html\">Rust API reference</a></html>\n",
79 )?;
80
81 Ok(())
82}
83
84pub fn stage(root: &Path, destination: &Path) -> Result<()> {
85 let tracked = Command::new("git")
86 .args(["ls-files", "-z", "--", ":(attr:site)"])
87 .current_dir(root)
88 .output()?;
89 let paths = util::checked(tracked)?;
90
91 fs::create_dir_all(destination)?;
92 fs::copy(root.join("book.toml"), destination.join("book.toml"))?;
93
94 for name in paths.split('\0').filter(|name| !name.is_empty()) {
97 let path = Path::new(name);
98 let target = destination.join(path);
99
100 fs::create_dir_all(target.parent().context("site file parent")?)?;
101 fs::copy(root.join(path), &target)?;
102 }
103
104 Ok(())
105}