Skip to content

Commit a83eabd

Browse files
authored
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.
1 parent b3689a4 commit a83eabd

File tree

2 files changed

+46
-12
lines changed

2 files changed

+46
-12
lines changed

lua/mason-core/spawn.lua

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,30 @@ local spawn = {
1414
}
1515

1616
---@param cmd string
17-
local function exepath(cmd)
17+
---@param path? string
18+
local function exepath(cmd, path)
19+
local function get_exepath(cmd)
20+
if path then
21+
local old_path = vim.env.PATH
22+
vim.env.PATH = path
23+
local expanded_cmd = vim.fn.exepath(cmd)
24+
vim.env.PATH = old_path
25+
return expanded_cmd
26+
else
27+
return vim.fn.exepath(cmd)
28+
end
29+
end
30+
1831
if platform.is.win then
1932
-- On Windows, exepath() assumes the system is capable of executing "Unix-like" executables if the shell is a Unix
2033
-- shell. We temporarily override it to a Windows shell ("powershell") to avoid that behaviour.
2134
local old_shell = vim.o.shell
2235
vim.o.shell = "powershell"
23-
local expanded_cmd = vim.fn.exepath(cmd)
36+
local expanded_cmd = get_exepath(cmd)
2437
vim.o.shell = old_shell
2538
return expanded_cmd
2639
else
27-
return vim.fn.exepath(cmd)
40+
return get_exepath(cmd)
2841
end
2942
end
3043

@@ -41,7 +54,7 @@ local function Failure(err, cmd)
4154
}))
4255
end
4356

44-
local has_path = _.any(_.starts_with "PATH=")
57+
local get_path_from_env_list = _.compose(_.strip_prefix "PATH=", _.find_first(_.starts_with "PATH="))
4558

4659
---@class SpawnArgs
4760
---@field with_paths string[]? Paths to add to the PATH environment variable.
@@ -80,9 +93,9 @@ setmetatable(spawn, {
8093

8194
-- Find the executable path via vim.fn.exepath on Windows because libuv fails to resolve certain executables
8295
-- in PATH.
83-
if platform.is.win and (spawn_args.env and has_path(spawn_args.env)) == nil then
96+
if platform.is.win then
8497
a.scheduler()
85-
local expanded_cmd = exepath(canonical_cmd)
98+
local expanded_cmd = exepath(canonical_cmd, spawn_args.env and get_path_from_env_list(spawn_args.env))
8699
if expanded_cmd ~= "" then
87100
cmd = expanded_cmd
88101
end

tests/mason-core/spawn_spec.lua

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ describe("async spawn", function()
148148
end)
149149

150150
describe("Windows", function()
151+
-- Note: Tests assume they're executed in a Unix environment (e.g. uses Unix path separators in tests).
152+
151153
before_each(function()
152154
platform.is.win = true
153155
end)
@@ -173,24 +175,43 @@ describe("async spawn", function()
173175
)
174176
end)
175177

176-
it("should not use exepath if env.PATH is set", function()
178+
it("should use exepath if env.PATH is set", function()
177179
stub(process, "spawn", function(_, _, callback)
178180
callback(true, 0, 0)
179181
end)
180182

181-
local result = a.run_blocking(spawn.bash, { "arg1", env = { PATH = "C:\\some\\path" } })
183+
local result = a.run_blocking(spawn.bash, { "arg1", env = { PATH = "C:\\some\\path:" .. vim.env.PATH } })
182184
assert.is_true(result:is_success())
183185
assert.spy(process.spawn).was_called(1)
184186
assert.spy(process.spawn).was_called_with(
185-
"bash",
187+
vim.fn.exepath "bash",
188+
match.tbl_containing {
189+
args = match.same { "arg1" },
190+
env = match.is_table(),
191+
},
192+
match.is_function()
193+
)
194+
end)
195+
196+
it("should use exepath if env_raw.PATH is set", function()
197+
stub(process, "spawn", function(_, _, callback)
198+
callback(true, 0, 0)
199+
end)
200+
201+
local result = a.run_blocking(spawn.bash, { "arg1", env_raw = { "PATH=C:\\some\\path:" .. vim.env.PATH } })
202+
assert.is_true(result:is_success())
203+
assert.spy(process.spawn).was_called(1)
204+
assert.spy(process.spawn).was_called_with(
205+
vim.fn.exepath "bash",
186206
match.tbl_containing {
187207
args = match.same { "arg1" },
208+
env = match.is_table(),
188209
},
189210
match.is_function()
190211
)
191212
end)
192213

193-
it("should not use exepath if env_raw.PATH is set", function()
214+
it("should default to provided cmd if exepath returns nothing", function()
194215
stub(process, "spawn", function(_, _, callback)
195216
callback(true, 0, 0)
196217
end)
@@ -207,7 +228,7 @@ describe("async spawn", function()
207228
)
208229
end)
209230

210-
it("should not use exepath if with_paths is provided", function()
231+
it("should use exepath if with_paths is provided", function()
211232
stub(process, "spawn", function(_, _, callback)
212233
callback(true, 0, 0)
213234
end)
@@ -216,7 +237,7 @@ describe("async spawn", function()
216237
assert.is_true(result:is_success())
217238
assert.spy(process.spawn).was_called(1)
218239
assert.spy(process.spawn).was_called_with(
219-
"bash",
240+
vim.fn.exepath "bash",
220241
match.tbl_containing {
221242
args = match.same { "arg1" },
222243
},

0 commit comments

Comments
 (0)