diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index d843db2..58f9e10 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -31,6 +31,9 @@ M.defaults = { -- increase downloads a lot. filter = true, }, + rocks = { + root = vim.fn.stdpath("data") .. "/lazy-rocks", + }, dev = { ---@type string | fun(plugin: LazyPlugin): string directory where you store your local plugin projects path = "~/projects", @@ -212,6 +215,9 @@ M.mapleader = nil ---@type string M.maplocalleader = nil +---@type {specs:string, tree:string, path:string, cpath:string} +M.rocks = {} + function M.headless() return #vim.api.nvim_list_uis() == 0 end @@ -262,6 +268,17 @@ function M.setup(opts) M.mapleader = vim.g.mapleader M.maplocalleader = vim.g.maplocalleader + M.rocks = { + specs = M.options.rocks.root .. "/specs", + tree = M.options.rocks.root .. "/tree", + path = M.options.rocks.root .. "/tree/share/lua/5.1", + cpath = M.options.rocks.root .. "/tree/lib/lua/5.1", + } + vim.fn.mkdir(M.rocks.specs, "p") + vim.fn.mkdir(M.rocks.tree, "p") + package.path = package.path .. ";" .. M.rocks.path .. "/?.lua;" .. M.rocks.path .. "/?/init.lua;" + package.cpath = package.cpath .. ";" .. M.rocks.cpath .. "/?." .. (jit.os:find("Windows") and "dll" or "so") .. ";" + if M.headless() then require("lazy.view.commands").setup() end diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua index e07328c..ffd8753 100644 --- a/lua/lazy/core/loader.lua +++ b/lua/lazy/core/loader.lua @@ -44,6 +44,7 @@ function M.setup() while M.install_missing() do count = count + 1 if count > 5 then + Util.error("Too many rounds of missing plugins") break end end @@ -66,7 +67,11 @@ end -- multiple rounds can happen when importing a spec from a missing plugin function M.install_missing() for _, plugin in pairs(Config.plugins) do - if not (plugin._.installed or Plugin.has_errors(plugin)) then + local installed = plugin._.installed + local has_errors = Plugin.has_errors(plugin) + local rocks_installed = plugin._.rocks_installed ~= false + + if not has_errors and not (installed and rocks_installed) then for _, colorscheme in ipairs(Config.options.install.colorscheme) do if colorscheme == "default" then break diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua index 3de712f..6c8d7da 100644 --- a/lua/lazy/core/plugin.lua +++ b/lua/lazy/core/plugin.lua @@ -570,6 +570,8 @@ function M.update_state() installed[name] = nil end + require("lazy.manage.rocks").update_state() + Config.to_clean = {} for pack, dir_type in pairs(installed) do table.insert(Config.to_clean, { diff --git a/lua/lazy/manage/init.lua b/lua/lazy/manage/init.lua index f5a96a9..308930e 100644 --- a/lua/lazy/manage/init.lua +++ b/lua/lazy/manage/init.lua @@ -82,12 +82,13 @@ function M.install(opts) pipeline = { "git.clone", { "git.checkout", lockfile = opts.lockfile }, + "rocks.install", "plugin.docs", "wait", "plugin.build", }, plugins = function(plugin) - return plugin.url and not plugin._.installed + return plugin.url and not (plugin._.installed and plugin._.rocks_installed ~= false) end, }, opts):wait(function() require("lazy.manage.lock").update() @@ -106,6 +107,7 @@ function M.update(opts) "git.fetch", "git.status", { "git.checkout", lockfile = opts.lockfile }, + "rocks.install", "plugin.docs", "wait", "plugin.build", diff --git a/lua/lazy/manage/rocks.lua b/lua/lazy/manage/rocks.lua new file mode 100644 index 0000000..efad65d --- /dev/null +++ b/lua/lazy/manage/rocks.lua @@ -0,0 +1,89 @@ +--# selene:allow(incorrect_standard_library_use) + +local Config = require("lazy.core.config") +local Util = require("lazy.core.util") + +---@class LazyRock +---@field plugin string +---@field name string +---@field spec string +---@field installed boolean + +local M = {} +---@type LazyRock[] +M.rocks = {} + +---@param ... string +---@return string[] +function M.args(...) + local ret = { "--tree", Config.rocks.tree, "--lua-version", "5.1" } + vim.list_extend(ret, { ... }) + return ret +end + +function M.parse(rockspec_file) + local rockspec = {} + local ret, err = loadfile(rockspec_file, "t", rockspec) + if not ret then + error(err) + end + ret() + return rockspec +end + +-- dd(M.parse("/home/folke/.local/share/nvim/lazy/neorg/neorg-scm-1.rockspec")) + +---@param plugin LazyPlugin +function M.get_rockspec(plugin) + assert(plugin.rocks and #plugin.rocks > 0, plugin.name .. " has no rocks") + local rockspec_file = Config.rocks.specs .. "/lazy-" .. plugin.name .. "-0.0-0.rockspec" + require("lazy.util").write_file( + rockspec_file, + ([[ +rockspec_format = "3.0" +package = "lazy-%s" +version = "0.0-0" +source = { url = "%s" } +dependencies = %s +build = { type = "builtin" } +]]):format(plugin.name, plugin.url, vim.inspect(plugin.rocks)) + ) + return rockspec_file +end + +function M.update_state() + local root = Config.rocks.tree .. "/lib/luarocks/rocks-5.1" + ---@type table + local installed = {} + Util.ls(root, function(_, name, type) + if type == "directory" then + installed[name] = name + end + end) + + ---@type LazyRock[] + local rocks = {} + M.rocks = rocks + + for _, plugin in pairs(Config.plugins) do + if plugin.rocks then + plugin._.rocks = {} + plugin._.rocks_installed = true + for _, spec in ipairs(plugin.rocks) do + spec = vim.trim(spec) + local name = spec:gsub("%s.*", "") + local rock = { + plugin = plugin.name, + name = name, + spec = spec, + installed = installed[name] ~= nil, + } + plugin._.rocks_installed = plugin._.rocks_installed and rock.installed + table.insert(plugin._.rocks, rock) + table.insert(rocks, rock) + end + end + end +end + +return M diff --git a/lua/lazy/manage/task/rocks.lua b/lua/lazy/manage/task/rocks.lua new file mode 100644 index 0000000..b2f61bf --- /dev/null +++ b/lua/lazy/manage/task/rocks.lua @@ -0,0 +1,57 @@ +local Rocks = require("lazy.manage.rocks") + +---@type table +local M = {} + +local running = false +local has_rocks = nil ---@type boolean? + +M.install = { + skip = function(plugin) + return plugin._.rocks_installed ~= false + end, + run = function(self) + if has_rocks == nil then + has_rocks = vim.fn.executable("luarocks") == 1 + end + if not has_rocks then + self.error = "This plugin has luarocks dependencies,\nbut the `luarocks` executable is not found.\nPlease install https://luarocks.org/ to continue.\n" + .. "luarock deps: " + .. vim.inspect(self.plugin.rocks) + return + end + + local started = false + + local function install() + started = true + self.status = "luarocks (install)" + vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false }) + self:spawn("luarocks", { + args = Rocks.args("install", "--deps-mode", "one", "--deps-only", Rocks.get_rockspec(self.plugin)), + on_exit = function(ok) + running = false + if ok then + self.plugin._.rocks_installed = true + end + end, + }) + end + + local timer = vim.uv.new_timer() + timer:start(0, 100, function() + if not running then + running = true + timer:stop() + vim.schedule(install) + end + end) + self.status = "luarocks (pending)" + + table.insert(self._running, function() + return not started + end) + end, +} + +return M diff --git a/lua/lazy/types.lua b/lua/lazy/types.lua index 30bf9e5..831327e 100644 --- a/lua/lazy/types.lua +++ b/lua/lazy/types.lua @@ -24,6 +24,8 @@ ---@field rtp_loaded? boolean ---@field handlers? LazyPluginHandlers ---@field cache? table +---@field rocks? LazyRock[] +---@field rocks_installed? boolean ---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table? @@ -60,6 +62,7 @@ ---@field lazy? boolean ---@field priority? number Only useful for lazy=false plugins to force loading certain plugins first. Default priority is 50 ---@field dev? boolean If set, then link to the respective folder under your ~/projects +---@field rocks? string[] ---@class LazyPlugin: LazyPluginBase,LazyPluginHandlers,LazyPluginHooks,LazyPluginRef ---@field dependencies? string[]