diff options
Diffstat (limited to 'lua/mason-core')
| -rw-r--r-- | lua/mason-core/async/init.lua | 5 | ||||
| -rw-r--r-- | lua/mason-core/installer/InstallHandle.lua | 10 | ||||
| -rw-r--r-- | lua/mason-core/installer/InstallLocation.lua | 11 | ||||
| -rw-r--r-- | lua/mason-core/installer/InstallRunner.lua | 25 | ||||
| -rw-r--r-- | lua/mason-core/installer/UninstallRunner.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/installer/context/InstallContextSpawn.lua | 4 | ||||
| -rw-r--r-- | lua/mason-core/installer/context/init.lua | 40 | ||||
| -rw-r--r-- | lua/mason-core/installer/linker.lua | 69 | ||||
| -rw-r--r-- | lua/mason-core/installer/managers/cargo.lua | 3 | ||||
| -rw-r--r-- | lua/mason-core/installer/managers/npm.lua | 3 | ||||
| -rw-r--r-- | lua/mason-core/installer/managers/pypi.lua | 19 | ||||
| -rw-r--r-- | lua/mason-core/package/AbstractPackage.lua | 13 | ||||
| -rw-r--r-- | lua/mason-core/package/init.lua | 13 | ||||
| -rw-r--r-- | lua/mason-core/result.lua | 5 | ||||
| -rw-r--r-- | lua/mason-core/spawn.lua | 23 | ||||
| -rw-r--r-- | lua/mason-core/system-package.lua | 96 |
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 |
