Build man pages in separate crate

Building man pages takes significant time due to Sphinx running for several
seconds, even when no updates are required. Previously, we added custom logic to
avoid calling `sphinx-build` if the inputs to `sphinx-build` had not changed
since a cached timestamp.

By moving this into its own crate, we can tell cargo to rebuild when the input
files changed and unrelated changes will have no effect on this crate. This
allows us to get rid of the custom code for tracking whether to recompile, while
keeping the effect of only calling `sphinx-build` when appropriate.

In order to avoid code duplication, a new `build-helper` crate is added,
which contains some functionality for use in `build.rs`.

Closes #11737
This commit is contained in:
Daniel Rainer
2025-08-20 15:19:20 +02:00
committed by Johannes Altmanninger
parent 32e5fa0c03
commit e96300b08e
8 changed files with 165 additions and 213 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "fish-build-helper"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
[dependencies]
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,26 @@
use std::{
env,
path::{Path, PathBuf},
};
pub fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
std::fs::canonicalize(path).unwrap()
}
pub fn get_repo_root() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
canonicalize(manifest_dir.ancestors().nth(2).unwrap())
}
pub fn get_target_dir() -> PathBuf {
option_env!("CARGO_TARGET_DIR")
.map(canonicalize)
.unwrap_or(get_repo_root().join("target"))
}
// TODO Move this to rsconf
pub fn rebuild_if_paths_changed<P: AsRef<Path>, I: IntoIterator<Item = P>>(paths: I) {
for path in paths {
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
}
}

View File

@@ -0,0 +1,13 @@
[package]
name = "fish-build-man-pages"
edition.workspace = true
rust-version.workspace = true
version = "0.0.0"
repository.workspace = true
[build-dependencies]
fish-build-helper.workspace = true
rsconf.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,82 @@
#[cfg(not(clippy))]
use std::path::Path;
fn main() {
let cargo_target_dir = fish_build_helper::get_target_dir();
let mandir = cargo_target_dir.join("fish-man");
let sec1dir = mandir.join("man1");
// Running `cargo clippy` on a clean build directory panics, because when rust-embed tries to
// embed a directory which does not exist it will panic.
let _ = std::fs::create_dir_all(sec1dir.to_str().unwrap());
#[cfg(not(clippy))]
build_man(&mandir);
}
#[cfg(not(clippy))]
fn build_man(man_dir: &Path) {
use std::{env, process::Command};
let repo_root_dir = fish_build_helper::get_repo_root();
let man_str = man_dir.to_str().unwrap();
let sec1_dir = man_dir.join("man1");
let sec1_str = sec1_dir.to_str().unwrap();
let docsrc_dir = repo_root_dir.join("doc_src");
let docsrc_str = docsrc_dir.to_str().unwrap();
let sphinx_doc_sources = [
repo_root_dir.join("CHANGELOG.rst"),
repo_root_dir.join("CONTRIBUTING.rst"),
docsrc_dir.clone(),
];
fish_build_helper::rebuild_if_paths_changed(sphinx_doc_sources);
let args = &[
"-j", "auto", "-q", "-b", "man", "-c", docsrc_str,
// doctree path - put this *above* the man1 dir to exclude it.
// this is ~6M
"-d", man_str, docsrc_str, sec1_str,
];
let _ = std::fs::create_dir_all(sec1_str);
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
if env::var("FISH_BUILD_DOCS") == Ok("0".to_string()) {
println!("cargo:warning=Skipping man pages because $FISH_BUILD_DOCS is set to 0");
return;
}
// We run sphinx to build the man pages.
// Every error here is fatal so cargo doesn't cache the result
// - if we skipped the docs with sphinx not installed, installing it would not then build the docs.
// That means you need to explicitly set $FISH_BUILD_DOCS=0 (`FISH_BUILD_DOCS=0 cargo install --path .`),
// which is unfortunate - but the docs are pretty important because they're also used for --help.
match Command::new("sphinx-build").args(args).spawn() {
Err(x) if x.kind() == std::io::ErrorKind::NotFound => {
if env::var("FISH_BUILD_DOCS") == Ok("1".to_string()) {
panic!("Could not find sphinx-build to build man pages.\nInstall sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0.");
}
println!("cargo:warning=Cannot find sphinx-build to build man pages.");
println!("cargo:warning=If you install it now you need to run `cargo clean` and rebuild, or set $FISH_BUILD_DOCS=1 explicitly.");
}
Err(x) => {
// Another error - permissions wrong etc
panic!("Error starting sphinx-build to build man pages: {:?}", x);
}
Ok(mut x) => match x.wait() {
Err(err) => {
panic!(
"Error waiting for sphinx-build to build man pages: {:?}",
err
);
}
Ok(out) => {
if !out.success() {
panic!("sphinx-build failed to build the man pages.");
}
}
},
}
}

View File

@@ -0,0 +1 @@