From 052fc18db92a0616efcdb9db8805c22bd72f844f Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 13 Jun 2025 11:19:02 +0200 Subject: [PATCH] Extract config path module. NFC This is the "code movement" part of bf65b9e3 ("Change `gettext` paths to be relocatable (#11195)"). While at it, fix some warnings. --- src/bin/fish.rs | 164 +++++----------------------------------- src/env/config_paths.rs | 147 +++++++++++++++++++++++++++++++++++ src/env/mod.rs | 2 + 3 files changed, 168 insertions(+), 145 deletions(-) create mode 100644 src/env/config_paths.rs diff --git a/src/bin/fish.rs b/src/bin/fish.rs index 9fee345cf..2f3fc928b 100644 --- a/src/bin/fish.rs +++ b/src/bin/fish.rs @@ -21,6 +21,8 @@ #![allow(unstable_name_collisions)] #![allow(clippy::uninlined_format_args)] +#[cfg(feature = "installable")] +use fish::common::get_executable_path; #[allow(unused_imports)] use fish::future::IsSomeAnd; use fish::{ @@ -29,12 +31,12 @@ BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, }, common::{ - escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer, - str2wcstring, wcs2osstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, + escape, save_term_foreground_process_group, scoped_push_replacer, str2wcstring, wcs2string, + PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, }, env::{ environment::{env_init, EnvStack, Environment}, - ConfigPaths, EnvMode, Statuses, + ConfigPaths, EnvMode, Statuses, CONFIG_PATHS, }, eprintf, event::{self, Event}, @@ -65,22 +67,16 @@ use std::fs::File; use std::mem::MaybeUninit; use std::os::unix::prelude::*; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::Arc; use std::{env, ops::ControlFlow}; -const DOC_DIR: &str = env!("DOCDIR"); -const DATA_DIR: &str = env!("DATADIR"); -const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR"); -const SYSCONF_DIR: &str = env!("SYSCONFDIR"); -const BIN_DIR: &str = env!("BINDIR"); - #[cfg(feature = "installable")] // Disable for clippy because otherwise it would require sphinx #[cfg(not(clippy))] -fn install(confirm: bool, dir: PathBuf) -> bool { +fn install(confirm: bool, dir: &PathBuf) -> bool { use rust_embed::RustEmbed; #[derive(RustEmbed)] @@ -192,7 +188,8 @@ fn install(confirm: bool, dir: PathBuf) -> bool { } #[cfg(any(clippy, not(feature = "installable")))] -fn install(_confirm: bool, _dir: PathBuf) -> bool { +#[allow(dead_code)] +fn install(_confirm: bool, _dir: &PathBuf) -> bool { eprintln!("Fish was built without support for self-installation"); return false; } @@ -264,128 +261,6 @@ fn print_rusage_self() { eprintln!(" signals: {signals}"); } -fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { - // PORTING: why is this not just an associated method on ConfigPaths? - - let mut paths = ConfigPaths::default(); - let mut done = false; - let exec_path = get_executable_path(argv0.as_ref()); - if let Ok(exec_path) = exec_path.canonicalize() { - FLOG!( - config, - format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref()) - ); - // TODO: we should determine program_name from argv0 somewhere in this file - - // Detect if we're running right out of the CMAKE build directory - if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) { - let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - FLOG!( - config, - "Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n", - manifest_dir.display() - ); - done = true; - paths = ConfigPaths { - data: manifest_dir.join("share"), - sysconf: manifest_dir.join("etc"), - doc: manifest_dir.join("user_doc/html"), - bin: Some(exec_path.parent().unwrap().to_owned()), - } - } - - if !done { - // The next check is that we are in a relocatable directory tree - if exec_path.ends_with("bin/fish") { - let base_path = exec_path.parent().unwrap().parent().unwrap(); - paths = ConfigPaths { - // One obvious path is ~/.local (with fish in ~/.local/bin/). - // If we picked ~/.local/share/fish as our data path, - // we would install there and erase history. - // So let's isolate us a bit more. - #[cfg(feature = "installable")] - data: base_path.join("share/fish/install"), - #[cfg(not(feature = "installable"))] - data: base_path.join("share/fish"), - sysconf: base_path.join("etc/fish"), - doc: base_path.join("share/doc/fish"), - bin: Some(base_path.join("bin")), - } - } else if exec_path.ends_with("fish") { - FLOG!( - config, - "'fish' not in a 'bin/', trying paths relative to source tree" - ); - let base_path = exec_path.parent().unwrap(); - paths = ConfigPaths { - #[cfg(feature = "installable")] - data: base_path.join("share/install"), - #[cfg(not(feature = "installable"))] - data: base_path.join("share"), - sysconf: base_path.join("etc"), - doc: base_path.join("user_doc/html"), - bin: Some(base_path.to_path_buf()), - } - } - - if paths.data.exists() && paths.sysconf.exists() { - // The docs dir may not exist; in that case fall back to the compiled in path. - if !paths.doc.exists() { - paths.doc = PathBuf::from(DOC_DIR); - } - done = true; - } - } - } - - if !done { - // Fall back to what got compiled in. - let data = if cfg!(feature = "installable") { - let Some(home) = fish::env::get_home() else { - FLOG!( - error, - "Cannot find home directory and will refuse to read configuration.\n", - "Consider installing into a directory tree with `fish --install=PATH`." - ); - return paths; - }; - - PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR) - } else { - Path::new(DATA_DIR).join(DATA_DIR_SUBDIR) - }; - let bin = if cfg!(feature = "installable") { - exec_path.parent().map(|x| x.to_path_buf()) - } else { - Some(PathBuf::from(BIN_DIR)) - }; - - FLOG!(config, "Using compiled in paths:"); - paths = ConfigPaths { - data, - sysconf: Path::new(SYSCONF_DIR).join("fish"), - doc: DOC_DIR.into(), - bin, - } - } - - FLOGF!( - config, - "determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \ - %ls\npaths.doc: %ls\npaths.bin: %ls", - paths.data.display().to_string(), - paths.sysconf.display().to_string(), - paths.doc.display().to_string(), - paths - .bin - .clone() - .map(|x| x.display().to_string()) - .unwrap_or("|not found|".to_string()), - ); - - paths -} - // Source the file config.fish in the given directory. // Returns true if successful, false if not. fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool { @@ -418,6 +293,7 @@ fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool { #[cfg(feature = "installable")] fn check_version_file(paths: &ConfigPaths, datapath: &wstr) -> Option { + use crate::common::wcs2osstring; // (false-positive, is_none_or is a backport, this builds with 1.70) #[allow(clippy::incompatible_msrv)] if paths @@ -463,7 +339,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) { ); } - install(true, PathBuf::from(wcs2osstring(&datapath))); + install(true, &PathBuf::from(wcs2osstring(&datapath))); // We try to go on if installation failed (or was rejected) here // If the assets are missing, we will trigger a later error, // if they are outdated, things will probably (tm) work somewhat. @@ -592,8 +468,9 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow ControlFlow opts.is_login = true, @@ -817,13 +692,12 @@ fn throwing_main() -> i32 { save_term_foreground_process_group(); } + let mut paths: Option<&ConfigPaths> = None; // If we're not executing, there's no need to find the config. - let paths: Option = if !opts.no_exec { - let paths = Some(determine_config_directory_paths(OsString::from_vec( - wcs2string(&args[0]), - ))); + if !opts.no_exec { + paths = Some(&*CONFIG_PATHS); env_init( - paths.as_ref(), + paths, /* do uvars */ !opts.no_config, /* default paths */ opts.no_config, ); diff --git a/src/env/config_paths.rs b/src/env/config_paths.rs new file mode 100644 index 000000000..c05f766f2 --- /dev/null +++ b/src/env/config_paths.rs @@ -0,0 +1,147 @@ +use super::ConfigPaths; +use crate::env; +use crate::{common::get_executable_path, FLOG, FLOGF}; +use once_cell::sync::Lazy; +use std::path::{Path, PathBuf}; + +const DOC_DIR: &str = env!("DOCDIR"); +const DATA_DIR: &str = env!("DATADIR"); +const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR"); +const SYSCONF_DIR: &str = env!("SYSCONFDIR"); +const BIN_DIR: &str = env!("BINDIR"); + +pub static CONFIG_PATHS: Lazy = Lazy::new(|| { + // Read the current executable and follow all symlinks to it. + // OpenBSD has issues with `std::env::current_exe`, see gh-9086 and + // https://github.com/rust-lang/rust/issues/60560 + let argv0 = PathBuf::from(std::env::args().next().unwrap()); + let argv0 = if argv0.exists() { + argv0 + } else { + std::env::current_exe().unwrap_or(argv0) + }; + let argv0 = argv0.canonicalize().unwrap_or(argv0); + determine_config_directory_paths(argv0) +}); + +fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { + // PORTING: why is this not just an associated method on ConfigPaths? + + let mut paths = ConfigPaths::default(); + let mut done = false; + let exec_path = get_executable_path(argv0.as_ref()); + if let Ok(exec_path) = exec_path.canonicalize() { + FLOG!( + config, + format!("exec_path: {:?}, argv[0]: {:?}", exec_path, argv0.as_ref()) + ); + // TODO: we should determine program_name from argv0 somewhere in this file + + // Detect if we're running right out of the CMAKE build directory + if exec_path.starts_with(env!("CARGO_MANIFEST_DIR")) { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + FLOG!( + config, + "Running out of target directory, using paths relative to CARGO_MANIFEST_DIR:\n", + manifest_dir.display() + ); + done = true; + paths = ConfigPaths { + data: manifest_dir.join("share"), + sysconf: manifest_dir.join("etc"), + doc: manifest_dir.join("user_doc/html"), + bin: Some(exec_path.parent().unwrap().to_owned()), + } + } + + if !done { + // The next check is that we are in a relocatable directory tree + if exec_path.ends_with("bin/fish") { + let base_path = exec_path.parent().unwrap().parent().unwrap(); + paths = ConfigPaths { + // One obvious path is ~/.local (with fish in ~/.local/bin/). + // If we picked ~/.local/share/fish as our data path, + // we would install there and erase history. + // So let's isolate us a bit more. + #[cfg(feature = "installable")] + data: base_path.join("share/fish/install"), + #[cfg(not(feature = "installable"))] + data: base_path.join("share/fish"), + sysconf: base_path.join("etc/fish"), + doc: base_path.join("share/doc/fish"), + bin: Some(base_path.join("bin")), + } + } else if exec_path.ends_with("fish") { + FLOG!( + config, + "'fish' not in a 'bin/', trying paths relative to source tree" + ); + let base_path = exec_path.parent().unwrap(); + paths = ConfigPaths { + #[cfg(feature = "installable")] + data: base_path.join("share/install"), + #[cfg(not(feature = "installable"))] + data: base_path.join("share"), + sysconf: base_path.join("etc"), + doc: base_path.join("user_doc/html"), + bin: Some(base_path.to_path_buf()), + } + } + + if paths.data.exists() && paths.sysconf.exists() { + // The docs dir may not exist; in that case fall back to the compiled in path. + if !paths.doc.exists() { + paths.doc = PathBuf::from(DOC_DIR); + } + done = true; + } + } + } + + if !done { + // Fall back to what got compiled in. + let data = if cfg!(feature = "installable") { + let Some(home) = env::get_home() else { + FLOG!( + error, + "Cannot find home directory and will refuse to read configuration.\n", + "Consider installing into a directory tree with `fish --install=PATH`." + ); + return paths; + }; + + PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR) + } else { + Path::new(DATA_DIR).join(DATA_DIR_SUBDIR) + }; + let bin = if cfg!(feature = "installable") { + exec_path.parent().map(|x| x.to_path_buf()) + } else { + Some(PathBuf::from(BIN_DIR)) + }; + + FLOG!(config, "Using compiled in paths:"); + paths = ConfigPaths { + data, + sysconf: Path::new(SYSCONF_DIR).join("fish"), + doc: DOC_DIR.into(), + bin, + } + } + + FLOGF!( + config, + "determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \ + %ls\npaths.doc: %ls\npaths.bin: %ls", + paths.data.display().to_string(), + paths.sysconf.display().to_string(), + paths.doc.display().to_string(), + paths + .bin + .clone() + .map(|x| x.display().to_string()) + .unwrap_or("|not found|".to_string()), + ); + + paths +} diff --git a/src/env/mod.rs b/src/env/mod.rs index 3f7f7213a..eea119918 100644 --- a/src/env/mod.rs +++ b/src/env/mod.rs @@ -1,6 +1,8 @@ +mod config_paths; pub mod environment; mod environment_impl; pub mod var; +pub use config_paths::CONFIG_PATHS; use crate::common::ToCString; pub use environment::*;