407 lines
9.7 KiB
Lua
407 lines
9.7 KiB
Lua
local logger = require("dep/log")
|
|
local proc = require("dep/proc")
|
|
|
|
logger:open()
|
|
|
|
local base_dir
|
|
local packages, package_roots
|
|
|
|
local function register(arg)
|
|
if type(arg) ~= "table" then
|
|
arg = { arg }
|
|
end
|
|
|
|
local id = arg[1]
|
|
local package = packages[id]
|
|
|
|
if not package then
|
|
package = {
|
|
id = id,
|
|
enabled = false,
|
|
exists = false,
|
|
added = false,
|
|
configured = false,
|
|
loaded = false,
|
|
on_setup = {},
|
|
on_config = {},
|
|
on_load = {},
|
|
root = true,
|
|
dependencies = {}, -- inward edges
|
|
dependents = {}, -- outward edges
|
|
}
|
|
|
|
packages[id] = package
|
|
end
|
|
|
|
local prev_dir = package.dir -- optimization
|
|
|
|
-- meta
|
|
package.name = arg.as or package.name or id:match("^[%w-_.]+/([%w-_.]+)$")
|
|
package.url = arg.url or package.url or ("https://github.com/" .. id .. ".git")
|
|
package.branch = arg.branch or package.branch
|
|
package.dir = base_dir .. package.name
|
|
package.pin = arg.pin or package.pin
|
|
package.enabled = not arg.disabled and package.enabled
|
|
|
|
if prev_dir ~= package.dir then
|
|
package.exists = vim.fn.isdirectory(package.dir) ~= 0
|
|
package.configured = package.exists
|
|
end
|
|
|
|
table.insert(package.on_setup, arg.setup)
|
|
table.insert(package.on_config, arg.config)
|
|
table.insert(package.on_load, arg[2])
|
|
|
|
for _, req in ipairs(type(arg.requires) == "table" and arg.requires or { arg.requires }) do
|
|
local parent, child = register(req), package
|
|
parent.dependents[child.id] = child
|
|
child.dependencies[parent.id], child.root = parent, false
|
|
end
|
|
|
|
for _, dep in ipairs(type(arg.deps) == "table" and arg.deps or { arg.deps }) do
|
|
local parent, child = package, register(dep)
|
|
parent.dependents[child.id] = child
|
|
child.dependencies[parent.id], child.root = parent, false
|
|
end
|
|
end
|
|
|
|
local function register_recursive(list)
|
|
for _, arg in ipairs(list) do
|
|
register(arg)
|
|
end
|
|
|
|
for _, module in ipairs(list.modules or {}) do
|
|
if type(module) == "string" then
|
|
module = require(module)
|
|
end
|
|
|
|
register_recursive(module)
|
|
end
|
|
end
|
|
|
|
local function find_cycle()
|
|
local index = 0
|
|
local indexes = {}
|
|
local lowlink = {}
|
|
local set = {}
|
|
local stack = {}
|
|
|
|
local function connect(package)
|
|
indexes[package.id], lowlink[package.id], set[package.id] = index, index, true
|
|
index = index + 1
|
|
table.insert(stack, package)
|
|
|
|
for _, dependent in pairs(package.dependents) do
|
|
if indexes[dependent.id] == nil then
|
|
local cycle = connect(dependent)
|
|
if cycle then
|
|
return cycle
|
|
else
|
|
lowlink[package.id] = math.min(lowlink[package.id], lowlink[dependent.id])
|
|
end
|
|
elseif set[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 = table.remove(stack)
|
|
set[node.id] = nil
|
|
table.insert(cycle, node)
|
|
until node == package
|
|
|
|
-- only consider multi-node components
|
|
if #cycle > 2 then
|
|
return cycle
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, package in pairs(packages) do
|
|
if indexes[package.id] == nil then
|
|
local cycle = connect(package)
|
|
if cycle then
|
|
return cycle
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function find_roots()
|
|
for _, package in pairs(packages) do
|
|
if package.root then
|
|
table.insert(package_roots, package)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function run_hooks(package, type)
|
|
for _, cb in ipairs(package["on_" .. type]) do
|
|
local ok, err = pcall(cb)
|
|
if not ok then
|
|
return false, err
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function ensure_added(package)
|
|
if not package.added then
|
|
local ok, err = pcall(vim.cmd, "packadd " .. package.name)
|
|
if ok then
|
|
package.added = true
|
|
else
|
|
return false, err
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function configure_recursive(package, force)
|
|
if not package.exists or not package.enabled then
|
|
return
|
|
end
|
|
|
|
if not package.configured or force then
|
|
local ok, err = run_hooks(package, "setup")
|
|
if not ok then
|
|
logger:log("error", string.format("failed to set up %s; reason: %s", package.id, err))
|
|
return
|
|
end
|
|
|
|
ok, err = ensure_added(package)
|
|
if not ok then
|
|
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
|
|
return
|
|
end
|
|
|
|
ok, err = run_hooks(package, "config")
|
|
if not ok then
|
|
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
|
|
return
|
|
end
|
|
|
|
package.configured, package.loaded = true, false
|
|
force = true
|
|
end
|
|
|
|
for _, dependent in pairs(package.dependents) do
|
|
configure_recursive(dependent, force)
|
|
end
|
|
end
|
|
|
|
local function load_recursive(package, force)
|
|
if not package.exists or not package.enabled then
|
|
return
|
|
end
|
|
|
|
if not package.loaded or force then
|
|
local ok, err = ensure_added(package)
|
|
if not ok then
|
|
logger:log("error", string.format("failed to configure %s; reason: %s", package.id, err))
|
|
return
|
|
end
|
|
|
|
ok, err = run_hooks(package, "load")
|
|
if not ok then
|
|
logger:log("error", string.format("failed to load %s; reason: %s", package.id, err))
|
|
return
|
|
end
|
|
|
|
package.loaded = true
|
|
force = true
|
|
end
|
|
|
|
for _, dependent in pairs(package.dependents) do
|
|
load_recursive(dependent, force)
|
|
end
|
|
end
|
|
|
|
local function reload_meta()
|
|
vim.cmd([[
|
|
silent! helptags ALL
|
|
silent! UpdateRemotePlugins
|
|
]])
|
|
end
|
|
|
|
local function reload_all()
|
|
for _, package in pairs(package_roots) do
|
|
configure_recursive(package)
|
|
end
|
|
|
|
for _, package in pairs(package_roots) do
|
|
load_recursive(package)
|
|
end
|
|
|
|
reload_meta()
|
|
end
|
|
|
|
local function clean()
|
|
vim.loop.fs_scandir(base_dir, 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] = base_dir .. name
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
for _, package in pairs(packages) do
|
|
queue[package.name] = nil
|
|
end
|
|
|
|
for name, dir in pairs(queue) do
|
|
-- todo: make this async
|
|
local ok = vim.fn.delete(dir, "rf")
|
|
if not ok then
|
|
logger:log("error", string.format("failed to delete %s", name))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function sync(package, cb)
|
|
if not package.enabled then
|
|
return
|
|
end
|
|
|
|
if package.exists then
|
|
if package.pin then
|
|
return
|
|
end
|
|
|
|
local function cb_err(err)
|
|
logger:log("error", string.format("failed to update %s; reason: %s", package.id, err))
|
|
cb(err)
|
|
end
|
|
|
|
proc.git_current_commit(package.dir, function(err, before)
|
|
if err then
|
|
cb_err(before)
|
|
else
|
|
proc.git_fetch(package.dir, package.branch or "HEAD", function(err, message)
|
|
if err then
|
|
cb_err(message)
|
|
else
|
|
proc.git_reset(package.dir, package.branch or "HEAD", function(err, message)
|
|
if err then
|
|
cb_err(message)
|
|
else
|
|
proc.get_current_commit(package.dir, function(err, after)
|
|
if err then
|
|
cb_err(after)
|
|
else
|
|
if before == after then
|
|
logger:log("skip", string.format("skipped %s", package.id))
|
|
else
|
|
package.added, package.configured = false, false
|
|
logger:log("update", string.format("updated %s", package.id))
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
else
|
|
proc.git_clone(package.dir, package.url, package.branch, function(err, message)
|
|
if err then
|
|
logger:log("error", string.format("failed to install %s; reason: %s", package.id, message))
|
|
else
|
|
package.exists, package.added, package.configured = true, false, false
|
|
logger:log("install", string.format("installed %s", package.id))
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
local function sync_list(list)
|
|
local progress = 0
|
|
|
|
for _, package in ipairs(list) do
|
|
sync(package, function(err)
|
|
progress = progress + 1
|
|
if progress == #list then
|
|
clean()
|
|
reload_all()
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
vim.cmd([[
|
|
command! DepSync lua require("dep").sync()
|
|
command! DepList lua require("dep").list()
|
|
command! DepClean lua require("dep").clean()
|
|
command! DepLog lua require("dep").open_log()
|
|
]])
|
|
|
|
--todo: prevent multiple execution of async routines
|
|
return setmetatable({
|
|
sync = function()
|
|
local targets = {}
|
|
|
|
for _, package in pairs(packages) do
|
|
table.insert(targets, package)
|
|
end
|
|
|
|
sync_list(targets)
|
|
end,
|
|
|
|
open_log = function()
|
|
vim.cmd("sp " .. logger.path)
|
|
end,
|
|
}, {
|
|
__call = function(config)
|
|
base_dir = config.base_dir or (vim.fn.stdpath("data") .. "/site/pack/deps/start/")
|
|
packages, package_roots = {}, {}
|
|
|
|
register_recursive({ "chiyadev/dep", modules = { config } })
|
|
|
|
local cycle = find_cycle()
|
|
if cycle then
|
|
local names = {}
|
|
for _, package in ipairs(cycle) do
|
|
table.insert(names, package.id)
|
|
end
|
|
error("circular dependency detected in package graph: " .. table.concat(names, " -> "))
|
|
end
|
|
|
|
find_roots()
|
|
reload_all()
|
|
|
|
local should_sync = function(package)
|
|
if config.sync == "new" or config.sync == nil then
|
|
return not package.exists
|
|
else
|
|
return config.sync == "always"
|
|
end
|
|
end
|
|
|
|
local targets = {}
|
|
|
|
for _, package in pairs(packages) do
|
|
if should_sync(package) then
|
|
table.insert(targets, package)
|
|
end
|
|
end
|
|
|
|
sync_list(targets)
|
|
end,
|
|
})
|