fix(rocks): better errors / warnings when something goes wrong with luarocks

This commit is contained in:
Folke Lemaitre 2024-06-25 13:23:25 +02:00
parent 9005e8ede7
commit 7d3f69104f
No known key found for this signature in database
GPG Key ID: 41F8B1FBACAE2040
11 changed files with 336 additions and 141 deletions

View File

@ -464,10 +464,8 @@ function M.add_to_rtp(plugin)
local rtp = vim.api.nvim_get_runtime_file("", true) local rtp = vim.api.nvim_get_runtime_file("", true)
local idx_dir, idx_after local idx_dir, idx_after
local is_win = jit.os:find("Windows")
for i, path in ipairs(rtp) do for i, path in ipairs(rtp) do
if is_win then if Util.is_win then
path = Util.norm(path) path = Util.norm(path)
end end
if path == Config.me then if path == Config.me then

View File

@ -33,9 +33,7 @@ function M:load_pkgs()
if not Config.options.pkg.enabled then if not Config.options.pkg.enabled then
return return
end end
local have_rockspec = false
for _, pkg in ipairs(Pkg.get()) do for _, pkg in ipairs(Pkg.get()) do
have_rockspec = have_rockspec or pkg.source == "rockspec"
local meta, fragment = self:add(pkg.spec) local meta, fragment = self:add(pkg.spec)
if meta and fragment then if meta and fragment then
meta._.pkg = pkg meta._.pkg = pkg
@ -48,12 +46,6 @@ function M:load_pkgs()
self.pkgs[pkg.dir] = fragment.id self.pkgs[pkg.dir] = fragment.id
end end
end end
if have_rockspec then
local hererocks = Pkg.hererocks()
if hererocks then
self:add(hererocks)
end
end
end end
--- Remove a plugin and all its fragments. --- Remove a plugin and all its fragments.

View File

@ -335,6 +335,20 @@ function M.load()
lazy._.loaded = {} lazy._.loaded = {}
end end
-- add hererocks when enabled and needed
if Config.options.rocks.hererocks then
for _, plugin in pairs(Config.spec.plugins) do
if plugin.build == "rockspec" then
Config.spec.meta:add({
"luarocks/hererocks",
build = "rockspec",
lazy = true,
})
break
end
end
end
local existing = Config.plugins local existing = Config.plugins
Config.plugins = Config.spec.plugins Config.plugins = Config.spec.plugins
-- copy state. This wont do anything during startup -- copy state. This wont do anything during startup

View File

@ -5,6 +5,7 @@ local M = {}
---@type LazyProfile[] ---@type LazyProfile[]
M._profiles = { { name = "lazy" } } M._profiles = { { name = "lazy" } }
M.is_win = jit.os:find("Windows")
---@param data (string|{[string]:string})? ---@param data (string|{[string]:string})?
---@param time number? ---@param time number?

View File

