Do not embed man pages in CMake builds

Commit 0709e4be8b (Use standalone code paths by default, 2025-10-26)
mainly wanted to embed internal functions to unbreak upgrade scenarios.

Embedding man pages in CMake has small-ish disadvantages:
1. extra space usage
2. less discoverability
3. a "cyclic" dependency:
   1. "sphinx-docs" depends on "fish_indent"
   2. "fish_indent" via "crates/build-man-pages" depends on "doc_src/".
   So every "touch doc_src/foo.rst && ninja -Cbuild sphinx-docs"
   re-builds fish, just to re-run sphinx-build.

The significant one is number 3.  It can be worked around by running
sphinx-build with stale "fish_indent" but I don't think we want to
do that.

Let's backtrack a little by stopping embedding man pages in CMake
builds; use the on-disk man pages (which we still install).

The remaining "regression" from 0709e4be8b is that "ninja -Cbuild
fish" needs to rebuild whenever anything in "share/" changes.  I don't
know if that's also annoying?

Since man pages for "build/fish" are not in share/, expose the exact
path as $__fish_man_dir.
This commit is contained in:
Johannes Altmanninger
2025-11-20 10:34:17 +01:00
parent 2a7c57035e
commit a1baf97f54
14 changed files with 86 additions and 39 deletions

View File

@@ -86,7 +86,7 @@ bitflags.workspace = true
cfg-if.workspace = true cfg-if.workspace = true
errno.workspace = true errno.workspace = true
fish-build-helper.workspace = true fish-build-helper.workspace = true
fish-build-man-pages.workspace = true fish-build-man-pages = { workspace = true, optional = true }
fish-gettext-extraction = { workspace = true, optional = true } fish-gettext-extraction = { workspace = true, optional = true }
fish-gettext-maps = { workspace = true, optional = true } fish-gettext-maps = { workspace = true, optional = true }
fish-printf.workspace = true fish-printf.workspace = true
@@ -150,8 +150,9 @@ name = "fish_key_reader"
path = "src/bin/fish_key_reader.rs" path = "src/bin/fish_key_reader.rs"
[features] [features]
default = ["localize-messages"] default = ["embed-manpages", "localize-messages"]
benchmark = [] benchmark = []
embed-manpages = ["dep:fish-build-man-pages"]
# Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at # Enable gettext localization at runtime. Requires the `msgfmt` tool to generate catalog data at
# build time. # build time.
localize-messages = ["dep:phf", "dep:fish-gettext-maps"] localize-messages = ["dep:phf", "dep:fish-gettext-maps"]

View File

@@ -184,13 +184,14 @@ You can also install Sphinx another way and drop the ``uv run --no-managed-pytho
This will place standalone binaries in ``~/.cargo/bin/``, but you can move them wherever you want. This will place standalone binaries in ``~/.cargo/bin/``, but you can move them wherever you want.
To disable translations, disable the ``localize-messages`` feature by passing ``--no-default-features`` to cargo. To disable translations, disable the ``localize-messages`` feature by passing ``--no-default-features --features=embed-manpages`` to cargo.
You can also link this build statically (but not against glibc) and move it to other computers. You can also link this build statically (but not against glibc) and move it to other computers.
Here are the remaining advantages of a full installation, as currently done by CMake: Here are the remaining advantages of a full installation, as currently done by CMake:
- Man pages like ``fish(1)`` installed in standard locations, easily accessible from outside fish. - Man pages like ``fish(1)`` installed in standard locations, easily accessible from outside fish.
- Separate files for builtins (e.g. ``$PREFIX/share/fish/man/man1/abbr.1``).
- A local copy of the HTML documentation, typically accessed via the ``help`` fish function. - A local copy of the HTML documentation, typically accessed via the ``help`` fish function.
In Cargo builds, ``help`` will redirect to `<https://fishshell.com/docs/current/>`__ In Cargo builds, ``help`` will redirect to `<https://fishshell.com/docs/current/>`__
- Ability to use our CMake options extra_functionsdir, extra_completionsdir and extra_confdir, - Ability to use our CMake options extra_functionsdir, extra_completionsdir and extra_confdir,

View File

