diff options
| author | William Boman <william@redwill.se> | 2025-09-30 20:02:22 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-30 20:02:22 +0200 |
| commit | a83eabdc8c49c0c93bf5bb162fa3b57404a9d095 (patch) | |
| tree | 9ad154d143e7deba8a6a7e3a47f716a5ff46a225 | |
| parent | ci: fix shellharden download url (#2022) (diff) | |
| download | mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar.gz mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar.bz2 mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar.lz mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar.xz mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.tar.zst mason-a83eabdc8c49c0c93bf5bb162fa3b57404a9d095.zip | |
fix(spawn): always expand executable path on Windows (#2021)
This fixes some issues with the logic for expanding paths on Windows. If
a process is spawned with a custom `PATH` (either via the `env` or
`env_raw` arg) we still expand the path manually but with a temporarily
modified `vim.env.PATH`.
Fixes #2009.
| -rw-r--r-- | lua/mason-core/spawn.lua | 25 | ||||
| -rw-r--r-- | tests/mason-core/spawn_spec.lua | 33 |
2 files changed, 46 insertions, 12 deletions
diff --git a/lua/mason-core/spawn.lua b/lua/mason-core/spawn.lua index d604dfe2..0da67569 100644 --- a/lua/mason-core/spawn.lua +++ b/lua/mason-core/spawn.lua @@ -14,17 +14,30 @@ local spawn = { } ---@param cmd string -local function exepath(cmd) +---@param path? string +local function exepath(cmd, path) + local function get_exepath(cmd) + if path then + local old_path = vim.env.PATH + vim.env.PATH = path + local expanded_cmd = vim.fn.exepath(cmd) + vim.env.PATH = old_path + return expanded_cmd + else + return vim.fn.exepath(cmd) + end + end + if platform.is.win then -- On Windows, exepath() assumes the system is capable of executing "Unix-like" executables if the shell is a Unix -- shell. We temporarily override it to a Windows shell ("powershell") to avoid that behaviour. local old_shell = vim.o.shell vim.o.shell = "powershell" - local expanded_cmd = vim.fn.exepath(cmd) + local expanded_cmd = get_exepath(cmd) vim.o.shell = old_shell return expanded_cmd else - return vim.fn.exepath(cmd) + return get_exepath(cmd) end end @@ -41,7 +54,7 @@ local function Failure(err, cmd) })) end -local has_path = _.any(_.starts_with "PATH=") +local get_path_from_env_list = _.compose(_.strip_prefix "PATH=", _.find_first(_.starts_with "PATH=")) ---@class SpawnArgs ---@field with_paths string[]? Paths to add to the PATH environment variable. @@ -80,9 +93,9 @@ setmetatable(spawn, { -- Find the executable path via vim.fn.exepath on Windows because libuv fails to resolve certain executables -- in PATH. - if platform.is.win and (spawn_args.env and has_path(spawn_args.env)) == nil then + if platform.is.win then a.scheduler() - local expanded_cmd = exepath(canonical_cmd) + local expanded_cmd = exepath(canonical_cmd, spawn_args.env and get_path_from_env_list(spawn_args.env)) if expanded_cmd ~= "" then cmd = expanded_cmd end diff --git a/tests/mason-core/spawn_spec.lua b/tests/mason-core/spawn_spec.lua index db8f9575..b224bfc3 100644 --- a/tests/mason-core/spawn_spec.lua +++ b/tests/mason-core/spawn_spec.lua @@ -148,6 +148,8 @@ describe("async spawn", function() end) describe("Windows", function() + -- Note: Tests assume they're executed in a Unix environment (e.g. uses Unix path separators in tests). + before_each(function() platform.is.win = true end) @@ -173,24 +175,43 @@ describe("async spawn", function() ) end) - it("should not use exepath if env.PATH is set", function() + it("should use exepath if env.PATH is set", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) - local result = a.run_blocking(spawn.bash, { "arg1", env = { PATH = "C:\\some\\path" } }) + local result = a.run_blocking(spawn.bash, { "arg1", env = { PATH = "C:\\some\\path:" .. vim.env.PATH } }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( - "bash", + vim.fn.exepath "bash", + match.tbl_containing { + args = match.same { "arg1" }, + env = match.is_table(), + }, + match.is_function() + ) + end) + + it("should use exepath if env_raw.PATH is set", function() + stub(process, "spawn", function(_, _, callback) + callback(true, 0, 0) + end) + + local result = a.run_blocking(spawn.bash, { "arg1", env_raw = { "PATH=C:\\some\\path:" .. vim.env.PATH } }) + assert.is_true(result:is_success()) + assert.spy(process.spawn).was_called(1) + assert.spy(process.spawn).was_called_with( + vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, + env = match.is_table(), }, match.is_function() ) end) - it("should not use exepath if env_raw.PATH is set", function() + it("should default to provided cmd if exepath returns nothing", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) @@ -207,7 +228,7 @@ describe("async spawn", function() ) end) - it("should not use exepath if with_paths is provided", function() + it("should use exepath if with_paths is provided", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) @@ -216,7 +237,7 @@ describe("async spawn", function() assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( - "bash", + vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, }, |
