aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core
diff options
context:
space:
mode:
Diffstat (limited to 'lua/mason-core')
-rw-r--r--lua/mason-core/async/init.lua5
-rw-r--r--lua/mason-core/installer/InstallHandle.lua10
-rw-r--r--lua/mason-core/installer/InstallLocation.lua11
-rw-r--r--lua/mason-core/installer/InstallRunner.lua25
-rw-r--r--lua/mason-core/installer/UninstallRunner.lua2
-rw-r--r--lua/mason-core/installer/context/InstallContextSpawn.lua4
-rw-r--r--lua/mason-core/installer/context/init.lua40
-rw-r--r--lua/mason-core/installer/linker.lua69
-rw-r--r--lua/mason-core/installer/managers/cargo.lua3
-rw-r--r--lua/mason-core/installer/managers/npm.lua3
-rw-r--r--lua/mason-core/installer/managers/pypi.lua19
-rw-r--r--lua/mason-core/package/AbstractPackage.lua13
-rw-r--r--lua/mason-core/package/init.lua13
-rw-r--r--lua/mason-core/result.lua5
-rw-r--r--lua/mason-core/spawn.lua23
-rw-r--r--lua/mason-core/system-package.lua96
16 files changed, 292 insertions, 49 deletions
diff --git a/lua/mason-core/async/init.lua b/lua/mason-core/async/init.lua
index 03963264..e3a2e850 100644
--- a/lua/mason-core/async/init.lua
+++ b/lua/mason-core/async/init.lua
@@ -78,7 +78,8 @@ local function new_execution_context(suspend_fn, callback, ...)
if cancelled or not thread then
return
end
- local ok, promise_or_result = co.resume(thread, ...)
+ local results = { co.resume(thread, ...) }
+ local ok, promise_or_result = results[1], results[2]
if cancelled or not thread then
return
end
@@ -88,7 +89,7 @@ local function new_execution_context(suspend_fn, callback, ...)
promise_or_result(step)
else
-- yield to parent coroutine
- step(coroutine.yield(promise_or_result))
+ step(coroutine.yield(promise_or_result, unpack(results, 3)))
end
else
callback(true, promise_or_result)
diff --git a/lua/mason-core/installer/InstallHandle.lua b/lua/mason-core/installer/InstallHandle.lua
index 3846659e..6492acd9 100644
--- a/lua/mason-core/installer/InstallHandle.lua
+++ b/lua/mason-core/installer/InstallHandle.lua
@@ -20,6 +20,7 @@ local uv = vim.loop
---@field pid integer
---@field cmd string
---@field args string[]
+---@field firewall boolean
local InstallHandleSpawnHandle = {}
InstallHandleSpawnHandle.__index = InstallHandleSpawnHandle
@@ -27,7 +28,8 @@ InstallHandleSpawnHandle.__index = InstallHandleSpawnHandle
---@param pid integer
---@param cmd string
---@param args string[]
-function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)
+---@param firewall boolean
+function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args, firewall)
---@type InstallHandleSpawnHandle
local instance = {}
setmetatable(instance, InstallHandleSpawnHandle)
@@ -35,6 +37,7 @@ function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)
instance.pid = pid
instance.cmd = cmd
instance.args = args
+ instance.firewall = firewall
return instance
end
@@ -73,8 +76,9 @@ end
---@param pid integer
---@param cmd string
---@param args string[]
-function InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args)
- local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)
+---@param firewall boolean
+function InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args, firewall)
+ local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args, firewall)
log.fmt_trace("Pushing spawn_handles stack for %s: %s (pid: %s)", self, spawn_handles, pid)
self.spawn_handles[#self.spawn_handles + 1] = spawn_handles
self:emit "spawn_handles:change"
diff --git a/lua/mason-core/installer/InstallLocation.lua b/lua/mason-core/installer/InstallLocation.lua
index 77252761..157adc8c 100644
--- a/lua/mason-core/installer/InstallLocation.lua
+++ b/lua/mason-core/installer/InstallLocation.lua
@@ -34,6 +34,7 @@ function InstallLocation:initialize()
self:bin(),
self:share(),
self:package(),
+ self:system_package(),
self:staging(),
} do
if not fs.sync.dir_exists(p) then
@@ -63,6 +64,11 @@ function InstallLocation:package(pkg)
return Path.concat { self.dir, "packages", pkg }
end
+---@param pkg string?
+function InstallLocation:system_package(pkg)
+ return Path.concat { self.dir, "system_packages", pkg }
+end
+
---@param path string?
function InstallLocation:staging(path)
return Path.concat { self.dir, "staging", path }
@@ -78,11 +84,6 @@ function InstallLocation:registry(path)
return Path.concat { self.dir, "registries", path }
end
----@param pkg string
-function InstallLocation:receipt(pkg)
- return Path.concat { self:package(pkg), "mason-receipt.json" }
-end
-
---@param opts { PATH: '"append"' | '"prepend"' | '"skip"' }
function InstallLocation:set_env(opts)
vim.env.MASON = self.dir
diff --git a/lua/mason-core/installer/InstallRunner.lua b/lua/mason-core/installer/InstallRunner.lua
index 93225e11..336f0c93 100644
--- a/lua/mason-core/installer/InstallRunner.lua
+++ b/lua/mason-core/installer/InstallRunner.lua
@@ -39,7 +39,14 @@ function InstallRunner:execute(opts, callback)
local handle = self.handle
log.fmt_info("Executing installer for %s %s", handle.package, opts)
- local context = InstallContext:new(handle, opts)
+ local context = InstallContext:new(handle, opts, {
+ suspend = function()
+ self:suspend()
+ end,
+ resume = function()
+ self:resume()
+ end,
+ })
local tailed_output = {}
@@ -135,7 +142,7 @@ function InstallRunner:execute(opts, callback)
end))
---@type InstallReceipt
local receipt = try(context:build_receipt())
- try(Result.pcall(fs.sync.write_file, handle.location:receipt(handle.package.name), receipt:to_json()))
+ try(Result.pcall(fs.sync.write_file, handle.package:get_receipt_path(handle.location), receipt:to_json()))
return {
receipt = receipt,
}
@@ -212,6 +219,20 @@ function InstallRunner:acquire_permit()
return Result.success(channel)
end
+function InstallRunner:suspend()
+ if self.global_permit then
+ self.global_permit:forget()
+ self.global_permit = nil
+ end
+end
+
+---@async
+function InstallRunner:resume()
+ self.handle:set_state "QUEUED"
+ self.global_permit = self.global_semaphore:acquire()
+ self.handle:set_state "ACTIVE"
+end
+
---@private
function InstallRunner:release_permit()
if self.global_permit then
diff --git a/lua/mason-core/installer/UninstallRunner.lua b/lua/mason-core/installer/UninstallRunner.lua
index 760ad88b..3429fc93 100644
--- a/lua/mason-core/installer/UninstallRunner.lua
+++ b/lua/mason-core/installer/UninstallRunner.lua
@@ -47,7 +47,7 @@ function UninstallRunner:execute(opts, callback)
else
try(pkg:unlink(location))
end
- fs.sync.rmrf(location:package(pkg.name))
+ fs.sync.rmrf(pkg:get_install_path(location))
return receipt
end):get_or_throw()
end, function(success, result)
diff --git a/lua/mason-core/installer/context/InstallContextSpawn.lua b/lua/mason-core/installer/context/InstallContextSpawn.lua
index 29e62101..25419980 100644
--- a/lua/mason-core/installer/context/InstallContextSpawn.lua
+++ b/lua/mason-core/installer/context/InstallContextSpawn.lua
@@ -22,7 +22,7 @@ end
---@param cmd string
function InstallContextSpawn:__index(cmd)
- ---@param args JobSpawnOpts
+ ---@param args SpawnArgs
return function(args)
args.cwd = args.cwd or self.cwd:get()
args.stdio_sink = args.stdio_sink or self.handle.stdio_sink
@@ -30,7 +30,7 @@ function InstallContextSpawn:__index(cmd)
local captured_handle
args.on_spawn = function(handle, stdio, pid, ...)
captured_handle = handle
- self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args))
+ self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args), args.firewall == true)
if on_spawn then
on_spawn(handle, stdio, pid, ...)
end
diff --git a/lua/mason-core/installer/context/init.lua b/lua/mason-core/installer/context/init.lua
index ae96f986..69738911 100644
--- a/lua/mason-core/installer/context/init.lua
+++ b/lua/mason-core/installer/context/init.lua
@@ -21,19 +21,22 @@ local receipt = require "mason-core.receipt"
---@field cwd InstallContextCwd
---@field opts PackageInstallOpts
---@field stdio_sink StdioSink
+---@field runner { suspend: fun(), resume: async fun() }
---@field links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> }
local InstallContext = {}
InstallContext.__index = InstallContext
---@param handle InstallHandle
---@param opts PackageInstallOpts
-function InstallContext:new(handle, opts)
+---@param runner { suspend: fun(), resume: async fun() }
+function InstallContext:new(handle, opts, runner)
local cwd = InstallContextCwd:new(handle)
local spawn = InstallContextSpawn:new(handle, cwd, false)
local fs = InstallContextFs:new(cwd)
return setmetatable({
cwd = cwd,
spawn = spawn,
+ runner = runner,
handle = handle,
location = handle.location, -- for convenience
package = handle.package, -- for convenience
@@ -264,6 +267,7 @@ function InstallContext:link_bin(executable, rel_path)
end
InstallContext.CONTEXT_REQUEST = {}
+InstallContext.ABORT = {}
---@generic T
---@param fn fun(context: InstallContext): T
@@ -277,14 +281,17 @@ function InstallContext:execute(fn)
local step
local ret_val
step = function(...)
- local ok, result = coroutine.resume(thread, ...)
+ local results = { coroutine.resume(thread, ...) }
+ local ok, result = results[1], results[2]
if not ok then
error(result, 0)
elseif result == InstallContext.CONTEXT_REQUEST then
step(self)
+ elseif result == InstallContext.ABORT then
+ ret_val = Result.failure(results[3])
elseif coroutine.status(thread) == "suspended" then
-- yield to parent coroutine
- step(coroutine.yield(result))
+ step(coroutine.yield(result, unpack(results, 3)))
else
ret_val = result
end
@@ -294,6 +301,31 @@ function InstallContext:execute(fn)
end
---@async
+---@param system_pkg SystemPackage
+function InstallContext:require(system_pkg)
+ local result = Result.try(function(try)
+ if try(system_pkg:needs_install()) then
+ self.stdio_sink:stdout("Installing dependency " .. system_pkg.name .. ".\n")
+ self.runner.suspend()
+ try(system_pkg:install():on_failure(function()
+ if self.opts.force then
+ self.runner.resume()
+ end
+ end))
+ self.runner.resume()
+ end
+ end)
+ if result:is_failure() then
+ if not self.opts.force then
+ self.stdio_sink:stderr "Run with :MasonInstall --force to attempt installation anyway.\n"
+ coroutine.yield(InstallContext.ABORT, result:err_or_nil())
+ else
+ self.stdio_sink:stderr(result:err_or_nil() .. "\n")
+ end
+ end
+end
+
+---@async
function InstallContext:build_receipt()
log.fmt_debug("Building receipt for %s", self.package)
return Result.pcall(function()
@@ -302,7 +334,7 @@ function InstallContext:build_receipt()
end
function InstallContext:get_install_path()
- return self.location:package(self.package.name)
+ return self.package:get_install_path(self.location)
end
return InstallContext
diff --git a/lua/mason-core/installer/linker.lua b/lua/mason-core/installer/linker.lua
index a26d2592..415f61eb 100644
--- a/lua/mason-core/installer/linker.lua
+++ b/lua/mason-core/installer/linker.lua
@@ -1,17 +1,17 @@
+local Path = require "mason-core.path"
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
local fs = require "mason-core.fs"
local log = require "mason-core.log"
-local path = require "mason-core.path"
local platform = require "mason-core.platform"
local M = {}
---@alias LinkContext { type: '"bin"' | '"opt"' | '"share"', prefix: fun(path: string, location: InstallLocation): string }
----@type table<'"BIN"' | '"OPT"' | '"SHARE"', LinkContext>
local LinkContext = {
+ ---@type LinkContext
BIN = {
type = "bin",
---@param path string
@@ -20,6 +20,7 @@ local LinkContext = {
return location:bin(path)
end,
},
+ ---@type LinkContext
OPT = {
type = "opt",
---@param path string
@@ -28,6 +29,7 @@ local LinkContext = {
return location:opt(path)
end,
},
+ ---@type LinkContext
SHARE = {
type = "share",
---@param path string
@@ -38,6 +40,36 @@ local LinkContext = {
},
}
+local SystemLinkContext = {
+ ---@type LinkContext
+ BIN = {
+ type = "bin",
+ ---@param path string
+ ---@param location InstallLocation
+ prefix = function(path, location)
+ return location:opt(Path.concat { "mason", "system", "bin", path })
+ end,
+ },
+ ---@type LinkContext
+ OPT = {
+ type = "opt",
+ ---@param path string
+ ---@param location InstallLocation
+ prefix = function(path, location)
+ return location:opt(Path.concat { "mason", "system", "opt", path })
+ end,
+ },
+ ---@type LinkContext
+ SHARE = {
+ type = "share",
+ ---@param path string
+ ---@param location InstallLocation
+ prefix = function(path, location)
+ return location:opt(Path.concat { "mason", "system", "share", path })
+ end,
+ },
+}
+
---@param receipt InstallReceipt
---@param link_context LinkContext
---@param location InstallLocation
@@ -48,7 +80,7 @@ local function unlink(receipt, link_context, location)
return
end
for linked_file in pairs(links) do
- if receipt:get_schema_version() == "1.0" and link_context == LinkContext.BIN and platform.is.win then
+ if receipt:get_schema_version() == "1.0" and link_context.type == "bin" and platform.is.win then
linked_file = linked_file .. ".cmd"
end
local share_path = link_context.prefix(linked_file, location)
@@ -63,10 +95,11 @@ end
---@nodiscard
function M.unlink(pkg, receipt, location)
log.fmt_debug("Unlinking %s", pkg, receipt:get_links())
+ local link_context = pkg.spec.system and SystemLinkContext or LinkContext
return Result.try(function(try)
- try(unlink(receipt, LinkContext.BIN, location))
- try(unlink(receipt, LinkContext.SHARE, location))
- try(unlink(receipt, LinkContext.OPT, location))
+ try(unlink(receipt, link_context.BIN, location))
+ try(unlink(receipt, link_context.SHARE, location))
+ try(unlink(receipt, link_context.OPT, location))
end)
end
@@ -78,12 +111,12 @@ local function link(context, link_context, link_fn)
log.trace("Linking", context.package, link_context.type, context.links[link_context.type])
return Result.try(function(try)
for name, rel_path in pairs(context.links[link_context.type]) do
- if platform.is.win and link_context == LinkContext.BIN then
+ if platform.is.win and link_context.type == "bin" then
name = ("%s.cmd"):format(name)
end
local new_abs_path = link_context.prefix(name, context.location)
- local target_abs_path = path.concat { context:get_install_path(), rel_path }
- local target_rel_path = path.relative(new_abs_path, target_abs_path)
+ local target_abs_path = Path.concat { context:get_install_path(), rel_path }
+ local target_rel_path = Path.relative(new_abs_path, target_abs_path)
-- 1. Ensure destination directory exists
a.scheduler()
@@ -129,8 +162,9 @@ local function copyfile(context, link_context)
end
---@param context InstallContext
-local function win_bin_wrapper(context)
- return link(context, LinkContext.BIN, function(new_abs_path, __, target_rel_path)
+---@param link_context LinkContext
+local function win_bin_wrapper(context, link_context)
+ return link(context, link_context, function(new_abs_path, __, target_rel_path)
local windows_target_rel_path = target_rel_path:gsub("/", "\\")
return Result.pcall(
fs.async.write_file,
@@ -156,15 +190,16 @@ end
---@nodiscard
function M.link(context)
log.fmt_debug("Linking %s", context.package)
+ local link_context = context.package.spec.system and SystemLinkContext or LinkContext
return Result.try(function(try)
if platform.is.win then
- try(win_bin_wrapper(context))
- try(copyfile(context, LinkContext.SHARE))
- try(copyfile(context, LinkContext.OPT))
+ try(win_bin_wrapper(context, link_context.BIN))
+ try(copyfile(context, link_context.SHARE))
+ try(copyfile(context, link_context.OPT))
else
- try(symlink(context, LinkContext.BIN))
- try(symlink(context, LinkContext.SHARE))
- try(symlink(context, LinkContext.OPT))
+ try(symlink(context, link_context.BIN))
+ try(symlink(context, link_context.SHARE))
+ try(symlink(context, link_context.OPT))
end
end)
end
diff --git a/lua/mason-core/installer/managers/cargo.lua b/lua/mason-core/installer/managers/cargo.lua
index 22ec9ed6..a6116f9a 100644
--- a/lua/mason-core/installer/managers/cargo.lua
+++ b/lua/mason-core/installer/managers/cargo.lua
@@ -1,4 +1,5 @@
local Result = require "mason-core.result"
+local SystemPackage = require "mason-core.system-package"
local _ = require "mason-core.functional"
local installer = require "mason-core.installer"
local log = require "mason-core.log"
@@ -15,6 +16,7 @@ function M.install(crate, version, opts)
opts = opts or {}
log.fmt_debug("cargo: install %s %s %s", crate, version, opts)
local ctx = installer.context()
+ ctx:require(SystemPackage.sfw)
ctx.stdio_sink:stdout(("Installing crate %s@%s…\n"):format(crate, version))
return ctx.spawn.cargo {
"install",
@@ -29,6 +31,7 @@ function M.install(crate, version, opts)
opts.features and { "--features", opts.features } or vim.NIL,
opts.locked and "--locked" or vim.NIL,
crate,
+ firewall = true,
}
end
diff --git a/lua/mason-core/installer/managers/npm.lua b/lua/mason-core/installer/managers/npm.lua
index 93af3a85..8a8d1582 100644
--- a/lua/mason-core/installer/managers/npm.lua
+++ b/lua/mason-core/installer/managers/npm.lua
@@ -1,4 +1,5 @@
local Result = require "mason-core.result"
+local SystemPackage = require "mason-core.system-package"
local _ = require "mason-core.functional"
local installer = require "mason-core.installer"
local log = require "mason-core.log"
@@ -62,12 +63,14 @@ function M.install(pkg, version, opts)
opts = opts or {}
log.fmt_debug("npm: install %s %s %s", pkg, version, opts)
local ctx = installer.context()
+ ctx:require(SystemPackage.sfw)
ctx.stdio_sink:stdout(("Installing npm package %s@%s…\n"):format(pkg, version))
return ctx.spawn.npm {
"install",
("%s@%s"):format(pkg, version),
opts.extra_packages or vim.NIL,
opts.install_extra_args or vim.NIL,
+ firewall = true,
}
end
diff --git a/lua/mason-core/installer/managers/pypi.lua b/lua/mason-core/installer/managers/pypi.lua
index 72b1b503..a8efd0dd 100644
--- a/lua/mason-core/installer/managers/pypi.lua
+++ b/lua/mason-core/installer/managers/pypi.lua
@@ -1,5 +1,6 @@
local Optional = require "mason-core.optional"
local Result = require "mason-core.result"
+local SystemPackage = require "mason-core.system-package"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
local installer = require "mason-core.installer"
@@ -66,13 +67,16 @@ local function get_versioned_candidates(supported_python_versions)
end
return Optional.of(executable)
end, {
- { semver.new "3.12.0", "python3.12" },
- { semver.new "3.11.0", "python3.11" },
- { semver.new "3.10.0", "python3.10" },
- { semver.new "3.9.0", "python3.9" },
- { semver.new "3.8.0", "python3.8" },
- { semver.new "3.7.0", "python3.7" },
+ -- IMPORTANT: should be in ASCENDING order
{ semver.new "3.6.0", "python3.6" },
+ { semver.new "3.7.0", "python3.7" },
+ { semver.new "3.8.0", "python3.8" },
+ { semver.new "3.9.0", "python3.9" },
+ { semver.new "3.10.0", "python3.10" },
+ { semver.new "3.11.0", "python3.11" },
+ { semver.new "3.12.0", "python3.12" },
+ { semver.new "3.13.0", "python3.13" },
+ { semver.new "3.14.0", "python3.14" },
})
end
@@ -170,6 +174,8 @@ end
---@param pkgs string[]
---@param extra_args? string[]
local function pip_install(pkgs, extra_args)
+ local ctx = installer.context()
+ ctx:require(SystemPackage.sfw)
return venv_python {
"-m",
"pip",
@@ -179,6 +185,7 @@ local function pip_install(pkgs, extra_args)
"--ignore-installed",
extra_args or vim.NIL,
pkgs,
+ firewall = true,
}
end
diff --git a/lua/mason-core/package/AbstractPackage.lua b/lua/mason-core/package/AbstractPackage.lua
index 5678f4dd..a852d350 100644
--- a/lua/mason-core/package/AbstractPackage.lua
+++ b/lua/mason-core/package/AbstractPackage.lua
@@ -6,6 +6,7 @@ local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local fs = require "mason-core.fs"
local log = require "mason-core.log"
+local path = require "mason-core.path"
local settings = require "mason.settings"
local Semaphore = require("mason-core.async.control").Semaphore
@@ -128,7 +129,7 @@ end
---@return Optional # Optional<InstallReceipt>
function AbstractPackage:get_receipt(location)
location = location or InstallLocation.global()
- local receipt_path = location:receipt(self.name)
+ local receipt_path = self:get_receipt_path(location)
if fs.sync.file_exists(receipt_path) then
local receipt = require "mason-core.receipt"
return Optional.of(receipt.InstallReceipt.from_json(vim.json.decode(fs.sync.read_file(receipt_path))))
@@ -137,6 +138,11 @@ function AbstractPackage:get_receipt(location)
end
---@param location? InstallLocation
+function AbstractPackage:get_receipt_path(location)
+ return path.concat { self:get_install_path(location), "mason-receipt.json" }
+end
+
+---@param location? InstallLocation
---@return boolean
function AbstractPackage:is_installed(location)
error "Unimplemented."
@@ -174,6 +180,11 @@ function AbstractPackage:get_installed_version(location)
:or_else(nil)
end
+---@param location? InstallLocation
+function AbstractPackage:get_install_path(location)
+ error "Unimplemented."
+end
+
---@param opts? PackageInstallOpts
---@param callback? InstallRunnerCallback
---@return InstallHandle
diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua
index cb4ef99e..07257d8d 100644
--- a/lua/mason-core/package/init.lua
+++ b/lua/mason-core/package/init.lua
@@ -76,6 +76,7 @@ Package.License = setmetatable({}, {
---@class RegistryPackageSpec
---@field schema RegistryPackageSpecSchema
---@field name string
+---@field system boolean?
---@field description string
---@field homepage string
---@field licenses string[]
@@ -147,9 +148,19 @@ function Package:uninstall(opts, callback)
end
---@param location? InstallLocation
+function Package:get_install_path(location)
+ location = location or InstallLocation.global()
+ if self.spec.system then
+ return location:system_package(self.name)
+ else
+ return location:package(self.name)
+ end
+end
+
+---@param location? InstallLocation
function Package:is_installed(location)
location = location or InstallLocation.global()
- local ok, stat = pcall(vim.loop.fs_stat, location:package(self.name))
+ local ok, stat = pcall(vim.loop.fs_stat, self:get_install_path(location))
if not ok or not stat then
return false
end
diff --git a/lua/mason-core/result.lua b/lua/mason-core/result.lua
index e98a11b3..27c46862 100644
--- a/lua/mason-core/result.lua
+++ b/lua/mason-core/result.lua
@@ -189,7 +189,8 @@ function Result.try(fn)
local thread = coroutine.create(fn)
local step
step = function(...)
- local ok, result = coroutine.resume(thread, ...)
+ local results = { coroutine.resume(thread, ...) }
+ local ok, result = results[1], results[2]
if not ok then
return Result.failure(result)
end
@@ -207,7 +208,7 @@ function Result.try(fn)
end
else
-- yield to parent coroutine
- return step(coroutine.yield(result))
+ return step(coroutine.yield(result, unpack(results, 3)))
end
end
return step(coroutine.yield)
diff --git a/lua/mason-core/spawn.lua b/lua/mason-core/spawn.lua
index 0da67569..78be4fd7 100644
--- a/lua/mason-core/spawn.lua
+++ b/lua/mason-core/spawn.lua
@@ -4,6 +4,7 @@ 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 settings = require "mason.settings"
local is_not_nil = _.complement(_.equals(vim.NIL))
@@ -54,15 +55,17 @@ local function Failure(err, cmd)
}))
end
-local get_path_from_env_list = _.compose(_.strip_prefix "PATH=", _.find_first(_.starts_with "PATH="))
+local get_path_from_env_list =
+ _.compose(_.if_else(_.is_nil, _.identity, _.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 env table<string, string>? Environment variables to merge with the current environment. Example { SOME_ENV = "value", SOME_OTHER_ENV = "some_value" }
+---@field env_raw string[]? The environment to start the process with, will not merge with the current environment. 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.
+---@field firewall boolean?
setmetatable(spawn, {
---@param canonical_cmd string
@@ -101,6 +104,20 @@ setmetatable(spawn, {
end
end
+ if args.firewall and settings.current.firewall.enabled then
+ a.scheduler()
+ table.insert(spawn_args.args, 1, cmd)
+ local expanded_cmd = exepath(
+ "sfw",
+ settings.current.firewall.auto_managed and vim.fn.expand "$MASON/opt/mason/system/bin" or nil
+ )
+ if expanded_cmd == "" then
+ return Failure({ stderr = "Failed to find sfw (Socket Firewall) in PATH." }, "sfw")
+ else
+ 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
diff --git a/lua/mason-core/system-package.lua b/lua/mason-core/system-package.lua
new file mode 100644
index 00000000..798db774
--- /dev/null
+++ b/lua/mason-core/system-package.lua
@@ -0,0 +1,96 @@
+local Result = require "mason-core.result"
+local _ = require "mason-core.functional"
+local a = require "mason-core.async"
+local registry = require "mason-registry"
+local settings = require "mason.settings"
+local OneShotChannel = require("mason-core.async.control").OneShotChannel
+
+---@class SystemPackage
+---@field name string
+---@field condition? fun(): bool
+local SystemPackage = {}
+SystemPackage.__index = SystemPackage
+
+---@type table<string, OneShotChannel>
+SystemPackage.channels = {}
+
+function SystemPackage:new(system_pkg_name)
+ ---@type SystemPackage
+ local instance = {}
+ setmetatable(instance, self)
+ instance.name = system_pkg_name
+ return instance
+end
+
+function SystemPackage:conditional(fn)
+ self.condition = fn
+ return self
+end
+
+---@async
+function SystemPackage:get_package()
+ a.scheduler()
+ pcall(a.wait, registry.refresh_system)
+ if not registry.has_system_package(self.name) then
+ -- Force update to the very latest registry version
+ pcall(a.wait, registry.update)
+ end
+ if not registry.has_system_package(self.name) then
+ return Result.failure("Unable to find system package " .. self.name)
+ end
+ return Result.pcall(registry.get_system_package, self.name)
+end
+
+---@async
+---@return Result<boolean>
+function SystemPackage:needs_install()
+ return Result.try(function(try)
+ if self.condition and not self.condition() then
+ return false
+ end
+ local pkg = try(self:get_package())
+ if not pkg:is_installed() or pkg:is_installing() then
+ return true
+ end
+ if pkg:get_installed_version() ~= pkg:get_latest_version() then
+ return true
+ end
+ return false
+ end)
+end
+
+---@async
+function SystemPackage:await_channel()
+ assert(SystemPackage.channels[self.name], "Tried to await non-existing channel.")
+ local success, result = SystemPackage.channels[self.name]:receive()
+ if not success then
+ return Result.failure("Failed to install system package " .. self.name .. ". Error: " .. result)
+ end
+ return Result.success()
+end
+
+---@async
+---@return Result
+function SystemPackage:install()
+ return Result.try(function(try)
+ local pkg = try(self:get_package())
+ if not pkg:is_installing() then
+ local channel = OneShotChannel:new()
+ SystemPackage.channels[self.name] = channel
+ pkg:install({}, function(success, result)
+ channel:send(success, result)
+ end)
+ end
+ return self:await_channel()
+ end)
+end
+
+function SystemPackage:__tostring()
+ return ("SystemPackage(name=%s)"):format(self.name)
+end
+
+SystemPackage.sfw = SystemPackage:new("sfw@latest"):conditional(function()
+ return settings.current.firewall.enabled and settings.current.firewall.auto_managed
+end)
+
+return SystemPackage