Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #152

Merged
merged 4 commits into from
Sep 3, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor PaqBuild
Public API:
- Rename `run` option to `build`.
- Rename `PaqRunHook` command to `PaqBuild`.

Paq will print a deprecation notice if the old names are used.
The reason for renaming these is explained in #143.

Internals:
- Refactor build related functions to ensure builds are executed after
  all packages have been installed/updated and loaded:
  - If the hook is a function, it might require a module inside a
    recently installed package.
  - If a is package is installed successfully and the build fails,
    it's better to notify that in two separate messages, instead of
    notifying the install failed.

- Rename `run_hook` to `run_build`, and refactor to make it clear
  there's 3 possible cases: function, ex command, shell commmand.

- Refactor `new_counter` to take a callback (passed in `exe_op`).
savq committed Sep 3, 2023
commit e6879acc4ddd623ea58379a38726e90d7cc2cc6a
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ require "paq" {

{ "lervag/vimtex", opt = true }, -- Use braces when passing options

{ 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' },
{ 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate' },
}
```

@@ -72,10 +72,10 @@ Then, source your configuration (using `:source %` or `:luafile %`) and run `:Pa
|--------|----------|-----------------------------------------------------------|
| as | string | Name to use for the package locally |
| branch | string | Branch of the repository |
| build | function | Lua function to run after install/update |
| build | string | Shell command to run after install/update |
| opt | boolean | Optional packages are not loaded on startup |
| pin | boolean | Pinned packages are not updated |
| run | string | Shell command to run after install/update |
| run | function | Lua function to run after install/update |
| url | string | URL of the remote repository, useful for non-GitHub repos |

For more details on each option, refer to the
59 changes: 32 additions & 27 deletions doc/paq-nvim.txt
Original file line number Diff line number Diff line change
@@ -100,16 +100,16 @@ imported as `paq`, the functions are:
|paq.install| *paq.install*
*:PaqInstall*
Installs all packages listed in your configuration. If a package is already
installed, the function ignores it. If a package has a `run` argument, it'll
be executed after the package is installed.
installed, the function ignores it. If a package has a `build` argument,
it'll be executed after the package is installed.


|paq.update| *paq.update*
*:PaqUpdate*
Updates the installed packages listed in your configuration. If a package
hasn't been installed with |PaqInstall|, the function ignores it.
If a package had changes and it has a `run` argument, then the `run`
argument will be executed.
hasn't been installed with |PaqInstall|, the function ignores it. If a
package had changes and it has a `build` argument, then the `build` argument
will be executed.


|paq.clean| *paq.clean*
@@ -125,12 +125,11 @@ imported as `paq`, the functions are:
out of order.


|paq._run_hook| *paq._run_hook*
*:PaqRunHook*
Takes as single argument a string with the name of a package. If the package
has a `run` hook (functions and shell commands), it will execute the hook.
This can be used when a hook fails, to run a hook without a package having
changed, or for other debugging purposes.
|PaqBuild| *:PaqBuild*
Takes as single argument with the name of a package. If the package has a
`build` option (function or shell command), it will execute it. This can be
used when a build fails, to run a build without a package having changed, or
for other debugging purposes.


|paq.list| *paq.list*
@@ -231,6 +230,26 @@ The options and their types are the following:
Default value: `nil`


`build` : function | string
Either a Lua function, a shell command, or an EX-command to be executed
after installing or updating a package. Useful for packages that require
a compiling step.

If a string, Paq will execute the string as a shell command in the
directory of the package (not in the current directory). If the first
character of the string is a `:`, it will be execute as vim `:command`.

If a function, Paq will execute the function right after installing
the package. The function cannot take any arguments.

Note that in Lua, you can use index notation to reference a VimL function
that contains special characters:
>lua
{ "<name-of-package>", build = vim.fn["<name-of-viml-function>"] }
<
Default value: `nil`


`opt` : boolean
Indicates whether the package is optional or not. If set, the package will
be in the optional packages directory. See |packages| and |packadd|.
@@ -246,21 +265,7 @@ The options and their types are the following:


`run` : string | function
Either a shell command or Lua function to be executed after installing or
updating a package. Useful for packages that require extra build steps.

If a string, Paq will execute the string as a shell command in the
directory of the package (not in the current directory). If the first
character of the string is a `:`, it will be execute as vim `:command`

If a function, Paq will execute the function right after installing
the package. The function cannot take any arguments.

Note that in Lua, you can wrap a VimL function like so:
>lua
{ "<name-of-package>", run = vim.fn["<name-of-viml-function>"] }
<
Default value: `nil`
Deprecated. Use `build` instead.


`url` : string
@@ -363,7 +368,7 @@ Here's a list of steps to take when something goes wrong with Paq:
so you might want to look from the bottom up.

4. If you think the error wasn't caused by git (or another external program
called with a hook), consider opening an issue on the paq-nvim GitHub
called with `build`), consider opening an issue on the paq-nvim GitHub
repository.

