Make LOCALEDIR relocatable as well

As explained in c3740b85be (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 bf65b9e3a7 (Change `gettext` paths to be relocatable (#11195),
2025-03-30).
This commit is contained in:
Johannes Altmanninger
2025-06-13 11:40:48 +02:00
parent 052fc18db9
commit 6fd0025f38
3 changed files with 43 additions and 15 deletions

View File

@@ -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<ConfigPaths> = Lazy::new(|| {
// Read the current executable and follow all symlinks to it.
@@ -51,6 +52,7 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> 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<Path>) -> 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<Path>) -> 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<Path>) -> 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<Path>) -> 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<Path>) -> 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

9
src/env/var.rs vendored
View File

@@ -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<PathBuf>, // 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<PathBuf>, // e.g., /usr/local/bin
pub locale: Option<PathBuf>, // e.g., /usr/local/share/locale
}
/// A collection of status and pipestatus.

View File

@@ -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);
}