aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/package/AbstractPackage.lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2023-11-07 00:29:18 +0100
committerWilliam Boman <william@redwill.se>2025-02-19 12:15:48 +0100
commit6a7662760c515c74f2c37fc825776ead65d307f9 (patch)
tree0f4496d0678c7029b10236cbf48cc0f5ff63c1dc /lua/mason-core/package/AbstractPackage.lua
parentfix(pypi): remove -U flag and fix log message (diff)
downloadmason-6a7662760c515c74f2c37fc825776ead65d307f9.tar
mason-6a7662760c515c74f2c37fc825776ead65d307f9.tar.gz
mason-6a7662760c515c74f2c37fc825776ead65d307f9.tar.bz2
mason-6a7662760c515c74f2c37fc825776ead65d307f9.tar.lz
mason-6a7662760c515c74f2c37fc825776ead65d307f9.tar.xz
mason-6a7662760c515c74f2c37fc825776ead65d307f9.tar.zst
mason-6a7662760c515c74f2c37fc825776ead65d307f9.zip
refactor!: change Package API
This changes the following public APIs: **(_breaking_) Events on the `Package` class** The `uninstall:success` event on the `Package` class now receives an `InstallReceipt` as argument, instead of an `InstallHandle`. This receipt is an in-memory representation of what was uninstalled. There's also a new `uninstall:failed` event for situations where uninstallation for some reason fails. Note: this also applies to the registry events (i.e. `package:uninstall:success` and `package:uninstall:failed`). --- **(_breaking_) `Package:uninstall()` is now asynchronous and receives two new arguments, similarly to `Package:install()`** While package uninstallations remain synchronous under the hood, the public API has been changed from synchronous -> asynchronous. Users of this method are recommended to provide a callback in situations where code needs to execute after uninstallation fully completes. --- **(_breaking_) `Package:get_install_path()` has been removed. --- **`Package:install()` now takes an optional callback** This callback allows consumers to be informed whether installation was successful or not without having to go through a different, low-level, API. See below for a comparison between the old and new APIs: ```lua -- before local handle = pkg:install() handle:once("closed", function () -- ... end) -- after pkg:install({}, function (success, result) -- ... end) ```
Diffstat (limited to 'lua/mason-core/package/AbstractPackage.lua')
-rw-r--r--lua/mason-core/package/AbstractPackage.lua203
1 files changed, 203 insertions, 0 deletions
diff --git a/lua/mason-core/package/AbstractPackage.lua b/lua/mason-core/package/AbstractPackage.lua
new file mode 100644
index 00000000..b490fc87
--- /dev/null
+++ b/lua/mason-core/package/AbstractPackage.lua
@@ -0,0 +1,203 @@
+local EventEmitter = require "mason-core.EventEmitter"
+local InstallLocation = require "mason-core.installer.InstallLocation"
+local Optional = require "mason-core.optional"
+local Purl = require "mason-core.purl"
+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
+
+---@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean, location?: InstallLocation }
+---@alias PackageUninstallOpts { bypass_permit?: boolean, location?: InstallLocation }
+
+---@class AbstractPackage : EventEmitter
+---@field name string
+---@field spec RegistryPackageSpec
+---@field private install_handle InstallHandle? The currently associated installation handle.
+---@field private uninstall_handle InstallHandle? The currently associated uninstallation handle.
+local AbstractPackage = {}
+AbstractPackage.__index = AbstractPackage
+setmetatable(AbstractPackage, { __index = EventEmitter })
+
+AbstractPackage.SEMAPHORE = Semaphore:new(settings.current.max_concurrent_installers)
+---@type PackageInstallOpts
+AbstractPackage.DEFAULT_INSTALL_OPTS = {
+ debug = false,
+ force = false,
+ strict = false,
+ target = nil,
+ version = nil,
+}
+
+---@param spec RegistryPackageSpec
+function AbstractPackage:new(spec)
+ local instance = EventEmitter.new(self)
+ instance.name = spec.name -- for convenient access
+ instance.spec = spec
+ return instance
+end
+
+---@return boolean
+function AbstractPackage:is_installing()
+ return self:get_install_handle()
+ :map(
+ ---@param handle InstallHandle
+ function(handle)
+ return not handle:is_closed()
+ end
+ )
+ :or_else(false)
+end
+
+---@return boolean
+function AbstractPackage:is_uninstalling()
+ return self:get_uninstall_handle()
+ :map(
+ ---@param handle InstallHandle
+ function(handle)
+ return not handle:is_closed()
+ end
+ )
+ :or_else(false)
+end
+
+function AbstractPackage:get_install_handle()
+ return Optional.of_nilable(self.install_handle)
+end
+
+function AbstractPackage:get_uninstall_handle()
+ return Optional.of_nilable(self.uninstall_handle)
+end
+
+---@param location InstallLocation
+function AbstractPackage:new_handle(location)
+ assert(location, "Cannot create new handle without a location.")
+ local InstallHandle = require "mason-core.installer.InstallHandle"
+ local handle = InstallHandle:new(self, location)
+ -- Ideally we'd decouple this and leverage Mason's event system, but to allow loading as little as possible during
+ -- setup (i.e. not load modules related to Mason's event system) of the mason.nvim plugin we explicitly call into
+ -- terminator here.
+ require("mason-core.terminator").register(handle)
+ return handle
+end
+
+---@param location? InstallLocation
+function AbstractPackage:new_install_handle(location)
+ location = location or InstallLocation.global()
+ log.fmt_trace("Creating new installation handle for %s", self)
+ self:get_install_handle():if_present(function(handle)
+ assert(handle:is_closed(), "Cannot create new install handle because existing handle is not closed.")
+ end)
+ self.install_handle = self:new_handle(location)
+ self:emit("install:handle", self.install_handle)
+ return self.install_handle
+end
+
+---@param location? InstallLocation
+function AbstractPackage:new_uninstall_handle(location)
+ location = location or InstallLocation.global()
+ log.fmt_trace("Creating new uninstallation handle for %s", self)
+ self:get_uninstall_handle():if_present(function(handle)
+ assert(handle:is_closed(), "Cannot create new uninstall handle because existing handle is not closed.")
+ end)
+ self.uninstall_handle = self:new_handle(location)
+ self:emit("uninstall:handle", self.uninstall_handle)
+ return self.uninstall_handle
+end
+
+---@param opts? PackageInstallOpts
+function AbstractPackage:is_installable(opts)
+ return require("mason-core.installer.compiler").parse(self.spec, opts or {}):is_success()
+end
+
+---@param location? InstallLocation
+---@return Optional # Optional<InstallReceipt>
+function AbstractPackage:get_receipt(location)
+ location = location or InstallLocation.global()
+ local receipt_path = location:receipt(self.name)
+ 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))))
+ end
+ return Optional.empty()
+end
+
+---@param location? InstallLocation
+---@return boolean
+function AbstractPackage:is_installed(location)
+ error "Unimplemented."
+end
+
+---@return Result # Result<string[]>
+function AbstractPackage:get_all_versions()
+ local compiler = require "mason-core.installer.compiler"
+ return Result.try(function(try)
+ ---@type Purl
+ local purl = try(Purl.parse(self.spec.source.id))
+ ---@type InstallerCompiler
+ local compiler = try(compiler.get_compiler(purl))
+ return compiler.get_versions(purl, self.spec.source)
+ end)
+end
+
+---@return string
+function AbstractPackage:get_latest_version()
+ return Purl.parse(self.spec.source.id)
+ :map(_.prop "version")
+ :get_or_throw(("Unable to retrieve version from malformed purl: %s."):format(self.spec.source.id))
+end
+
+---@param location? InstallLocation
+---@return string?
+function AbstractPackage:get_installed_version(location)
+ return self:get_receipt(location)
+ :and_then(
+ ---@param receipt InstallReceipt
+ function(receipt)
+ local source = receipt:get_source()
+ if source.id then
+ return Purl.parse(source.id):map(_.prop "version"):ok()
+ else
+ return Optional.empty()
+ end
+ end
+ )
+ :or_else(nil)
+end
+
+---@param opts? PackageInstallOpts
+---@param callback? InstallRunnerCallback
+---@return InstallHandle
+function AbstractPackage:install(opts, callback)
+ error "Unimplemented."
+end
+
+---@param opts? PackageUninstallOpts
+---@param callback? InstallRunnerCallback
+---@return InstallHandle
+function AbstractPackage:uninstall(opts, callback)
+ error "Unimplemented."
+end
+
+---@private
+---@param location? InstallLocation
+function AbstractPackage:unlink(location)
+ location = location or InstallLocation.global()
+ log.fmt_trace("Unlinking", self, location)
+ local linker = require "mason-core.installer.linker"
+ return self:get_receipt(location):ok_or("Unable to find receipt."):and_then(function(receipt)
+ return linker.unlink(self, receipt, location)
+ end)
+end
+
+---@async
+---@private
+---@return Permit
+function AbstractPackage:acquire_permit()
+ error "Unimplemented."
+end
+
+return AbstractPackage