@ -8,23 +8,62 @@ local start = vim.health.start or vim.health.report_start
local ok = vim.health.ok or vim.health.report_ok local ok = vim.health.ok or vim.health.report_ok
local warn = vim.health.warn or vim.health.report_warn local warn = vim.health.warn or vim.health.report_warn
local error = vim.health.error or vim.health.report_error local error = vim.health.error or vim.health.report_error
local info = vim.health.info or vim.health.report_info
---@class LazyHealth
---@field error? fun(msg:string)
---@field warn? fun(msg:string)
---@field ok? fun(msg:string)
---@class LazyHealthHave : LazyHealth
---@field version? string
---@field version_pattern? string
---@field optional? boolean
---@param cmd string|string[]
---@param opts? LazyHealthHave
function M.have(cmd, opts)
opts = vim.tbl_extend("force", {
error = error,
warn = warn,
ok = ok,
version = "--version",
}, opts or {})
cmd = type(cmd) == "table" and cmd or { cmd }
---@cast cmd string[]
---@type string?
local found
for _, c in ipairs(cmd) do
if vim.fn.executable(c) == 1 then
local version = vim.fn.system(c .. " " .. opts.version) or ""
version = vim.trim(vim.split(version, "\n")[1])
version = version:gsub("^%s*" .. vim.pesc(c) .. "%s*", "")
if opts.version_pattern and not version:find(opts.version_pattern, 1, true) then
opts.warn(("`%s` version `%s` needed, but found `%s`"):format(c, opts.version_pattern, version))
else
found = ("{%s} `%s`"):format(c, version)
break
end
end
end
if found then
opts.ok(found)
return true
else
(opts.optional and opts.warn or opts.error)(
("{%s} %snot installed"):format(
table.concat(cmd, "} or {"),
opts.version_pattern and "version `" .. opts.version_pattern .. "` " or ""
)
)
end
end
function M.check() function M.check()
start("lazy.nvim") start("lazy.nvim")
if vim.fn.executable("git") == 1 then M.have("git")
ok("'git' installed")
else
error("'git' not installed?")
end
if Config.options.rocks.enabled then
if vim.fn.executable("luarocks") == 1 then
ok("'luarocks' installed")
else
error("'luarocks' not installed. Either install it or set opts.rocks.enabled = false")
end
end
local sites = vim.opt.packpath:get() local sites = vim.opt.packpath:get()
local default_site = vim.fn.stdpath("data") .. "/site" local default_site = vim.fn.stdpath("data") .. "/site"
@ -45,7 +84,7 @@ function M.check()
ok("no existing packages found by other package managers") ok("no existing packages found by other package managers")
end end
for _, name in ipairs({ "packer", "plugged", "paq" }) do for _, name in ipairs({ "packer", "plugged", "paq", "pckr", "mini.deps" }) do
for _, path in ipairs(vim.opt.rtp:get()) do for _, path in ipairs(vim.opt.rtp:get()) do
if path:find(name, 1, true) then if path:find(name, 1, true) then
error("Found paths on the rtp from another plugin manager `" .. name .. "`") error("Found paths on the rtp from another plugin manager `" .. name .. "`")
@ -82,6 +121,18 @@ function M.check()
end end
end end
end end
start("luarocks")
if Config.options.rocks.enabled then
if Config.options.rocks.hererocks then
info("checking `hererocks` installation")
else
info("checking `luarocks` installation")
end
require("lazy.pkg.rockspec").check({ error = error, warn = warn, ok = ok })
else
ok("luarocks disabled")
end
end end
---@param plugin LazyPlugin ---@param plugin LazyPlugin

View File

@ -12,6 +12,7 @@ local Process = require("lazy.manage.process")
---@field output string ---@field output string
---@field status string ---@field status string
---@field error? string ---@field error? string
---@field warn? string
---@field private _task fun(task:LazyTask) ---@field private _task fun(task:LazyTask)
---@field private _running LazyPluginState[] ---@field private _running LazyPluginState[]
---@field private _started? number ---@field private _started? number
@ -74,6 +75,30 @@ function Task:start()
self:_check() self:_check()
end end
---@param msg string|string[]
---@param severity? vim.diagnostic.Severity
function Task:notify(msg, severity)
local var = severity == vim.diagnostic.severity.ERROR and "error"
or severity == vim.diagnostic.severity.WARN and "warn"
or "output"
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
---@cast msg string
---@diagnostic disable-next-line: no-unknown
self[var] = self[var] and (self[var] .. "\n" .. msg) or msg
self.status = msg
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
end
---@param msg string|string[]
function Task:notify_error(msg)
self:notify(msg, vim.diagnostic.severity.ERROR)
end
---@param msg string|string[]
function Task:notify_warn(msg)
self:notify(msg, vim.diagnostic.severity.WARN)
end
---@param fn async fun() ---@param fn async fun()
function Task:async(fn) function Task:async(fn)
local co = coroutine.create(fn) local co = coroutine.create(fn)

View File

