Use directories for autoloading (#13382)

fixes https://github.com/nushell/nushell/issues/13378

# Description

This PR tries to improve usage of system APIs to determine the location
of vendored autoload files.

# User-Facing Changes
The paths listed in #13180 and #13217 are changing. This has not been
part of a release yet, so arguably the user facing changes are only to
unreleased features anyway.

# Tests + Formatting
Haven't done, but if someone wants to help me here, I'm open to doing
it. I just don't know how to properly test this.

# After Submitting
This commit is contained in:
Jan Christian Grünhage 2024-07-19 12:47:07 +02:00 committed by GitHub
parent e281c03403
commit 4665323bb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 107 additions and 51 deletions

3
Cargo.lock generated
View File

@ -3344,6 +3344,8 @@ dependencies = [
"chrono", "chrono",
"chrono-humanize", "chrono-humanize",
"convert_case", "convert_case",
"dirs",
"dirs-sys",
"fancy-regex", "fancy-regex",
"indexmap", "indexmap",
"log", "log",
@ -3367,6 +3369,7 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"typetag", "typetag",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]

View File

@ -84,6 +84,7 @@ deunicode = "1.6.0"
dialoguer = { default-features = false, version = "0.11" } dialoguer = { default-features = false, version = "0.11" }
digest = { default-features = false, version = "0.10" } digest = { default-features = false, version = "0.10" }
dirs = "5.0" dirs = "5.0"
dirs-sys = "0.4"
dtparse = "2.0" dtparse = "2.0"
encoding_rs = "0.8" encoding_rs = "0.8"
fancy-regex = "0.13" fancy-regex = "0.13"
@ -178,6 +179,7 @@ v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
which = "6.0.0" which = "6.0.0"
windows = "0.54" windows = "0.54"
windows-sys = "0.48"
winreg = "0.52" winreg = "0.52"
[dependencies] [dependencies]

View File

@ -833,7 +833,7 @@ fn variables_completions() {
"plugin-path".into(), "plugin-path".into(),
"startup-time".into(), "startup-time".into(),
"temp-path".into(), "temp-path".into(),
"vendor-autoload-dir".into(), "vendor-autoload-dirs".into(),
]; ];
// Match results // Match results

View File

