Skip to content

Commit

Permalink
Refactor PaqBuild
Browse files Browse the repository at this point in the history
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`).
  • Loading branch information
savq committed Sep 3, 2023
1 parent 920391d commit 058efd4
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 88 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
}
```

Expand All @@ -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
Expand Down
59 changes: 32 additions & 27 deletions doc/paq-nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand All @@ -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*
Expand Down Expand Up @@ -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|.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
135 changes: 77 additions & 58 deletions lua/paq.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 })
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

0 comments on commit 058efd4

Please sign in to comment.