1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
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 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 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 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)
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)
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_abs_path)
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
SET dp0=%%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
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)
return Result.try(function(try)
try(link_bin(context))
try(link_share(context))
end)
end
return M
|