@ -23,6 +23,7 @@ byte-unit = { version = "5.1", features = [ "serde" ] }
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false } chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
chrono-humanize = { workspace = true } chrono-humanize = { workspace = true }
convert_case = { workspace = true } convert_case = { workspace = true }
dirs = { workspace = true }
fancy-regex = { workspace = true } fancy-regex = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
lru = { workspace = true } lru = { workspace = true }
@ -38,6 +39,10 @@ log = { workspace = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { workspace = true, default-features = false, features = ["signal"] } nix = { workspace = true, default-features = false, features = ["signal"] }
[target.'cfg(windows)'.dependencies]
dirs-sys = { workspace = true }
windows-sys = { workspace = true }
[features] [features]
plugin = [ plugin = [
"brotli", "brotli",

View File

@ -185,24 +185,15 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
}, },
); );
// Create a system level directory for nushell scripts, modules, completions, etc
// that can be changed by setting the NU_VENDOR_AUTOLOAD_DIR env var on any platform
// before nushell is compiled OR if NU_VENDOR_AUTOLOAD_DIR is not set for non-windows
// systems, the PREFIX env var can be set before compile and used as PREFIX/nushell/vendor/autoload
record.push( record.push(
"vendor-autoload-dir", "vendor-autoload-dirs",
// pseudo code Value::list(
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it get_vendor_autoload_dirs(engine_state)
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload .iter()
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload .map(|path| Value::string(path.to_string_lossy(), span))
// if not, use the default /usr/share/nushell/vendor/autoload .collect(),
span,
// check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default ),
if let Some(path) = get_vendor_autoload_dir(engine_state) {
Value::string(path.to_string_lossy(), span)
} else {
Value::error(ShellError::ConfigDirNotFound { span: Some(span) }, span)
},
); );
record.push("temp-path", { record.push("temp-path", {
@ -259,39 +250,95 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
Value::record(record, span) Value::record(record, span)
} }
pub fn get_vendor_autoload_dir(engine_state: &EngineState) -> Option<PathBuf> { pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
// pseudo code // load order for autoload dirs
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it // /Library/Application Support/nushell/vendor/autoload on macOS
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload // <dir>/nushell/vendor/autoload for every dir in XDG_DATA_DIRS in reverse order on platforms other than windows. If XDG_DATA_DIRS is not set, it falls back to <PREFIX>/share if PREFIX ends in local, or <PREFIX>/local/share:<PREFIX>/share otherwise. If PREFIX is not set, fall back to /usr/local/share:/usr/share.
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload // %ProgramData%\nushell\vendor\autoload on windows
// if not, use the default /usr/share/nushell/vendor/autoload // NU_VENDOR_AUTOLOAD_DIR from compile time, if env var is set at compile time
// if on macOS, additionally check XDG_DATA_HOME, which `dirs` is only doing on Linux
// <data_dir>/nushell/vendor/autoload of the current user according to the `dirs` crate
// NU_VENDOR_AUTOLOAD_DIR at runtime, if env var is set
// check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default let into_autoload_path_fn = |mut path: PathBuf| {
Some( path.push("nushell");
option_env!("NU_VENDOR_AUTOLOAD_DIR") path.push("vendor");
.map(String::from) path.push("autoload");
.unwrap_or_else(|| { path
if cfg!(windows) { };
let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") {
Some(v) => format!( let mut dirs = Vec::new();
"{}\\nushell\\vendor\\autoload",
v.coerce_string().unwrap_or("C:\\ProgramData".into()) let mut append_fn = |path: PathBuf| {
), if !dirs.contains(&path) {
None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(), dirs.push(path)
}; }
all_user_profile };
} else {
// In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set #[cfg(target_os = "macos")]
// check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload std::iter::once("/Library/Application Support")
// otherwise default to /usr/share/nushell/vendor/autoload .map(PathBuf::from)
option_env!("PREFIX").map(String::from).map_or_else( .map(into_autoload_path_fn)
|| "/usr/local/share/nushell/vendor/autoload".into(), .for_each(&mut append_fn);
|prefix| format!("{}/share/nushell/vendor/autoload", prefix), #[cfg(unix)]
) {
} use std::os::unix::ffi::OsStrExt;
std::env::var_os("XDG_DATA_DIRS")
.or_else(|| {
option_env!("PREFIX").map(|prefix| {
if prefix.ends_with("local") {
std::ffi::OsString::from(format!("{prefix}/share"))
} else {
std::ffi::OsString::from(format!("{prefix}/local/share:{prefix}/share"))
}
})
}) })
.into(), .unwrap_or_else(|| std::ffi::OsString::from("/usr/local/share/:/usr/share/"))
) .as_encoded_bytes()
.split(|b| *b == b':')
.map(|split| into_autoload_path_fn(PathBuf::from(std::ffi::OsStr::from_bytes(split))))
.rev()
.for_each(&mut append_fn);
}
#[cfg(target_os = "windows")]
dirs_sys::known_folder(windows_sys::Win32::UI::Shell::FOLDERID_ProgramData)
.into_iter()
.map(into_autoload_path_fn)
.for_each(&mut append_fn);
option_env!("NU_VENDOR_AUTOLOAD_DIR")
.into_iter()
.map(PathBuf::from)
.for_each(&mut append_fn);
#[cfg(target_os = "macos")]
std::env::var("XDG_DATA_HOME")
.ok()
.map(PathBuf::from)
.or_else(|| {
dirs::home_dir().map(|mut home| {
home.push(".local");
home.push("share");
home
})
})
.map(into_autoload_path_fn)
.into_iter()
.for_each(&mut append_fn);
dirs::data_dir()
.into_iter()
.map(into_autoload_path_fn)
.for_each(&mut append_fn);
std::env::var_os("NU_VENDOR_AUTOLOAD_DIR")
.into_iter()
.map(PathBuf::from)
.for_each(&mut append_fn);
dirs
} }
fn eval_const_call( fn eval_const_call(

View File

@ -200,8 +200,7 @@ pub(crate) fn read_vendor_autoload_files(engine_state: &mut EngineState, stack:
column!() column!()
); );
// read and source vendor_autoload_files file if exists for autoload_dir in nu_protocol::eval_const::get_vendor_autoload_dirs(engine_state) {
if let Some(autoload_dir) = nu_protocol::eval_const::get_vendor_autoload_dir(engine_state) {
warn!("read_vendor_autoload_files: {}", autoload_dir.display()); warn!("read_vendor_autoload_files: {}", autoload_dir.display());
if autoload_dir.exists() { if autoload_dir.exists() {