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

feat: lockfile support #244

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
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
2 changes: 2 additions & 0 deletions doc/rocks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ 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. Defaults to the bundled installation if executable.
{lazy?} (boolean)
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.

8 changes: 1 addition & 7 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
3 changes: 3 additions & 0 deletions lua/rocks/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ local config = {}
--- Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`.
---@field config_path? string
---
--- 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
---
Expand Down
2 changes: 2 additions & 0 deletions lua/rocks/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ local default_config = {
rocks_path = default_rocks_path,
---@type string Rocks declaration file path
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
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
35 changes: 21 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,9 +118,6 @@ 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()
Expand All @@ -122,9 +131,7 @@ helpers.install = nio.create(function(rock_spec, progress_handle)
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
Loading