LuaRocks 发布与 CI:从 rockspec 到自动化分发

全面掌握 LuaRocks 包发布流程:包目录结构、rockspec 编写规范、语义化版本与依赖管理、GitHub Actions 自动化测试与发布,以及命名空间与生态分发的最佳实践。

LuaRocks 与包生态

LuaRocks 是 Lua 事实上的包管理器,负责构建、安装、管理 Lua 模块及其依赖。与 Lua 模块与包管理:require、package 与 LuaRocks 关注的「如何使用 LuaRocks 安装模块」不同,本文聚焦「如何发布一个高质量的 Lua 包」。

一个 Lua 包从源码到被全球开发者 luarocks install,需要经历:合理的目录结构、规范的 rockspec、版本与依赖管理、自动化 CI,以及公开分发。这套流程正是 Lua 生态工程化的核心。

# 发布前的本地安装验证
luarocks make --local      # 从本地 rockspec 构建并安装到用户树
luarocks install           # 从服务器安装(用户视角)

包目录结构

一个规范的 LuaRocks 包遵循 src 布局约定:

my-awesome-lua/
├── src/                    # 源码(rockspec 会映射到 lua/ 路径)
│   └── awesome/
│       ├── init.lua
│       ├── core.lua
│       └── utils.lua
├── spec/                   # 测试(busted 等)
│   └── core_spec.lua
├── doc/
│   └── awesome.md
├── rockspec/               # rockspec 文件(或放根目录)
│   └── awesome-0.1.0-1.rockspec
├── LICENSE
├── README.md
└── .github/workflows/ci.yml

LuaRocks 通过 rockspec 的 build 字段把源文件安装到运行时的 lua/ 目录。目录结构直接影响可维护性与发布体验:

  • src/ 与 spec/ 分离,测试不污染发布包。
  • 模块命名与目录对应,awesome/core.lua → require("awesome.core")。
  • 根目录放 rockspec、LICENSE、README,与 GitHub 仓库结构保持一致。

rockspec 编写规范

rockspec 是 LuaRocks 的构建清单,本身是 Lua 文件,以 rockspec_format 开头。

基本字段

-- awesome-0.1.0-1.rockspec
rockspec_format = "3.0"

package = "awesome"
version = "0.1.0-1"

source = {
    url = "git+https://github.com/user/awesome-lua.git",
    tag = "v0.1.0",
}

description = {
    summary = "一个极简的 Lua 工具库",
    detailed = [[
        awesome 提供字符串处理、缓存与协程工具,
        全部代码零依赖,兼容 Lua 5.1 / 5.3 / 5.4 与 LuaJIT。
    ]],
    homepage = "https://github.com/user/awesome-lua",
    license = "MIT",
    issues_url = "https://github.com/user/awesome-lua/issues",
}

dependencies = {
    "lua >= 5.1",
}

build = {
    type = "builtin",
    modules = {
        ["awesome"] = "src/awesome/init.lua",
        ["awesome.core"] = "src/awesome/core.lua",
        ["awesome.utils"] = "src/awesome/utils.lua",
    },
    copy_directories = {
        "doc",
    },
}

构建方式

LuaRocks 支持三种构建类型,按复杂度递增:

类型说明适用场景
builtin纯 Lua 模块,只做文件复制绝大多数纯 Lua 包
make调用 Makefile需要编译或复杂安装步骤
cmake调用 CMake带 C 扩展的跨平台包

builtin 的 modules 字段是核心:映射「运行时模块名 → 源文件」。C 扩展则用 make 配合自定义构建脚本:

build = {
    type = "make",
    build_variables = {
        LIBNAME = "cawesome",
    },
    build_target = "cawesome.so",
    install_target = "cawesome.so",
    modules = {
        ["cawesome"] = {
            sources = { "src/cawesome.c" },
            libraries = { "m" },
        },
    },
}

依赖声明

依赖分三类:运行时 dependencies、构建时 build_dependencies、测试时 test_dependencies。正确分类让安装器不会装多余的包:

dependencies = {
    "lua >= 5.1, < 5.5",
    "luafilesystem >= 1.7",
}

build_dependencies = {
    "luarocks-build-make",
}

test_dependencies = {
    "busted >= 2.0",
}

版本管理与依赖策略

语义化版本

LuaRocks 版本由「包版本 + rockspec 修订号」组成:0.1.0-1 表示包版本 0.1.0、rockspec 修订第 1 次。发布遵循语义化版本(SemVer):

  • 补丁(0.1.1):修复,不破坏 API。
  • 次版本(0.2.0):向后兼容的新功能。
  • 主版本(1.0.0):破坏性变更。

每个发布都需要对应一个 git tag,如 v0.1.0。LuaRocks 服务器要求 rockspec 文件名与 package、version 一致。

版本约束

rockspec 的依赖约束控制安装时如何选择版本:

dependencies = {
    "luafilesystem >= 1.6",
    "lua-cjson ~> 2.1",      -- >= 2.1, < 2.2(次版本内)
    "copas ^2.0",             -- >= 2.0, < 3.0(主版本内)
}
约束符含义
>= x大于等于 x
~> x.y从 x.y 到下一个次版本前(含补丁)
^x.y从 x.y 到下一个主版本前
x.y(裸)精确匹配

约束过宽会增加兼容风险,过窄会减少可安装场景。对纯 Lua 包建议放宽到「最低要求 + 主版本锁定」。

本地验证与发布

