From 6fd0025f3891d78aa67a640c775bb48f0fa572d7 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 13 Jun 2025 11:40:48 +0200 Subject: [PATCH] Make LOCALEDIR relocatable as well As explained in c3740b85be4 (config_paths: fix compiled-in locale dir, 2025-06-12), fish is "relocatable", i.e. "mv /usr/ /usr2/" will leave "/usr2/bin/fish" fully functional. There is one exception: for LOCALEDIR we always use the path determined at compile time. This seems wrong; let's use the same relocatable-logic as for other paths. Inspired by bf65b9e3a74 (Change `gettext` paths to be relocatable (#11195), 2025-03-30). --- src/env/config_paths.rs | 38 +++++++++++++++++++++++++++++--------- src/env/var.rs | 9 +++++---- src/wutil/gettext.rs | 11 +++++++++-- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/env/config_paths.rs b/src/env/config_paths.rs index c05f766f2..47851b262 100644 --- a/src/env/config_paths.rs +++ b/src/env/config_paths.rs @@ -9,6 +9,7 @@ const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR"); const SYSCONF_DIR: &str = env!("SYSCONFDIR"); const BIN_DIR: &str = env!("BINDIR"); +const LOCALE_DIR: &str = env!("LOCALEDIR"); pub static CONFIG_PATHS: Lazy = Lazy::new(|| { // Read the current executable and follow all symlinks to it. @@ -51,6 +52,7 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { sysconf: manifest_dir.join("etc"), doc: manifest_dir.join("user_doc/html"), bin: Some(exec_path.parent().unwrap().to_owned()), + locale: Some(manifest_dir.join("share/locale")), } } @@ -58,18 +60,21 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { // 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(); + #[cfg(feature = "installable")] + let data = base_path.join("share/fish/install"); + #[cfg(not(feature = "installable"))] + let data = base_path.join("share/fish"); + let locale = Some(data.join("locale")); 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"), + data, sysconf: base_path.join("etc/fish"), doc: base_path.join("share/doc/fish"), bin: Some(base_path.join("bin")), + locale, } } else if exec_path.ends_with("fish") { FLOG!( @@ -77,14 +82,18 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { "'fish' not in a 'bin/', trying paths relative to source tree" ); let base_path = exec_path.parent().unwrap(); + #[cfg(feature = "installable")] + let data = base_path.join("share/install"); + #[cfg(not(feature = "installable"))] + let data = base_path.join("share"); + let locale = Some(data.join("locale")); + paths = ConfigPaths { - #[cfg(feature = "installable")] - data: base_path.join("share/install"), - #[cfg(not(feature = "installable"))] - data: base_path.join("share"), + data, sysconf: base_path.join("etc"), doc: base_path.join("user_doc/html"), bin: Some(base_path.to_path_buf()), + locale, } } @@ -119,6 +128,11 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { } else { Some(PathBuf::from(BIN_DIR)) }; + let locale = if cfg!(feature = "installable") { + None + } else { + Some(PathBuf::from(LOCALE_DIR)) + }; FLOG!(config, "Using compiled in paths:"); paths = ConfigPaths { @@ -126,13 +140,14 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { sysconf: Path::new(SYSCONF_DIR).join("fish"), doc: DOC_DIR.into(), bin, + locale, } } FLOGF!( config, "determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: \ - %ls\npaths.doc: %ls\npaths.bin: %ls", + %ls\npaths.doc: %ls\npaths.bin: %ls\npaths.locale: %ls", paths.data.display().to_string(), paths.sysconf.display().to_string(), paths.doc.display().to_string(), @@ -141,6 +156,11 @@ fn determine_config_directory_paths(argv0: impl AsRef) -> ConfigPaths { .clone() .map(|x| x.display().to_string()) .unwrap_or("|not found|".to_string()), + paths + .locale + .clone() + .map(|x| x.display().to_string()) + .unwrap_or("|not found|".to_string()), ); paths diff --git a/src/env/var.rs b/src/env/var.rs index 9e8f10e62..1eefb9905 100644 --- a/src/env/var.rs +++ b/src/env/var.rs @@ -50,10 +50,11 @@ fn from(val: EnvMode) -> Self { /// env_init. #[derive(Default)] pub struct ConfigPaths { - pub data: PathBuf, // e.g., /usr/local/share - pub sysconf: PathBuf, // e.g., /usr/local/etc - pub doc: PathBuf, // e.g., /usr/local/share/doc/fish - pub bin: Option, // e.g., /usr/local/bin + pub data: PathBuf, // e.g., /usr/local/share + pub sysconf: PathBuf, // e.g., /usr/local/etc + pub doc: PathBuf, // e.g., /usr/local/share/doc/fish + pub bin: Option, // e.g., /usr/local/bin + pub locale: Option, // e.g., /usr/local/share/locale } /// A collection of status and pipestatus. diff --git a/src/wutil/gettext.rs b/src/wutil/gettext.rs index ab968ed34..4f934c282 100644 --- a/src/wutil/gettext.rs +++ b/src/wutil/gettext.rs @@ -1,11 +1,15 @@ use std::collections::HashMap; use std::ffi::CString; +use std::os::unix::ffi::OsStrExt; use std::sync::Mutex; -use crate::common::{charptr2wcstring, truncate_at_nul, wcs2zstring, PACKAGE_NAME}; #[cfg(test)] use crate::tests::prelude::*; use crate::wchar::prelude::*; +use crate::{ + common::{charptr2wcstring, truncate_at_nul, wcs2zstring, PACKAGE_NAME}, + env::CONFIG_PATHS, +}; use errno::{errno, set_errno}; use once_cell::sync::{Lazy, OnceCell}; @@ -47,8 +51,11 @@ pub fn fish_textdomain(_domainname: &CStr) -> *mut c_char { // Really init wgettext. fn wgettext_really_init() { + let Some(ref localepath) = CONFIG_PATHS.locale else { + return; + }; let package_name = CString::new(PACKAGE_NAME).unwrap(); - let localedir = CString::new(env!("LOCALEDIR")).unwrap(); + let localedir = CString::new(localepath.as_os_str().as_bytes()).unwrap(); fish_bindtextdomain(&package_name, &localedir); fish_textdomain(&package_name); }