diff options
Diffstat (limited to 'lua/mason-core/installer/context.lua')
| -rw-r--r-- | lua/mason-core/installer/context.lua | 399 |
1 files changed, 0 insertions, 399 deletions
diff --git a/lua/mason-core/installer/context.lua b/lua/mason-core/installer/context.lua deleted file mode 100644 index a991cd9f..00000000 --- a/lua/mason-core/installer/context.lua +++ /dev/null @@ -1,399 +0,0 @@ -local Optional = require "mason-core.optional" -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 links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> } -local InstallContext = {} -InstallContext.__index = InstallContext - ----@param handle InstallHandle ----@param opts PackageInstallOpts -function InstallContext.new(handle, opts) - local cwd_manager = CwdManager.new(path.install_prefix()) - return setmetatable({ - cwd = cwd_manager, - spawn = ContextualSpawn.new(cwd_manager, handle, false), - handle = handle, - package = handle.package, -- for convenience - fs = ContextualFs.new(cwd_manager), - receipt = receipt.InstallReceiptBuilder.new(), - requested_version = Optional.of_nilable(opts.version), - stdio_sink = handle.stdio.sink, - links = { - bin = {}, - share = {}, - opt = {}, - }, - opts = opts, - }, InstallContext) -end - ----@async -function InstallContext:promote_cwd() - local cwd = self.cwd:get() - local install_path = self.package:get_install_path() - if install_path == cwd then - log.fmt_debug("cwd %s is already promoted (at %s)", cwd, install_path) - return - end - log.fmt_debug("Promoting cwd %s to %s", cwd, install_path) - -- 1. Unlink any existing installation - self.handle.package:unlink() - -- 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. - fs.async.mkdir(install_path) - end - -- 3. Move the cwd to the final installation directory - fs.async.rename(cwd, install_path) - -- 4. Update cwd - self.cwd:set(install_path) -end - ----@param rel_path string The relative path from the current working directory to change cwd to. Will only restore to the initial cwd after execution of fn (if provided). ----@param fn async (fun(): any)? The function to run in the context of the given path. -function InstallContext:chdir(rel_path, fn) - local old_cwd = self.cwd:get() - self.cwd:set(path.concat { old_cwd, rel_path }) - if fn then - local ok, result = pcall(fn) - self.cwd:set(old_cwd) - if not ok then - error(result, 0) - end - return result - end -end - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param script_rel_path string Relative path to the Node.js script. -function InstallContext:write_node_exec_wrapper(new_executable_rel_path, script_rel_path) - if not self.fs:file_exists(script_rel_path) then - error(("Cannot write Node exec wrapper for path %q as it doesn't exist."):format(script_rel_path), 0) - end - return self:write_shell_exec_wrapper( - new_executable_rel_path, - ("node %q"):format(path.concat { - self.package:get_install_path(), - script_rel_path, - }) - ) -end - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param script_rel_path string Relative path to the Node.js script. -function InstallContext:write_ruby_exec_wrapper(new_executable_rel_path, script_rel_path) - if not self.fs:file_exists(script_rel_path) then - error(("Cannot write Ruby exec wrapper for path %q as it doesn't exist."):format(script_rel_path), 0) - end - return self:write_shell_exec_wrapper( - new_executable_rel_path, - ("ruby %q"):format(path.concat { - self.package:get_install_path(), - script_rel_path, - }) - ) -end - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param script_rel_path string Relative path to the PHP script. -function InstallContext:write_php_exec_wrapper(new_executable_rel_path, script_rel_path) - if not self.fs:file_exists(script_rel_path) then - error(("Cannot write PHP exec wrapper for path %q as it doesn't exist."):format(script_rel_path), 0) - end - return self:write_shell_exec_wrapper( - new_executable_rel_path, - ("php %q"):format(path.concat { - self.package:get_install_path(), - script_rel_path, - }) - ) -end - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param module string The python module to call. -function InstallContext:write_pyvenv_exec_wrapper(new_executable_rel_path, module) - local pypi = require "mason-core.installer.managers.pypi" - local module_exists, module_err = pcall(function() - local result = - self.spawn.python { "-c", ("import %s"):format(module), with_paths = { pypi.venv_path(self.cwd:get()) } } - if not self.spawn.strict_mode then - result:get_or_throw() - end - end) - if not module_exists then - log.fmt_error("Failed to find module %q for package %q. %s", module, self.package, module_err) - error(("Cannot write Python exec wrapper for module %q as it doesn't exist."):format(module), 0) - end - return self:write_shell_exec_wrapper( - new_executable_rel_path, - ("%q -m %s"):format( - path.concat { - pypi.venv_path(self.package:get_install_path()), - "python", - }, - module - ) - ) -end - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param target_executable_rel_path string -function InstallContext:write_exec_wrapper(new_executable_rel_path, target_executable_rel_path) - if not self.fs:file_exists(target_executable_rel_path) then - error(("Cannot write exec wrapper for path %q as it doesn't exist."):format(target_executable_rel_path), 0) - end - if platform.is.unix then - self.fs:chmod_exec(target_executable_rel_path) - end - return self:write_shell_exec_wrapper( - new_executable_rel_path, - ("%q"):format(path.concat { - self.package:get_install_path(), - target_executable_rel_path, - }) - ) -end - -local BASH_TEMPLATE = _.dedent [[ -#!/usr/bin/env bash -%s -exec %s "$@" -]] - -local BATCH_TEMPLATE = _.dedent [[ -@ECHO off -%s -%s %%* -]] - ----@param new_executable_rel_path string Relative path to the executable file to create. ----@param command string The shell command to run. ----@param env table<string, string>? ----@return string # The created executable filename. -function InstallContext:write_shell_exec_wrapper(new_executable_rel_path, command, env) - if self.fs:file_exists(new_executable_rel_path) or self.fs:dir_exists(new_executable_rel_path) then - error(("Cannot write exec wrapper to %q because the file already exists."):format(new_executable_rel_path), 0) - end - return platform.when { - unix = function() - local formatted_envs = _.map(function(pair) - local var, value = pair[1], pair[2] - return ("export %s=%q"):format(var, value) - end, _.to_pairs(env or {})) - - self.fs:write_file(new_executable_rel_path, BASH_TEMPLATE:format(_.join("\n", formatted_envs), command)) - self.fs:chmod_exec(new_executable_rel_path) - return new_executable_rel_path - end, - win = function() - local executable_file = ("%s.cmd"):format(new_executable_rel_path) - local formatted_envs = _.map(function(pair) - local var, value = pair[1], pair[2] - return ("SET %s=%s"):format(var, value) - end, _.to_pairs(env or {})) - - self.fs:write_file(executable_file, BATCH_TEMPLATE:format(_.join("\n", formatted_envs), command)) - return executable_file - end, - } -end - ----@param executable string ----@param rel_path string -function InstallContext:link_bin(executable, rel_path) - self.links.bin[executable] = rel_path - return self -end - -return InstallContext |
