Work around $PATH issues under WSL (#10506)

A common complaint has been the massive amount of directories Windows appends to
$PATH slowing down fish when it attempts to find a non-existent binary (which it
does a lot more often than someone not in the know might think). The typical
workaround suggested is to trim unneeded entries from $PATH, but this a) has
considerable friction, b) breaks resolution of Windows binaries (you can no
longer use `clip.exe`, `cmd.exe`, etc).

This patch introduces a two-PATH workaround. If the cmd we are executing does
not contain a period (i.e. has no extension) it by definition cannot be a
Windows executable. In this case, we skip searching for it in any of the
auto-mounted, auto-PATH-appended directories like `/mnt/c/Windows/` or
`/mnt/c/Program Files`, but we *do* include those directories if what we're
searching for could be a Windows executable. (For now, instead of hard-coding a
list of known Windows executable extensions like .bat, .cmd, .exe, etc, we just
depend on the presence of an extension at all).

e.g. this is what starting up fish prints with logging enabled (that has been
removed):

    bypassing 100 dirs for lookup of kill
    bypassing 100 dirs for lookup of zoxide
    bypassing 100 dirs for lookup of zoxide
    bypassing 100 dirs for lookup of fd
    not bypassing dirs for lookup of open.exe
    not bypassing dirs for lookup of git.exe

This has resulted in a massive speedup of common fish functions, especially
anywhere we internally use or perform the equivalent of `if command -q foo`.

Note that the `is_windows_subsystem_for_linux()` check will need to be patched to
extend this workaround to WSLv2, but I'll do that separately.

Under WSL:
* Benchmark `external_cmds` improves by 10%
* Benchmark `load_completions` improves by an incredible 77%
This commit is contained in:
Mahmoud Al-Qudsi 2024-05-20 10:29:32 -05:00 committed by GitHub
parent bd4e5fe69a
commit 3374692b91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2,7 +2,7 @@
//! for testing if a command with a given name can be found in the PATH, and various other
//! path-related issues.
use crate::common::wcs2zstring;
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2zstring};
use crate::env::{EnvMode, EnvStack, Environment};
use crate::expand::{expand_tilde, HOME_DIRECTORY};
use crate::flog::{FLOG, FLOGF};
@ -308,6 +308,30 @@ fn path_get_path_core<S: AsRef<wstr>>(cmd: &wstr, pathsv: &[S]) -> GetPathResult
return GetPathResult::new(test_path(cmd).err(), cmd.to_owned());
}
// WSLv1/WSLv2 tack on the entire Windows PATH to the end of the PATH environment variable, and
// accessing these paths from WSL binaries is pathalogically slow. We also don't expect to find
// any "normal" nix binaries under these paths, so we can skip them unless we are executing bins
// with Windows-ish names. We try to keep paths manually added to $fish_user_paths by only
// chopping off entries after the last "normal" PATH entry.
let pathsv = if is_wsl() && !cmd.contains('.') {
let win_path_count = pathsv
.iter()
.rev()
.take_while(|p| {
let p = p.as_ref();
p.starts_with("/mnt/")
&& p.chars()
.skip("/mnt/x".len())
.next()
.map(|c| c == '/')
.unwrap_or(false)
})
.count();
&pathsv[..pathsv.len() - win_path_count]
} else {
&pathsv
};
let mut best = noent_res;
for next_path in pathsv {
let next_path: &wstr = next_path.as_ref();