发布前在本地完整走一遍安装流程:

# 1. 构建并安装到本地树
luarocks make --local

# 2. 运行测试(busted)
busted spec/

# 3. 生成 rockspec 校验
luarocks lint awesome-0.1.0-1.rockspec

# 4. 打包
luarocks pack awesome 0.1.0-1

# 5. 本地安装打包结果,验证依赖解析
luarocks install awesome-0.1.0-1.src.rock

luarocks lint 会检查 rockspec 语法与必填字段,是发布前的快速门禁。测试环节见 Lua 测试工程化指南:busted 框架与 BDD 实践。

发布到官方仓库需要先在 LuaRocks.org 创建账号并上传:

luarocks --api-key=$LUAROCKS_API_KEY upload awesome-0.1.0-1.rockspec

CI 自动化发布:GitHub Actions

把「测试 + 发布」放进 CI,可以让每次 tag 打版都自动完成质量门禁与分发。

测试矩阵

# .github/workflows/ci.yml
name: CI

on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - lua-version: "5.1"
            with-luajit: true
          - lua-version: "5.3"
          - lua-version: "5.4"
    steps:
      - uses: actions/checkout@v4
      - uses: leafo/gh-actions-lua@v10
        with:
          luaVersion: ${{ matrix.lua-version }}
      - uses: leafo/gh-actions-luarocks@v4
      - run: luarocks make --local
      - run: luarocks install --local busted
      - run: busted spec/

矩阵覆盖多 Lua 版本的价值:5.1 与 5.4 在整数语义、table.unpack、标准库差异上的坑(见 Lua 版本对比),只有在矩阵测试中才能稳定暴露。fail-fast: false 保证一个版本失败不中断其他版本。

自动发布

发布 Job 只在打 tag 时触发,测试通过后自动上传 LuaRocks:

  publish:
    needs: test
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: leafo/gh-actions-lua@v10
        with:
          luaVersion: "5.1"
      - uses: leafo/gh-actions-luarocks@v4
      - name: 生成 rockspec 并上传
        env:
          LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
        run: |
          # 根据 tag 版本生成 rockspec
          version="${GITHUB_REF_NAME#v}"
          sed "s/@VERSION@/$version/" rockspec/awesome.template.rockspec > awesome-$version-1.rockspec
          luarocks --api-key="$LUAROCKS_API_KEY" upload awesome-$version-1.rockspec --force

实践中常用「template rockspec」:awesome.template.rockspec 中把版本号写成 @VERSION@ 占位符,CI 依据 git tag 生成具体 rockspec。这样发版只做一件事:打 tag。

生态分发:命名空间与公开

发布到 LuaRocks 官方仓库后,包进入全球分发网络。工程化的分发还涉及:

  • 命名规范:模块名与包名一致,避免与既有包冲突;可在 LuaRocks 搜索确认名称占用。
  • 兼容性声明:在 README 与 rockspec 中声明支持的 Lua 版本与平台。
  • 变更日志:CHANGELOG 记录每次版本的行为变化,配合语义化版本让用户判断升级风险。
  • 维护策略:及时响应 issue,破坏性变更预留迁移窗口。
<!-- README 中的版本与依赖表 -->
| 版本 | Lua 5.1 | Lua 5.3 | Lua 5.4 | LuaJIT |
|------|---------|---------|---------|--------|
| 0.1.0 | ✓       | ✓       | ✓       | ✓      |
| 0.2.0 | ✓       | ✓       | ✓       | ✓      |

生态分发还有一个维度:私有分发。企业内部包可以自建 LuaRocks 服务器或用私有仓库地址,rockspec 的 source.url 指向私有 git。发布流程与公开包一致,只是访问控制不同。

常见问题(FAQ)

rockspec 与直接 git 安装有什么区别?

git 安装(git clone + 手动 package.path)适合快速开发;rockspec 提供依赖解析、版本管理、可重复安装与生态可见性。发布到 LuaRocks 后,用户 luarocks install awesome 一行完成,且版本可锁定。

为什么 upload 失败或找不到包?

常见原因:rockspec 文件名与 package + version 不一致;未登录或 API key 无效;未打对应 git tag(source.tag 不存在);LuaRocks 服务器同步延迟(上传后需要几分钟到十几分钟生效)。

builtin 构建的模块找不到?

检查 build.modules 的映射:模块名 awesome.core 应指向 src/awesome/core.lua。若源码里有 require("awesome.init") 而模块只注册了 awesome,会运行时找不到。建议模块名与文件一一对应,避免 init.lua 与目录名混用造成的歧义。

如何保证包在 LuaJIT 下也能用?

在 CI 矩阵中加入 LuaJIT(with-luajit: true)。注意 LuaJIT 基于 5.1,goto、位运算库与 64 位整数行为与 PUC Lua 不同,纯 Lua 包通常没问题,但涉及 C 扩展或整数边界时要专门回归。

依赖版本约束放太宽会怎样?

约束过宽(如 lua >= 5.1 不带上限)可能在未来 Lua 6.0 上出问题;约束过窄(如 lua == 5.4.4)会显著缩小用户群。最佳实践是声明「最低版本 + 主版本范围」,并在 CI 中覆盖主要版本做验证。

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「lua」更多文章

  1. Lua 与 AI/LLM:Agent 脚本、NPC 智能与动态内容
  2. Neovim 插件工程化:从架构到发布的完整指南
  3. Lua 测试工程化指南:busted 框架与 BDD 实践