diff --git a/Cargo.toml b/Cargo.toml index a63b853b..38983f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,11 @@ path = "src/main.rs" name = "just" test = false +[[bin]] +path = "src/bin/echoargs.rs" +name = "echoargs" +test = false + # The public documentation is minimal and doesn't change between # platforms, so we only build them for linux on docs.rs to save # their build machines some cycles. diff --git a/src/bin/echoargs.rs b/src/bin/echoargs.rs new file mode 100644 index 00000000..53c3f9ae --- /dev/null +++ b/src/bin/echoargs.rs @@ -0,0 +1,6 @@ +use std::env; + +fn main() { + let args: Vec = env::args().skip(1).collect(); + println!("{}", args.join(" ")); +} diff --git a/src/command_ext.rs b/src/command_ext.rs index 1cd7ef03..b47e5cf1 100644 --- a/src/command_ext.rs +++ b/src/command_ext.rs @@ -1,6 +1,8 @@ use super::*; pub(crate) trait CommandExt { + fn with_explicit_path(&mut self) -> &mut Command; + fn export( &mut self, settings: &Settings, @@ -19,6 +21,18 @@ pub(crate) trait CommandExt { } impl CommandExt for Command { + fn with_explicit_path(&mut self) -> &mut Command { + // On Windows, set child's path explicitly so that spawned executable + // is located on path before System32 fallback, not the other way around. + // https://blog.rust-lang.org/2022/01/13/Rust-1.58.0/#reduced-windows-command-search-path + #[cfg(windows)] + if let Some(path) = std::env::var_os("Path") { + self.env("Path", path); + } + + self + } + fn export( &mut self, settings: &Settings, diff --git a/src/executor.rs b/src/executor.rs index 49c7c89b..1887a166 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -16,6 +16,7 @@ impl Executor<'_> { match self { Self::Command(interpreter) => { let mut command = Command::new(&interpreter.command.cooked); + command.with_explicit_path(); if let Some(working_directory) = working_directory { command.current_dir(working_directory); diff --git a/src/justfile.rs b/src/justfile.rs index 52e91bce..50df193c 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -157,7 +157,9 @@ impl<'src> Justfile<'src> { command.arg(binary); command } else { - Command::new(binary) + let mut command = Command::new(binary); + command.with_explicit_path(); + command }; command diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 13c5f329..44bf753c 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -32,6 +32,7 @@ impl PlatformInterface for Platform { }; let mut cmd = Command::new(command.as_ref()); + cmd.with_explicit_path(); if let Some(working_directory) = working_directory { cmd.current_dir(working_directory); diff --git a/src/settings.rs b/src/settings.rs index 78240ca5..5a592c65 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -106,6 +106,7 @@ impl<'src> Settings<'src> { let (command, args) = self.shell(config); let mut cmd = Command::new(command); + cmd.with_explicit_path(); cmd.args(args); diff --git a/src/subcommand.rs b/src/subcommand.rs index 9b15b520..1928b898 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -307,7 +307,7 @@ impl Subcommand { .or_else(|| env::var_os("EDITOR")) .unwrap_or_else(|| "vim".into()); - let error = Command::new(&editor) + let error = Command::new(&editor).with_explicit_path() .current_dir(&search.working_directory) .arg(&search.justfile) .status(); diff --git a/tests/lib.rs b/tests/lib.rs index 363c8730..02af351a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -135,6 +135,8 @@ mod unstable; mod which_function; #[cfg(windows)] mod windows; +#[cfg(windows)] +mod windows_path_resolution; #[cfg(target_family = "windows")] mod windows_shell; mod working_directory; diff --git a/tests/windows_path_resolution.rs b/tests/windows_path_resolution.rs new file mode 100644 index 00000000..1641ee5c --- /dev/null +++ b/tests/windows_path_resolution.rs @@ -0,0 +1,41 @@ +use super::*; + +#[cfg(windows)] +#[test] +fn windows_path_resolution() { + + // Goal: Confirm that $PATH entries are respected *before* C:\Windows\System32 when + // locating shell executable. This can happen when PATH is configured to prefer + // Git-for-Windows' bash.exe, and just must not call C:\Windows\System32\bash.exe + // https://github.com/casey/just/issues/2947 + + // Copy echoargs.exe to temp directory as where.exe, to intentionally match + // the name of an executable in C:\Windows\System32 + let mut echoargs_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + echoargs_path.push("target"); + echoargs_path.push("debug"); + echoargs_path.push("echoargs.exe"); + let tmp = tempdir(); + let tmp_subdir= tmp.path().join("subdir"); + let tmp_subdir_path = tmp_subdir.as_path(); + let exe_path = tmp_subdir_path.join("where.exe"); + std::fs::create_dir(tmp_subdir_path).expect("Failed to create temp subdirectory"); + std::fs::copy(&echoargs_path, &exe_path) + .expect("Failed to copy exe to temp directory"); + + // Prepend temp directory to PATH + let new_path = tmp_subdir_path.to_str().unwrap().to_owned() + ";" + &env::var("PATH").unwrap(); + + Test::with_tempdir(tmp) + .shell(false) + .env("Path", &new_path) + .justfile( + r#" + set shell := ['where.exe'] + @default: + test_marker + "#, + ) + .stdout("test_marker\n") + .run(); +}