aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-rw-r--r--lua/mason-core/installer/compiler/compilers/cargo.lua (renamed from lua/mason-core/installer/registry/providers/cargo.lua)2
-rw-r--r--lua/mason-core/installer/compiler/compilers/composer.lua (renamed from lua/mason-core/installer/registry/providers/composer.lua)2
-rw-r--r--lua/mason-core/installer/compiler/compilers/gem.lua (renamed from lua/mason-core/installer/registry/providers/gem.lua)2
-rw-r--r--lua/mason-core/installer/compiler/compilers/generic/build.lua (renamed from lua/mason-core/installer/registry/providers/generic/build.lua)4
-rw-r--r--lua/mason-core/installer/compiler/compilers/generic/download.lua (renamed from lua/mason-core/installer/registry/providers/generic/download.lua)4
-rw-r--r--lua/mason-core/installer/compiler/compilers/generic/init.lua (renamed from lua/mason-core/installer/registry/providers/generic/init.lua)8
-rw-r--r--lua/mason-core/installer/compiler/compilers/github/build.lua (renamed from lua/mason-core/installer/registry/providers/github/build.lua)4
-rw-r--r--lua/mason-core/installer/compiler/compilers/github/init.lua (renamed from lua/mason-core/installer/registry/providers/github/init.lua)10
-rw-r--r--lua/mason-core/installer/compiler/compilers/github/release.lua (renamed from lua/mason-core/installer/registry/providers/github/release.lua)11
-rw-r--r--lua/mason-core/installer/compiler/compilers/golang.lua (renamed from lua/mason-core/installer/registry/providers/golang.lua)2
-rw-r--r--lua/mason-core/installer/compiler/compilers/luarocks.lua (renamed from lua/mason-core/installer/registry/providers/luarocks.lua)0
-rw-r--r--lua/mason-core/installer/compiler/compilers/mason.lua (renamed from lua/mason-core/installer/registry/providers/mason.lua)0
-rw-r--r--lua/mason-core/installer/compiler/compilers/npm.lua (renamed from lua/mason-core/installer/registry/providers/npm.lua)0
-rw-r--r--lua/mason-core/installer/compiler/compilers/nuget.lua (renamed from lua/mason-core/installer/registry/providers/nuget.lua)0
-rw-r--r--lua/mason-core/installer/compiler/compilers/opam.lua (renamed from lua/mason-core/installer/registry/providers/opam.lua)0
-rw-r--r--lua/mason-core/installer/compiler/compilers/openvsx.lua (renamed from lua/mason-core/installer/registry/providers/openvsx.lua)7
-rw-r--r--lua/mason-core/installer/compiler/compilers/pypi.lua (renamed from lua/mason-core/installer/registry/providers/pypi.lua)2
-rw-r--r--lua/mason-core/installer/compiler/expr.lua (renamed from lua/mason-core/installer/registry/expr.lua)0
-rw-r--r--lua/mason-core/installer/compiler/init.lua (renamed from lua/mason-core/installer/registry/init.lua)66
-rw-r--r--lua/mason-core/installer/compiler/link.lua (renamed from lua/mason-core/installer/registry/link.lua)2
-rw-r--r--lua/mason-core/installer/compiler/schemas.lua (renamed from lua/mason-core/installer/registry/schemas.lua)2
-rw-r--r--lua/mason-core/installer/compiler/util.lua (renamed from lua/mason-core/installer/registry/util.lua)0
-rw-r--r--lua/mason-core/installer/context/cwd.lua48
-rw-r--r--lua/mason-core/installer/context/fs.lua108
-rw-r--r--lua/mason-core/installer/context/init.lua (renamed from lua/mason-core/installer/context.lua)250
-rw-r--r--lua/mason-core/installer/context/spawn.lua46
-rw-r--r--lua/mason-core/installer/handle.lua4
-rw-r--r--lua/mason-core/installer/init.lua255
-rw-r--r--lua/mason-core/installer/location.lua63
-rw-r--r--lua/mason-core/installer/runner.lua218
-rw-r--r--lua/mason-core/package/init.lua114
-rw-r--r--lua/mason-core/receipt.lua14
-rw-r--r--lua/mason-registry/sources/util.lua4
-rw-r--r--lua/mason-test/helpers.lua33
34 files changed, 701 insertions, 584 deletions
diff --git a/lua/mason-core/installer/registry/providers/cargo.lua b/lua/mason-core/installer/compiler/compilers/cargo.lua
index f4904b73..e0f281c5 100644
--- a/lua/mason-core/installer/registry/providers/cargo.lua
+++ b/lua/mason-core/installer/compiler/compilers/cargo.lua
@@ -1,7 +1,7 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local providers = require "mason-core.providers"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/composer.lua b/lua/mason-core/installer/compiler/compilers/composer.lua
index d85dd2ba..259512a2 100644
--- a/lua/mason-core/installer/registry/providers/composer.lua
+++ b/lua/mason-core/installer/compiler/compilers/composer.lua
@@ -1,6 +1,6 @@
local Result = require "mason-core.result"
local providers = require "mason-core.providers"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/gem.lua b/lua/mason-core/installer/compiler/compilers/gem.lua
index 9653f116..7a343eec 100644
--- a/lua/mason-core/installer/registry/providers/gem.lua
+++ b/lua/mason-core/installer/compiler/compilers/gem.lua
@@ -1,7 +1,7 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local providers = require "mason-core.providers"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/generic/build.lua b/lua/mason-core/installer/compiler/compilers/generic/build.lua
index a0d517d8..df97a118 100644
--- a/lua/mason-core/installer/registry/providers/generic/build.lua
+++ b/lua/mason-core/installer/compiler/compilers/generic/build.lua
@@ -1,8 +1,8 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local common = require "mason-core.installer.managers.common"
-local expr = require "mason-core.installer.registry.expr"
-local util = require "mason-core.installer.registry.util"
+local expr = require "mason-core.installer.compiler.expr"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/generic/download.lua b/lua/mason-core/installer/compiler/compilers/generic/download.lua
index 4622a844..37e54d96 100644
--- a/lua/mason-core/installer/registry/providers/generic/download.lua
+++ b/lua/mason-core/installer/compiler/compilers/generic/download.lua
@@ -1,8 +1,8 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local common = require "mason-core.installer.managers.common"
-local expr = require "mason-core.installer.registry.expr"
-local util = require "mason-core.installer.registry.util"
+local expr = require "mason-core.installer.compiler.expr"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/generic/init.lua b/lua/mason-core/installer/compiler/compilers/generic/init.lua
index 1bf79e94..8206883f 100644
--- a/lua/mason-core/installer/registry/providers/generic/init.lua
+++ b/lua/mason-core/installer/compiler/compilers/generic/init.lua
@@ -9,10 +9,10 @@ local M = {}
function M.parse(source, purl, opts)
if source.download then
source = source --[[@as GenericDownloadSource]]
- return require("mason-core.installer.registry.providers.generic.download").parse(source, purl, opts)
+ return require("mason-core.installer.compiler.compilers.generic.download").parse(source, purl, opts)
elseif source.build then
source = source --[[@as GenericBuildSource]]
- return require("mason-core.installer.registry.providers.generic.build").parse(source, purl, opts)
+ return require("mason-core.installer.compiler.compilers.generic.build").parse(source, purl, opts)
else
return Result.failure "Unknown source type."
end
@@ -24,10 +24,10 @@ end
function M.install(ctx, source)
if source.download then
source = source --[[@as ParsedGenericDownloadSource]]
- return require("mason-core.installer.registry.providers.generic.download").install(ctx, source)
+ return require("mason-core.installer.compiler.compilers.generic.download").install(ctx, source)
elseif source.build then
source = source --[[@as ParsedGenericBuildSource]]
- return require("mason-core.installer.registry.providers.generic.build").install(ctx, source)
+ return require("mason-core.installer.compiler.compilers.generic.build").install(ctx, source)
else
return Result.failure "Unknown source type."
end
diff --git a/lua/mason-core/installer/registry/providers/github/build.lua b/lua/mason-core/installer/compiler/compilers/github/build.lua
index 1c17bb1a..22f3e3cc 100644
--- a/lua/mason-core/installer/registry/providers/github/build.lua
+++ b/lua/mason-core/installer/compiler/compilers/github/build.lua
@@ -1,8 +1,8 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local common = require "mason-core.installer.managers.common"
-local expr = require "mason-core.installer.registry.expr"
-local util = require "mason-core.installer.registry.util"
+local expr = require "mason-core.installer.compiler.expr"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/github/init.lua b/lua/mason-core/installer/compiler/compilers/github/init.lua
index 0d68f3a5..d8646975 100644
--- a/lua/mason-core/installer/registry/providers/github/init.lua
+++ b/lua/mason-core/installer/compiler/compilers/github/init.lua
@@ -8,10 +8,10 @@ local M = {}
function M.parse(source, purl, opts)
if source.asset then
source = source --[[@as GitHubReleaseSource]]
- return require("mason-core.installer.registry.providers.github.release").parse(source, purl, opts)
+ return require("mason-core.installer.compiler.compilers.github.release").parse(source, purl, opts)
elseif source.build then
source = source --[[@as GitHubBuildSource]]
- return require("mason-core.installer.registry.providers.github.build").parse(source, purl, opts)
+ return require("mason-core.installer.compiler.compilers.github.build").parse(source, purl, opts)
else
return Result.failure "Unknown source type."
end
@@ -23,10 +23,10 @@ end
function M.install(ctx, source, purl)
if source.asset then
source = source--[[@as ParsedGitHubReleaseSource]]
- return require("mason-core.installer.registry.providers.github.release").install(ctx, source)
+ return require("mason-core.installer.compiler.compilers.github.release").install(ctx, source)
elseif source.build then
source = source--[[@as ParsedGitHubBuildSource]]
- return require("mason-core.installer.registry.providers.github.build").install(ctx, source)
+ return require("mason-core.installer.compiler.compilers.github.build").install(ctx, source)
else
return Result.failure "Unknown source type."
end
@@ -37,7 +37,7 @@ end
---@param source GitHubReleaseSource | GitHubBuildSource
function M.get_versions(purl, source)
if source.asset then
- return require("mason-core.installer.registry.providers.github.release").get_versions(purl)
+ return require("mason-core.installer.compiler.compilers.github.release").get_versions(purl)
elseif source.build then
-- We can't yet reliably determine the true source (release, tag, commit, etc.) for "build" sources.
return Result.failure "Unimplemented."
diff --git a/lua/mason-core/installer/registry/providers/github/release.lua b/lua/mason-core/installer/compiler/compilers/github/release.lua
index 8c8a8a8f..39f7d862 100644
--- a/lua/mason-core/installer/registry/providers/github/release.lua
+++ b/lua/mason-core/installer/compiler/compilers/github/release.lua
@@ -1,13 +1,16 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local common = require "mason-core.installer.managers.common"
-local expr = require "mason-core.installer.registry.expr"
+local expr = require "mason-core.installer.compiler.expr"
local providers = require "mason-core.providers"
local settings = require "mason.settings"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
+
+---@class GitHubReleaseSourceAsset : FileDownloadSpec
+---@field target? Platform | Platform[]
---@class GitHubReleaseSource : RegistryPackageSource
----@field asset FileDownloadSpec | FileDownloadSpec[]
+---@field asset GitHubReleaseSourceAsset | GitHubReleaseSourceAsset[]
local M = {}
@@ -17,7 +20,7 @@ local M = {}
function M.parse(source, purl, opts)
return Result.try(function(try)
local expr_ctx = { version = purl.version }
- ---@type FileDownloadSpec
+ ---@type GitHubReleaseSourceAsset
local asset = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.asset, expr_ctx)), opts))
local downloads = common.parse_downloads(asset, function(file)
diff --git a/lua/mason-core/installer/registry/providers/golang.lua b/lua/mason-core/installer/compiler/compilers/golang.lua
index 896d9bf9..01807088 100644
--- a/lua/mason-core/installer/registry/providers/golang.lua
+++ b/lua/mason-core/installer/compiler/compilers/golang.lua
@@ -1,7 +1,7 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local providers = require "mason-core.providers"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/providers/luarocks.lua b/lua/mason-core/installer/compiler/compilers/luarocks.lua
index 356857c0..356857c0 100644
--- a/lua/mason-core/installer/registry/providers/luarocks.lua
+++ b/lua/mason-core/installer/compiler/compilers/luarocks.lua
diff --git a/lua/mason-core/installer/registry/providers/mason.lua b/lua/mason-core/installer/compiler/compilers/mason.lua
index 3490ebaa..3490ebaa 100644
--- a/lua/mason-core/installer/registry/providers/mason.lua
+++ b/lua/mason-core/installer/compiler/compilers/mason.lua
diff --git a/lua/mason-core/installer/registry/providers/npm.lua b/lua/mason-core/installer/compiler/compilers/npm.lua
index e8489fe8..e8489fe8 100644
--- a/lua/mason-core/installer/registry/providers/npm.lua
+++ b/lua/mason-core/installer/compiler/compilers/npm.lua
diff --git a/lua/mason-core/installer/registry/providers/nuget.lua b/lua/mason-core/installer/compiler/compilers/nuget.lua
index 370c7b95..370c7b95 100644
--- a/lua/mason-core/installer/registry/providers/nuget.lua
+++ b/lua/mason-core/installer/compiler/compilers/nuget.lua
diff --git a/lua/mason-core/installer/registry/providers/opam.lua b/lua/mason-core/installer/compiler/compilers/opam.lua
index 276686ae..276686ae 100644
--- a/lua/mason-core/installer/registry/providers/opam.lua
+++ b/lua/mason-core/installer/compiler/compilers/opam.lua
diff --git a/lua/mason-core/installer/registry/providers/openvsx.lua b/lua/mason-core/installer/compiler/compilers/openvsx.lua
index df52807a..bf31e2f9 100644
--- a/lua/mason-core/installer/registry/providers/openvsx.lua
+++ b/lua/mason-core/installer/compiler/compilers/openvsx.lua
@@ -1,16 +1,17 @@
local Result = require "mason-core.result"
local common = require "mason-core.installer.managers.common"
-local expr = require "mason-core.installer.registry.expr"
+local expr = require "mason-core.installer.compiler.expr"
local providers = require "mason-core.providers"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
---@class OpenVSXSourceDownload : FileDownloadSpec
+---@field target? Platform | Platform[]
---@field target_platform? string
---@class OpenVSXSource : RegistryPackageSource
----@field download FileDownloadSpec | FileDownloadSpec[]
+---@field download OpenVSXSourceDownload | OpenVSXSourceDownload[]
---@param source OpenVSXSource
---@param purl Purl
diff --git a/lua/mason-core/installer/registry/providers/pypi.lua b/lua/mason-core/installer/compiler/compilers/pypi.lua
index 3fe6f89e..c44fcfe1 100644
--- a/lua/mason-core/installer/registry/providers/pypi.lua
+++ b/lua/mason-core/installer/compiler/compilers/pypi.lua
@@ -2,7 +2,7 @@ local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local providers = require "mason-core.providers"
local settings = require "mason.settings"
-local util = require "mason-core.installer.registry.util"
+local util = require "mason-core.installer.compiler.util"
local M = {}
diff --git a/lua/mason-core/installer/registry/expr.lua b/lua/mason-core/installer/compiler/expr.lua
index a07fc00d..a07fc00d 100644
--- a/lua/mason-core/installer/registry/expr.lua
+++ b/lua/mason-core/installer/compiler/expr.lua
diff --git a/lua/mason-core/installer/registry/init.lua b/lua/mason-core/installer/compiler/init.lua
index 7376db86..e1df6784 100644
--- a/lua/mason-core/installer/registry/init.lua
+++ b/lua/mason-core/installer/compiler/init.lua
@@ -3,10 +3,10 @@ local Purl = require "mason-core.purl"
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
-local link = require "mason-core.installer.registry.link"
+local link = require "mason-core.installer.compiler.link"
local log = require "mason-core.log"
-local schemas = require "mason-core.installer.registry.schemas"
-local util = require "mason-core.installer.registry.util"
+local schemas = require "mason-core.installer.compiler.schemas"
+local util = require "mason-core.installer.compiler.util"
local M = {}
@@ -15,35 +15,37 @@ M.SCHEMA_CAP = _.set_of {
"registry+v1",
}
----@type table<string, InstallerProvider>
-local PROVIDERS = {}
+---@type table<string, InstallerCompiler>
+local COMPILERS = {}
---@param id string
----@param provider InstallerProvider
-function M.register_provider(id, provider)
- PROVIDERS[id] = provider
+---@param compiler InstallerCompiler
+function M.register_compiler(id, compiler)
+ COMPILERS[id] = compiler
end
-M.register_provider("cargo", _.lazy_require "mason-core.installer.registry.providers.cargo")
-M.register_provider("composer", _.lazy_require "mason-core.installer.registry.providers.composer")
-M.register_provider("gem", _.lazy_require "mason-core.installer.registry.providers.gem")
-M.register_provider("generic", _.lazy_require "mason-core.installer.registry.providers.generic")
-M.register_provider("github", _.lazy_require "mason-core.installer.registry.providers.github")
-M.register_provider("golang", _.lazy_require "mason-core.installer.registry.providers.golang")
-M.register_provider("luarocks", _.lazy_require "mason-core.installer.registry.providers.luarocks")
-M.register_provider("npm", _.lazy_require "mason-core.installer.registry.providers.npm")
-M.register_provider("nuget", _.lazy_require "mason-core.installer.registry.providers.nuget")
-M.register_provider("opam", _.lazy_require "mason-core.installer.registry.providers.opam")
-M.register_provider("openvsx", _.lazy_require "mason-core.installer.registry.providers.openvsx")
-M.register_provider("pypi", _.lazy_require "mason-core.installer.registry.providers.pypi")
-M.register_provider("mason", _.lazy_require "mason-core.installer.registry.providers.mason")
+M.register_compiler("cargo", _.lazy_require "mason-core.installer.compiler.compilers.cargo")
+M.register_compiler("composer", _.lazy_require "mason-core.installer.compiler.compilers.composer")
+M.register_compiler("gem", _.lazy_require "mason-core.installer.compiler.compilers.gem")
+M.register_compiler("generic", _.lazy_require "mason-core.installer.compiler.compilers.generic")
+M.register_compiler("github", _.lazy_require "mason-core.installer.compiler.compilers.github")
+M.register_compiler("golang", _.lazy_require "mason-core.installer.compiler.compilers.golang")
+M.register_compiler("luarocks", _.lazy_require "mason-core.installer.compiler.compilers.luarocks")
+M.register_compiler("mason", _.lazy_require "mason-core.installer.compiler.compilers.mason")
+M.register_compiler("npm", _.lazy_require "mason-core.installer.compiler.compilers.npm")
+M.register_compiler("nuget", _.lazy_require "mason-core.installer.compiler.compilers.nuget")
+M.register_compiler("opam", _.lazy_require "mason-core.installer.compiler.compilers.opam")
+M.register_compiler("openvsx", _.lazy_require "mason-core.installer.compiler.compilers.openvsx")
+M.register_compiler("pypi", _.lazy_require "mason-core.installer.compiler.compilers.pypi")
---@param purl Purl
-function M.get_provider(purl)
- return Optional.of_nilable(PROVIDERS[purl.type]):ok_or(("Unknown purl type: %s"):format(purl.type))
+---@return Result # Result<InstallerCompiler>
+function M.get_compiler(purl)
+ return Optional.of_nilable(COMPILERS[purl.type])
+ :ok_or(("Current version of mason.nvim is not capable of parsing package type %q."):format(purl.type))
end
----@class InstallerProvider
+---@class InstallerCompiler
---@field parse fun(source: RegistryPackageSource, purl: Purl, opts: PackageInstallOpts): Result
---@field install async fun(ctx: InstallContext, source: ParsedPackageSource, purl: Purl): Result
---@field get_versions async fun(purl: Purl, source: RegistryPackageSource): Result # Result<string[]>
@@ -128,13 +130,13 @@ function M.parse(spec, opts)
purl.version = opts.version
end
- ---@type InstallerProvider
- local provider = try(M.get_provider(purl))
- log.trace("Found provider for purl.", source.id)
- local parsed_source = try(provider.parse(source, purl, opts))
+ ---@type InstallerCompiler
+ local compiler = try(M.get_compiler(purl))
+ log.trace("Found compiler for purl.", source.id)
+ local parsed_source = try(compiler.parse(source, purl, opts))
log.trace("Parsed source for purl.", source.id, parsed_source)
return {
- provider = provider,
+ compiler = compiler,
source = vim.tbl_extend("keep", parsed_source, source),
raw_source = source,
purl = purl,
@@ -167,7 +169,7 @@ function M.compile(spec, opts)
{ _.T, _.identity },
}
- ---@type { purl: Purl, provider: InstallerProvider, source: ParsedPackageSource, raw_source: RegistryPackageSource }
+ ---@type { purl: Purl, compiler: InstallerCompiler, source: ParsedPackageSource, raw_source: RegistryPackageSource }
local parsed = try(M.parse(spec, opts):map_err(map_parse_err))
---@async
@@ -176,13 +178,13 @@ function M.compile(spec, opts)
return Result.try(function(try)
if ctx.opts.version then
try(util.ensure_valid_version(function()
- return parsed.provider.get_versions(parsed.purl, parsed.raw_source)
+ return parsed.compiler.get_versions(parsed.purl, parsed.raw_source)
end))
end
-- Run installer
a.scheduler()
- try(parsed.provider.install(ctx, parsed.source, parsed.purl))
+ try(parsed.compiler.install(ctx, parsed.source, parsed.purl))
if spec.schemas then
local result = schemas.download(ctx, spec, parsed.purl, parsed.source):on_failure(function(err)
diff --git a/lua/mason-core/installer/registry/link.lua b/lua/mason-core/installer/compiler/link.lua
index 85e751b7..5d136322 100644
--- a/lua/mason-core/installer/registry/link.lua
+++ b/lua/mason-core/installer/compiler/link.lua
@@ -2,7 +2,7 @@ local Optional = require "mason-core.optional"
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
-local expr = require "mason-core.installer.registry.expr"
+local expr = require "mason-core.installer.compiler.expr"
local fs = require "mason-core.fs"
local log = require "mason-core.log"
local path = require "mason-core.path"
diff --git a/lua/mason-core/installer/registry/schemas.lua b/lua/mason-core/installer/compiler/schemas.lua
index f9d044af..5e578dbd 100644
--- a/lua/mason-core/installer/registry/schemas.lua
+++ b/lua/mason-core/installer/compiler/schemas.lua
@@ -1,7 +1,7 @@
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local a = require "mason-core.async"
-local expr = require "mason-core.installer.registry.expr"
+local expr = require "mason-core.installer.compiler.expr"
local fetch = require "mason-core.fetch"
local log = require "mason-core.log"
local path = require "mason-core.path"
diff --git a/lua/mason-core/installer/registry/util.lua b/lua/mason-core/installer/compiler/util.lua
index b3735c9c..b3735c9c 100644
--- a/lua/mason-core/installer/registry/util.lua
+++ b/lua/mason-core/installer/compiler/util.lua
diff --git a/lua/mason-core/installer/context/cwd.lua b/lua/mason-core/installer/context/cwd.lua
new file mode 100644
index 00000000..4f645fbb
--- /dev/null
+++ b/lua/mason-core/installer/context/cwd.lua
@@ -0,0 +1,48 @@
+local Result = require "mason-core.result"
+local fs = require "mason-core.fs"
+local path = require "mason-core.path"
+
+---@class InstallContextCwd
+---@field private location InstallLocation Defines the upper boundary for which paths are allowed as cwd.
+---@field private cwd string?
+local InstallContextCwd = {}
+InstallContextCwd.__index = InstallContextCwd
+
+---@param location InstallLocation
+function InstallContextCwd.new(location)
+ assert(location, "location not provided")
+ return setmetatable({
+ location = location,
+ cwd = nil,
+ }, InstallContextCwd)
+end
+
+---@param handle InstallHandle
+function InstallContextCwd:initialize(handle)
+ return Result.try(function(try)
+ local staging_dir = self.location:staging(handle.package.name)
+ if fs.async.dir_exists(staging_dir) then
+ try(Result.pcall(fs.async.rmrf, staging_dir))
+ end
+ try(Result.pcall(fs.async.mkdirp, staging_dir))
+ self:set(staging_dir)
+ end)
+end
+
+function InstallContextCwd:get()
+ assert(self.cwd ~= nil, "Tried to access cwd before it was set.")
+ return self.cwd
+end
+
+---@param new_abs_cwd string
+function InstallContextCwd:set(new_abs_cwd)
+ assert(type(new_abs_cwd) == "string", "new_cwd is not a string")
+ assert(
+ path.is_subdirectory(self.location:get_dir(), new_abs_cwd),
+ ("%q is not a subdirectory of %q"):format(new_abs_cwd, self.location)
+ )
+ self.cwd = new_abs_cwd
+ return self
+end
+
+return InstallContextCwd
diff --git a/lua/mason-core/installer/context/fs.lua b/lua/mason-core/installer/context/fs.lua
new file mode 100644
index 00000000..5c51fb56
--- /dev/null
+++ b/lua/mason-core/installer/context/fs.lua
@@ -0,0 +1,108 @@
+local fs = require "mason-core.fs"
+local log = require "mason-core.log"
+local path = require "mason-core.path"
+
+---@class InstallContextFs
+---@field private cwd InstallContextCwd
+local InstallContextFs = {}
+InstallContextFs.__index = InstallContextFs
+
+---@param cwd InstallContextCwd
+function InstallContextFs.new(cwd)
+ return setmetatable({ cwd = cwd }, InstallContextFs)
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory to the file to append.
+---@param contents string
+function InstallContextFs:append_file(rel_path, contents)
+ return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents)
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory to the file to write.
+---@param contents string
+function InstallContextFs:write_file(rel_path, contents)
+ return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents)
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory to the file to read.
+function InstallContextFs:read_file(rel_path)
+ return fs.async.read_file(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory.
+function InstallContextFs:file_exists(rel_path)
+ return fs.async.file_exists(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory.
+function InstallContextFs:dir_exists(rel_path)
+ return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory.
+function InstallContextFs:rmrf(rel_path)
+ return fs.async.rmrf(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param rel_path string The relative path from the current working directory.
+function InstallContextFs:unlink(rel_path)
+ return fs.async.unlink(path.concat { self.cwd:get(), rel_path })
+end
+
+---@async
+---@param old_path string
+---@param new_path string
+function InstallContextFs:rename(old_path, new_path)
+ return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path })
+end
+
+---@async
+---@param dir_path string
+function InstallContextFs:mkdir(dir_path)
+ return fs.async.mkdir(path.concat { self.cwd:get(), dir_path })
+end
+
+---@async
+---@param dir_path string
+function InstallContextFs:mkdirp(dir_path)
+ return fs.async.mkdirp(path.concat { self.cwd:get(), dir_path })
+end
+
+---@async
+---@param file_path string
+function InstallContextFs:chmod_exec(file_path)
+ local bit = require "bit"
+ -- see chmod(2)
+ local USR_EXEC = 0x40
+ local GRP_EXEC = 0x8
+ local ALL_EXEC = 0x1
+ local EXEC = bit.bor(USR_EXEC, GRP_EXEC, ALL_EXEC)
+ local fstat = self:fstat(file_path)
+ if bit.band(fstat.mode, EXEC) ~= EXEC then
+ local plus_exec = bit.bor(fstat.mode, EXEC)
+ log.fmt_debug("Setting exec flags on file %s %o -> %o", file_path, fstat.mode, plus_exec)
+ self:chmod(file_path, plus_exec) -- chmod +x
+ end
+end
+
+---@async
+---@param file_path string
+---@param mode integer
+function InstallContextFs:chmod(file_path, mode)
+ return fs.async.chmod(path.concat { self.cwd:get(), file_path }, mode)
+end
+
+---@async
+---@param file_path string
+function InstallContextFs:fstat(file_path)
+ return fs.async.fstat(path.concat { self.cwd:get(), file_path })
+end
+
+return InstallContextFs
diff --git a/lua/mason-core/installer/context.lua b/lua/mason-core/installer/context/init.lua
index a991cd9f..0d178c4e 100644
--- a/lua/mason-core/installer/context.lua
+++ b/lua/mason-core/installer/context/init.lua
@@ -1,213 +1,37 @@
-local Optional = require "mason-core.optional"
+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 platform = require "mason-core.platform"
local receipt = require "mason-core.receipt"
-local spawn = require "mason-core.spawn"
-
----@class ContextualSpawn
----@field strict_mode boolean Whether spawn failures should raise an exception rather then return a Result.
----@field cwd CwdManager
----@field handle InstallHandle
----@field [string] async fun(opts: SpawnArgs): Result
-local ContextualSpawn = {}
-
----@param cwd CwdManager
----@param handle InstallHandle
----@param strict_mode boolean
-function ContextualSpawn.new(cwd, handle, strict_mode)
- return setmetatable({ cwd = cwd, handle = handle, strict_mode = strict_mode }, ContextualSpawn)
-end
-
----@param cmd string
-function ContextualSpawn:__index(cmd)
- ---@param args JobSpawnOpts
- return function(args)
- args.cwd = args.cwd or self.cwd:get()
- args.stdio_sink = args.stdio_sink or self.handle.stdio.sink
- local on_spawn = args.on_spawn
- 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))
- if on_spawn then
- on_spawn(handle, stdio, pid, ...)
- end
- end
- local function pop_spawn_stack()
- if captured_handle then
- self.handle:deregister_spawn_handle(captured_handle)
- end
- end
- local result = spawn[cmd](args):on_success(pop_spawn_stack):on_failure(pop_spawn_stack)
- if self.strict_mode then
- return result:get_or_throw()
- else
- return result
- end
- end
-end
-
----@class ContextualFs
----@field private cwd CwdManager
-local ContextualFs = {}
-ContextualFs.__index = ContextualFs
-
----@param cwd CwdManager
-function ContextualFs.new(cwd)
- return setmetatable({ cwd = cwd }, ContextualFs)
-end
-
----@async
----@param rel_path string The relative path from the current working directory to the file to append.
----@param contents string
-function ContextualFs:append_file(rel_path, contents)
- return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents)
-end
-
----@async
----@param rel_path string The relative path from the current working directory to the file to write.
----@param contents string
-function ContextualFs:write_file(rel_path, contents)
- return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents)
-end
-
----@async
----@param rel_path string The relative path from the current working directory to the file to read.
-function ContextualFs:read_file(rel_path)
- return fs.async.read_file(path.concat { self.cwd:get(), rel_path })
-end
-
----@async
----@param rel_path string The relative path from the current working directory.
-function ContextualFs:file_exists(rel_path)
- return fs.async.file_exists(path.concat { self.cwd:get(), rel_path })
-end
-
----@async
----@param rel_path string The relative path from the current working directory.
-function ContextualFs:dir_exists(rel_path)
- return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path })
-end
-
----@async
----@param rel_path string The relative path from the current working directory.
-function ContextualFs:rmrf(rel_path)
- return fs.async.rmrf(path.concat { self.cwd:get(), rel_path })
-end
-
----@async
----@param rel_path string The relative path from the current working directory.
-function ContextualFs:unlink(rel_path)
- return fs.async.unlink(path.concat { self.cwd:get(), rel_path })
-end
-
----@async
----@param old_path string
----@param new_path string
-function ContextualFs:rename(old_path, new_path)
- return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path })
-end
-
----@async
----@param dir_path string
-function ContextualFs:mkdir(dir_path)
- return fs.async.mkdir(path.concat { self.cwd:get(), dir_path })
-end
-
----@async
----@param dir_path string
-function ContextualFs:mkdirp(dir_path)
- return fs.async.mkdirp(path.concat { self.cwd:get(), dir_path })
-end
-
----@async
----@param file_path string
-function ContextualFs:chmod_exec(file_path)
- local bit = require "bit"
- -- see chmod(2)
- local USR_EXEC = 0x40
- local GRP_EXEC = 0x8
- local ALL_EXEC = 0x1
- local EXEC = bit.bor(USR_EXEC, GRP_EXEC, ALL_EXEC)
- local fstat = self:fstat(file_path)
- if bit.band(fstat.mode, EXEC) ~= EXEC then
- local plus_exec = bit.bor(fstat.mode, EXEC)
- log.fmt_debug("Setting exec flags on file %s %o -> %o", file_path, fstat.mode, plus_exec)
- self:chmod(file_path, plus_exec) -- chmod +x
- end
-end
-
----@async
----@param file_path string
----@param mode integer
-function ContextualFs:chmod(file_path, mode)
- return fs.async.chmod(path.concat { self.cwd:get(), file_path }, mode)
-end
-
----@async
----@param file_path string
-function ContextualFs:fstat(file_path)
- return fs.async.fstat(path.concat { self.cwd:get(), file_path })
-end
-
----@class CwdManager
----@field private install_prefix string Defines the upper boundary for which paths are allowed as cwd.
----@field private cwd string
-local CwdManager = {}
-CwdManager.__index = CwdManager
-
-function CwdManager.new(install_prefix)
- assert(type(install_prefix) == "string", "install_prefix not provided")
- return setmetatable({
- install_prefix = install_prefix,
- cwd = nil,
- }, CwdManager)
-end
-
-function CwdManager:get()
- assert(self.cwd ~= nil, "Tried to access cwd before it was set.")
- return self.cwd
-end
-
----@param new_cwd string
-function CwdManager:set(new_cwd)
- assert(type(new_cwd) == "string", "new_cwd is not a string")
- assert(
- path.is_subdirectory(self.install_prefix, new_cwd),
- ("%q is not a subdirectory of %q"):format(new_cwd, self.install_prefix)
- )
- self.cwd = new_cwd
-end
---@class InstallContext
----@field public receipt InstallReceiptBuilder
----@field public requested_version Optional
----@field public fs ContextualFs
----@field public spawn ContextualSpawn
----@field public handle InstallHandle
----@field public package Package
----@field public cwd CwdManager
----@field public opts PackageInstallOpts
----@field public stdio_sink StdioSink
+---@field receipt InstallReceiptBuilder
+---@field fs InstallContextFs
+---@field spawn InstallContextSpawn
+---@field handle InstallHandle
+---@field package Package
+---@field cwd InstallContextCwd
+---@field opts PackageInstallOpts
+---@field stdio_sink StdioSink
---@field links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> }
local InstallContext = {}
InstallContext.__index = InstallContext
---@param handle InstallHandle
+---@param cwd InstallContextCwd
+---@param spawn InstallContextSpawn
+---@param fs InstallContextFs
---@param opts PackageInstallOpts
-function InstallContext.new(handle, opts)
- local cwd_manager = CwdManager.new(path.install_prefix())
+function InstallContext.new(handle, cwd, spawn, fs, opts)
return setmetatable({
- cwd = cwd_manager,
- spawn = ContextualSpawn.new(cwd_manager, handle, false),
+ cwd = cwd,
+ spawn = spawn,
handle = handle,
package = handle.package, -- for convenience
- fs = ContextualFs.new(cwd_manager),
+ fs = fs,
receipt = receipt.InstallReceiptBuilder.new(),
- requested_version = Optional.of_nilable(opts.version),
stdio_sink = handle.stdio.sink,
links = {
bin = {},
@@ -227,8 +51,8 @@ function InstallContext:promote_cwd()
return
end
log.fmt_debug("Promoting cwd %s to %s", cwd, install_path)
- -- 1. Unlink any existing installation
- self.handle.package:unlink()
+ -- 1. Uninstall any existing installation
+ self.handle.package:uninstall()
-- 2. Prepare for renaming cwd to destination
if platform.is.unix then
-- Some Unix systems will raise an error when renaming a directory to a destination that does not already exist.
@@ -396,4 +220,42 @@ function InstallContext:link_bin(executable, rel_path)
return self
end
+InstallContext.CONTEXT_REQUEST = {}
+
+---@generic T
+---@param fn fun(context: InstallContext): T
+---@return T
+function InstallContext:execute(fn)
+ local thread = coroutine.create(function(...)
+ -- We wrap the function to allow it to be a spy instance (in which case it's not actually a function, but a
+ -- callable metatable - coroutine.create strictly expects functions only)
+ return fn(...)
+ end)
+ local step
+ local ret_val
+ step = function(...)
+ local ok, result = coroutine.resume(thread, ...)
+ if not ok then
+ error(result, 0)
+ elseif result == InstallContext.CONTEXT_REQUEST then
+ step(self)
+ elseif coroutine.status(thread) == "suspended" then
+ -- yield to parent coroutine
+ step(coroutine.yield(result))
+ else
+ ret_val = result
+ end
+ end
+ step(self)
+ return ret_val
+end
+
+---@async
+function InstallContext:build_receipt()
+ log.fmt_debug("Building receipt for %s", self.package)
+ return Result.pcall(function()
+ return self.receipt:with_name(self.package.name):with_completion_time(vim.loop.gettimeofday()):build()
+ end)
+end
+
return InstallContext
diff --git a/lua/mason-core/installer/context/spawn.lua b/lua/mason-core/installer/context/spawn.lua
new file mode 100644
index 00000000..6528c4b3
--- /dev/null
+++ b/lua/mason-core/installer/context/spawn.lua
@@ -0,0 +1,46 @@
+local spawn = require "mason-core.spawn"
+
+---@class InstallContextSpawn
+---@field strict_mode boolean Whether spawn failures should raise an exception rather then return a Result.
+---@field private cwd InstallContextCwd
+---@field private handle InstallHandle
+---@field [string] async fun(opts: SpawnArgs): Result
+local InstallContextSpawn = {}
+
+---@param cwd InstallContextCwd
+---@param handle InstallHandle
+---@param strict_mode boolean
+function InstallContextSpawn.new(cwd, handle, strict_mode)
+ return setmetatable({ cwd = cwd, handle = handle, strict_mode = strict_mode }, InstallContextSpawn)
+end
+
+---@param cmd string
+function InstallContextSpawn:__index(cmd)
+ ---@param args JobSpawnOpts
+ return function(args)
+ args.cwd = args.cwd or self.cwd:get()
+ args.stdio_sink = args.stdio_sink or self.handle.stdio.sink
+ local on_spawn = args.on_spawn
+ 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))
+ if on_spawn then
+ on_spawn(handle, stdio, pid, ...)
+ end
+ end
+ local function pop_spawn_stack()
+ if captured_handle then
+ self.handle:deregister_spawn_handle(captured_handle)
+ end
+ end
+ local result = spawn[cmd](args):on_success(pop_spawn_stack):on_failure(pop_spawn_stack)
+ if self.strict_mode then
+ return result:get_or_throw()
+ else
+ return result
+ end
+ end
+end
+
+return InstallContextSpawn
diff --git a/lua/mason-core/installer/handle.lua b/lua/mason-core/installer/handle.lua
index f9b03557..96acbdd1 100644
--- a/lua/mason-core/installer/handle.lua
+++ b/lua/mason-core/installer/handle.lua
@@ -120,6 +120,10 @@ function InstallHandle:is_closed()
return self.state == "CLOSED"
end
+function InstallHandle:is_closing()
+ return self:is_closed() or self.is_terminated
+end
+
---@param new_state InstallHandleState
function InstallHandle:set_state(new_state)
local old_state = self.state
diff --git a/lua/mason-core/installer/init.lua b/lua/mason-core/installer/init.lua
index 45bba46b..37c74fcb 100644
--- a/lua/mason-core/installer/init.lua
+++ b/lua/mason-core/installer/init.lua
@@ -1,263 +1,10 @@
local InstallContext = require "mason-core.installer.context"
-local Result = require "mason-core.result"
-local _ = require "mason-core.functional"
-local a = require "mason-core.async"
-local control = require "mason-core.async.control"
-local fs = require "mason-core.fs"
-local linker = require "mason-core.installer.linker"
-local log = require "mason-core.log"
-local path = require "mason-core.path"
-local settings = require "mason.settings"
-
-local Semaphore = control.Semaphore
-
-local sem = Semaphore.new(settings.current.max_concurrent_installers)
local M = {}
----@async
-function M.create_prefix_dirs()
- return Result.try(function(try)
- for _, p in ipairs {
- path.install_prefix(),
- path.bin_prefix(),
- path.share_prefix(),
- path.package_prefix(),
- path.package_build_prefix(),
- } do
- if not fs.async.dir_exists(p) then
- try(Result.pcall(fs.async.mkdirp, p))
- end
- end
- end)
-end
-
----@async
----@param context InstallContext
-local function build_receipt(context)
- return Result.pcall(function()
- log.fmt_debug("Building receipt for %s", context.package)
- return context.receipt:with_name(context.package.name):with_completion_time(vim.loop.gettimeofday()):build()
- end)
-end
-
-local CONTEXT_REQUEST = {}
-
---@return InstallContext
function M.context()
- return coroutine.yield(CONTEXT_REQUEST)
-end
-
----@async
----@param ctx InstallContext
-local function lock_package(ctx)
- log.debug("Attempting to lock package", ctx.package)
- local lockfile = path.package_lock(ctx.package.name)
- if not ctx.opts.force and fs.async.file_exists(lockfile) then
- log.error("Lockfile already exists.", ctx.package)
- return Result.failure(
- ("Lockfile exists, installation is already running in another process (pid: %s). Run with :MasonInstall --force to bypass."):format(
- fs.sync.read_file(lockfile)
- )
- )
- end
- a.scheduler()
- fs.async.write_file(lockfile, vim.fn.getpid())
- log.debug("Wrote lockfile", ctx.package)
- return Result.success(lockfile)
-end
-
----@async
----@param context InstallContext
-function M.prepare_installer(context)
- local installer = require "mason-core.installer.registry"
- return Result.try(function(try)
- local package_build_prefix = path.package_build_prefix(context.package.name)
- if fs.async.dir_exists(package_build_prefix) then
- try(Result.pcall(fs.async.rmrf, package_build_prefix))
- end
- try(Result.pcall(fs.async.mkdirp, package_build_prefix))
- context.cwd:set(package_build_prefix)
-
- return try(installer.compile(context.handle.package.spec, context.opts))
- end)
-end
-
----@generic T
----@param context InstallContext
----@param fn fun(context: InstallContext): T
----@return T
-function M.exec_in_context(context, fn)
- local thread = coroutine.create(function(...)
- -- We wrap the function to allow it to be a spy instance (in which case it's not actually a function, but a
- -- callable metatable - coroutine.create strictly expects functions only)
- return fn(...)
- end)
- local step
- local ret_val
- step = function(...)
- local ok, result = coroutine.resume(thread, ...)
- if not ok then
- error(result, 0)
- elseif result == CONTEXT_REQUEST then
- step(context)
- elseif coroutine.status(thread) == "suspended" then
- -- yield to parent coroutine
- step(coroutine.yield(result))
- else
- ret_val = result
- end
- end
- context.receipt:with_start_time(vim.loop.gettimeofday())
- step(context)
- return ret_val
-end
-
----@async
----@param context InstallContext
----@param installer async fun(ctx: InstallContext)
-local function run_installer(context, installer)
- local handle = context.handle
- return Result.pcall(function()
- return a.wait(function(resolve, reject)
- local cancel_thread = a.run(M.exec_in_context, function(success, result)
- if success then
- resolve(result)
- else
- reject(result)
- end
- end, context, installer)
-
- handle:once("terminate", function()
- cancel_thread()
- if handle:is_closed() then
- reject "Installation was aborted."
- else
- handle:once("closed", function()
- reject "Installation was aborted."
- end)
- end
- end)
- end)
- end)
-end
-
----@async
----@param handle InstallHandle
----@param opts PackageInstallOpts
-function M.execute(handle, opts)
- if handle:is_active() or handle:is_closed() then
- log.fmt_debug("Received active or closed handle %s", handle)
- return Result.failure "Invalid handle state."
- end
-
- handle:queued()
- local permit = sem:acquire()
- if handle:is_closed() then
- permit:forget()
- log.fmt_trace("Installation was aborted %s", handle)
- return Result.failure "Installation was aborted."
- end
- log.fmt_trace("Activating handle %s", handle)
- handle:active()
-
- local pkg = handle.package
- local context = InstallContext.new(handle, opts)
- local tailed_output = {}
-
- if opts.debug then
- local function append_log(chunk)
- tailed_output[#tailed_output + 1] = chunk
- end
- handle:on("stdout", append_log)
- handle:on("stderr", append_log)
- end
-
- log.fmt_info("Executing installer for %s %s", pkg, opts)
-
- return M.create_prefix_dirs()
- :and_then(function()
- return lock_package(context)
- end)
- :and_then(function(lockfile)
- local release_lock = _.partial(pcall, fs.async.unlink, lockfile)
- return Result.try(function(try)
- -- 1. prepare directories and initialize cwd
- local installer = try(M.prepare_installer(context))
-
- -- 2. execute installer
- try(run_installer(context, installer))
-
- -- 3. promote temporary installation dir
- try(Result.pcall(function()
- context:promote_cwd()
- end))
-
- -- 4. link package
- try(linker.link(context))
-
- -- 5. build & write receipt
- ---@type InstallReceipt
- local receipt = try(build_receipt(context))
- try(Result.pcall(function()
- receipt:write(context.cwd:get())
- end))
- end)
- :on_success(function()
- release_lock()
- if opts.debug then
- context.fs:write_file("mason-debug.log", table.concat(tailed_output, ""))
- end
- end)
- :on_failure(function()
- release_lock()
- if not opts.debug then
- -- clean up installation dir
- pcall(function()
- fs.async.rmrf(context.cwd:get())
- end)
- else
- context.fs:write_file("mason-debug.log", table.concat(tailed_output, ""))
- context.stdio_sink.stdout(
- ("[debug] Installation directory retained at %q.\n"):format(context.cwd:get())
- )
- end
-
- -- unlink linked executables (in the occasion an error occurs after linking)
- build_receipt(context):on_success(function(receipt)
- linker.unlink(context.package, receipt):on_failure(function(err)
- log.error("Failed to unlink failed installation", err)
- end)
- end)
- end)
- end)
- :on_success(function()
- permit:forget()
- handle:close()
- log.fmt_info("Installation succeeded for %s", pkg)
- end)
- :on_failure(function(failure)
- permit:forget()
- log.fmt_error("Installation failed for %s error=%s", pkg, failure)
- context.stdio_sink.stderr(tostring(failure))
- context.stdio_sink.stderr "\n"
-
- if not handle:is_closed() and not handle.is_terminated then
- handle:close()
- end
- end)
-end
-
----Runs the provided async functions concurrently and returns their result, once all are resolved.
----This is really just a wrapper around a.wait_all() that makes sure to patch the coroutine context before creating the
----new async execution contexts.
----@async
----@param suspend_fns async fun(ctx: InstallContext)[]
-function M.run_concurrently(suspend_fns)
- local context = M.context()
- return a.wait_all(_.map(function(suspend_fn)
- return _.partial(M.exec_in_context, context, suspend_fn)
- end, suspend_fns))
+ return coroutine.yield(InstallContext.CONTEXT_REQUEST)
end
return M
diff --git a/lua/mason-core/installer/location.lua b/lua/mason-core/installer/location.lua
new file mode 100644
index 00000000..2cc038e4
--- /dev/null
+++ b/lua/mason-core/installer/location.lua
@@ -0,0 +1,63 @@
+local Path = require "mason-core.path"
+local Result = require "mason-core.result"
+local fs = require "mason-core.fs"
+
+---@class InstallLocation
+---@field private dir string
+local InstallLocation = {}
+InstallLocation.__index = InstallLocation
+
+---@param dir string
+function InstallLocation.new(dir)
+ return setmetatable({
+ dir = dir,
+ }, InstallLocation)
+end
+
+function InstallLocation:get_dir()
+ return self.dir
+end
+
+---@async
+function InstallLocation:initialize()
+ return Result.try(function(try)
+ for _, p in ipairs {
+ self.dir,
+ self:bin(),
+ self:share(),
+ self:package(),
+ self:staging(),
+ } do
+ if not fs.async.dir_exists(p) then
+ try(Result.pcall(fs.async.mkdirp, p))
+ end
+ end
+ end)
+end
+
+---@param path string?
+function InstallLocation:bin(path)
+ return Path.concat { self.dir, "bin", path }
+end
+
+---@param path string?
+function InstallLocation:share(path)
+ return Path.concat { self.dir, "share", path }
+end
+
+---@param path string?
+function InstallLocation:package(path)
+ return Path.concat { self.dir, "packages", path }
+end
+
+---@param path string?
+function InstallLocation:staging(path)
+ return Path.concat { self.dir, "staging", path }
+end
+
+---@param name string
+function InstallLocation:lockfile(name)
+ return self:staging(("%s.lock"):format(name))
+end
+
+return InstallLocation
diff --git a/lua/mason-core/installer/runner.lua b/lua/mason-core/installer/runner.lua
new file mode 100644
index 00000000..175610d5
--- /dev/null
+++ b/lua/mason-core/installer/runner.lua
@@ -0,0 +1,218 @@
+local Result = require "mason-core.result"
+local _ = require "mason-core.functional"
+local a = require "mason-core.async"
+local compiler = require "mason-core.installer.compiler"
+local fs = require "mason-core.fs"
+local linker = require "mason-core.installer.linker"
+local log = require "mason-core.log"
+local registry = require "mason-registry"
+
+local InstallContext = require "mason-core.installer.context"
+local InstallContextCwd = require "mason-core.installer.context.cwd"
+local InstallContextFs = require "mason-core.installer.context.fs"
+local InstallContextSpawn = require "mason-core.installer.context.spawn"
+
+---@class InstallRunner
+---@field location InstallLocation
+---@field handle InstallHandle
+---@field semaphore Semaphore
+---@field permit Permit?
+local InstallRunner = {}
+InstallRunner.__index = InstallRunner
+
+---@param location InstallLocation
+---@param handle InstallHandle
+---@param semaphore Semaphore
+function InstallRunner.new(location, handle, semaphore)
+ return setmetatable({
+ location = location,
+ semaphore = semaphore,
+ handle = handle,
+ }, InstallRunner)
+end
+
+---@param opts PackageInstallOpts
+---@param callback? fun(success: boolean, result: any)
+function InstallRunner:execute(opts, callback)
+ local handle = self.handle
+ log.fmt_info("Executing installer for %s %s", handle.package, opts)
+
+ local context_cwd = InstallContextCwd.new(self.location)
+ local context_spawn = InstallContextSpawn.new(context_cwd, handle, false)
+ local context_fs = InstallContextFs.new(context_cwd)
+ local context = InstallContext.new(handle, context_cwd, context_spawn, context_fs, opts)
+
+ local tailed_output = {}
+
+ if opts.debug then
+ local function append_log(chunk)
+ tailed_output[#tailed_output + 1] = chunk
+ end
+ handle:on("stdout", append_log)
+ handle:on("stderr", append_log)
+ end
+
+ ---@async
+ local function finalize_logs(success, result)
+ if not success then
+ context.stdio_sink.stderr(tostring(result))
+ context.stdio_sink.stderr "\n"
+ end
+
+ if opts.debug then
+ context.fs:write_file("mason-debug.log", table.concat(tailed_output, ""))
+ context.stdio_sink.stdout(("[debug] Installation directory retained at %q.\n"):format(context.cwd:get()))
+ end
+ end
+
+ ---@async
+ local finalize = a.scope(function(success, result)
+ finalize_logs(success, result)
+
+ if not opts.debug and not success then
+ -- clean up installation dir
+ pcall(function()
+ fs.async.rmrf(context.cwd:get())
+ end)
+ end
+
+ if not handle:is_closing() then
+ handle:close()
+ end
+
+ self:release_lock()
+ self:release_permit()
+
+ if callback then
+ callback(success, result)
+ end
+
+ if success then
+ log.fmt_info("Installation succeeded for %s", handle.package)
+ handle.package:emit("install:success", handle)
+ registry:emit("package:install:success", handle.package, handle)
+ else
+ log.fmt_error("Installation failed for %s error=%s", handle.package, result)
+ handle.package:emit("install:failed", handle, result)
+ registry:emit("package:install:failed", handle.package, handle, result)
+ end
+ end)
+
+ local cancel_execution = a.run(function()
+ return Result.try(function(try)
+ try(self:acquire_permit())
+ try(self.location:initialize())
+ try(self:acquire_lock(opts.force))
+
+ context.receipt:with_start_time(vim.loop.gettimeofday())
+
+ -- 1. initialize working directory
+ try(context_cwd:initialize(handle))
+
+ -- 2. run installer
+ ---@type async fun(ctx: InstallContext): Result
+ local installer = try(compiler.compile(handle.package.spec, opts))
+ try(context:execute(installer))
+
+ -- 3. promote temporary installation dir
+ try(Result.pcall(function()
+ context:promote_cwd()
+ end))
+
+ -- 4. link package & write receipt
+ return linker
+ .link(context)
+ :and_then(function()
+ return context:build_receipt(context)
+ end)
+ :and_then(
+ ---@param receipt InstallReceipt
+ function(receipt)
+ return receipt:write(context.cwd:get())
+ end
+ )
+ :on_failure(function()
+ -- unlink any links that were made before failure
+ context:build_receipt():on_success(
+ ---@param receipt InstallReceipt
+ function(receipt)
+ linker.unlink(handle.package, receipt):on_failure(function(err)
+ log.error("Failed to unlink failed installation.", err)
+ end)
+ end
+ )
+ end)
+ end):get_or_throw()
+ end, finalize)
+
+ handle:once("terminate", function()
+ cancel_execution()
+ local function on_close()
+ finalize(false, "Installation was aborted.")
+ end
+ if handle:is_closed() then
+ on_close()
+ else
+ handle:once("closed", on_close)
+ end
+ end)
+end
+
+---@async
+---@private
+function InstallRunner:release_lock()
+ pcall(fs.async.unlink, self.location:lockfile(self.handle.package.name))
+end
+
+---@async
+---@param force boolean?
+---@private
+function InstallRunner:acquire_lock(force)
+ local pkg = self.handle.package
+ log.debug("Attempting to lock package", pkg)
+ local lockfile = self.location:lockfile(pkg.name)
+ if force ~= true and fs.async.file_exists(lockfile) then
+ log.error("Lockfile already exists.", pkg)
+ return Result.failure(
+ ("Lockfile exists, installation is already running in another process (pid: %s). Run with :MasonInstall --force to bypass."):format(
+ fs.async.read_file(lockfile)
+ )
+ )
+ end
+ a.scheduler()
+ fs.async.write_file(lockfile, vim.fn.getpid())
+ log.debug("Wrote lockfile", pkg)
+ return Result.success(lockfile)
+end
+
+---@async
+---@private
+function InstallRunner:acquire_permit()
+ local handle = self.handle
+ if handle:is_active() or handle:is_closed() then
+ log.fmt_debug("Received active or closed handle %s", handle)
+ return Result.failure "Invalid handle state."
+ end
+
+ handle:queued()
+ local permit = self.semaphore:acquire()
+ if handle:is_closed() then
+ permit:forget()
+ log.fmt_trace("Installation was aborted %s", handle)
+ return Result.failure "Installation was aborted."
+ end
+ log.fmt_trace("Activating handle %s", handle)
+ handle:active()
+ self.permit = permit
+ return Result.success()
+end
+
+---@private
+function InstallRunner:release_permit()
+ if self.permit then
+ self.permit:forget()
+ self.permit = nil
+ end
+end
+
+return InstallRunner
diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua
index bc98a72a..b0da8a61 100644
--- a/lua/mason-core/package/init.lua
+++ b/lua/mason-core/package/init.lua
@@ -1,14 +1,17 @@
local EventEmitter = require "mason-core.EventEmitter"
+local InstallLocation = require "mason-core.installer.location"
+local InstallRunner = require "mason-core.installer.runner"
local Optional = require "mason-core.optional"
local Purl = require "mason-core.purl"
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 registry = require "mason-registry"
+local settings = require "mason.settings"
+local Semaphore = require("mason-core.async.control").Semaphore
---@class Package : EventEmitter
---@field name string
@@ -135,81 +138,56 @@ end
---@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean }
----@param opts? PackageInstallOpts
----@return InstallHandle
-function Package:install(opts)
- opts = opts or {}
+-- TODO this needs to be elsewhere
+local semaphore = Semaphore.new(settings.current.max_concurrent_installers)
+
+function Package:is_installing()
return self:get_handle()
- :map(function(handle)
- if not handle:is_closed() then
- log.fmt_debug("Handle %s already exist for package %s", handle, self)
- return handle
+ :map(
+ ---@param handle InstallHandle
+ function(handle)
+ return not handle:is_closed()
end
- end)
- :or_else_get(function()
- local handle = self:new_handle()
- a.run(
- require("mason-core.installer").execute,
- ---@param success boolean
- ---@param result Result
- function(success, result)
- if not success then
- -- Installer failed abnormally (i.e. unexpected exception in the installer code itself).
- log.error("Unexpected error", result)
- handle.stdio.sink.stderr(tostring(result))
- handle.stdio.sink.stderr "\nInstallation failed abnormally. Please report this error."
- self:emit("install:failed", handle)
- registry:emit("package:install:failed", self, handle)
+ )
+ :or_else(false)
+end
- -- We terminate _after_ emitting failure events because [termination -> failed] have different
- -- meaning than [failed -> terminate] ([termination -> failed] is interpreted as a triggered
- -- termination).
- if not handle:is_closed() and not handle.is_terminated then
- handle:terminate()
- end
- return
- end
- result
- :on_success(function()
- self:emit("install:success", handle)
- registry:emit("package:install:success", self, handle)
- end)
- :on_failure(function()
- self:emit("install:failed", handle)
- registry:emit("package:install:failed", self, handle)
- end)
- end,
- handle,
- opts
- )
- return handle
- end)
+---@param opts? PackageInstallOpts
+---@param callback? fun(success: boolean, result: any)
+---@return InstallHandle
+function Package:install(opts, callback)
+ opts = opts or {}
+ assert(not self:is_installing(), "Package is already installing.")
+ local handle = self:new_handle()
+ local runner = InstallRunner.new(InstallLocation.new(settings.current.install_root_dir), handle, semaphore)
+ runner:execute(opts, callback)
+ return handle
end
+---@return boolean
function Package:uninstall()
- local was_unlinked = self:unlink()
- if was_unlinked then
- self:emit "uninstall:success"
- registry:emit("package:uninstall:success", self)
- end
- return was_unlinked
+ return self:get_receipt()
+ :map(function(receipt)
+ self:unlink(receipt)
+ self:emit("uninstall:success", receipt)
+ registry:emit("package:uninstall:success", self, receipt)
+ return true
+ end)
+ :or_else(false)
end
-function Package:unlink()
+---@private
+---@param receipt InstallReceipt
+function Package:unlink(receipt)
log.fmt_trace("Unlinking %s", self)
local install_path = self:get_install_path()
+
-- 1. Unlink
- self:get_receipt():if_present(function(receipt)
- local linker = require "mason-core.installer.linker"
- linker.unlink(self, receipt):get_or_throw()
- end)
+ local linker = require "mason-core.installer.linker"
+ linker.unlink(self, receipt):get_or_throw()
-- 2. Remove installation artifacts
- if fs.sync.dir_exists(install_path) then
- fs.sync.rmrf(install_path)
- return true
- end
- return false
+ fs.sync.rmrf(install_path)
end
function Package:is_installed()
@@ -260,18 +238,18 @@ end
---@param opts? PackageInstallOpts
function Package:is_installable(opts)
- return require("mason-core.installer.registry").parse(self.spec, opts or {}):is_success()
+ return require("mason-core.installer.compiler").parse(self.spec, opts or {}):is_success()
end
---@return Result # Result<string[]>
function Package:get_all_versions()
- local registry_installer = require "mason-core.installer.registry"
+ local compiler = require "mason-core.installer.compiler"
return Result.try(function(try)
---@type Purl
local purl = try(Purl.parse(self.spec.source.id))
- ---@type InstallerProvider
- local provider = try(registry_installer.get_provider(purl))
- return provider.get_versions(purl, self.spec.source)
+ ---@type InstallerCompiler
+ local compiler = try(compiler.get_compiler(purl))
+ return compiler.get_versions(purl, self.spec.source)
end)
end
diff --git a/lua/mason-core/receipt.lua b/lua/mason-core/receipt.lua
index d9fe9d88..748cab38 100644
--- a/lua/mason-core/receipt.lua
+++ b/lua/mason-core/receipt.lua
@@ -1,3 +1,7 @@
+local Result = require "mason-core.result"
+local fs = require "mason-core.fs"
+local path = require "mason-core.path"
+
local M = {}
---@alias InstallReceiptSchemaVersion
@@ -56,11 +60,11 @@ function InstallReceipt:get_links()
end
---@async
----@param cwd string
-function InstallReceipt:write(cwd)
- local path = require "mason-core.path"
- local fs = require "mason-core.fs"
- fs.async.write_file(path.concat { cwd, "mason-receipt.json" }, vim.json.encode(self))
+---@param dir string
+function InstallReceipt:write(dir)
+ return Result.pcall(function()
+ fs.async.write_file(path.concat { dir, "mason-receipt.json" }, vim.json.encode(self))
+ end)
end
---@class InstallReceiptBuilder
diff --git a/lua/mason-registry/sources/util.lua b/lua/mason-registry/sources/util.lua
index 80d5f16f..04ab7845 100644
--- a/lua/mason-registry/sources/util.lua
+++ b/lua/mason-registry/sources/util.lua
@@ -1,8 +1,8 @@
local Optional = require "mason-core.optional"
local Pkg = require "mason-core.package"
local _ = require "mason-core.functional"
+local compiler = require "mason-core.installer.compiler"
local log = require "mason-core.log"
-local registry_installer = require "mason-core.installer.registry"
local M = {}
@@ -10,7 +10,7 @@ local M = {}
function M.map_registry_spec(spec)
spec.schema = spec.schema or "registry+v1"
- if not registry_installer.SCHEMA_CAP[spec.schema] then
+ if not compiler.SCHEMA_CAP[spec.schema] then
log.fmt_debug("Excluding package=%s with unsupported schema_version=%s", spec.name, spec.schema)
return Optional.empty()
end
diff --git a/lua/mason-test/helpers.lua b/lua/mason-test/helpers.lua
new file mode 100644
index 00000000..57b486ea
--- /dev/null
+++ b/lua/mason-test/helpers.lua
@@ -0,0 +1,33 @@
+local InstallContext = require "mason-core.installer.context"
+local InstallContextCwd = require "mason-core.installer.context.cwd"
+local InstallContextFs = require "mason-core.installer.context.fs"
+local InstallContextSpawn = require "mason-core.installer.context.spawn"
+local InstallHandle = require "mason-core.installer.handle"
+local InstallLocation = require "mason-core.installer.location"
+local Result = require "mason-core.result"
+local registry = require "mason-registry"
+local spy = require "luassert.spy"
+
+local M = {}
+
+---@param opts? { install_opts?: PackageInstallOpts, package?: string }
+function M.create_context(opts)
+ local pkg = registry.get_package(opts and opts.package or "dummy")
+ local handle = InstallHandle.new(pkg)
+ local location = InstallLocation.new "/tmp/install-dir"
+ local context_cwd = InstallContextCwd.new(location):set(location.dir)
+ local context_spawn = InstallContextSpawn.new(context_cwd, handle, false)
+ local context_fs = InstallContextFs.new(context_cwd)
+ local context = InstallContext.new(handle, context_cwd, context_spawn, context_fs, opts and opts.install_opts or {})
+ context.spawn = setmetatable({}, {
+ __index = function(s, cmd)
+ s[cmd] = spy.new(function()
+ return Result.success { stdout = nil, stderr = nil }
+ end)
+ return s[cmd]
+ end,
+ })
+ return context
+end
+
+return M