@@ -70,8 +70,7 @@ function __fish_complete_man
# Fish commands are not given by apropos # Fish commands are not given by apropos
if not set -ql exclude_fish_commands if not set -ql exclude_fish_commands
__fish_data_list_files man/man1 | string join \n -- (__fish_man1_pages)\t'1: fish command'
string replace -rf '.*/([^/]+)\.1(\.gz)?$' '$1\t1: fish command'
end end
else else
return 1 return 1

View File

@@ -5,8 +5,8 @@ function __fish_data_with_directory
set -l cmd $argv[3..] set -l cmd $argv[3..]
set -l temp set -l temp
set -l directory_ref set -l directory_ref
if false if test $relative_directory = man/man1 && not __fish_tried_to_embed_manpages
set directory_ref $__fish_data_dir/$relative_directory set directory_ref $__fish_man_dir/man1
else else
set temp (__fish_mktemp_relative -d fish-data) set temp (__fish_mktemp_relative -d fish-data)
or return or return

View File

@@ -0,0 +1,10 @@
# localization: skip(private)
function __fish_man1_pages
if __fish_tried_to_embed_manpages
status list-files man/man1
else
files=$__fish_man_dir/man1/* string join -- \n $files
end |
path basename |
string replace -r -- '.1(.gz)?$' ''
end

View File

@@ -1,7 +1,5 @@
# localization: skip(private) # localization: skip(private)
function __fish_print_commands --description "Print a list of documented fish commands" function __fish_print_commands --description "Print a list of documented fish commands"
__fish_data_list_files man/man1 | __fish_man1_pages |
string replace -r '.*/' '' |
string replace -r '.1(.gz)?$' '' |
string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$' string match -rv '^fish-(?:changelog|completions|doc|tutorial|faq|for-bash-users|interactive|language|releasenotes|terminal-compatibility)$'
end end

View File

@@ -0,0 +1,4 @@
# localization: skip(private)
function __fish_tried_to_embed_manpages
status build-info | string match -rq '(\s)embed-manpages(\s|$)'
end

View File

@@ -6,17 +6,16 @@ if not command -qs man
end end
function man function man
# If we have an embedded page, reuse a function that happens to do the
# right thing.
if not set -q argv[2] &&
status list-files "man/man1/$(__fish_canonicalize_builtin $argv).1" &>/dev/null
__fish_print_help $argv[1]
return
end
set -l manpath set -l manpath
if false if __fish_tried_to_embed_manpages
and set -l fish_manpath (path filter -d $__fish_data_dir/man) # If we have an embedded page, reuse a function that happens to do
# the right thing.
if not set -q argv[2] &&
status list-files "man/man1/$(__fish_canonicalize_builtin $argv).1" &>/dev/null
__fish_print_help $argv[1]
return
end
else if set -l fish_manpath (path filter -d $__fish_man_dir)
# Prepend fish's man directory if available. # Prepend fish's man directory if available.
# Work around the "builtin" manpage that everything symlinks to, # Work around the "builtin" manpage that everything symlinks to,

View File

