mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
Allow installable builds to be installed into a specific path (#10923)
* Pass path to install() It was dirty that it would re-get $HOME there anyway. * Import wcs2osstring * Allow installable builds to use a relocatable tree If you give a path to `--install`, it will install fish into a relocatable tree there, so PATH/share/fish contains the datafiles PATH/bin/fish contains the fish executable PATH/etc/fish is sysconf I am absolutely not sold on that last one - the way I always used sysconfdir is that it is always /etc. This would be easy to fix but should probably also be fixed for "regular" relocatable builds (no idea who uses them). An attempt at #10916 * Move install path into "install/" subdir * Disable --install harder if not installable
This commit is contained in:
parent
b19a467ea6
commit
3dc49d9d93
2 changed files with 76 additions and 16 deletions
|
@ -40,9 +40,11 @@ The following options are available:
|
||||||
**-i** or **--interactive**
|
**-i** or **--interactive**
|
||||||
The shell is interactive.
|
The shell is interactive.
|
||||||
|
|
||||||
**--install**
|
**--install[=PATH]**
|
||||||
When built as self-installable (via cargo), this will unpack fish's datafiles and place them in ~/.local/share/fish/install/.
|
When built as self-installable (via cargo), this will unpack fish's datafiles and place them in ~/.local/share/fish/install/.
|
||||||
Fish will also ask to do this automatically when run interactively.
|
Fish will also ask to do this automatically when run interactively.
|
||||||
|
If PATH is given, fish will install itself into a relocatable directory tree rooted at that path.
|
||||||
|
That means it will install the datafiles to PATH/share/fish and copy itself to PATH/bin/fish.
|
||||||
|
|
||||||
**-l** or **--login**
|
**-l** or **--login**
|
||||||
Act as if invoked as a login shell.
|
Act as if invoked as a login shell.
|
||||||
|
|
|
@ -30,7 +30,7 @@ use fish::{
|
||||||
},
|
},
|
||||||
common::{
|
common::{
|
||||||
escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer,
|
escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer,
|
||||||
str2wcstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
|
str2wcstring, wcs2osstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME,
|
||||||
},
|
},
|
||||||
env::{
|
env::{
|
||||||
environment::{env_init, EnvStack, Environment},
|
environment::{env_init, EnvStack, Environment},
|
||||||
|
@ -80,7 +80,7 @@ const BIN_DIR: &str = env!("BINDIR");
|
||||||
#[cfg(feature = "installable")]
|
#[cfg(feature = "installable")]
|
||||||
// Disable for clippy because otherwise it would require sphinx
|
// Disable for clippy because otherwise it would require sphinx
|
||||||
#[cfg(not(clippy))]
|
#[cfg(not(clippy))]
|
||||||
fn install(confirm: bool) -> bool {
|
fn install(confirm: bool, dir: PathBuf) -> bool {
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
|
@ -96,11 +96,6 @@ fn install(confirm: bool) -> bool {
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::io::{stderr, stdin};
|
use std::io::{stderr, stdin};
|
||||||
let Some(home) = fish::env::get_home() else {
|
|
||||||
FLOG!(error, "Can't find home directory.");
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let dir = PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR);
|
|
||||||
|
|
||||||
// TODO: Translation,
|
// TODO: Translation,
|
||||||
// FLOG?
|
// FLOG?
|
||||||
|
@ -197,7 +192,7 @@ fn install(confirm: bool) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(clippy, not(feature = "installable")))]
|
#[cfg(any(clippy, not(feature = "installable")))]
|
||||||
fn install(_confirm: bool) -> bool {
|
fn install(_confirm: bool, _dir: PathBuf) -> bool {
|
||||||
eprintln!("Fish was built without support for self-installation");
|
eprintln!("Fish was built without support for self-installation");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -300,10 +295,17 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !done {
|
if !done {
|
||||||
// The next check is that we are in a reloctable directory tree
|
// The next check is that we are in a relocatable directory tree
|
||||||
if exec_path.ends_with("bin/fish") {
|
if exec_path.ends_with("bin/fish") {
|
||||||
let base_path = exec_path.parent().unwrap().parent().unwrap();
|
let base_path = exec_path.parent().unwrap().parent().unwrap();
|
||||||
paths = ConfigPaths {
|
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: base_path.join("share/fish"),
|
||||||
sysconf: base_path.join("etc/fish"),
|
sysconf: base_path.join("etc/fish"),
|
||||||
doc: base_path.join("share/doc/fish"),
|
doc: base_path.join("share/doc/fish"),
|
||||||
|
@ -316,6 +318,9 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||||
);
|
);
|
||||||
let base_path = exec_path.parent().unwrap();
|
let base_path = exec_path.parent().unwrap();
|
||||||
paths = ConfigPaths {
|
paths = ConfigPaths {
|
||||||
|
#[cfg(feature = "installable")]
|
||||||
|
data: base_path.join("share/install"),
|
||||||
|
#[cfg(not(feature = "installable"))]
|
||||||
data: base_path.join("share"),
|
data: base_path.join("share"),
|
||||||
sysconf: base_path.join("etc"),
|
sysconf: base_path.join("etc"),
|
||||||
doc: base_path.join("user_doc/html"),
|
doc: base_path.join("user_doc/html"),
|
||||||
|
@ -339,7 +344,8 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
|
||||||
let Some(home) = fish::env::get_home() else {
|
let Some(home) = fish::env::get_home() else {
|
||||||
FLOG!(
|
FLOG!(
|
||||||
error,
|
error,
|
||||||
"Cannot find home directory and will refuse to read configuration"
|
"Cannot find home directory and will refuse to read configuration.\n",
|
||||||
|
"Consider installing into a directory tree with `fish --install=PATH`."
|
||||||
);
|
);
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
@ -421,8 +427,7 @@ fn check_version_file(paths: &ConfigPaths, datapath: &wstr) -> Option<bool> {
|
||||||
{
|
{
|
||||||
// When fish is installable, we write the version to a file,
|
// When fish is installable, we write the version to a file,
|
||||||
// now we check it.
|
// now we check it.
|
||||||
let verfile =
|
let verfile = PathBuf::from(wcs2osstring(datapath)).join("fish-install-version");
|
||||||
PathBuf::from(fish::common::wcs2osstring(datapath)).join("fish-install-version");
|
|
||||||
let version = std::fs::read_to_string(verfile).ok()?;
|
let version = std::fs::read_to_string(verfile).ok()?;
|
||||||
|
|
||||||
return Some(version == fish::BUILD_VERSION);
|
return Some(version == fish::BUILD_VERSION);
|
||||||
|
@ -458,7 +463,7 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
install(true);
|
install(true, PathBuf::from(wcs2osstring(&datapath)));
|
||||||
// We try to go on if installation failed (or was rejected) here
|
// We try to go on if installation failed (or was rejected) here
|
||||||
// If the assets are missing, we will trigger a later error,
|
// If the assets are missing, we will trigger a later error,
|
||||||
// if they are outdated, things will probably (tm) work somewhat.
|
// if they are outdated, things will probably (tm) work somewhat.
|
||||||
|
@ -540,7 +545,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
|
||||||
wopt(L!("no-config"), NoArgument, 'N'),
|
wopt(L!("no-config"), NoArgument, 'N'),
|
||||||
wopt(L!("no-execute"), NoArgument, 'n'),
|
wopt(L!("no-execute"), NoArgument, 'n'),
|
||||||
wopt(L!("print-rusage-self"), NoArgument, RUSAGE_ARG),
|
wopt(L!("print-rusage-self"), NoArgument, RUSAGE_ARG),
|
||||||
wopt(L!("install"), NoArgument, 'I'),
|
wopt(L!("install"), OptionalArgument, 'I'),
|
||||||
wopt(
|
wopt(
|
||||||
L!("print-debug-categories"),
|
L!("print-debug-categories"),
|
||||||
NoArgument,
|
NoArgument,
|
||||||
|
@ -576,7 +581,60 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
|
||||||
'h' => opts.batch_cmds.push("__fish_print_help fish".into()),
|
'h' => opts.batch_cmds.push("__fish_print_help fish".into()),
|
||||||
'i' => opts.is_interactive_session = true,
|
'i' => opts.is_interactive_session = true,
|
||||||
'I' => {
|
'I' => {
|
||||||
install(false);
|
#[cfg(not(feature = "installable"))]
|
||||||
|
eprintln!("Fish was built without support for self-installation");
|
||||||
|
#[cfg(feature = "installable")]
|
||||||
|
if let Some(path) = w.woptarg {
|
||||||
|
// We were given an explicit path.
|
||||||
|
// Install us there as a relocatable install.
|
||||||
|
// That means:
|
||||||
|
// path/bin/fish is the fish binary
|
||||||
|
// path/share/fish/ is the data directory
|
||||||
|
// path/etc/fish is sysconf????
|
||||||
|
use std::fs;
|
||||||
|
let dir = PathBuf::from(wcs2osstring(path));
|
||||||
|
if install(true, dir.join("share/fish/install")) {
|
||||||
|
for sub in &["share/fish/install", "etc/fish", "bin"] {
|
||||||
|
let p = dir.join(sub);
|
||||||
|
let Ok(_) = fs::create_dir_all(p.clone()) else {
|
||||||
|
eprintln!("Creating directory '{}' failed", p.display());
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy ourselves there.
|
||||||
|
let argv0 = OsString::from_vec(wcs2string(&args[0]));
|
||||||
|
let exec_path =
|
||||||
|
get_executable_path(<OsString as AsRef<Path>>::as_ref(&argv0));
|
||||||
|
let binpath = dir.join("bin/fish");
|
||||||
|
if let Ok(exec_path) = exec_path.canonicalize() {
|
||||||
|
if exec_path != binpath {
|
||||||
|
if let Err(err) = std::fs::copy(exec_path, binpath.clone()) {
|
||||||
|
FLOG!(error, "Cannot copy fish to", binpath.display());
|
||||||
|
FLOG!(error, err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"Fish installed in '{}'. Start that from now on.",
|
||||||
|
binpath.display()
|
||||||
|
);
|
||||||
|
// TODO: Reexec fish?
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FLOG!(error, "Cannot copy fish to '%ls'. Please copy the fish binary there manually", binpath.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let paths = Some(determine_config_directory_paths(OsString::from_vec(
|
||||||
|
wcs2string(&args[0]),
|
||||||
|
)));
|
||||||
|
let Some(paths) = paths else {
|
||||||
|
FLOG!(error, "Cannot find config paths");
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
|
install(true, paths.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'l' => opts.is_login = true,
|
'l' => opts.is_login = true,
|
||||||
'N' => {
|
'N' => {
|
||||||
|
|
Loading…
Reference in a new issue