diff options
| author | William Boman <william@redwill.se> | 2023-11-07 00:29:18 +0100 |
|---|---|---|
| committer | William Boman <william@redwill.se> | 2025-02-19 12:15:48 +0100 |
| commit | 6a7662760c515c74f2c37fc825776ead65d307f9 (patch) | |
| tree | 0f4496d0678c7029b10236cbf48cc0f5ff63c1dc /lua/mason-core/package/AbstractPackage.lua | |
| parent | fix(pypi): remove -U flag and fix log message (diff) | |
| download | mason-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.lua | 203 |
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 |
