diff options
| author | William Boman <william@redwill.se> | 2022-07-08 18:34:38 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-08 18:34:38 +0200 |
| commit | 976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch) | |
| tree | 5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/installer/init.lua | |
| parent | feat: add gotests, gomodifytags, impl (#28) (diff) | |
| download | mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.gz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.bz2 mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.lz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.xz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.zst mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.zip | |
refactor: add mason-schemas and mason-core modules (#29)
* refactor: add mason-schemas and move generated filetype map to mason-lspconfig
* refactor: add mason-core module
Diffstat (limited to 'lua/mason-core/installer/init.lua')
| -rw-r--r-- | lua/mason-core/installer/init.lua | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/lua/mason-core/installer/init.lua b/lua/mason-core/installer/init.lua new file mode 100644 index 00000000..ecf4f2f0 --- /dev/null +++ b/lua/mason-core/installer/init.lua @@ -0,0 +1,176 @@ +local log = require "mason-core.log" +local _ = require "mason-core.functional" +local path = require "mason-core.path" +local fs = require "mason-core.fs" +local a = require "mason-core.async" +local Result = require "mason-core.result" +local InstallContext = require "mason-core.installer.context" +local settings = require "mason.settings" +local linker = require "mason-core.installer.linker" +local control = require "mason-core.async.control" + +local Semaphore = control.Semaphore + +local sem = Semaphore.new(settings.current.max_concurrent_installers) + +local M = {} + +---@async +local function create_prefix_dirs() + for _, p in ipairs { path.install_prefix(), path.bin_prefix(), path.package_prefix(), path.package_build_prefix() } do + if not fs.async.dir_exists(p) then + fs.async.mkdirp(p) + end + end +end + +---@async +---@param context InstallContext +local function write_receipt(context) + log.fmt_debug("Writing receipt for %s", context.package) + context.receipt + :with_name(context.package.name) + :with_schema_version("1.0") + :with_completion_time(vim.loop.gettimeofday()) + local receipt_path = path.concat { context.cwd:get(), "mason-receipt.json" } + local install_receipt = context.receipt:build() + fs.async.write_file(receipt_path, vim.json.encode(install_receipt)) +end + +local CONTEXT_REQUEST = {} + +---@return InstallContext +function M.context() + return coroutine.yield(CONTEXT_REQUEST) +end + +---@async +---@param context InstallContext +function M.prepare_installer(context) + create_prefix_dirs() + local package_build_prefix = path.package_build_prefix(context.package.name) + if fs.async.dir_exists(package_build_prefix) then + fs.async.rmrf(package_build_prefix) + end + fs.async.mkdirp(package_build_prefix) + context.cwd:set(package_build_prefix) +end + +---@async +---@param context InstallContext +---@param installer async fun(context: InstallContext) +function M.run_installer(context, installer) + local thread = coroutine.create(function(...) + -- We wrap the installer with a function to allow it to be a spy instance (in which case it's not a function, but a metatable - coroutine.create expects functions only) + return installer(...) + 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()) + M.prepare_installer(context) + step(context) + return ret_val +end + +---@async +---@param handle InstallHandle +---@param opts InstallContextOpts +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) + + log.fmt_info("Executing installer for %s", pkg) + return Result.run_catching(function() + -- 1. run installer + a.wait(function(resolve, reject) + local cancel_thread = a.run(M.run_installer, function(success, result) + if success then + resolve(result) + else + reject(result) + end + end, context, pkg.spec.install) + + handle:once("terminate", function() + handle:once("closed", function() + reject "Installation was aborted." + end) + cancel_thread() + end) + end) + + -- 2. promote temporary installation dir + context:promote_cwd() + + -- 3. link package + linker.link(context) + + -- 4. write receipt + write_receipt(context) + 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" + + -- clean up installation dir + pcall(function() + fs.async.rmrf(context.cwd:get()) + end) + + -- unlink linked executables (in the rare occassion an error occurs after linking) + linker.unlink(context.package, context.receipt.links) + + 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.run_installer, context, suspend_fn) + end, suspend_fns)) +end + +return M |
