diff --git a/.github/resources/init-windows.lua b/.github/resources/init-integration.lua similarity index 68% rename from .github/resources/init-windows.lua rename to .github/resources/init-integration.lua index 6a2550d7..e74eb581 100644 --- a/.github/resources/init-windows.lua +++ b/.github/resources/init-integration.lua @@ -1,3 +1,4 @@ +-- init.lua used for integration tests vim.g.rocks_nvim = { rocks_path = vim.fs.joinpath(vim.fn.getcwd(), "rocks"), _log_level = vim.log.levels.TRACE, @@ -10,6 +11,10 @@ local luarocks_path = { package.path = package.path .. ";" .. table.concat(luarocks_path, ";") local luarocks_cpath = { + vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib", "lua", "5.1", "?.so"), + vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib64", "lua", "5.1", "?.so"), + vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib", "lua", "5.1", "?.dylib"), + vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib64", "lua", "5.1", "?.dylib"), vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib", "lua", "5.1", "?.dll"), vim.fs.joinpath(vim.g.rocks_nvim.rocks_path, "lib64", "lua", "5.1", "?.dll"), } diff --git a/.github/resources/install-colorscheme.lua b/.github/resources/install-colorscheme.lua new file mode 100644 index 00000000..68773f64 --- /dev/null +++ b/.github/resources/install-colorscheme.lua @@ -0,0 +1,8 @@ +local done = false +require("rocks.api").install("sweetie.nvim", "2.4.0", function() + vim.uv.sleep(2000) -- Leave time for symlinks to be created + done = true +end) +vim.fn.wait(60000, function() + return done and 1 or 0 +end) diff --git a/.github/workflows/windows.yml b/.github/workflows/integration.yml similarity index 60% rename from .github/workflows/windows.yml rename to .github/workflows/integration.yml index c02a9d40..6d5358ce 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/integration.yml @@ -1,14 +1,20 @@ --- -name: Windows test -# An impure test case to help debug issues with rocks.nvim on windows +name: Integration tests +# Impure, cross platform integration tests on: pull_request: - # We only want to manually trigger this as needed workflow_dispatch: jobs: - build: - runs-on: windows-2019 + test: + runs-on: ${{ matrix.os.host }} + strategy: + matrix: + os: + - host: ubuntu-20.04 + - host: windows-2019 + - host: macos-11 # x86_64 + - host: macos-14 # aarch64 steps: - uses: actions/checkout@v4 - name: Install C/C++ Compiler @@ -38,7 +44,14 @@ jobs: shell: bash run: | ls rocks/lib/lua/5.1 - nvim -u .github/resources/init-windows.lua -c "lua print('OK')" +q + nvim -u .github/resources/init-integration.lua -l .github/resources/install-colorscheme.lua +q ls rocks # Will fail if rocks.log does not exist cat rocks/rocks.log + echo "vim.cmd.colorscheme('sweetie')" >> .github/resources/init-integration.lua + echo "vim.cmd.e('success')" >> .github/resources/init-integration.lua + nvim -u .github/resources/init-integration.lua -c +wq + if [ ! -f success ]; then + echo "Integration test failed!" + exit 1 + fi diff --git a/lua/rocks/adapter.lua b/lua/rocks/adapter.lua index adcb350a..b65c4f96 100644 --- a/lua/rocks/adapter.lua +++ b/lua/rocks/adapter.lua @@ -11,7 +11,7 @@ -- -- License: GPLv3 -- Created: 13 Mar 2024 --- Updated: 13 Mar 2024 +-- Updated: 19 Apr 2024 -- Homepage: https://github.com/nvim-neorocks/rocks.nvim -- Maintainers: NTBBloodbath , Vhyrro , mrcjkb @@ -19,32 +19,40 @@ local adapter = {} local nio = require("nio") local log = require("rocks.log") +local fs = require("rocks.fs") local config = require("rocks.config.internal") local rtp_link_dir = vim.fs.joinpath(config.rocks_path, "rocks_rtp") vim.opt.runtimepath:append(rtp_link_dir) -local rocks_parser_dir = vim.fs.joinpath(config.rocks_path, "lib", "lua", "5.1", "parser") +local data_dir = vim.fn.stdpath("data") +---@cast data_dir string +local site_link_dir = vim.fs.joinpath(data_dir, "site", "pack", "luarocks", "opt") ---- Initialise the rocks_rtp directory ----@return boolean success -local function init_rocks_rtp_dir() - if not vim.uv.fs_stat(rtp_link_dir) and vim.fn.mkdir(rtp_link_dir, "p") ~= 1 then - log.error("Failed to create rocks_rtp symlink directory.") - return false - end - return true -end +local rocks_parser_dir = vim.fs.joinpath(config.rocks_path, "lib", "lua", "5.1", "parser") ----@type async fun(symlink_dir_name: string, dest_dir_path: string) -local add_rtp_symlink = nio.create(function(symlink_dir_name, dest_dir_path) - local symlink_dir_path = vim.fs.joinpath(rtp_link_dir, symlink_dir_name) +---@type async fun(symlink_location: string, symlink_dir_name: string, dest_dir_path: string) +local create_symlink = nio.create(function(symlink_location, symlink_dir_name, dest_dir_path) + local symlink_dir_path = vim.fs.joinpath(symlink_location, symlink_dir_name) -- NOTE: nio.uv.fs_stat behaves differently than vim.uv.fs_stat if not vim.uv.fs_stat(symlink_dir_path) then log.info("Creating symlink directory: " .. symlink_dir_name) nio.uv.fs_symlink(dest_dir_path, symlink_dir_path) end -end, 2) +end, 3) + +---@param symlink_dir string +local function validate_symlink_dir(symlink_dir) + if not vim.uv.fs_stat(symlink_dir) and not vim.uv.fs_unlink(symlink_dir) then + log.error("Failed to remove symlink: " .. symlink_dir) + end +end + +--- Check if the tree-sitter parser symlink is valid, +--- and remove it if it isn't +function adapter.validate_tree_sitter_parser_symlink() + validate_symlink_dir(vim.fs.joinpath(rtp_link_dir, "parser")) +end --- Neovim doesn't support `:checkhealth` for luarocks plugins. --- To work around this, we create a symlink in the `rocks_path` that @@ -52,7 +60,7 @@ end, 2) local function init_checkhealth_symlink() local rocks_lua_dir = vim.fs.joinpath(config.rocks_path, "share", "lua", "5.1") if vim.uv.fs_stat(rocks_lua_dir) then - add_rtp_symlink("lua", rocks_lua_dir) + create_symlink(rtp_link_dir, "lua", rocks_lua_dir) end end @@ -60,21 +68,55 @@ end -- initialise a symlink so that Neovim can find them. function adapter.init_tree_sitter_parser_symlink() if vim.uv.fs_stat(rocks_parser_dir) then - add_rtp_symlink("parser", rocks_parser_dir) + create_symlink(rtp_link_dir, "parser", rocks_parser_dir) end end ---- Check if the tree-sitter parser symlink is valid, ---- and remove it if it isn't -function adapter.validate_tree_sitter_parser_symlink() - local symlink_dir_path = vim.fs.joinpath(rtp_link_dir, "parser") - if not vim.uv.fs_stat(rocks_parser_dir) and not vim.uv.fs_unlink(symlink_dir_path) then - log.error("Failed to remove 'parser' symlink: " .. symlink_dir_path) +--- Check if the site symlinks are valid, +--- and remove them if they aren't +function adapter.validate_site_symlinks() + local handle = vim.uv.fs_scandir(site_link_dir) + while handle do + local name, ty = vim.uv.fs_scandir_next(handle) + if not name then + return + end + if ty == "link" then + validate_symlink_dir(vim.fs.joinpath(site_link_dir, name)) + end end end -function adapter.init() - local ok = init_rocks_rtp_dir() +--- @param rock Rock +local function init_site_symlink(rock) + local rock_dir = vim.fs.joinpath(config.rocks_path, "lib", "luarocks", "rocks-5.1", rock.name) + local handle = vim.uv.fs_scandir(rock_dir) + while handle do + local name, ty = vim.uv.fs_scandir_next(handle) + if not name then + return + end + if ty == "directory" and name:find("^" .. rock.version) ~= nil then + local rock_version_dir = vim.fs.joinpath(rock_dir, name) + create_symlink(site_link_dir, rock.name, rock_version_dir) + return + end + end +end + +--- Loop over the installed rocks and create symlinks in site/pack/luarocks/opt, +--- so that rtp paths like 'autoload' and 'color' are available before rocks.nvim +--- has initialised. +adapter.init_site_symlinks = nio.create(function() + local state = require("rocks.state") + for _, rock in pairs(state.installed_rocks()) do + init_site_symlink(rock) + end +end) + +--- Initialise/validate runtimepath symlinks for tree-sitter parsers and health checks +local function init_rtp_links() + local ok = fs.mkdir_p(rtp_link_dir) if not ok then return end @@ -83,4 +125,20 @@ function adapter.init() adapter.init_tree_sitter_parser_symlink() end +--- Initialise/validate site symlinks so that 'autoload' and 'colors', etc. +--- are available on the rtp (without sourcing plugins) before rocks.nvim is loaded. +local function init_site_links() + local ok = fs.mkdir_p(site_link_dir) + if not ok then + return + end + adapter.validate_site_symlinks() + adapter.init_site_symlinks() +end + +function adapter.init() + init_rtp_links() + init_site_links() +end + return adapter diff --git a/lua/rocks/fs.lua b/lua/rocks/fs.lua index c5abd1df..cf42206c 100644 --- a/lua/rocks/fs.lua +++ b/lua/rocks/fs.lua @@ -41,7 +41,7 @@ end ---@param contents string file contents function fs.write_file(location, mode, contents) local dir = vim.fn.fnamemodify(location, ":h") - vim.fn.mkdir(dir, "p") + fs.mkdir_p(dir) -- 644 sets read and write permissions for the owner, and it sets read-only -- mode for the group and others uv.fs_open(location, mode, tonumber("644", 8), function(err, file) @@ -81,6 +81,29 @@ function fs.read_or_create(location, default) return content end +---Create directory, including parents +---@param dir string +---@return boolean success +function fs.mkdir_p(dir) + local mode = 493 + local mod = "" + local path = dir + while vim.fn.isdirectory(path) == 0 do + mod = mod .. ":h" + path = vim.fn.fnamemodify(dir, mod) + end + while mod ~= "" do + mod = string.sub(mod, 3) + path = vim.fn.fnamemodify(dir, mod) + vim.uv.fs_mkdir(path, mode) + end + if not vim.uv.fs_stat(dir) then + log.error("Failed to create directory: " .. dir) + return false + end + return true +end + return fs --- fs.lua ends here diff --git a/lua/rocks/operations/helpers.lua b/lua/rocks/operations/helpers.lua index abe95dc3..0a05842c 100644 --- a/lua/rocks/operations/helpers.lua +++ b/lua/rocks/operations/helpers.lua @@ -1,11 +1,10 @@ ---@mod rocks.operations.helpers -- --- Copyright (C) 2023 Neorocks Org. +-- Copyright (C) 2024 Neorocks Org. -- --- Version: 0.1.0 -- License: GPLv3 -- Created: 07 Mar 2024 --- Updated: 07 Mar 2024 +-- Updated: 19 Apr 2024 -- Homepage: https://github.com/nvim-neorocks/rocks.nvim -- Maintainers: NTBBloodbath , Vhyrro , mrcjkb -- @@ -86,9 +85,10 @@ helpers.install = function(rock_spec, progress_handle) progress_handle:report({ message = message }) end + adapter.init_tree_sitter_parser_symlink() + adapter.init_site_symlinks() if config.dynamic_rtp and not rock_spec.opt then runtime.packadd(name) - adapter.init_tree_sitter_parser_symlink() end future.set(installed_rock) @@ -127,6 +127,7 @@ helpers.remove = function(name, progress_handle) future.set(sc) end adapter.validate_tree_sitter_parser_symlink() + adapter.validate_site_symlinks() end) return future end diff --git a/spec/fs_spec.lua b/spec/fs_spec.lua new file mode 100644 index 00000000..b2a548c0 --- /dev/null +++ b/spec/fs_spec.lua @@ -0,0 +1,9 @@ +local fs = require("rocks.fs") +local tempdir = vim.fn.tempname() +describe("rocks.fs", function() + it("mkdir_p", function() + local dir = vim.fs.joinpath(tempdir, "bar", "baz", "bat") + fs.mkdir_p(dir) + assert.is_not_nil(vim.uv.fs_stat(dir)) + end) +end)