Skip to content

Commit

Permalink
feat: lockfile support
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Jul 7, 2024
1 parent 38dc510 commit d87d586
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 75 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- Name-based installation
(` "nvim-neorg/neorg" ` becomes `:Rocks install neorg` instead).
- Supports [multiple versions of the same dependency](https://github.com/luarocks/luarocks/wiki/Using-LuaRocks#multiple-versions-using-the-luarocks-package-loader).
- Lockfile `rocks.lock` for dependencies.
- Minimal, non-intrusive UI.
- Async execution.
- Extensible, with a Lua API.
Expand Down Expand Up @@ -469,6 +470,12 @@ You can also pin/unpin installed plugins with:
:Rocks [pin|unpin] {rock}
```

### lockfile

When installing or updating, `rocks.nvim` maintains a `rocks.lock` file,
which pins all SemVer dependency versions for each plugin.
You can check the lockfile into SCM.

## :package: Extending `rocks.nvim`

This plugin provides a Lua API for extensibility.
Expand Down
4 changes: 3 additions & 1 deletion doc/rocks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ RocksOpts *RocksOpts*
(Default: a `rocks` directory in `vim.fn.stdpath("data")`).
{config_path?} (string)
Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`.
{lockfile_path?} (string)
Rocks lockfile path. Defaults to `rocks.lock` in `vim.fn.stdpath("config")`.
{luarocks_binary?} (string)
Luarocks binary path (Default: `{rocks_path}/bin/luarocks`).
Luarocks binary path. Defaults to the bundled installation if executable.
{lazy?} (boolean)
Whether to query luarocks.org lazily (Default: `false`).
Setting this to `true` may improve startup time,
Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 2 additions & 8 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@
"x86_64-darwin"
"aarch64-darwin"
];
perSystem = {
config,
self',
inputs',
system,
...
}: let
perSystem = {system, ...}: let
pkgs = import nixpkgs {
inherit system;
overlays = [
Expand Down Expand Up @@ -140,7 +134,7 @@

packages = rec {
default = rocks-nvim;
inherit (pkgs.luajitPackages) rocks-nvim luarocks-rock;
inherit (pkgs.luajitPackages) rocks-nvim;
inherit
(pkgs)
neovim-with-rocks
Expand Down
5 changes: 4 additions & 1 deletion lua/rocks/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ local config = {}
--- Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`.
---@field config_path? string
---
--- Luarocks binary path (Default: `{rocks_path}/bin/luarocks`).
--- Rocks lockfile path. Defaults to `rocks.lock` in `vim.fn.stdpath("config")`.
---@field lockfile_path? string
---
--- Luarocks binary path. Defaults to the bundled installation if executable.
---@field luarocks_binary? string
---
--- Whether to query luarocks.org lazily (Default: `false`).
Expand Down
5 changes: 3 additions & 2 deletions lua/rocks/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ local default_config = {
---@type string Local path in your filesystem to install rocks
rocks_path = default_rocks_path,
---@type string Rocks declaration file path
---@diagnostic disable-next-line: param-type-mismatch
config_path = vim.fs.joinpath(vim.fn.stdpath("config"), "rocks.toml"),
config_path = vim.fs.joinpath(vim.fn.stdpath("config") --[[@as string]], "rocks.toml"),
---@type string Rocks lockfile path
lockfile_path = vim.fs.joinpath(vim.fn.stdpath("config") --[[@as string]], "rocks.lock"),
---@type string Luarocks binary path
luarocks_binary = get_default_luarocks_binary(default_rocks_path),
---@type boolean Whether to query luarocks.org lazily
Expand Down
11 changes: 8 additions & 3 deletions lua/rocks/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ function fs.file_exists(location)
return false
end

--- Write `contents` to a file
--- Write `contents` to a file asynchronously
---@param location string file path
---@param mode string mode to open the file for
---@param contents string file contents
---@param callback? function
function fs.write_file(location, mode, contents, callback)
local dir = vim.fn.fnamemodify(location, ":h")
local dir = vim.fsdirname(location)
fs.mkdir_p(dir)
-- 644 sets read and write permissions for the owner, and it sets read-only
-- mode for the group and others
Expand All @@ -55,7 +55,12 @@ function fs.write_file(location, mode, contents, callback)
if write_err then
local msg = ("Error writing %s: %s"):format(location, err)
log.error(msg)
vim.notify(msg, vim.log.levels.ERROR)
vim.schedule(function()
vim.notify(msg, vim.log.levels.ERROR)
end)
if file then
uv.fs_close(file)
end
end
uv.fs_close(file)
if callback then
Expand Down
2 changes: 2 additions & 0 deletions lua/rocks/operations/add.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local config = require("rocks.config.internal")
local cache = require("rocks.cache")
local helpers = require("rocks.operations.helpers")
local handlers = require("rocks.operations.handlers")
local lock = require("rocks.operations.lock")
local parser = require("rocks.operations.parser")
local nio = require("nio")
local progress = require("fidget.progress")
Expand Down Expand Up @@ -203,6 +204,7 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim.
user_rocks.plugins[rock_name] = installed_rock.version
end
fs.write_file_await(config.config_path, "w", tostring(user_rocks))
lock.update_lockfile(installed_rock.name)
cache.populate_removable_rock_cache()
vim.schedule(function()
-- Re-generate help tags
Expand Down
38 changes: 24 additions & 14 deletions lua/rocks/operations/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
---@brief ]]

local luarocks = require("rocks.luarocks")
local lock = require("rocks.operations.lock")
local constants = require("rocks.constants")
local config = require("rocks.config.internal")
local fs = require("rocks.fs")
Expand Down Expand Up @@ -47,18 +48,19 @@ function helpers.get_rock_and_key(rocks_toml, rock_name)
return rocks_key, rocks_key and rocks_toml[rocks_key][rock_name]
end

---@class rocks.helpers.InstallOpts
---@field use_lockfile boolean

---@param rock_spec RockSpec
---@param progress_handle? ProgressHandle
---@param opts? rocks.helpers.InstallOpts
---@return nio.control.Future
helpers.install = nio.create(function(rock_spec, progress_handle)
helpers.install = nio.create(function(rock_spec, opts)
opts = opts or {}
cache.invalidate_removable_rocks()
local name = rock_spec.name:lower()
local version = rock_spec.version
local message = version and ("Installing: %s -> %s"):format(name, version) or ("Installing: %s"):format(name)
log.info(message)
if progress_handle then
progress_handle:report({ message = message })
end
-- TODO(vhyrro): Input checking on name and version
local future = nio.control.future()
local install_cmd = {
Expand Down Expand Up @@ -86,14 +88,24 @@ helpers.install = nio.create(function(rock_spec, progress_handle)
table.insert(install_cmd, install_arg)
end)
table.insert(install_cmd, 2, "--force")
local install_opts = {
servers = servers,
}
if opts.use_lockfile then
-- luarocks locks dependencies when there is a lockfile in the cwd
local lockfile = lock.create_luarocks_lock(rock_spec.name)
if lockfile and vim.uv.fs_stat(lockfile) then
install_opts.cwd = vim.fs.dirname(lockfile)
end
end
-- We always want to insert --pin so that the luarocks.lock is created in the
-- install directory on the rtp
table.insert(install_cmd, "--pin")
luarocks.cli(install_cmd, function(sc)
---@cast sc vim.SystemCompleted
if sc.code ~= 0 then
message = ("Failed to install %s"):format(name)
log.error(message)
if progress_handle then
progress_handle:report({ message = message })
end
future.set_error(sc.stderr)
else
---@type Rock
Expand All @@ -106,22 +118,20 @@ helpers.install = nio.create(function(rock_spec, progress_handle)
}
message = ("Installed: %s -> %s"):format(installed_rock.name, installed_rock.version)
log.info(message)
if progress_handle then
progress_handle:report({ message = message })
end

nio.run(function()
adapter.init_site_symlinks()
if config.dynamic_rtp and not rock_spec.opt then
nio.scheduler()
runtime.packadd(name)
else
-- Add rock to the rtp, but don't source any scripts
runtime.packadd(name, { bang = true })
end
future.set(installed_rock)
end)
end
end, {
servers = servers,
})
end, install_opts)
return future
end, 2)

Expand Down
87 changes: 87 additions & 0 deletions lua/rocks/operations/lock.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---@mod rocks.operations.lock
--
-- Copyright (C) 2024 Neorocks Org.
--
-- License: GPLv3
-- Created: 7 Jul 2024
-- Updated: 7 Jul 2024
-- Homepage: https://github.com/nvim-neorocks/rocks.nvim
-- Maintainers: NTBBloodbath <[email protected]>, Vhyrro <[email protected]>, mrcjkb <[email protected]>
--
---@brief [[
--
-- Lockfile management.
--
---@brief ]]

local config = require("rocks.config.internal")
local fs = require("rocks.fs")
local nio = require("nio")

local lock = {}

---@param reset boolean
local function parse_rocks_lock(reset)
local lockfile = reset and "" or fs.read_or_create(config.lockfile_path, "")
return require("toml_edit").parse(lockfile)
end

---@param rock_name? rock_name
lock.update_lockfile = nio.create(function(rock_name)
local luarocks_lockfiles = vim.iter(vim.api.nvim_get_runtime_file("luarocks.lock", true))
:filter(function(path)
return not rock_name or path:find(rock_name .. "/[^%/]+/luarocks.lock$") ~= nil
end)
:totable()
local reset = rock_name == nil
local rocks_lock = parse_rocks_lock(reset)
for _, luarocks_lockfile in ipairs(luarocks_lockfiles) do
local rock_key = rock_name or luarocks_lockfile:match("/([^%/]+)/[^%/]+/luarocks.lock$")
if rock_key then
local ok, loader = pcall(loadfile, luarocks_lockfile)
if not ok or not loader then
return
end
local success, luarocks_lock_tbl = pcall(loader)
if not success or not luarocks_lock_tbl or not luarocks_lock_tbl.dependencies then
return
end
rocks_lock[rock_key] = {}
local has_deps = false
for dep, version in pairs(luarocks_lock_tbl.dependencies) do
local is_semver = pcall(vim.version.parse, version:match("([^-]+)") or version)
if is_semver and dep ~= "lua" then
rocks_lock[rock_key][dep] = version
has_deps = true
end
end
if not has_deps then
rocks_lock[rock_key] = nil
end
end
end
fs.write_file_await(config.lockfile_path, "w", tostring(rocks_lock))
end, 1)

---@param rock_name rock_name
---@return string | nil luarocks_lock
lock.create_luarocks_lock = nio.create(function(rock_name)
local lockfile = require("toml_edit").parse_as_tbl(fs.read_or_create(config.lockfile_path, ""))
local dependencies = lockfile[rock_name]
if not dependencies then
return
end
local temp_dir =
vim.fs.joinpath(vim.fn.stdpath("run") --[[@as string]], ("luarocks-lock-%X"):format(math.random(256 ^ 7)))
fs.mkdir_p(temp_dir)
local luarocks_lock = vim.fs.joinpath(temp_dir, "luarocks.lock")
local content = ([[
return {
dependencies = %s,
}
]]):format(vim.inspect(dependencies))
fs.write_file_await(luarocks_lock, "w", content)
return luarocks_lock
end, 1)

return lock
2 changes: 2 additions & 0 deletions lua/rocks/operations/prune.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local config = require("rocks.config.internal")
local cache = require("rocks.cache")
local helpers = require("rocks.operations.helpers")
local handlers = require("rocks.operations.handlers")
local lock = require("rocks.operations.lock")
local nio = require("nio")
local progress = require("fidget.progress")

Expand Down Expand Up @@ -59,6 +60,7 @@ prune.prune = function(rock_name)
success = false
end
fs.write_file_await(config.config_path, "w", tostring(user_config))
lock.update_lockfile()
local user_rocks = config.get_user_rocks()
handlers.prune_user_rocks(user_rocks, report_progress, report_error)
cache.populate_removable_rock_cache()
Expand Down
4 changes: 2 additions & 2 deletions lua/rocks/operations/sync.lua
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ operations.sync = function(user_rocks, on_complete)
if vim.startswith(user_rocks[key].version, "scm-") then
user_rocks[key].version = "dev"
end
local future = helpers.install(user_rocks[key])
local future = helpers.install(user_rocks[key], { use_lockfile = true })
local success = pcall(future.wait)

ct = ct + 1
Expand Down Expand Up @@ -162,7 +162,7 @@ operations.sync = function(user_rocks, on_complete)
message = is_downgrading and ("Downgrading: %s"):format(key) or ("Updating: %s"):format(key),
})

local future = helpers.install(user_rocks[key])
local future = helpers.install(user_rocks[key], { use_lockfile = true })
local success = pcall(future.wait)

ct = ct + 1
Expand Down
2 changes: 2 additions & 0 deletions lua/rocks/operations/update.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local config = require("rocks.config.internal")
local state = require("rocks.state")
local cache = require("rocks.cache")
local helpers = require("rocks.operations.helpers")
local lock = require("rocks.operations.lock")
local handlers = require("rocks.operations.handlers")
local nio = require("nio")
local progress = require("fidget.progress")
Expand Down Expand Up @@ -170,6 +171,7 @@ update.update = function(on_complete, opts)
end
end
fs.write_file_await(config.config_path, "w", tostring(user_rocks))
lock.update_lockfile()
nio.scheduler()
if not vim.tbl_isempty(error_handles) then
local message = "Update completed with errors! Run ':Rocks log' for details."
Expand Down
Loading

0 comments on commit d87d586

Please sign in to comment.