Some common issues are listed below.
135 changes: 77 additions & 58 deletions lua/paq.lua
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ local function report(op, name, res, n, total)
install = { ok = "Installed", err = "Failed to install" },
update = { ok = "Updated", err = "Failed to update", nop = "(up-to-date)" },
remove = { ok = "Removed", err = "Failed to remove" },
hook = { ok = "Ran hook for", err = "Failed to run hook for" },
build = { ok = "Built", err = "Failed to build" },
}
local count = n and string.format(" [%d/%d]", n, total) or ""
vim.notify(
@@ -74,7 +74,7 @@ local function lock_write()
-- json.encode doesn't support functions
local pkgs = vim.deepcopy(packages)
for p, _ in pairs(pkgs) do
pkgs[p].run = nil
pkgs[p].build = nil
end
local file = uv.fs_open(lockfile, "w", 438)
if file then
@@ -103,24 +103,6 @@ local function lock_load()
return vim.deepcopy(packages)
end

local function new_counter()
return coroutine.wrap(function(op, total)
local c = { ok = 0, err = 0, nop = 0 }
while c.ok + c.err + c.nop < total do
local name, res, over_op = coroutine.yield(true)
c[res] = c[res] + 1
if res ~= "nop" or cfg.verbose then
report(over_op or op, name, res, c.ok + c.nop, total)
end
end
local summary = " Paq: %s complete. %d ok; %d errors;" .. (c.nop > 0 and " %d no-ops" or "")
vim.notify(string.format(summary, op, c.ok, c.err, c.nop))
vim.cmd("packloadall! | silent! helptags ALL")
vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper))
return true
end)
end

local function call_proc(process, args, cwd, cb, print_stdout)
local log = uv.fs_open(logfile, "a+", 0x1A4)
local stderr = uv.new_pipe(false)
@@ -141,32 +123,25 @@ local function call_proc(process, args, cwd, cb, print_stdout)
end
end

local function run_hook(pkg, counter, sync)
local t = type(pkg.run)
local function run_build(pkg)
local t = type(pkg.build)
if t == "function" then
vim.cmd("packadd " .. pkg.name)
local res = pcall(pkg.run) and "ok" or "err"
report("hook", pkg.name, res)
return counter and counter(pkg.name, res, sync)
local ok = pcall(pkg.run)
report("build", pkg.name, ok and "ok" or "err")
elseif t == "string" and pkg.build:sub(1, 1) == ":" then
local ok = pcall(vim.cmd, pkg.run)
report("build", pkg.name, ok and "ok" or "err")
elseif t == "string" then
local args = {}
if pkg.run:sub(1, 1) == ":" then
vim.cmd(pkg.run)
else
for word in pkg.run:gmatch("%S+") do
table.insert(args, word)
end
call_proc(table.remove(args, 1), args, pkg.dir, function(ok)
local res = ok and "ok" or "err"
report("hook", pkg.name, res)
return counter and counter(pkg.name, res, sync)
end)
for word in pkg.build:gmatch("%S+") do
table.insert(args, word)
end
return true
call_proc(table.remove(args, 1), args, pkg.dir, function(ok)
report("build", pkg.name, ok and "ok" or "err")
end)
end
end

local function clone(pkg, counter, sync)
local function clone(pkg, counter, build_queue, sync)
local args = { "clone", pkg.url, "--depth=1", "--recurse-submodules", "--shallow-submodules" }
if pkg.branch then
vim.list_extend(args, { "-b", pkg.branch })
@@ -177,7 +152,10 @@ local function clone(pkg, counter, sync)
pkg.status = status.CLONED
lock_write()
lock = vim.deepcopy(packages)
return pkg.run and run_hook(pkg, counter, sync) or counter(pkg.name, "ok", sync)
counter(pkg.name, "ok", sync)
if pkg.build then
table.insert(build_queue, pkg)
end
else
counter(pkg.name, "err", sync)
end
@@ -219,7 +197,7 @@ local function log_update_changes(pkg, prev_hash, cur_hash)
end)
end