@ -1,5 +1,6 @@
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Loader = require("lazy.core.loader") local Loader = require("lazy.core.loader")
local Rocks = require("lazy.pkg.rockspec")
local Util = require("lazy.util") local Util = require("lazy.util")
---@type table<string, LazyTaskDef> ---@type table<string, LazyTaskDef>
@ -16,52 +17,6 @@ end
local B = {} local B = {}
---@param task LazyTask
function B.rockspec(task)
---@type table<string, string>
local env = {}
local luarocks = "luarocks"
if Config.options.rocks.hererocks then
local is_win = jit.os:find("Windows")
local sep = is_win and ";" or ":"
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
if is_win then
hererocks = hererocks:gsub("/", "\\")
end
local path = vim.split(vim.env.PATH, sep)
table.insert(path, 1, hererocks)
env = {
PATH = table.concat(path, sep),
}
if is_win then
luarocks = luarocks .. ".bat"
end
local plugin = Config.plugins.hererocks
-- hererocks is still building, so skip for now
if plugin and plugin._.build then
return
end
end
local root = Config.options.rocks.root .. "/" .. task.plugin.name
task:spawn(luarocks, {
args = {
"--tree",
root,
"--server",
Config.options.rocks.server,
"--dev",
"--lua-version",
"5.1",
"make",
"--force-fast",
},
cwd = task.plugin.dir,
env = env,
})
end
---@param task LazyTask ---@param task LazyTask
---@param build string ---@param build string
function B.cmd(task, build) function B.cmd(task, build)
@ -114,7 +69,7 @@ M.build = {
build(self.plugin) build(self.plugin)
end) end)
elseif build == "rockspec" then elseif build == "rockspec" then
B.rockspec(self) Rocks.build(self)
elseif build:sub(1, 1) == ":" then elseif build:sub(1, 1) == ":" then
B.cmd(self, build) B.cmd(self, build)
elseif build:match("%.lua$") then elseif build:match("%.lua$") then

View File

@ -112,32 +112,6 @@ local function _load()
Util.track() Util.track()
end end
---@return LazyPluginSpec?, string?
function M.hererocks()
if not (Config.options.rocks.enabled and Config.options.rocks.hererocks) then
return
end
local root = Config.options.rocks.root .. "/hererocks"
local cmd = {
"python",
"hererocks.py",
"--verbose",
"-l",
"5.1",
"-r",
"latest",
root,
}
return {
"luarocks/hererocks",
lazy = true,
build = table.concat(cmd, " "),
}, root
end
---@param dir string ---@param dir string
---@return LazyPkg? ---@return LazyPkg?
---@overload fun():LazyPkg[] ---@overload fun():LazyPkg[]

View File

