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-humanize",
"convert_case",
"dirs",
"dirs-sys",
"fancy-regex",
"indexmap",
"log",
@ -3367,6 +3369,7 @@ dependencies = [
"tempfile",
"thiserror",
"typetag",
"windows-sys 0.48.0",
]
[[package]]

View File

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

View File

@ -833,7 +833,7 @@ fn variables_completions() {
"plugin-path".into(),
"startup-time".into(),
"temp-path".into(),
"vendor-autoload-dir".into(),
"vendor-autoload-dirs".into(),
];
// 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-humanize = { workspace = true }
convert_case = { workspace = true }
dirs = { workspace = true }
fancy-regex = { workspace = true }
indexmap = { workspace = true }
lru = { workspace = true }
@ -38,6 +39,10 @@ log = { workspace = true }
[target.'cfg(unix)'.dependencies]
nix = { workspace = true, default-features = false, features = ["signal"] }
[target.'cfg(windows)'.dependencies]
dirs-sys = { workspace = true }
windows-sys = { workspace = true }
[features]
plugin = [
"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(
"vendor-autoload-dir",
// pseudo code
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload
// if not, use the default /usr/share/nushell/vendor/autoload
// 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)
},
"vendor-autoload-dirs",
Value::list(
get_vendor_autoload_dirs(engine_state)
.iter()
.map(|path| Value::string(path.to_string_lossy(), span))
.collect(),
span,
),
);
record.push("temp-path", {
@ -259,39 +250,95 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
Value::record(record, span)
}
pub fn get_vendor_autoload_dir(engine_state: &EngineState) -> Option<PathBuf> {
// pseudo code
// if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it
// if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload
// if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload
// if not, use the default /usr/share/nushell/vendor/autoload
pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
// load order for autoload dirs
// /Library/Application Support/nushell/vendor/autoload on macOS
// <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.
// %ProgramData%\nushell\vendor\autoload on windows
// 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
Some(
option_env!("NU_VENDOR_AUTOLOAD_DIR")
.map(String::from)
.unwrap_or_else(|| {
if cfg!(windows) {
let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") {
Some(v) => format!(
"{}\\nushell\\vendor\\autoload",
v.coerce_string().unwrap_or("C:\\ProgramData".into())
),
None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(),
};
all_user_profile
} else {
// In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set
// check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload
// otherwise default to /usr/share/nushell/vendor/autoload
option_env!("PREFIX").map(String::from).map_or_else(
|| "/usr/local/share/nushell/vendor/autoload".into(),
|prefix| format!("{}/share/nushell/vendor/autoload", prefix),
)
}
let into_autoload_path_fn = |mut path: PathBuf| {
path.push("nushell");
path.push("vendor");
path.push("autoload");
path
};
let mut dirs = Vec::new();
let mut append_fn = |path: PathBuf| {
if !dirs.contains(&path) {
dirs.push(path)
}
};
#[cfg(target_os = "macos")]
std::iter::once("/Library/Application Support")
.map(PathBuf::from)
.map(into_autoload_path_fn)
.for_each(&mut append_fn);
#[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(

View File

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