diff options
| author | William Boman <william@redwill.se> | 2026-05-14 17:21:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-14 17:21:42 +0200 |
| commit | 8e921c2b68571e978db5d4d3fef9c9a7f8755473 (patch) | |
| tree | c16d4b5c8a6e33877c523d2e7ff714090432a8de /lua | |
| parent | fix(pypi): add python 3.13 and 3.14 to list of fallbacks (#2081) (diff) | |
| download | mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar.gz mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar.bz2 mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar.lz mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar.xz mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.tar.zst mason-8e921c2b68571e978db5d4d3fef9c9a7f8755473.zip | |
This enables `mason.nvim` to start managing certain packages that:
1) are not suitable for the core registry
2) should not surface in existing APIs and UIs
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/mason-core/installer/InstallLocation.lua | 11 | ||||
| -rw-r--r-- | lua/mason-core/installer/InstallRunner.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/installer/UninstallRunner.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/installer/context/init.lua | 2 | ||||
| -rw-r--r-- | lua/mason-core/installer/linker.lua | 69 | ||||
| -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-registry/init.lua | 125 | ||||
| -rw-r--r-- | lua/mason-registry/installer.lua | 29 | ||||
| -rw-r--r-- | lua/mason-registry/sources/file.lua | 4 | ||||
| -rw-r--r-- | lua/mason-registry/sources/github.lua | 4 | ||||
| -rw-r--r-- | lua/mason-registry/sources/init.lua | 70 | ||||
| -rw-r--r-- | lua/mason-registry/sources/lua.lua | 4 | ||||
| -rw-r--r-- | lua/mason-registry/sources/synthesized.lua | 23 | ||||
| -rw-r--r-- | lua/mason-registry/sources/util.lua | 4 | ||||
| -rw-r--r-- | lua/mason/init.lua | 3 | ||||
| -rw-r--r-- | lua/mason/settings.lua | 8 |
17 files changed, 262 insertions, 124 deletions
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..3eba879a 100644 --- a/lua/mason-core/installer/InstallRunner.lua +++ b/lua/mason-core/installer/InstallRunner.lua @@ -135,7 +135,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, } 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/init.lua b/lua/mason-core/installer/context/init.lua index ae96f986..9225315f 100644 --- a/lua/mason-core/installer/context/init.lua +++ b/lua/mason-core/installer/context/init.lua @@ -302,7 +302,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/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-registry/init.lua b/lua/mason-registry/init.lua index 5806c30a..1e59175c 100644 --- a/lua/mason-registry/init.lua +++ b/lua/mason-registry/init.lua @@ -1,13 +1,17 @@ local EventEmitter = require "mason-core.EventEmitter" local InstallLocation = require "mason-core.installer.InstallLocation" local log = require "mason-core.log" +local path = require "mason-core.path" local uv = vim.loop local LazySourceCollection = require "mason-registry.sources" -- singleton local Registry = EventEmitter:new() -Registry.sources = LazySourceCollection:new() +Registry.sources = LazySourceCollection:new(path.concat { vim.fn.stdpath "cache", "mason-registry-update" }) +Registry.system_sources = + LazySourceCollection:new(path.concat { vim.fn.stdpath "cache", "mason-system-registry-update" }, true) + ---@type table<string, string[]> Registry.aliases = {} @@ -37,6 +41,21 @@ function Registry.has_package(pkg_name) return ok end +---Returns an instance of the Package class if the provided package name exists. This function errors if a package +---cannot be found. +---@param pkg_name string +---@return Package +function Registry.get_system_package(pkg_name) + for source in Registry.system_sources:iterate() do + local pkg = source:get_package(pkg_name) + if pkg then + return pkg + end + end + log.fmt_error("Cannot find system package %q.", pkg_name) + error(("Cannot find system package %q."):format(pkg_name)) +end + function Registry.get_installed_package_names() local fs = require "mason-core.fs" if not fs.sync.dir_exists(InstallLocation.global():package()) then @@ -103,49 +122,61 @@ function Registry.get_package_aliases(name) return Registry.aliases[name] or {} end ----@param callback? fun(success: boolean, updated_registries: RegistrySource[]) -function Registry.update(callback) - local a = require "mason-core.async" +---@async +---@param sources LazySourceCollection +local function update(sources) local installer = require "mason-registry.installer" + + if sources.install_channel then + log.debug "Registry update already in progress." + return sources.install_channel:receive():get_or_throw() + else + log.debug "Updating the registry." + Registry:emit("update:start", sources) + return installer + .install(sources, function(finished, all) + Registry:emit("update:progress", finished, all) + end) + :on_success(function(updated_registries) + log.fmt_debug("Successfully updated %d registries.", #updated_registries) + Registry:emit("update:success", updated_registries) + end) + :on_failure(function(errors) + log.error("Failed to update registries.", errors) + Registry:emit("update:failed", errors) + end) + :get_or_throw() + end +end + +---@alias RegistryUpdateCallback fun(success: boolean, updated_registries: RegistrySource[]) + +---@param callback? RegistryUpdateCallback +function Registry.update_system(callback) + local a = require "mason-core.async" local noop = function() end + a.run(update, callback or noop, Registry.system_sources) +end - a.run(function() - if installer.channel then - log.debug "Registry update already in progress." - return installer.channel:receive():get_or_throw() - else - log.debug "Updating the registry." - Registry:emit("update:start", Registry.sources) - return installer - .install(Registry.sources, function(finished, all) - Registry:emit("update:progress", finished, all) - end) - :on_success(function(updated_registries) - log.fmt_debug("Successfully updated %d registries.", #updated_registries) - Registry:emit("update:success", updated_registries) - end) - :on_failure(function(errors) - log.error("Failed to update registries.", errors) - Registry:emit("update:failed", errors) - end) - :get_or_throw() - end - end, callback or noop) +---@param callback? RegistryUpdateCallback +function Registry.update(callback) + local a = require "mason-core.async" + local noop = function() end + a.run(update, callback or noop, Registry.sources) end local REGISTRY_STORE_TTL = 86400 -- 24 hrs ----@param callback? fun(success: boolean, updated_registries: RegistrySource[]) -function Registry.refresh(callback) - log.debug "Refreshing the registry." +---@param sources LazySourceCollection +---@param callback? RegistryUpdateCallback +local function refresh(sources, callback) local a = require "mason-core.async" - local installer = require "mason-registry.installer" - local state = installer.get_registry_state() - if state and Registry.sources:is_all_installed() then + local state = sources:get_install_state() + if state and sources:is_all_installed() then local registry_age = os.time() - state.timestamp - if registry_age <= REGISTRY_STORE_TTL and state.checksum == Registry.sources:checksum() then + if registry_age <= REGISTRY_STORE_TTL and state.checksum == sources:checksum() then log.fmt_debug( "Registry refresh is not necessary yet. Registry age=%d, checksum=%s", registry_age, @@ -158,25 +189,25 @@ function Registry.refresh(callback) end end - local function async_update() - return a.wait(function(resolve, reject) - Registry.update(function(success, result) - if success then - resolve(result) - else - reject(result) - end - end) - end) - end - if not callback then -- We don't want to error in the synchronous version because of how this function is recommended to be used in -- 3rd party code. If accessing the update result is required, users are recommended to pass a callback. - pcall(a.run_blocking, async_update) + pcall(a.run_blocking, update, sources) else - a.run(async_update, callback) + a.run(update, callback, sources) end end +---@param callback? RegistryUpdateCallback +function Registry.refresh(callback) + log.debug "Refreshing the registry." + refresh(Registry.sources, callback) +end + +---@param callback? RegistryUpdateCallback +function Registry.refresh_system(callback) + log.debug "Refreshing the system registry." + refresh(Registry.system_sources, callback) +end + return Registry diff --git a/lua/mason-registry/installer.lua b/lua/mason-registry/installer.lua index 05592227..b832805b 100644 --- a/lua/mason-registry/installer.lua +++ b/lua/mason-registry/installer.lua @@ -4,30 +4,19 @@ local OneShotChannel = require("mason-core.async.control").OneShotChannel local Result = require "mason-core.result" local _ = require "mason-core.functional" local fs = require "mason-core.fs" -local path = require "mason-core.path" local M = {} -local STATE_FILE = path.concat { vim.fn.stdpath "cache", "mason-registry-update" } - ---@param sources LazySourceCollection ---@param time integer local function update_registry_state(sources, time) log.trace("Updating registry state", sources, time) - local dir = vim.fn.fnamemodify(STATE_FILE, ":h") + local state_file = sources:get_state_file() + local dir = vim.fn.fnamemodify(state_file, ":h") if not fs.sync.dir_exists(dir) then fs.sync.mkdirp(dir) end - fs.sync.write_file(STATE_FILE, _.join("\n", { sources:checksum(), tostring(time) })) -end - ----@return { checksum: string, timestamp: integer }? -function M.get_registry_state() - if fs.sync.file_exists(STATE_FILE) then - local parse_state_file = - _.compose(_.evolve { timestamp = tonumber }, _.zip_table { "checksum", "timestamp" }, _.split "\n") - return parse_state_file(fs.sync.read_file(STATE_FILE)) - end + fs.sync.write_file(state_file, _.join("\n", { sources:checksum(), tostring(time) })) end ---@async @@ -36,8 +25,8 @@ end ---@return Result # Result<RegistrySource[]> function M.install(sources, on_progress) log.debug("Installing registries.", sources) - assert(not M.channel, "Cannot install when channel is active.") - M.channel = OneShotChannel:new() + assert(not sources.install_channel, "Cannot install when channel is active.") + sources.install_channel = OneShotChannel:new() local finished_registries = {} local registries = sources:to_list { include_uninstalled = true, include_synthesized = false } @@ -69,15 +58,15 @@ function M.install(sources, on_progress) if any_failed then local unwrap_failures = _.compose(_.map(Result.err_or_nil), _.filter(Result.is_failure)) local result = Result.failure(unwrap_failures(results)) - M.channel:send(result) - M.channel = nil + sources.install_channel:send(result) + sources.install_channel = nil return result else local result = Result.success(_.map(Result.get_or_nil, results)) a.scheduler() update_registry_state(sources, os.time()) - M.channel:send(result) - M.channel = nil + sources.install_channel:send(result) + sources.install_channel = nil return result end end diff --git a/lua/mason-registry/sources/file.lua b/lua/mason-registry/sources/file.lua index 663efaaa..f29cf2dc 100644 --- a/lua/mason-registry/sources/file.lua +++ b/lua/mason-registry/sources/file.lua @@ -21,12 +21,14 @@ local FileRegistrySource = {} FileRegistrySource.__index = FileRegistrySource ---@param spec FileRegistrySourceSpec -function FileRegistrySource:new(spec) +---@param system boolean +function FileRegistrySource:new(spec, system) ---@type FileRegistrySource local instance = {} setmetatable(instance, self) instance.id = spec.id instance.spec = spec + instance.system = system return instance end diff --git a/lua/mason-registry/sources/github.lua b/lua/mason-registry/sources/github.lua index 2b177bdd..7b650ae8 100644 --- a/lua/mason-registry/sources/github.lua +++ b/lua/mason-registry/sources/github.lua @@ -29,7 +29,8 @@ local GitHubRegistrySource = {} GitHubRegistrySource.__index = GitHubRegistrySource ---@param spec GitHubRegistrySourceSpec -function GitHubRegistrySource:new(spec) +---@param system boolean +function GitHubRegistrySource:new(spec, system) ---@type GitHubRegistrySource local instance = {} setmetatable(instance, GitHubRegistrySource) @@ -38,6 +39,7 @@ function GitHubRegistrySource:new(spec) instance.spec = spec instance.repo = ("%s/%s"):format(spec.namespace, spec.name) instance.root_dir = root_dir + instance.system = system instance.data_file = path.concat { root_dir, "registry.json" } instance.info_file = path.concat { root_dir, "info.json" } return instance diff --git a/lua/mason-registry/sources/init.lua b/lua/mason-registry/sources/init.lua index 36e62ec5..b69fa8b1 100644 --- a/lua/mason-registry/sources/init.lua +++ b/lua/mason-registry/sources/init.lua @@ -1,7 +1,9 @@ +local _ = require "mason-core.functional" local log = require "mason-core.log" ---@class RegistrySource ---@field id string +---@field system boolean ---@field get_package fun(self: RegistrySource, pkg_name: string): Package? ---@field get_all_package_names fun(self: RegistrySource): string[] ---@field get_all_package_specs fun(self: RegistrySource): RegistryPackageSpec[] @@ -16,42 +18,46 @@ local log = require "mason-core.log" ---@class LazySource ---@field type RegistrySourceType ---@field id string ----@field init fun(id: string): RegistrySource +---@field init fun(id: string, system: boolean): RegistrySource +---@field system boolean local LazySource = {} LazySource.__index = LazySource ---@param id string -function LazySource.GitHub(id) +---@param system boolean +function LazySource.GitHub(id, system) local namespace, name = id:match "^(.+)/(.+)$" if not namespace or not name then error(("Failed to parse repository from GitHub registry: %q"):format(id), 0) end local name, version = unpack(vim.split(name, "@")) local GitHubRegistrySource = require "mason-registry.sources.github" - return GitHubRegistrySource:new { + return GitHubRegistrySource:new({ id = id, namespace = namespace, name = name, version = version, - } + }, system) end ---@param id string -function LazySource.Lua(id) +---@param system boolean +function LazySource.Lua(id, system) local LuaRegistrySource = require "mason-registry.sources.lua" - return LuaRegistrySource:new { + return LuaRegistrySource:new({ id = id, mod = id, - } + }, system) end ---@param id string -function LazySource.File(id) +---@param system boolean +function LazySource.File(id, system) local FileRegistrySource = require "mason-registry.sources.file" - return FileRegistrySource:new { + return FileRegistrySource:new({ id = id, path = id, - } + }, system) end function LazySource.Synthesized() @@ -62,17 +68,20 @@ end ---@param type RegistrySourceType ---@param id string ---@param init fun(id: string): RegistrySource -function LazySource:new(type, id, init) +---@param system boolean +function LazySource:new(type, id, init, system) + ---@type LazySource local instance = setmetatable({}, self) instance.type = type instance.id = id instance.init = init + instance.system = system return instance end function LazySource:get() if not self.instance then - self.instance = self.init(self.id) + self.instance = self.init(self.id, self.system) end return self.instance end @@ -105,43 +114,66 @@ local function split_once_left(str, char) end ---@param registry_id string -local function parse(registry_id) +---@param system boolean +local function parse(registry_id, system) local type, id = split_once_left(registry_id, ":") assert(id, ("Malformed registry %q"):format(registry_id)) if type == "github" then - return LazySource:new(type, id, LazySource.GitHub) + return LazySource:new(type, id, LazySource.GitHub, system) elseif type == "lua" then - return LazySource:new(type, id, LazySource.Lua) + return LazySource:new(type, id, LazySource.Lua, system) elseif type == "file" then - return LazySource:new(type, id, LazySource.File) + return LazySource:new(type, id, LazySource.File, system) end error(("Unknown registry type: %s"):format(type)) end ---@class LazySourceCollection +---@field state_file string +---@field system boolean? ---@field list LazySource[] ---@field synthesized LazySource +---@field install_channel OneShotChannel? local LazySourceCollection = {} LazySourceCollection.__index = LazySourceCollection ---@return LazySourceCollection -function LazySourceCollection:new() +---@param state_file string +---@param system boolean? +function LazySourceCollection:new(state_file, system) + ---@type LazySourceCollection local instance = {} setmetatable(instance, self) + instance.state_file = state_file + instance.system = system instance.list = {} instance.synthesized = LazySource:new("synthesized", "synthesized", LazySource.Synthesized) return instance end +---@return { checksum: string, timestamp: integer }? +function LazySourceCollection:get_install_state() + local fs = require "mason-core.fs" + if fs.sync.file_exists(self.state_file) then + local parse_state_file = + _.compose(_.evolve { timestamp = tonumber }, _.zip_table { "checksum", "timestamp" }, _.split "\n") + return parse_state_file(fs.sync.read_file(self.state_file)) + end +end + +function LazySourceCollection:get_state_file() + return self.state_file +end + ---@param registry string function LazySourceCollection:append(registry) - self:unique_insert(parse(registry)) + self:unique_insert(parse(registry, not not self.system)) return self end ---@param registry string function LazySourceCollection:prepend(registry) - self:unique_insert(parse(registry), 1) + self:unique_insert(parse(registry, not not self.system), 1) return self end diff --git a/lua/mason-registry/sources/lua.lua b/lua/mason-registry/sources/lua.lua index 40e728b6..273655c0 100644 --- a/lua/mason-registry/sources/lua.lua +++ b/lua/mason-registry/sources/lua.lua @@ -13,12 +13,14 @@ local LuaRegistrySource = {} LuaRegistrySource.__index = LuaRegistrySource ---@param spec LuaRegistrySourceSpec -function LuaRegistrySource:new(spec) +---@param system boolean +function LuaRegistrySource:new(spec, system) ---@type LuaRegistrySource local instance = {} setmetatable(instance, LuaRegistrySource) instance.id = spec.id instance.spec = spec + instance.system = system return instance end diff --git a/lua/mason-registry/sources/synthesized.lua b/lua/mason-registry/sources/synthesized.lua index 8cad635e..2a6cd281 100644 --- a/lua/mason-registry/sources/synthesized.lua +++ b/lua/mason-registry/sources/synthesized.lua @@ -5,6 +5,7 @@ local InstallReceipt = require("mason-core.receipt").InstallReceipt local InstallLocation = require "mason-core.installer.InstallLocation" local fs = require "mason-core.fs" local log = require "mason-core.log" +local path = require "mason-core.path" ---@class SynthesizedRegistrySource : RegistrySource ---@field buffer table<string, Package> @@ -68,14 +69,20 @@ end ---@param pkg_name string ---@return Package? function SynthesizedRegistrySource:get_package(pkg_name) - local receipt_path = InstallLocation.global():receipt(pkg_name) - if fs.sync.file_exists(receipt_path) then - local ok, receipt_json = pcall(vim.json.decode, fs.sync.read_file(receipt_path)) - if ok then - local receipt = InstallReceipt.from_json(receipt_json) - return self:load_package(pkg_name, receipt) - else - log.error("Failed to decode package receipt", pkg_name, receipt_json) + local location = InstallLocation.global() + local receipt_paths = { + path.concat { location:package(pkg_name), "mason-receipt.json" }, + path.concat { location:system_package(pkg_name), "mason-receipt.json" }, + } + for _, receipt_path in ipairs(receipt_paths) do + if fs.sync.file_exists(receipt_path) then + local ok, receipt_json = pcall(vim.json.decode, fs.sync.read_file(receipt_path)) + if ok then + local receipt = InstallReceipt.from_json(receipt_json) + return self:load_package(pkg_name, receipt) + else + log.error("Failed to decode package receipt", pkg_name, receipt_json) + end end end end diff --git a/lua/mason-registry/sources/util.lua b/lua/mason-registry/sources/util.lua index 9efa1420..e37dabfe 100644 --- a/lua/mason-registry/sources/util.lua +++ b/lua/mason-registry/sources/util.lua @@ -30,6 +30,10 @@ M.hydrate_package = _.curryN(function(registry, buffer, spec) local _ = Package.License[lang] end, spec.licenses) + if registry.system then + spec = _.assoc("system", true, spec) + end + local existing_instance = buffer[spec.name] if existing_instance then -- Apply spec to the existing Package instances. This is important as to not have lingering package instances. diff --git a/lua/mason/init.lua b/lua/mason/init.lua index 9c507f5c..043fb651 100644 --- a/lua/mason/init.lua +++ b/lua/mason/init.lua @@ -26,6 +26,9 @@ function M.setup(config) for _, registry in ipairs(settings.current.registries) do Registry.sources:append(registry) end + for _, registry in ipairs(settings.current.system_registries) do + Registry.system_sources:append(registry) + end require "mason.api.command" setup_autocmds() diff --git a/lua/mason/settings.lua b/lua/mason/settings.lua index e70d90a4..1788c035 100644 --- a/lua/mason/settings.lua +++ b/lua/mason/settings.lua @@ -34,6 +34,14 @@ local DEFAULT_SETTINGS = { "github:mason-org/mason-registry", }, + ---@since 2.3.0 + -- [Advanced setting] + -- The registries to source system packages from. Accepts multiple entries. Should a package with the same name exist in + -- multiple registries, the registry listed first will be used. + system_registries = { + "github:mason-org/mason-system-registry", + }, + ---@since 1.0.0 -- The provider implementations to use for resolving supplementary package metadata (e.g., all available versions). -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. |