@ -1,31 +1,8 @@
--# selene:allow(incorrect_standard_library_use) --# selene:allow(incorrect_standard_library_use)
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
local Health = require("lazy.health")
local Util = require("lazy.util") local Util = require("lazy.util")
local M = {}
M.dev_suffix = "-1.rockspec"
M.skip = { "lua" }
M.rewrites = {
["plenary.nvim"] = { "nvim-lua/plenary.nvim", lazy = true },
}
---@param plugin LazyPlugin
function M.deps(plugin)
local root = Config.options.rocks.root .. "/" .. plugin.name
local manifest_file = root .. "/lib/luarocks/rocks-5.1/manifest"
local manifest = {}
pcall(function()
local load, err = loadfile(manifest_file, "t", manifest)
if not load then
error(err)
end
load()
end)
return vim.tbl_keys(manifest.repository or {})
end
---@class RockSpec ---@class RockSpec
---@field rockspec_format string ---@field rockspec_format string
---@field package string ---@field package string
@ -33,6 +10,190 @@ end
---@field dependencies string[] ---@field dependencies string[]
---@field build? {build_type?: string, modules?: any[]} ---@field build? {build_type?: string, modules?: any[]}
---@class RockManifest
---@field repository table<string, any>
local M = {}
M.dev_suffix = "-1.rockspec"
M.skip = { "lua" }
M.rewrites = {
["plenary.nvim"] = { "nvim-lua/plenary.nvim", lazy = true },
}
M.python = { "python3", "python" }
---@class HereRocks
M.hererocks = {}
---@param task LazyTask
function M.hererocks.build(task)
local root = Config.options.rocks.root .. "/hererocks"
---@param p string
local python = vim.tbl_filter(function(p)
return vim.fn.executable(p) == 1
end, M.python)[1]
task:spawn(python, {
args = {
"hererocks.py",
"--verbose",
"-l",
"5.1",
"-r",
"latest",
root,
},
cwd = task.plugin.dir,
})
end
---@param bin string
function M.hererocks.bin(bin)
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
if Util.is_win then
bin = bin .. ".bat"
end
return Util.norm(hererocks .. "/" .. bin)
end
-- check if hererocks is building
---@return boolean?
function M.hererocks.building()
return vim.tbl_get(Config.plugins.hererocks or {}, "_", "build")
end
---@param opts? LazyHealth
function M.check(opts)
opts = vim.tbl_extend("force", {
error = Util.error,
warn = Util.warn,
ok = function() end,
}, opts or {})
local ok = false
if Config.options.rocks.hererocks then
if M.hererocks.building() then
ok = true
else
ok = Health.have(M.python, opts)
ok = Health.have(M.hererocks.bin("luarocks")) and ok
ok = Health.have(
M.hererocks.bin("lua"),
vim.tbl_extend("force", opts, {
version = "-v",
version_pattern = "5.1",
})
) and ok
end
else
ok = Health.have("luarocks", opts)
ok = (
Health.have(
{ "lua5.1", "lua" },
vim.tbl_extend("force", opts, {
version = "-v",
version_pattern = "5.1",
})
)
) and ok
end
return ok
end
---@param task LazyTask
function M.build(task)
if
not M.check({
error = function(msg)
task:notify_error(msg:gsub("[{}]", "`"))
end,
warn = function(msg)
task:notify_warn(msg)
end,
ok = function(msg) end,
})
then
task:notify_warn({
"",
"This plugin requires `luarocks`. Try one of the following:",
" - fix your `luarocks` installation",
Config.options.rocks.hererocks and " - disable *hererocks* with `opts.rocks.hererocks = false`"
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
})
return
end
if task.plugin.name == "hererocks" then
return M.hererocks.build(task)
end
local env = {}
local luarocks = "luarocks"
if Config.options.rocks.hererocks then
-- hererocks is still building, so skip for now
-- a new build will happen in the next round
if M.hererocks.building() then
return
end
local sep = Util.is_win and ";" or ":"
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
if Util.is_win then
hererocks = hererocks:gsub("/", "\\")
end
local path = vim.split(vim.env.PATH, sep)
table.insert(path, 1, hererocks)
env = {
PATH = table.concat(path, sep),
}
if Util.is_win then
luarocks = luarocks .. ".bat"
end
end
local root = Config.options.rocks.root .. "/" .. task.plugin.name
task:spawn(luarocks, {
args = {
"--tree",
root,
"--server",
Config.options.rocks.server,
"--dev",
"--lua-version",
"5.1",
"make",
"--force-fast",
},
cwd = task.plugin.dir,
env = env,
})
end
---@param file string
---@return table?
function M.parse(file)
local ret = {}
return pcall(function()
loadfile(file, "t", ret)()
end) and ret or nil
end
---@param plugin LazyPlugin
function M.deps(plugin)
local root = Config.options.rocks.root .. "/" .. plugin.name
---@type RockManifest?
local manifest = M.parse(root .. "/lib/luarocks/rocks-5.1/manifest")
return manifest and vim.tbl_keys(manifest.repository or {})
end
---@param file string
---@return RockSpec?
function M.rockspec(file)
return M.parse(file)
end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
---@return LazyPkgSpec? ---@return LazyPkgSpec?
function M.get(plugin) function M.get(plugin)
@ -56,14 +217,7 @@ function M.get(plugin)
return return
end end
---@type RockSpec? local rockspec = M.rockspec(rockspec_file)
---@diagnostic disable-next-line: missing-fields
local rockspec = {}
local load, err = loadfile(rockspec_file, "t", rockspec)
if not load then
error(err)
end
load()
if not rockspec then if not rockspec then
return return

