Skip to content

Commit

Permalink
feat: lockfile support
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Jun 11, 2024
1 parent 08c6087 commit 52d49c0
Show file tree
Hide file tree
Showing 13 changed files with 312 additions and 39 deletions.
1 change: 1 addition & 0 deletions .busted
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ return {
_all = {
coverage = false,
lpath = "lua/?.lua;lua/?/init.lua",
verbose = true,
},
default = {
verbose = true,
Expand Down
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ indent_size = unset
indent_size = unset
indent_style = unset

[spec/*.lua]
[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 @@ -31,6 +31,7 @@
(` "nvim-neorg/neorg" ` becomes `:Rocks install neorg` instead).
- Automatic dependency and build script management.
- 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.
- True semver versioning!
- Minimal, non-intrusive UI.
- Async execution.
Expand Down Expand Up @@ -456,6 +457,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
1 change: 1 addition & 0 deletions doc/rocks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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 the bundled installation if executable.
{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
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.

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 the bundled installation if executable.
---@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
7 changes: 4 additions & 3 deletions lua/rocks/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--
-- License: GPLv3
-- Created: 05 Jul 2023
-- Updated: 15 May 2024
-- Updated: 11 Jun 2024
-- Homepage: https://github.com/nvim-neorocks/rocks.nvim
-- Maintainers: NTBBloodbath <[email protected]>, Vhyrro <[email protected]>, mrcjkb <[email protected]>
--
Expand Down Expand Up @@ -42,8 +42,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.fs.dirname(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
39 changes: 24 additions & 15 deletions lua/rocks/operations/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--
-- License: GPLv3
-- Created: 07 Mar 2024
-- Updated: 19 Apr 2024
-- Updated: 11 Jun 2024
-- Homepage: https://github.com/nvim-neorocks/rocks.nvim
-- Maintainers: NTBBloodbath <[email protected]>, Vhyrro <[email protected]>, mrcjkb <[email protected]>
--
Expand All @@ -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 runtime = require("rocks.runtime")
Expand All @@ -27,18 +28,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 nio.control.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,24 @@ helpers.install = function(rock_spec, progress_handle)
end
end
table.insert(install_cmd, 2, "--force")
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)
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 @@ -82,22 +93,20 @@ 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

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

Expand Down
10 changes: 7 additions & 3 deletions lua/rocks/operations/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
-- Version: 0.1.0
-- License: GPLv3
-- Created: 05 Jul 2023
-- Updated: 07 Mar 2024
-- Updated: 11 Jun 2024
-- Homepage: https://github.com/nvim-neorocks/rocks.nvim
-- Maintainers: NTBBloodbath <[email protected]>, Vhyrro <[email protected]>, mrcjkb <[email protected]>
--
Expand All @@ -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 @@ -200,7 +201,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 @@ -245,7 +246,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 Expand Up @@ -451,6 +452,7 @@ operations.update = function(on_complete)
progress_handle:report({ message = "Nothing to update!", percentage = 100 })
else
fs.write_file_await(config.config_path, "w", tostring(user_rocks))
lock.update_lockfile()
end
nio.scheduler()
if not vim.tbl_isempty(error_handles) then
Expand Down Expand Up @@ -630,6 +632,7 @@ operations.add = function(arg_list, callback)
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 Expand Up @@ -670,6 +673,7 @@ operations.prune = function(rock_name)
nio.fn.keys(vim.tbl_deep_extend("force", user_config.rocks or {}, user_config.plugins or {}))
local success = helpers.remove_recursive(rock_name, user_rock_names, progress_handle)
fs.write_file_await(config.config_path, "w", tostring(user_config))
lock.update_lockfile()
cache.populate_removable_rock_cache()
vim.schedule(function()
if success then
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: 11 Jun 2024
-- Updated: 11 Jun 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
Loading

0 comments on commit 52d49c0

Please sign in to comment.