1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
local log = require "mason-core.log"
local platform = require "mason-core.platform"
local process = require "mason-core.process"
local is_not_nil = _.complement(_.equals(vim.NIL))
---@alias JobSpawn table<string, async fun(opts: SpawnArgs): Result>
---@type JobSpawn
local spawn = {
_flatten_cmd_args = _.compose(_.filter(is_not_nil), _.flatten),
}
---@param cmd string
---@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 = get_exepath(cmd)
vim.o.shell = old_shell
return expanded_cmd
else
return get_exepath(cmd)
end
end
local function Failure(err, cmd)
return Result.failure(setmetatable(err, {
__tostring = function()
return ("spawn: %s failed with exit code %s and signal %s. %s"):format(
cmd,
err.exit_code or "-",
err.signal or "-",
err.stderr or ""
)
end,
}))
end
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.
---@field env table<string, string>? Example { SOME_ENV = "value", SOME_OTHER_ENV = "some_value" }
---@field env_raw string[]? Example: { "SOME_ENV=value", "SOME_OTHER_ENV=some_value" }
---@field stdio_sink StdioSink? If provided, will be used to write to stdout and stderr.
---@field cwd string?
---@field on_spawn (fun(handle: luv_handle, stdio: luv_pipe[], pid: integer))? Will be called when the process successfully spawns.
setmetatable(spawn, {
---@param canonical_cmd string
__index = function(self, canonical_cmd)
---@param args SpawnArgs
return function(args)
local cmd_args = self._flatten_cmd_args(args)
local env = args.env
if args.with_paths then
env = env or {}
env.PATH = process.extend_path(args.with_paths)
end
---@type JobSpawnOpts
local spawn_args = {
stdio_sink = args.stdio_sink,
cwd = args.cwd,
env = env and process.graft_env(env) or args.env_raw,
args = cmd_args,
}
if not spawn_args.stdio_sink then
spawn_args.stdio_sink = process.BufferedSink:new()
end
local cmd = canonical_cmd
-- Find the executable path via vim.fn.exepath on Windows because libuv fails to resolve certain executables
-- in PATH.
if platform.is.win then
a.scheduler()
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
end
local _, exit_code, signal = a.wait(function(resolve)
local handle, stdio, pid = process.spawn(cmd, spawn_args, resolve)
if args.on_spawn and handle and stdio and pid then
args.on_spawn(handle, stdio, pid)
end
end)
if exit_code == 0 and signal == 0 then
if getmetatable(spawn_args.stdio_sink) == process.BufferedSink then
local sink = spawn_args.stdio_sink --[[@as BufferedSink]]
return Result.success {
stdout = table.concat(sink.buffers.stdout, "") or nil,
stderr = table.concat(sink.buffers.stderr, "") or nil,
}
else
return Result.success()
end
else
if getmetatable(spawn_args.stdio_sink) == process.BufferedSink then
local sink = spawn_args.stdio_sink --[[@as BufferedSink]]
return Failure({
exit_code = exit_code,
signal = signal,
stdout = table.concat(sink.buffers.stdout, "") or nil,
stderr = table.concat(sink.buffers.stderr, "") or nil,
}, canonical_cmd)
else
return Failure({
exit_code = exit_code,
signal = signal,
}, canonical_cmd)
end
end
end
end,
})
return spawn
|