View File

@ -31,18 +31,20 @@ M.colors = {
ButtonActive = "Visual", ButtonActive = "Visual",
TaskOutput = "MsgArea", -- task output TaskOutput = "MsgArea", -- task output
TaskError = "ErrorMsg", -- task errors TaskError = "ErrorMsg", -- task errors
TaskWarning = "WarningMsg", -- task errors
Dir = "@markup.link", -- directory Dir = "@markup.link", -- directory
Url = "@markup.link", -- url Url = "@markup.link", -- url
Bold = { bold = true },
Italic = { italic = true },
} }
M.did_setup = false M.did_setup = false
function M.set_hl() function M.set_hl()
for hl_group, link in pairs(M.colors) do for hl_group, link in pairs(M.colors) do
vim.api.nvim_set_hl(0, "Lazy" .. hl_group, { local hl = type(link) == "table" and link or { link = link }
link = link, hl.default = true
default = true, vim.api.nvim_set_hl(0, "Lazy" .. hl_group, hl)
})
end end
end end
@ -54,6 +56,11 @@ function M.setup()
M.did_setup = true M.did_setup = true
M.set_hl() M.set_hl()
vim.api.nvim_create_autocmd("VimEnter", {
callback = function()
M.set_hl()
end,
})
vim.api.nvim_create_autocmd("ColorScheme", { vim.api.nvim_create_autocmd("ColorScheme", {
callback = function() callback = function()
M.set_hl() M.set_hl()

View File

@ -394,6 +394,11 @@ function M:diagnostics(plugin)
message = task.name .. " failed", message = task.name .. " failed",
severity = vim.diagnostic.severity.ERROR, severity = vim.diagnostic.severity.ERROR,
}) })
elseif task.warn then
self:diagnostic({
message = task.name .. " warning",
severity = vim.diagnostic.severity.WARN,
})
end end
end end
end end
@ -434,6 +439,22 @@ function M:plugin(plugin)
{ name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind } { name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind }
end end
---@param str string
---@param hl? string|Extmark
---@param opts? {indent?: number, prefix?: string, wrap?: boolean}
function M:markdown(str, hl, opts)
local lines = vim.split(str, "\n")
for _, line in ipairs(lines) do
self:append(line, hl, opts):highlight({
["`.-`"] = "@markup.raw.markdown_inline",
["%*.-%*"] = "LazyItalic",
["%*%*.-%*%*"] = "LazyBold",
["^%s*-"] = "Special",
})
self:nl()
end
end
---@param plugin LazyPlugin ---@param plugin LazyPlugin
function M:tasks(plugin) function M:tasks(plugin)
for _, task in ipairs(plugin._.tasks or {}) do for _, task in ipairs(plugin._.tasks or {}) do
@ -443,13 +464,16 @@ function M:tasks(plugin)
self:nl() self:nl()
end end
if task.error then if task.error then
self:append(vim.trim(task.error), "LazyTaskError", { indent = 6 }) self:markdown(task.error, "LazyTaskError", { indent = 6 })
self:nl() end
elseif task.name == "log" then if task.warn then
self:markdown(task.warn, "LazyTaskWarning", { indent = 6 })
end
if not task.error and not task.warn and task.name == "log" then
self:log(task) self:log(task)
elseif self.view:is_selected(plugin) and task.output ~= "" and task.output ~= task.error then end
self:append(vim.trim(task.output), "LazyTaskOutput", { indent = 6 }) if self.view:is_selected(plugin) or (task.error or task.warn) then
self:nl() self:markdown(vim.trim(task.output), "LazyTaskOutput", { indent = 6 })
end end
end end
end end
@ -512,7 +536,7 @@ function M:details(plugin)
end end
end end
local rocks = require("lazy.pkg.rockspec").deps(plugin) local rocks = require("lazy.pkg.rockspec").deps(plugin)
if not vim.tbl_isempty(rocks) then if rocks then
table.insert(props, { "rocks", vim.inspect(rocks) }) table.insert(props, { "rocks", vim.inspect(rocks) })
end end