diff options
Diffstat (limited to 'lua/mason-core/installer/linker.lua')
| -rw-r--r-- | lua/mason-core/installer/linker.lua | 152 |
1 files changed, 108 insertions, 44 deletions
diff --git a/lua/mason-core/installer/linker.lua b/lua/mason-core/installer/linker.lua index a1cc53d0..6de95160 100644 --- a/lua/mason-core/installer/linker.lua +++ b/lua/mason-core/installer/linker.lua @@ -1,62 +1,85 @@ local path = require "mason-core.path" +local Result = require "mason-core.result" local platform = require "mason-core.platform" local _ = require "mason-core.functional" local log = require "mason-core.log" local fs = require "mason-core.fs" +local a = require "mason-core.async" local M = {} ----@param pkg Package ----@param links InstallReceiptLinks -local function unlink_bin(pkg, links) - for executable in pairs(links.bin) do +---@param receipt InstallReceipt +local function unlink_bin(receipt) + local bin = receipt.links.bin + if not bin then + return + end + -- Windows executables did not include file extension in bin receipts on 1.0. + local should_append_cmd = platform.is.win and receipt.schema_version == "1.0" + for executable in pairs(bin) do + if should_append_cmd then + executable = executable .. ".cmd" + end local bin_path = path.bin_prefix(executable) fs.sync.unlink(bin_path) end end ----@param pkg Package ----@param links InstallReceiptLinks -function M.unlink(pkg, links) - log.fmt_debug("Unlinking %s", pkg) - unlink_bin(pkg, links) +---@param receipt InstallReceipt +local function unlink_share(receipt) + local share = receipt.links.share + if not share then + return + end + for share_file in pairs(share) do + local bin_path = path.share_prefix(share_file) + fs.sync.unlink(bin_path) + end end ----@param to string -local function relative_path_from_bin(to) - local _, match_end = to:find(path.install_prefix(), 1, true) - assert(match_end, "Failed to produce relative path.") - local relative_path = to:sub(match_end + 1) - return ".." .. relative_path +---@param pkg Package +---@param receipt InstallReceipt +function M.unlink(pkg, receipt) + log.fmt_debug("Unlinking %s", pkg, receipt.links) + unlink_bin(receipt) + unlink_share(receipt) end ---@async ---@param context InstallContext local function link_bin(context) - local links = context.bin_links - local pkg = context.package - for name, rel_path in pairs(links) do - local target_abs_path = path.concat { pkg:get_install_path(), rel_path } - local target_rel_path = relative_path_from_bin(target_abs_path) - local bin_path = path.bin_prefix(name) + return Result.try(function(try) + local links = context.links.bin + local pkg = context.package + for name, rel_path in pairs(links) do + if platform.is.win then + name = ("%s.cmd"):format(name) + end + local target_abs_path = path.concat { pkg:get_install_path(), rel_path } + local bin_path = path.bin_prefix(name) - assert(not fs.async.file_exists(bin_path), ("bin/%s is already linked."):format(name)) - assert(fs.async.file_exists(target_abs_path), ("Link target %q does not exist."):format(target_abs_path)) + if not context.opts.force and fs.async.file_exists(bin_path) then + return Result.failure(("bin/%s is already linked."):format(name)) + end + if not fs.async.file_exists(target_abs_path) then + return Result.failure(("Link target %q does not exist."):format(target_abs_path)) + end - log.fmt_debug("Linking bin %s to %s", name, target_rel_path) + log.fmt_debug("Linking bin %s to %s", name, target_abs_path) - platform.when { - unix = function() - fs.async.symlink(target_rel_path, bin_path) - end, - win = function() - -- We don't "symlink" on Windows because: - -- 1) .LNK is not commonly found in PATHEXT - -- 2) some executables can only run from their true installation location - -- 3) many utilities only consider .COM, .EXE, .CMD, .BAT files as candidates by default when resolving executables (e.g. neovim's |exepath()| and |executable()|) - fs.async.write_file( - ("%s.cmd"):format(bin_path), - _.dedent(([[ + platform.when { + unix = function() + try(Result.pcall(fs.async.symlink, target_abs_path, bin_path)) + end, + win = function() + -- We don't "symlink" on Windows because: + -- 1) .LNK is not commonly found in PATHEXT + -- 2) some executables can only run from their true installation location + -- 3) many utilities only consider .COM, .EXE, .CMD, .BAT files as candidates by default when resolving executables (e.g. neovim's |exepath()| and |executable()|) + try(Result.pcall( + fs.async.write_file, + bin_path, + _.dedent(([[ @ECHO off GOTO start :find_dp0 @@ -66,20 +89,61 @@ local function link_bin(context) SETLOCAL CALL :find_dp0 - endLocal & goto #_undefined_# 2>NUL || title %%COMSPEC%% & "%%dp0%%\%s" %%* - ]]):format(target_rel_path)) - ) - end, - } - context.receipt:with_link("bin", name, rel_path) - end + endLocal & goto #_undefined_# 2>NUL || title %%COMSPEC%% & "%s" %%* + ]]):format(target_abs_path)) + )) + end, + } + context.receipt:with_link("bin", name, rel_path) + end + end) +end + +---@async +---@param context InstallContext +local function link_share(context) + return Result.try(function(try) + for name, rel_path in pairs(context.links.share) do + local dest = path.share_prefix(name) + + do + if vim.in_fast_event() then + a.scheduler() + end + + local dir = vim.fn.fnamemodify(dest, ":h") + if not fs.async.dir_exists(dir) then + try(Result.pcall(fs.async.mkdirp, dir)) + end + end + + local target_abs_path = path.concat { context.package:get_install_path(), rel_path } + + if context.opts.force then + if fs.async.file_exists(dest) then + try(Result.pcall(fs.async.unlink, dest)) + end + elseif fs.async.file_exists(dest) then + return Result.failure(("share/%s is already linked."):format(name)) + end + if not fs.async.file_exists(target_abs_path) then + return Result.failure(("Link target %q does not exist."):format(target_abs_path)) + end + + try(Result.pcall(fs.async.symlink, target_abs_path, dest)) + context.receipt:with_link("share", name, rel_path) + end + end) end ---@async ---@param context InstallContext function M.link(context) log.fmt_debug("Linking %s", context.package) - link_bin(context) + return Result.try(function(try) + try(link_bin(context)) + try(link_share(context)) + end) end return M |
