more fixes

This commit is contained in:
2025-04-23 00:15:13 -05:00
parent 254436c24d
commit c29395004d
6 changed files with 282 additions and 183 deletions

View File

@ -2,15 +2,10 @@ local logger = require('dep.log')
local git = require('dep.git')
local packager = require('dep.package')
---all functions for convenience
---@type table
-- all functions for convenience
local M = {}
---@type boolean
local initialized
---performance logging
---@type table
-- performance logging
local perf = {}
--- get execution time of a function
@ -30,13 +25,13 @@ function M.registertree(speclist, overrides)
overrides = overrides or {}
-- recurse the packages
local over = overrides
for _, spec in pairs(speclist) do
-- make sure the overrides override and take into account the packages spec
---@diagnostic disable-next-line: missing-fields
overrides = {
pin = overrides.pin or spec.pin,
disable = overrides.disable or spec.disable
over = {
pin = over.pin or spec.pin,
disable = over.disable or spec.disable
}
local ok = packager:new(spec, overrides)
@ -48,8 +43,48 @@ function M.registertree(speclist, overrides)
end
end
--- clean out old packages
function M.clean()
vim.loop.fs_scandir(
packager.get_base_dir(),
vim.schedule_wrap(function(err, handle)
if err then
logger:log("error", string.format("failed to clean; reason: %s", err))
else
local queue = {}
while handle do
local name = vim.loop.fs_scandir_next(handle)
if name then
queue[name] = packager.get_base_dir()..name
else
break
end
end
-- keep packages that still exist
for _, package in pairs(packager.get_packages()) do
queue[package.name] = nil
end
for name, dir in pairs(queue) do
local co = coroutine.create(function()
local ok = vim.fn.delete(dir, "rf")
if ok then
logger:log("clean", string.format("deleted %s", name))
else
logger:log("error", string.format("failed to delete %s", name))
end
end)
coroutine.resume(co)
end
end
end)
)
end
--- reload all packages in package table spec
---@param force boolean|nil force all packages to load
---@param force boolean? force all packages to load
function M.reload(force)
local reloaded = packager.get_root():loadtree(force)
@ -72,63 +107,6 @@ function M.reload(force)
end
end
--- check if there's a circular dependency in the package tree
function M.findcycle()
local index = 0
local indexes = {}
local lowlink = {}
local stack = {}
-- use tarjan algorithm to find circular dependencies (strongly connected
-- components)
local function connect(package)
indexes[package.id], lowlink[package.id] = index, index
stack[#stack + 1], stack[package.id] = package, true
index = index + 1
for i = 1, #package.dependents do
local dependent = package.dependents[i]
if not indexes[dependent.id] then
local cycle = connect(dependent)
if cycle then
return cycle
else
lowlink[package.id] = math.min(lowlink[package.id], lowlink[dependent.id])
end
elseif stack[dependent.id] then
lowlink[package.id] = math.min(lowlink[package.id], indexes[dependent.id])
end
end
if lowlink[package.id] == indexes[package.id] then
local cycle = { package }
local node
repeat
node = stack[#stack]
stack[#stack], stack[node.id] = nil, nil
cycle[#cycle + 1] = node
until node == package
-- a node is by definition strongly connected to itself ignore single-node
-- components unless it explicitly specified itself as a dependency
if #cycle > 2 or package.dependents[package.id] then
return cycle
end
end
end
for _, package in pairs(packager.get_packages()) do
if not indexes[package.id] then
local cycle = connect(package)
if cycle then
return cycle
end
end
end
end
--- sync a tree of plugins
---@param tree package[] tree of plugins
---@param cb function? callback
@ -141,16 +119,15 @@ function M.synctree(tree, cb)
has_errors = has_errors or err
if progress == #tree then
-- TODO: implement clean
-- clean()
M.reload()
if has_errors then
logger:log("error", "there were errors during sync; see :messages or :DepLog for more information")
else
logger:log("update", "synchronized %s %s", #tree, #tree == 1 and "package" or "packages")
end
M.clean()
M.reload()
if cb then
cb()
end
@ -158,7 +135,10 @@ function M.synctree(tree, cb)
end
for _, package in pairs(tree) do
git.sync(package, done)
local co = coroutine.create(function()
git.sync(package, done)
end)
coroutine.resume(co)
end
end
@ -167,8 +147,8 @@ return function(opts)
logger.pipe = logger:setup()
--- make comparison for table.sort
---@param a table package spec a
---@param b table package spec b
---@param a package package spec a
---@param b package package spec b
---@return boolean
local function comp(a, b)
-- NOTE: this doesn't have to be in any real order, it just has to be
@ -177,7 +157,7 @@ return function(opts)
return a.id < b.id
end
initialized, err = pcall(function()
local initialized, err = pcall(function()
packager.set_base_dir(opts.base_dir or vim.fn.stdpath("data").."/site/pack/deps/opt/")
M.benchmark("load", function()
-- register all packages
@ -199,7 +179,10 @@ return function(opts)
end
-- make sure there arent any circular dependencies
M.findcycle()
local ok = packager.findcycle(packager.get_packages())
if type(ok) == "table" then
logger:log("error", "found a cycle in the package spec here: %s", vim.inspect(ok))
end
end)
-- load packages
@ -218,12 +201,13 @@ return function(opts)
-- get all package that need syncing
local targets = {}
for i, package in pairs(packager.get_packages()) do
for _, package in pairs(packager.get_packages()) do
if shouldsync(package) then
targets[i] = package
table.insert(targets, package)
end
end
-- install all targets
M.synctree(targets)
end)
@ -231,17 +215,18 @@ return function(opts)
logger:log("error", err)
end
-- add some user commands
vim.api.nvim_create_user_command("DepLog", function()
vim.cmd('vsp '..logger.path)
vim.opt_local.readonly = true
-- make the log auto update while it's open
local w = vim.uv.new_fs_event()
local function watch_file(fname)
local fullpath = vim.api.nvim_call_function(
'fnamemodify', { fname, ':p' })
w:start(fullpath, {}, vim.schedule_wrap(function(...)
vim.api.nvim_command('checktime')
-- Debounce: stop/start.
w:stop()
watch_file(fname)
end))
@ -258,5 +243,11 @@ return function(opts)
M.reload()
end, {})
vim.api.nvim_create_user_command("DepClean", function()
-- clean AND reload to make sure that all old packages are gone
M.clean()
M.reload()
end, {})
logger:cleanup()
end

View File

@ -1,3 +1,7 @@
-- TODO: clean this up, it's a mess
-- the nesting of all the proc calls is really annoying, and I need to find a
-- cleaner way to do it
local logger = require('dep.log')
local proc = require('dep.proc')
@ -7,31 +11,47 @@ local git = {}
---@param package package package to update/install
---@param cb function callback
function git.sync(package, cb)
if package.exists then
git.update(package, cb)
else
git.install(package, cb)
local function sync()
-- update or install
if package.exists then
git.update(package, cb)
else
git.install(package, cb)
end
end
-- handle arbitrary branches here
if package.branch then
proc.git_resolve_branch(package.url, package.branch, function(err, message)
if not err then
package.branch = message
sync()
end
end)
else
sync()
end
end
--- configure a package
---@param package table package spec
local function configurepkg(package)
package:runhooks("on_config")
logger:log("config", "package: %s configured", package.id)
package.configured = true
end
--- install a given package
---@param package package package to install
---@param cb function callback
function git.install(package, cb)
local function configurepkg()
package:runhooks("on_config")
logger:log("config", "package: %s configured", package.id)
package.configured = true
end
if not package.enabled then
cb()
return
end
logger:log("error", "%s: doesn't exist", package.id)
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
if err then
logger:log("error", "failed to install %s; reason: %s",
@ -44,15 +64,15 @@ function git.install(package, cb)
else
package.exists = true
package:unconfiguretree()
configurepkg()
logger:log("install", "installed %s", package.id)
configurepkg(package)
end
end)
else
package.exists = true
package:unconfiguretree()
configurepkg()
logger:log("install", "installed %s", package.id)
configurepkg(package)
end
end
@ -74,15 +94,6 @@ function git.update(package, cb)
logger:log("error", "failed to update %s; reason: %s", package.id, err)
end
--- configure a package
---@param pkg table package spec
local function configurepkg(pkg)
package:runhooks("on_config")
logger:log("config", "package: %s configured", pkg.id)
package.configured = true
end
if package.pin then
cb()
return
@ -112,8 +123,8 @@ function git.update(package, cb)
cb(err)
else
package:unconfiguretree()
configurepkg(package)
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
configurepkg(package)
end
end)
end
@ -124,7 +135,7 @@ function git.update(package, cb)
log_err(message)
cb(err)
else
proc.git_rev_parse(package.dir, "FETCH_HEAD", function(err, after)
proc.git_rev_parse(package.dir, "FETCH_HEAD^{commit}", function(err, after)
if err then
log_err(after)
cb(err)
@ -137,8 +148,8 @@ function git.update(package, cb)
log_err(message)
else
package:unconfiguretree()
configurepkg(package)
logger:log("update", "updated %s; %s -> %s", package.id, before, after)
configurepkg(package)
end
cb(err)

View File

@ -76,7 +76,7 @@ function logger:log(level, message, ...)
-- write to the pipe if it's open
if logger.pipe then
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%Y/%m/%d"), source.short_src:gsub('.*%/', ''), source.currentline, message))
logger.pipe:write(string.format("[%s] %s:%s: %s\n", os.date("%T"), source.short_src:gsub('.*%/', ''), source.currentline, message))
end
end)
end

View File

@ -23,13 +23,13 @@ local logger = require('dep.log')
---@field lazy boolean if the package is lazy loaded in any way
---@field added boolean if the package has been added in vim
---@field configured boolean if the package has been configured
---@field lazied boolean if the packages lazy loading has been set
---@field loaded boolean if a package has been loaded
---@field subtree_loaded boolean is the subtree has been loaded
---@field on_config function[] table of functions to run on config
---@field on_setup function[] table of function to run on setup
---@field on_load function[] table of functions to run on load
---@field lazy_load function[] table of functions to run which will tell the
--- package when to load
---@field lazy_load function[] table of functions to run which will tell the package when to load
---@field requirements package[] this package's requirements
---@field dependents package[] packages that require this package
---@field perf table performance metrics for the package
@ -71,7 +71,7 @@ local function check_spec(spec)
local name = spec[1]:match("^[%w-_.]+/([%w-_.]+)$")
if not name then
logger:log("spec",'invalid name "%s"; must be in the format "user/package"', spec[1])
logger:log("spec", 'invalid name "%s"; must be in the format "user/package"', spec[1])
return false
end
end
@ -153,6 +153,7 @@ local function check_spec(spec)
return false
end
-- turn an id into a spec
if (is == "string") then
spec.reqs = { spec.reqs }
end
@ -165,6 +166,7 @@ local function check_spec(spec)
return false
end
-- turn an id into a spec
if (is == "string") then
spec.deps = { spec.deps }
end
@ -212,31 +214,33 @@ function package:new(spec, overrides)
local id = spec[1]
local o = packages[id] or {}
self.__index = self
setmetatable(o, self)
-- if package hasn't been registered already, get the inital spec regisitered
if not o.id then
o.id = id -- id of the package
o.enabled = true -- whether it's going to be used
o.exists = false -- if the package exists on the filesystem
o.lazy = false -- if the package is lazy loaded in any way
o.added = false -- if the package has been added in vim
o.configured = false -- if the package has been configured
o.loaded = false -- if a package has been loaded
o.id = id
o.enabled = true
o.exists = false
o.lazy = false
o.added = false
o.lazied = false
o.configured = false
o.loaded = false
o.subtree_loaded = false
o.on_config = {} -- table of functions to run on config
o.on_setup = {} -- table of function to run on setup
o.on_load = {} -- table of functions to run on load
o.lazy_load = {} -- table of functions to run which will tell the package
-- when to load
o.requirements = {} -- this package's requirements
o.dependents = {} -- packages that require this package
o.on_config = {}
o.on_setup = {}
o.on_load = {}
o.lazy_load = {}
o.requirements = {}
o.dependents = {}
o.perf = {}
packages[id] = o
end
o.name = spec.as or o.name or id
o.name = spec.as or o.name or id:match("^[%w-_.]+/([%w-_.]+)$")
o.url = spec.url or o.url or ("https://github.com/"..id..".git")
o.branch = spec.branch or o.branch
o.dir = base_dir..o.name
@ -287,26 +291,32 @@ function package:new(spec, overrides)
if spec.deps then
---it is the correct type as asserted in check_spec()
---@diagnostic disable-next-line: param-type-mismatch
for _, v in pairs(spec.deps) do
local pkg = package:new(v)
if type(pkg) ~= "table" then
for _, dep in pairs(spec.deps) do
local pkg = package:new(dep)
if not pkg then
return false
end
o:link_dependency(nil, pkg)
o:link_dependency(o, pkg)
-- if the child package is lazy loaded make sure the child package
-- is only loaded when the parent package has finished loading
if package.lazy then
table.insert(package.on_load, function()
o:loadtree(true)
if o.lazy then
table.insert(o.on_load, function()
local ok = o:loadtree(true)
if not ok then
logger:log("lazy",
"failed to run loadtree for %s, some packages may not be loaded",
o.id)
end
end)
-- tell the dep that it's gonna be lazy
pkg.lazy = true
table.insert(pkg.lazy_load, function(_) end)
end
end
end
self.__index = self
return o
end
@ -316,14 +326,23 @@ function package.set_base_dir(_base_dir)
base_dir = _base_dir
end
--- get the base directory for packages
---@return string base_dir
---@nodiscard
function package.get_base_dir()
return base_dir
end
--- get the root directory
---@return package root
---@nodiscard
function package.get_root()
return root
end
--- get the packages in dep
---@return package root
---@nodiscard
function package.get_packages()
return packages
end
@ -376,12 +395,12 @@ function package:ensureadded(force)
end
end
-- run setup hooks
pkg:runhooks("on_setup")
-- now start loading our plugin
local start = os.clock()
-- run setup hooks
self:runhooks("on_setup")
-- trigger the packadd for the plugin
---@diagnostic disable-next-line: param-type-mismatch
local ok, err = pcall(vim.cmd, "packadd "..pkg.name)
@ -394,23 +413,24 @@ function package:ensureadded(force)
logger:log("vim", "packadd completed for %s", pkg.id)
-- set the package to loaded
self.loaded = true
logger:log("load", "loaded %s", self.id)
pkg.loaded = true
logger:log("load", "loaded %s", pkg.id)
-- trigger the on_load hooks
ok, err = self:runhooks("on_load")
ok, err = pkg:runhooks("on_load")
if not ok then
logger:log("error", "failed to load %s; reason: %s", self.id, err)
logger:log("error", "failed to load %s; reason: %s", pkg.id, err)
return
end
end
-- make sure the package is lazy loaded if need be
if not self.added and not self.lazy or force then
if not self.added and not self.loaded and not self.lazy or force then
loadpkg(self)
elseif not self.added and self.lazy then
logger:log("lazy", "registering %d lazy hooks for %s", #self.lazy_load,
self.id)
self.lazied = true
for _, load_cond in pairs(self.lazy_load) do
-- configure the lazy loader for the user
local l = require('lazy.utils'):new()
@ -436,26 +456,31 @@ end
--- load all packages in package tree
---@param force boolean? force lazy packages to load
---@return boolean boolean if tree was successfully loaded
---@nodiscard
function package:loadtree(force)
-- if the package doesn't exist or isn't enabled then don't load it
if not self.exists or not self.enabled then
logger:log("load", "package %s doesn't exist or is not enabled", self.id)
return false
end
if self.subtree_loaded then
logger:log("load", "package %s's subtree is already loaded", self.id)
-- if the subtree is loaded then it's already loaded unless it needs forcing
if not force and self.subtree_loaded then
return true
end
-- if the package isn't lazy check that it's requirements are loaded
if not self.lazy then
for _, requirement in pairs(self.requirements) do
if not requirement.loaded then
logger:log("load", "package %s requires %s to be loaded first", self.id, requirement.id)
if not requirement.loaded and not requirement.lazy then
logger:log("error", "failed to load %s; requirement: %s isn't loaded",
self.id, requirement.id)
return false
end
end
end
-- if the package isn't loaded and isn't lazy then it should probably be
-- loaded
if not self.loaded then
local ok, err = self:ensureadded(force)
if not ok then
@ -464,8 +489,9 @@ function package:loadtree(force)
end
end
package.subtree_loaded = true
self.subtree_loaded = true
-- make sure the dependants are loaded
for _, dependant in pairs(self.dependents) do
self.subtree_loaded = dependant:loadtree(force) and self.subtree_loaded
end
@ -490,4 +516,68 @@ function package:unconfiguretree()
end
end
--- check a list of packages for any cycles
---@param pkgs package[] list of packages
---@return package[]|false cycle the cycle that was found or false if not found
---@nodisacard
function package.findcycle(pkgs)
local index = 0
local indexes = {}
local lowlink = {}
local stack = {}
--- use tarjan algorithm to find circular dependencies (strongly connected
--- components)
---@param pkg package
local function connect(pkg)
indexes[pkg.id], lowlink[pkg.id] = index, index
stack[#stack + 1], stack[pkg.id] = pkg, true
index = index + 1
for i = 1, #pkg.dependents do
local dependent = pkg.dependents[i]
if not indexes[dependent.id] then
local cycle = connect(dependent)
if cycle then
return cycle
else
lowlink[pkg.id] = math.min(lowlink[pkg.id], lowlink[dependent.id])
end
elseif stack[dependent.id] then
lowlink[pkg.id] = math.min(lowlink[pkg.id], indexes[dependent.id])
end
end
if lowlink[pkg.id] == indexes[pkg.id] then
local cycle = { pkg }
local node
repeat
node = stack[#stack]
stack[#stack], stack[node.id] = nil, nil
cycle[#cycle + 1] = node
until node == pkg
-- a node is by definition strongly connected to itself ignore single-node
-- components unless it explicitly specified itself as a dependency
if #cycle > 2 or pkg.dependents[pkg.id] then
return cycle
end
end
end
-- actually check the cycle
for _, pkg in pairs(pkgs) do
if not indexes[package.id] then
local cycle = connect(pkg)
if cycle then
return cycle
end
end
end
return false
end
return package

View File

@ -1,12 +1,18 @@
local proc = {}
--- execute a process
---@param process string the program
---@param args string[] the args
---@param cwd string? the pwd
---@param env table env
---@param cb function callback
function proc.exec(process, args, cwd, env, cb)
local buffer = {}
local function cb_output(_, data, _)
table.insert(buffer, table.concat(data))
end
local function cb_exit(job_id, exit_code, _)
local function cb_exit(_, exit_code, _)
local output = table.concat(buffer)
cb(exit_code ~= 0, output)
end
@ -62,54 +68,56 @@ function proc.git_checkout(dir, branch, commit, cb)
end
function proc.git_resolve_branch(url, branch, cb)
if string.match(branch or "", "*") ~= "*" then
-- if the branch doesn't contain a * then return the branch
if not string.match(branch, "*") then
cb(false, branch)
return
end
local buffer = {}
local buffer = {}
local function cb_output(_, data, _)
if data[1] ~= "" then
buffer = data
end
end
vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url, },
vim.fn.jobstart({ "git", "ls-remote", "--tags", "--sort", "v:refname", url },
{
cwd = nil,
env = { GIT_TERMINAL_PROMPT = 0 },
env = git_env,
stdin = nil,
on_stdout = cb_output,
on_stderr = cb_output,
on_exit = function(_, exit_code, _)
if exit_code == 0 then
-- get a list of all versions
local versions = {}
for _, v in pairs(buffer) do
local s, e = string.find(v, "refs/tags/.+")
if not s or not e then
goto continue
end
if exit_code ~= 0 then
return
end
local tag = string.sub(v, s, e)
tag = string.gsub(tag, "refs/tags/", "")
tag = string.gsub(tag, "%^{}", "")
table.insert(versions, tag)
::continue::
-- get a list of all versions
local versions = {}
for _, v in pairs(buffer) do
local s, e = string.find(v, "refs/tags/.+")
if not s or not e then
goto continue
end
-- match the chosen version against all versions
for i = #versions, 1, -1 do
if branch == "*" then
cb(false, versions[i])
local tag = string.sub(v, s, e)
tag = tag:gsub("refs/tags/", ""):gsub("%^{}", "")
table.insert(versions, tag)
::continue::
end
-- match the chosen version against all versions
for i = #versions, 1, -1 do
if branch == "*" then
cb(false, versions[i])
return
else
local r = string.match(versions[i], branch)
if r then
cb(false, r)
return
else
local r = string.match(versions[i], branch)
if r then
cb(false, r)
return
end
end
end
end

View File

@ -37,7 +37,7 @@ end
---@param opts vim.api.keyset.user_command? options
function lazy:cmd(name, opts)
opts = opts or {}
vim.api.nvim_create_user_command(name, function(o)
vim.api.nvim_create_user_command(name, opts['callback'] or function()
self:cleanup()
end, opts)
@ -49,9 +49,8 @@ end
---@param opts vim.api.keyset.create_autocmd? options
function lazy:auto(event, opts)
opts = opts or {}
opts['once'] = true
opts['callback'] = function()
opts['once'] = opts['once'] or true
opts['callback'] = opts['callback'] or function()
self:cleanup()
end
@ -64,7 +63,7 @@ end
---@param opts vim.keymap.set.Opts? options
function lazy:keymap(mode, bind, opts)
opts = opts or {}
vim.keymap.set(mode, bind, function()
vim.keymap.set(mode, bind, opts['callback'] or function()
self:cleanup()
-- register keymap unload