@@ -337,11 +337,16 @@ fn parse_cmd_opts(
#[folder = "user_doc/man/man1"] #[folder = "user_doc/man/man1"]
#[prefix = "man/man1/"] #[prefix = "man/man1/"]
struct Docs; struct Docs;
} else { } else if #[cfg(feature = "embed-manpages")] {
#[derive(RustEmbed)] #[derive(RustEmbed)]
#[folder = "$FISH_RESOLVED_BUILD_DIR/fish-man/man1"] #[folder = "$FISH_RESOLVED_BUILD_DIR/fish-man/man1"]
#[prefix = "man/man1/"] #[prefix = "man/man1/"]
struct Docs; struct Docs;
} else {
#[derive(RustEmbed)]
#[folder = "$FISH_RESOLVED_BUILD_DIR"]
#[include = ""]
struct Docs;
} }
); );
@@ -601,6 +606,8 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
streams.out.appendln(profile); streams.out.appendln(profile);
streams.out.append(L!("Features: ")); streams.out.append(L!("Features: "));
let features: &[&str] = &[ let features: &[&str] = &[
#[cfg(feature = "embed-manpages")]
"embed-manpages",
#[cfg(feature = "localize-messages")] #[cfg(feature = "localize-messages")]
"localize-messages", "localize-messages",
#[cfg(target_feature = "crt-static")] #[cfg(target_feature = "crt-static")]

View File

@@ -12,6 +12,7 @@ pub struct ConfigPaths {
pub sysconf: PathBuf, // e.g., /usr/local/etc pub sysconf: PathBuf, // e.g., /usr/local/etc
pub bin: Option<PathBuf>, // e.g., /usr/local/bin pub bin: Option<PathBuf>, // e.g., /usr/local/bin
pub data: Option<PathBuf>, // e.g., /usr/local/share pub data: Option<PathBuf>, // e.g., /usr/local/share
pub man: Option<PathBuf>, // e.g., /usr/local/share/fish/man
pub doc: Option<PathBuf>, // e.g., /usr/local/share/doc/fish pub doc: Option<PathBuf>, // e.g., /usr/local/share/doc/fish
} }
@@ -50,19 +51,24 @@ macro_rules! log_optional_path {
} }
log_optional_path!(bin); log_optional_path!(bin);
log_optional_path!(data); log_optional_path!(data);
log_optional_path!(man);
log_optional_path!(doc); log_optional_path!(doc);
paths paths
} }
fn from_exec_path(unresolved_exec_path: &'static FishPath) -> Self { fn from_exec_path(unresolved_exec_path: &'static FishPath) -> Self {
let default_layout = |exec_path_parent: Option<&Path>| Self { let default_layout = |exec_path_parent: Option<&Path>| {
sysconf: PathBuf::from(SYSCONF_DIR).join("fish"), let data = option_env!("DATADIR").map(|p| PathBuf::from(p).join("fish"));
bin: option_env!("BINDIR") Self {
.map(PathBuf::from) sysconf: PathBuf::from(SYSCONF_DIR).join("fish"),
// N.B. the argument may be non-canonical here. bin: option_env!("BINDIR")
.or_else(|| exec_path_parent.map(|p| p.to_owned())), .map(PathBuf::from)
data: option_env!("DATADIR").map(|p| PathBuf::from(p).join("fish")), // N.B. the argument may be non-canonical here.
doc: option_env!("DOCDIR").map(PathBuf::from), .or_else(|| exec_path_parent.map(|p| p.to_owned())),
data: data.clone(),
man: data.map(|data| data.join("man")),
doc: option_env!("DOCDIR").map(PathBuf::from),
}
}; };
let exec_path = { let exec_path = {
@@ -113,10 +119,12 @@ fn from_exec_path(unresolved_exec_path: &'static FishPath) -> Self {
} { } {
FLOG!(config, "Running from relocatable tree"); FLOG!(config, "Running from relocatable tree");
let prefix = exec_path_parent.parent().unwrap(); let prefix = exec_path_parent.parent().unwrap();
let data = prefix.join("share/fish");
Self { Self {
sysconf: prefix.join("etc/fish"), sysconf: prefix.join("etc/fish"),
bin: Some(exec_path_parent.to_owned()), bin: Some(exec_path_parent.to_owned()),
data: Some(prefix.join("share/fish")), data: Some(data.clone()),
man: Some(data.join("man")),
doc: Some(prefix.join("share/doc/fish")), doc: Some(prefix.join("share/doc/fish")),
} }
} else if exec_path.starts_with(BUILD_DIR) { } else if exec_path.starts_with(BUILD_DIR) {
@@ -127,17 +135,21 @@ fn from_exec_path(unresolved_exec_path: &'static FishPath) -> Self {
workspace_root.display() workspace_root.display()
), ),
); );
let user_doc_join = |dir| {
if cfg!(use_prebuilt_docs) {
Some(workspace_root)
} else {
cfg!(using_cmake).then_some(Path::new(BUILD_DIR))
}
.map(|path| path.join("user_doc").join(dir))
};
// If we're in Cargo's target directory or in CMake's build directory, use the source files. // If we're in Cargo's target directory or in CMake's build directory, use the source files.
Self { Self {
sysconf: workspace_root.join("etc"), sysconf: workspace_root.join("etc"),
bin: Some(exec_path_parent.to_owned()), bin: Some(exec_path_parent.to_owned()),
data: Some(workspace_root.join("share")), data: Some(workspace_root.join("share")),
doc: if cfg!(use_prebuilt_docs) { man: user_doc_join("man"),
Some(workspace_root) doc: user_doc_join("html"),
} else {
cfg!(using_cmake).then_some(Path::new(BUILD_DIR))
}
.map(|p| p.join("user_doc/html")),
} }
} else { } else {
FLOG!( FLOG!(

View File

@@ -418,6 +418,7 @@ fn get_pwd_slash(&self) -> WString {
const FISH_DATADIR_VAR: &wstr = L!("__fish_data_dir"); const FISH_DATADIR_VAR: &wstr = L!("__fish_data_dir");
const FISH_SYSCONFDIR_VAR: &wstr = L!("__fish_sysconf_dir"); const FISH_SYSCONFDIR_VAR: &wstr = L!("__fish_sysconf_dir");
const FISH_HELPDIR_VAR: &wstr = L!("__fish_help_dir"); const FISH_HELPDIR_VAR: &wstr = L!("__fish_help_dir");
const FISH_MANDIR_VAR: &wstr = L!("__fish_man_dir");
const FISH_BIN_DIR: &wstr = L!("__fish_bin_dir"); const FISH_BIN_DIR: &wstr = L!("__fish_bin_dir");
const FISH_CONFIG_DIR: &wstr = L!("__fish_config_dir"); const FISH_CONFIG_DIR: &wstr = L!("__fish_config_dir");
const FISH_USER_DATA_DIR: &wstr = L!("__fish_user_data_dir"); const FISH_USER_DATA_DIR: &wstr = L!("__fish_user_data_dir");
@@ -647,6 +648,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
set_path(FISH_BIN_DIR, paths.bin.as_ref()); set_path(FISH_BIN_DIR, paths.bin.as_ref());
set_path(FISH_DATADIR_VAR, paths.data.as_ref()); set_path(FISH_DATADIR_VAR, paths.data.as_ref());
set_path(FISH_MANDIR_VAR, paths.man.as_ref());
set_path(FISH_HELPDIR_VAR, paths.doc.as_ref()); set_path(FISH_HELPDIR_VAR, paths.doc.as_ref());
} }

View File

@@ -0,0 +1,13 @@
# RUN: %fish %s
set -l value (string join , \
embed-manpages="$(__fish_tried_to_embed_manpages && echo true || echo false)" \
"build-system=$(status build-info | string replace -rf 'Build system: (.*)' '$1')"
)
switch $value
case embed-manpages=true,build-system=Cargo
case embed-manpages=false,build-system=CMake
case '*'
echo Expected embedded man pages to be detected as true iff using Cargo
echo got: $value
end

View File

@@ -11,6 +11,7 @@
# NOTE: When our executable is located outside the build directory, these are different. # NOTE: When our executable is located outside the build directory, these are different.
# CHECKERR: config: paths.data: {{.*}}/share # CHECKERR: config: paths.data: {{.*}}/share
# CHECKERR: config: paths.man: {{.*/user_doc/man|\|not found\|}}
# CHECKERR: config: paths.doc: {{.*/user_doc/html|\|not found\|}} # CHECKERR: config: paths.doc: {{.*/user_doc/html|\|not found\|}}
# CHECKERR: config: sourcing {{.+}}/etc/config.fish # CHECKERR: config: sourcing {{.+}}/etc/config.fish

View File

@@ -1,12 +1,12 @@
# RUN: %fish %s # RUN: %fish %s
# Override the test-override again.
__fish_data_with_file functions/__fish_print_help.fish source
# REQUIRES: command -v sphinx-build # REQUIRES: command -v sphinx-build
# REQUIRES: command -v man # REQUIRES: command -v man
# REQUIRES: test "$FISH_BUILD_DOCS" != "0" # REQUIRES: test "$FISH_BUILD_DOCS" != "0"
# Override the test-override again.
__fish_data_with_file functions/__fish_print_help.fish source
set -l deroff col -b -p -x set -l deroff col -b -p -x
set -lx MANWIDTH 80 set -lx MANWIDTH 80