从一个配方到合入的 PR。精确规则见 XPackage V2;本章是路径。
流程
- 创建一个 Add XPackage issue:包名、一句话介绍、主页、许可证。
- 把模板复制为
pkgs/<首字母>/<name>.lua。 - 填写
package表和钩子(见下文)。 - 添加
tests/<首字母>/test_<name>.py(测试)。 - 在隔离的 home 中安装它,覆盖它声明的每个平台和架构。
- 发起 PR,并在 issue 中链接它。
package 表
package = {
spec = "2",
name = "demo",
description = "One sentence on what it is",
type = "package",
licenses = {"MIT"},
repo = "https://github.com/acme/demo",
archs = {"x86_64", "aarch64"},
status = "stable",
programs = {"demo"},
xvm_enable = true,
xpm = {
source = "https://github.com/acme/demo/releases/download/v${version}/demo-${os}-${arch_alias}.${ext}",
linux = {
["latest"] = { ref = "1.0.0" },
["1.0.0"] = {
arch_alias = { x86_64 = "amd64", aarch64 = "arm64" },
sha256 = { x86_64 = "<amd64-sha256>", aarch64 = "<arm64-sha256>" },
},
},
},
}
archs会与宿主比对,不匹配即失败:只声明测试过的架构。programs列出包放到 PATH 上的每个命令;站点和路由表都读取它。latest用ref指向一个真实版本,并且只向前移动。- 没有网络资源的版本(脚本、配置)写空表:
["0.0.1"] = {}。
资源的写法
保持 platform -> version 模型,按顺序选第一个适用的:
| 情形 | 写法 |
|---|---|
| xlings-res 上的官方资源 | xpm.source = "xlings-res",加每个架构的 sha256 |
| URL 规则统一的第三方 release | xpm.source = "<含 ${version} 的模板>",加每个架构的 sha256 |
| 各架构 URL 不规则 | 按架构分别写资源表,每项 { url, sha256 } |
| 单个特殊或历史版本 | 在该版本项写显式 url / 镜像表,覆盖 source |
| 旧配方 | "XLINGS_RES"、res = true、单个 URL、单个 hash —— 仍然兼容 |
每个架构都要有 sha256。没有权威 hash 时,先保留旧写法,等资源和它的 sidecar 都存在后 再迁移;不能发布没有 hash 的新官方二进制版本。
钩子
import("xim.libxpkg.pkginfo")
import("xim.libxpkg.xvm")
function install()
os.tryrm(pkginfo.install_dir())
os.mv(pkginfo.install_file():replace(".tar.gz", ""), pkginfo.install_dir())
return true
end
function config()
xvm.add("demo", { bindir = path.join(pkginfo.install_dir(), "bin") })
return true
end
function uninstall()
xvm.remove("demo")
return true
end
install()放置文件;没有它时,安装目录恰好是归档中的条目。config()注册命令、链接进 sysroot、声明环境变量 —— 每个 scope 运行一次,按 revision 记录。- 钩子只使用标准 Lua 和
xim.libxpkg.*:不调用宿主的包管理器,不改.bashrc,不直接 改PATH(隔离测试会让这些失败)。 - 配置类的包在
config()中写配置,保留用户已有的配置,不输出 token。
依赖
deps = {
runtime = { "xim:glibc", "xim:gtk3" },
build = { "xim:7zip@26.02" },
}
runtime会激活到用户的 workspace 并进入 RPATH 闭包;build只在install()运行时 存在,不在 PATH 上。- 只在
install()里用的工具(7zip、patchelf、cmake)是build依赖;用pkginfo.build_dep("7zip")获取 —— 裸名。 - 获取 runtime 依赖时,用声明时的坐标:
pkginfo.dep_install_dir("xim:glibc")。不要自己 拼载荷路径。 - 用分离的写法;不要在位置列表中混写
build(tests/test_deps_shape.py)。 - 自带共享库、靠
$ORIGIN找到它们的载荷(xPack、部分 Electron 和 Bun 应用)不要 声明 loader 依赖:声明了xim:glibc,elfpatch 就会替换它的DT_RPATH,自带的库全部 失联,而安装仍然报告成功。依据实测的DT_NEEDED闭包判断 (LD_TRACE_LOADED_OBJECTS=1 <bin>),并在测试中真正运行一次程序 ——--version看不出来。
保证包正确的规则
- 库载荷不携带编译器的运行时。 在 Windows 上即
vcruntime140*.dll、msvcp140*.dll、concrt140.dll、vccorlib140.dll及Microsoft.VC*.CRT目录;只有工具集包(msvc)带它们 (tests/test_no_compiler_runtime_in_payloads.py)。 - 目标 sysroot 不是宿主库。 交叉或裸机 sysroot(
picolibc-riscv)的头文件绝不进 SubOS 的usr/include,所有平台用同一个 sha256(它的字节与宿主无关),并且不设ci。 - config 的效果由
revision标识。 改变config()注册、链接、声明或写出的内容时, 提高版本项的revision,否则已经配置过的 scope 永远看不到变化。config()不把 scope 相关的数据写进共享的载荷。 - 载荷中不能有硬链接。 xlings 用
link(2)解出硬链接,Android 应用沙箱和 FAT 会拒绝 (payload-hard-links.yml)。打包时用tar --hard-dereference。 - 钩子不从环境猜测架构:2026.10.8.1 之前的客户端在钩子中没有
os.arch(),要从载荷 内容判断(对它带的 loader 用os.isfile)。
在本地测试
python3 .github/scripts/version-check.py --workspace .
pytest tests/<letter>/test_<name>.py -m "static or isolation"
安装使用隔离的 home,不用你自己的:
TMP_HOME="$(mktemp -d)"
XLINGS_HOME="$TMP_HOME" xlings config --add-xpkg pkgs/<letter>/<name>.lua
XLINGS_HOME="$TMP_HOME" xlings install <name>@<version> -y
XLINGS_HOME="$TMP_HOME" xlings remove <name> -y
涉及多个架构时,检查 x86_64 和 aarch64 上的解析结果;涉及镜像时,检查 GLOBAL 和 CN 的 实际响应及其 sha256。
PR
描述中写明:
- 包是什么,支持的平台和架构;
- 资源:来源、版本、镜像 release、sha256;
- install、config、uninstall 是否改变用户环境;
- 运行了什么:命令、pytest、隔离安装;
latest移动时,它指向的已验证版本。
提交遵循 <type>(<scope>): <description> —— feat(pkg): add foo 1.2.0、
fix(index): add missing aarch64 checksums。Linux、macOS、Windows 和索引检查都通过后合入。
职责边界
xlings 与 libxpkg 是配方、兼容规则和资源解析的唯一实现;本索引只声明数据和钩子。 配方不重新实现平台判断、资源服务器 URL 或模板展开。