Skip to content

Commit

Permalink
feat: lockfile support
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Apr 7, 2024
1 parent efc87a3 commit 0132598
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 18 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ indent_size = unset
[*/**/config/internal.lua]
indent_size = unset
indent_style = unset

[spec/operations_lock_spec.lua]
indent_size = unset
indent_style = unset
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- Command completions for plugins and versions on luarocks.org.
- Binary rocks pulled from [rocks-binaries](https://nvim-neorocks.github.io/rocks-binaries/)
so you don't have to compile them.
- Lockfile support.

![demo](https://github.com/nvim-neorocks/rocks.nvim/assets/12857160/955c3ae7-c916-4a70-8fbd-4e28b7f0d77e)

Expand Down Expand Up @@ -328,6 +329,12 @@ Or, before rocks.nvim is initialised, with `require("rocks").packadd("<rock_name
> (if it has `ftdetect` or `plugin` scripts), so you may or may
> not benefit from loading them lazily.
### 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
1 change: 1 addition & 0 deletions doc/rocks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ RocksOpts *RocksOpts*
Fields: ~
{rocks_path?} (string) Local path in your filesystem to install rocks. Defaults to a `rocks` directory in `vim.fn.stdpath("data")`.
{config_path?} (string) Rocks declaration file path. Defaults to `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 `luarocks`.
{lazy?} (boolean) Whether to query luarocks.org lazily. Defaults to `false`. Setting this to `true` may improve startup time, but features like auto-completion will lag initially.
{dynamic_rtp?} (boolean) Whether to automatically add freshly installed plugins to the 'runtimepath'. Defaults to `true` for the best default experience.
Expand Down
1 change: 1 addition & 0 deletions lua/rocks/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local config = {}
---@class RocksOpts
---@field rocks_path? string Local path in your filesystem to install rocks. Defaults to a `rocks` directory in `vim.fn.stdpath("data")`.
---@field config_path? string Rocks declaration file path. Defaults to `rocks.toml` in `vim.fn.stdpath("config")`.
---@field lockfile_path? string Rocks lockfile path. Defaults to `rocks.lock` in `vim.fn.stdpath("config")`.
---@field luarocks_binary? string Luarocks binary path. Defaults to `luarocks`.
---@field lazy? boolean Whether to query luarocks.org lazily. Defaults to `false`. Setting this to `true` may improve startup time, but features like auto-completion will lag initially.
---@field dynamic_rtp? boolean Whether to automatically add freshly installed plugins to the 'runtimepath'. Defaults to `true` for the best default experience.
Expand Down
3 changes: 3 additions & 0 deletions lua/rocks/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ local default_config = {
---@type string Rocks declaration file path
---@diagnostic disable-next-line: param-type-mismatch
config_path = vim.fs.joinpath(vim.fn.stdpath("config"), "rocks.toml"),
---@type string Rocks lockfile path
---@diagnostic disable-next-line: param-type-mismatch
lockfile_path = vim.fs.joinpath(vim.fn.stdpath("config"), "rocks.lock"),
---@type string Luarocks binary path
luarocks_binary = "luarocks",
---@type boolean Whether to query luarocks.org lazily
Expand Down
9 changes: 7 additions & 2 deletions lua/rocks/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ end
---@param mode string mode to open the file for
---@param contents string file contents
function fs.write_file(location, mode, contents)
local dir = vim.fn.fnamemodify(location, ":h")
local dir = vim.fs.dirname(location)
vim.fn.mkdir(dir, "p")
-- 644 sets read and write permissions for the owner, and it sets read-only
-- mode for the group and others
Expand All @@ -54,7 +54,12 @@ function fs.write_file(location, mode, contents)
else
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
end)
end
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 @@ -17,6 +17,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 runtime = require("rocks.runtime")
Expand All @@ -28,18 +29,18 @@ local nio = require("nio")

local helpers = {}

---@class InstallOpts
---@field use_lockfile boolean

---@param rock_spec RockSpec
---@param progress_handle? ProgressHandle
---@param opts? InstallOpts
---@return Future
helpers.install = function(rock_spec, progress_handle)
helpers.install = function(rock_spec, opts)
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 All @@ -62,14 +63,22 @@ helpers.install = function(rock_spec, progress_handle)
table.insert(install_cmd, version)
end
end
local install_opts = {
servers = servers,
}
if opts and opts.use_lockfile then
-- luarocks locks dependencies when there is a lockfile in the cwd
local lockfile = lock.create_luarocks_lock(rock_spec.name)
install_opts.cwd = vim.fs.dirname(lockfile)
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")
local systemObj = 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 @@ -82,20 +91,18 @@ helpers.install = 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

if config.dynamic_rtp and not rock_spec.opt then
runtime.packadd(name)
adapter.init_tree_sitter_parser_symlink()
else
-- Add rock to the rtp, but don't source any scripts
runtime.packadd(name, { bang = true })
end

future.set(installed_rock)
end
end, {
servers = servers,
})
end, install_opts)
return {
wait = future.wait,
wait_sync = function()
Expand Down
8 changes: 6 additions & 2 deletions lua/rocks/operations/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ local handlers = require("rocks.operations.handlers")
local parser = require("rocks.operations.parser")
local nio = require("nio")
local progress = require("fidget.progress")
local lock = require("rocks.operations.lock")

local operations = {}

Expand Down Expand Up @@ -195,7 +196,7 @@ operations.sync = function(user_rocks)
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 @@ -243,7 +244,7 @@ operations.sync = function(user_rocks)
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 Expand Up @@ -436,6 +437,7 @@ operations.update = function()
else
vim_schedule_nio_wait(function()
fs.write_file(config.config_path, "w", tostring(user_rocks))
lock.update_lockfile()
end)
end
nio.scheduler()
Expand Down Expand Up @@ -603,6 +605,7 @@ operations.add = function(arg_list, callback)
user_rocks.plugins[rock_name] = installed_rock.version
end
fs.write_file(config.config_path, "w", tostring(user_rocks))
lock.update_lockfile(installed_rock.name)
if success then
progress_handle:finish()
if callback then
Expand Down Expand Up @@ -642,6 +645,7 @@ operations.prune = function(rock_name)
local success = helpers.remove_recursive(rock_name, user_rock_names, progress_handle)
vim_schedule_nio_wait(function()
fs.write_file(config.config_path, "w", tostring(user_config))
lock.update_lockfile()
if success then
progress_handle:finish()
else
Expand Down
90 changes: 90 additions & 0 deletions lua/rocks/operations/lock.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---@mod rocks.operations.lock
--
-- Copyright (C) 2024 Neorocks Org.
--
-- License: GPLv3
-- Created: 04 Apr 2024
-- Updated: 04 Apr 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 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
function lock.update_lockfile(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(config.lockfile_path, "w", tostring(rocks_lock))
end

---@param rock_name rock_name
---@return string | nil luarocks_lock
function lock.create_luarocks_lock(rock_name)
local lockfile = require("toml").decode(fs.read_or_create(config.lockfile_path, ""))
local dependencies = lockfile[rock_name]
if not dependencies then
return
end
local temp_dir = vim.fs.dirname(vim.fn.tempname())
local luarocks_lock = vim.fs.joinpath(temp_dir, "luarocks.lock")
local content = ([[
return {
dependencies = %s
}
]]):format(vim.inspect(dependencies))
-- NOTE: Because luarocks is going to use the lockfile immediately,
-- we have to write it synchronously
local fh = io.open(luarocks_lock, "w")
if fh then
fh:write(content)
fh:close()
return luarocks_lock
end
end

return lock
Loading

0 comments on commit 0132598

Please sign in to comment.