Eric's Blog 时光荏苒,岁月如梭

Neovim 中使用 luarocks

2025-11-27
Eric Wong

luarocks 是 lua 常用的包管理器,类似于 python 的 pip。前面使用 Lua 实现了一个 Neovim 的插件管理器,而目前我自己正在维护的插件也都是使用 Lua 来实现的。 因此使用 luarocks 来管理插件,同时又让插件管理器 nvim-plug 支持 luarocks 就显得很有必要了。看了下目前主流的插件管理器 lazy.nvim、rocks.nvim 也是支持 luarcoks 的。

安装 luarocks

在 Windows 下可以使用 scoop 命令进行安装:

scoop install luarocks

这里需要注意一下,上述命令会自动安装 lua 5.4,但是 Neovim 内默认使用的是 luajit 兼容的 lua 5.1。因此为了避免下载的 rocks 不兼容。可以再执行以下命令:

scoop uninstall lua
scoop install lua51

安装完成后检查一下:

luarocks config | rg deploy

输出内容:

deploy_bin_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\bin"
deploy_lib_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\lib\\lua\\5.1"
deploy_lua_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\share\\lua\\5.1"

在 Neovim 内使用 luarocks

在 Neovim 内使用 :lua 命令或者使用 lua 开发 Neovim 插件时, 若想要使用 luarocks 安装的包,其原理就是将 luarocks 所安装的包位置加入到 package.pathpackage.cpath

nvim-plug 中实现这一步骤的逻辑如下:

lua/plug/rocks/init.lua

function M.enable()
    if enabled then
        return
    end
    local ok, _ = pcall(function()
        local luarocks_config = vim.json.decode(
            vim.system({ 'luarocks', 'config', '--json' }):wait().stdout
        )
        package.path = package.path
            .. ';'
            .. luarocks_config.deploy_lua_dir
            .. [[\?.lua]]
            .. ';'
            .. luarocks_config.deploy_lua_dir
            .. [[\?\init.lua]]
            .. ';'
        package.cpath = package.cpath
            .. ';'
            .. luarocks_config.deploy_lib_dir
            .. '\\?.'
            .. luarocks_config.external_lib_extension
        -- 此处,还可以将 luarcoks bin 目录加入到 PATH
        vim.env.PATH = vim.env.PATH .. ';' .. luarocks_config.deploy_bin_dir
    end)
    if ok then
        enabled = true
    end
end

使用 nvim-plug 下载 rocks

可以在添加插件时,指定 type = 'rocks',比如:

plugins/mru.lua

return {
    'wsdjeg/mru.nvim',
    events = { 'UIEnter' },
    opts = {
        enable_cache = true,
        ignore_path_regexs = {
            '/.git/',
            '/nvim/runtime/doc/',
            '.mp3$',
            '.mp4$',
            '.png$',
            '.jpg$',
            '.exe$',
            'nvim-mru.json$',
            'tags$',
        },
        enable_logger = true,
        sort_by = 'lastenter',
    },
    type = 'rocks',
    desc = 'mru(most recently used) files',
}

参考以上方式添加插件后,nvim-plug 在安装插件时会自动调用 luarocks install plugin_name 这一命令。

luarocks 的限制

在实现完上述功能后,才发现 luarocks 这个包管理器似乎还有一些限制。比如:

  1. 不支持同时安装多个插件。

    因为 nvim-plug 是使用异步 job 调用外部命令的,因此支持多线程。 但是起初实现后发现,当同时执行多个 luarocks install 命令时, 只有第一个是成功的,后续的命令都有会报这一错误:Error: command ‘install’ requires exclusive write access

    解决的办法是为 luarocks 实现单独的 tasks 序列,逐一执行,这样的话插件的安装会非常慢。一个是单线程,一个是 16 线程 (max_processes = 16)

  2. 无法根据 plugSpec 获取的 rtp 目录位置

    一个最简单 plugSpec 比如 { 'wsdjeg/mru.nvim' }, 默认 type 是 git,我是可以获取到该插件默认的 runtimepath 值为 plug.config.bundle_dir .. '/' .. 'wsdjeg/mru.nvim', 此时就可以根据这个目录是否存在来判断插件是否已安装。

    但是,这样一个 plugSpec:

    return {
      'wsdjeg/mru.nvim',
      type = 'rocks',
    }
    

    将无法获取到默认的 runtimepath 目录位置,因为他的格式是

    D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/mru.nvim/1.4.0-1
    

    最后面这个版本号,除非是 plugSpec 内指定,否则是无法判断到默认的值的。

    最终的解决方案是分析 luarocks list 命令的输出内容,返回一个类似与这样的 lua table:

    return {
      ['mru.nvim'] = {
        rtp = 'D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/mru.nvim/1.4.0-1',
      },
      ['rooter.nvim'] = {
        rtp = 'D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/rooter.nvim/1.3.0-1',
      },
    }
    

将插件发布到 LuaRocks

这里主要使用到两个 Github actions:

  • googleapis/release-please-action
  • nvim-neorocks/luarocks-tag-release

使用 googleapis/release-please-action 来自动打 tag 并且新建 GitHub release,可以参考之前的文章《Github 仓库自动 release》

使用 nvim-neorocks/luarocks-tag-release GitHub action 自动将 tag 上传到 luarocks.org。

在仓库根目录新建文件 .github/workflows/luarocks.yml:

name: Push to Luarocks

on:
  push:
    tags: # Will upload to luarocks.org when a tag is pushed
      - "*"
  pull_request: # Will test a local install without uploading to luarocks.org
  workflow_dispatch:

jobs:
  luarocks-upload:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - name: LuaRocks Upload
        uses: nvim-neorocks/luarocks-tag-release@v7
        env:
          LUAROCKS_API_KEY: $

模块载入问题

neovim 中直接使用 rocks 似乎还有问题,dll 文件载入时会报错,估计跟 luarocks 的包编译方式有关

D:\wsdjeg\my-blog>luarocks list | rg file -A 2
luafilesystem
   1.8.0-1 (installed) - D:\Scoop\apps\luarocks\current\rocks\lib\luarocks\rocks-5.1

然后在 Neovim 内执行 :lua require('lfs') 时,报错:

E5108: Error executing lua error loading module 'lfs' from file 'D:\Scoop\apps\luarocks\current\rocks\lib\lua\5.1\lfs.dll':
        找不到指定的模块。

stack traceback:
        [C]: at 0x7ff83ac1bdb0
        [C]: in function 'require'
        [string ":lua"]:1: in main chunk

实际上这个 dll 文件是存在的:


D:\Scoop\apps\luarocks\current\rocks\lib\lua\5.1>ls
lfs.dll

排除问题

使用 scoop 安装 dependencies,

scoop install dependencies

打开 lfs.dll 文件,发现确实是依赖问题:

lfs.dll

在 lua51 的安装目录里:

D:\Scoop\apps\lua51\current>ls
Microsoft.VC80.CRT  install.json  lua5.1.dll.manifest  lua51.dll           manifest.json
bin2c5.1.exe        liblua5.1.a   lua5.1.exe           lua51.dll.manifest  wlua5.1.exe
include             lua5.1.dll    lua5.1.exe.manifest  luac5.1.exe         wlua5.1.exe.manifest

而 Neovim 中 :lua 调用的是:

D:\Scoop\apps\neovim\current\bin>ls
dbghelp.dll  lua51.dll  nvim.exe  platforms  win32yank.exe  xxd.exe

使用 scoop 安装 luajit:

scoop install luajit

看下 luajit 的目录结构

D:\Scoop\apps\luajit/..
  2.1.1762795099-1
  current ➛ 2.1.1762795099-1
    bin
       lua51.dll
       luajit
       luajit-2.1.1762795099.exe
       luajit.exe
    include/luajit-2.1
       lauxlib.h
       lua.h
       lua.hpp
       luaconf.h
       luajit.h
       lualib.h
    lib
    share
     install.json
     manifest.json

修改 D:\Scoop\apps\luarocks\current\config.lua 为:

lua_interpreter = "D:/Scoop/apps/luajit/current/bin/luajit.exe"
lua_version = "5.1"
rocks_trees = {
   "D:/Scoop/apps/luarocks/current/rocks"
}
variables = {
   LUA = "D:/Scoop/apps/luajit/current/bin/luajit.exe",
   LUA_BINDIR = "D:/Scoop/apps/luajit/current/bin",
   LUA_INCDIR = "D:/Scoop/apps/luajit/current/include/luajit-2.1",
   LUA_DIR = "D:/Scoop/apps/luajit/current/bin"
}

重新安装 luafilesystem:

luarocks install luafilesystem --force

此时再使用 Dependencies 查看 lfs.dll:

lfs-ok

此时在 Neovim 中执行 :=require('lfs') 就会看到:

{
  _COPYRIGHT = "Copyright (C) 2003-2017 Kepler Project",
  _DESCRIPTION = "LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution",
  _VERSION = "LuaFileSystem 1.8.0",
  attributes = <function 1>,
  chdir = <function 2>,
  currentdir = <function 3>,
  dir = <function 4>,
  link = <function 5>,
  lock = <function 6>,
  lock_dir = <function 7>,
  mkdir = <function 8>,
  rmdir = <function 9>,
  setmode = <function 10>,
  symlinkattributes = <function 11>,
  touch = <function 12>,
  unlock = <function 13>
}

Neovim 终端中使用

为了能在 Neovim 内置终端中使用 lua,luajit,luarocks 等,给 nvim-plug 增加这样一个 patch:

diff --git a/lua/plug/rocks/init.lua b/lua/plug/rocks/init.lua
index e336791..58f391d 100644
--- a/lua/plug/rocks/init.lua
+++ b/lua/plug/rocks/init.lua
@@ -71,6 +71,8 @@ function M.enable()
             .. luarocks_config.deploy_lib_dir
             .. '\\?.'
             .. luarocks_config.external_lib_extension
+        vim.env.LUA_PATH = package.path
+        vim.env.LUA_CPATH = package.cpath
     end)
     if ok then
         enabled = true

这样在 Neovim 内置终端内使用 lua 命令,或者 luajit 命令,就会自动读取这两个变量值。

D:\wsdjeg\my-blog>lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> print(require('lfs'))
table: 00000000004DCAC0
> 

D:\wsdjeg\my-blog>luajit
LuaJIT 2.1.1762795099 -- Copyright (C) 2005-2025 Mike Pall. https://luajit.org/
JIT: ON SSE3 SSE4.1 BMI2 fold cse dce fwd dse narrow loop abc sink fuse
> print(require('lfs'))
table: 0x01f72937bc70
> 
D:\wsdjeg\my-blog>echo print(require("lfs")) | nvim -l -  
table: 0x01dcba84a148

版权声明:本文为原创文章,遵循 署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)版权协议,转载请附上原文出处链接和本声明。


延生阅读

分享到:

评论