local function pull(pkg, counter, sync)
local function pull(pkg, counter, build_queue, sync)
local prev_hash = lock[pkg.name] and lock[pkg.name].hash or pkg.hash
call_proc("git", { "pull", "--recurse-submodules", "--update-shallow" }, pkg.dir, function(ok)
if not ok then
@@ -231,19 +209,22 @@ local function pull(pkg, counter, sync)
pkg.status = status.UPDATED
lock_write()
lock = vim.deepcopy(packages)
return pkg.run and run_hook(pkg, counter, sync) or counter(pkg.name, "ok", sync)
counter(pkg.name, "ok", sync)
if pkg.build then
table.insert(build_queue, pkg)
end
else
counter(pkg.name, "nop", sync)
end
end
end)
end

local function clone_or_pull(pkg, counter)
local function clone_or_pull(pkg, counter, build_queue)
if filter.to_update(pkg) then
pull(pkg, counter, "update")
pull(pkg, counter, build_queue, "update")
elseif filter.to_install(pkg) then
clone(pkg, counter, "install")
clone(pkg, counter, build_queue, "install")
end
end

@@ -285,24 +266,53 @@ local function remove(p, counter)
end
end

-- Object to track result of operations (installs, updates, etc.)
local function new_counter(op, total, callback)
return coroutine.wrap(function()
local c = { ok = 0, err = 0, nop = 0 }
while c.ok + c.err + c.nop < total do
local name, res, over_op = coroutine.yield(true)
c[res] = c[res] + 1
if res ~= "nop" or cfg.verbose then
report(over_op or op, name, res, c.ok + c.nop, total)
end
end
callback(c.ok, c.err, c.nop)
return true
end)
end

-- Boilerplate around operations (autocmds, counter initialization, etc.)
local function exe_op(op, fn, pkgs)
if #pkgs == 0 then
vim.notify(" Paq: Nothing to " .. op)
vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper))
return
end
local counter = new_counter()
counter(op, #pkgs)

local build_queue = {}

local function after(ok, err, nop)
local summary = " Paq: %s complete. %d ok; %d errors;" .. (nop > 0 and " %d no-ops" or "")
vim.notify(string.format(summary, op, ok, err, nop))
vim.cmd("packloadall! | silent! helptags ALL")
if #build_queue ~= 0 then
exe_op("build", run_build, build_queue)
end
vim.cmd("doautocmd User PaqDone" .. op:gsub("^%l", string.upper))
end

local counter = new_counter(op, #pkgs, after)
counter() -- Initialize counter
for _, pkg in pairs(pkgs) do
fn(pkg, counter)
fn(pkg, counter, build_queue)
end
end

local function sort_by_name(t)
table.sort(t, function(a, b) return a.name < b.name end)
end

-- stylua: ignore
local function list()
local installed = vim.tbl_filter(filter.installed, lock)
local removed = vim.tbl_filter(filter.removed, lock)
@@ -339,9 +349,12 @@ local function register(pkg)
status = uv.fs_stat(dir) and status.INSTALLED or status.LISTED,
hash = get_git_hash(dir),
pin = pkg.pin,
run = pkg.run, -- TODO(breaking): Rename
build = pkg.build or pkg.run,
url = url,
}
if pkg.run then
vim.deprecate("`run` option", "`build`", "3.0", "Paq", false)
end
end

-- PUBLIC API:
@@ -373,12 +386,18 @@ do
vim.api.nvim_create_user_command(cmd_name, function(_) fn() end, { bar = true })
end

vim.api.nvim_create_user_command("PaqRunHook", function(a) run_hook(packages[a.args]) end, {
bar = true,
nargs = 1,
complete = function()
return vim.tbl_keys(vim.tbl_map(function(pkg) return pkg.run end, packages))
end,
})
-- stylua: ignore
do
local build_cmd_opts = {
bar = true,
nargs = 1,
complete = function() return vim.tbl_keys(vim.tbl_map(function(pkg) return pkg.build end, packages)) end,
}
vim.api.nvim_create_user_command("PaqBuild", function(a) run_hook(packages[a.args]) end, build_cmd_opts)
vim.api.nvim_create_user_command("PaqRunHook", function(a)
vim.deprecate("`PaqRunHook` command", "`PaqBuild`", "3.0", "Paq", false)
run_hook(packages[a.args])
end, build_cmd_opts)
end

return paq