用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/openxlings/xim-pkgindex --skill xpkg-creater命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | xpkg-creater |
| description | 在 xim-pkgindex 中创建/更新 xpkg 包(V2/兼容 V1),遵守 xlings SubOS 隔离规范,补齐多架构资源与测试,并在本地与测试集验证通过后再提交 PR。 |
用于在 xim-pkgindex 仓库中新增或维护 xpkg 包文件,确保满足:
spec = "2",历史 V1 继续兼容)install / config)详细安装命令、测试命令清单、相关链接见:
references/xlings-setup-and-links.mdreferences/testing-and-acceptance.md
开发/验证 xpkg 之前,先确保环境可用:
xlings(用于 xim/xlings/xvm 命令)xlings 命令在 shell 中可执行安装方式与快速命令见 references/xlings-setup-and-links.md。
一个 xpkg 文件由两部分组成:
package = { ... } 元数据域installed/build/install/config/uninstall,按需实现)至少保证:
spec = "2"(仅维护历史配方时可继续使用 "1")namedescriptiontype(常见:package/script/config/template)xpm(平台、版本、资源映射)常见推荐字段:
archs, status, categories, keywordsauthors/maintainers/licenses/repo/docs/homepagexvm_enable = true(需要 xvm 管理时)windows/linux/macosx/ubuntu/debian/...{"latest" = { ref = "x.y.z" }}{"x.y.z" = { url = "...", sha256 = "..." }}"XLINGS_RES"xpm.source:
source = "xlings-res":官方资源服务器,版本项提供每个架构的 sha256source = "https://.../${version}/...${arch}...":第三方 URL templateurl 覆盖默认 source。sha256 必须覆盖每个受支持架构;缺失时版本检查器必须 fail closed。ci = { mirror = true, update = true } 只声明是否加入官方自动化;扫描周期统一由
仓库 .github/xpkg-ci.yml 管理,不能在单个包中写入周期。ubuntu = { ref = "linux" }["0.0.1"] = {}官方二进制优先使用 xpm.source = "xlings-res",并为每个平台/架构写入权威 SHA256。
第三方 release 使用 URL template + per-arch SHA256;只有 URL 不规则时才展开 per-arch
resource map。显式版本 url 可以覆盖根级或平台级 source。
xpm = {
source = "xlings-res",
linux = {
["latest"] = { ref = "1.0.0" },
["1.0.0"] = {
sha256 = {
x86_64 = "<linux-x86_64-sha256>",
aarch64 = "<linux-aarch64-sha256>",
},
},
},
},
参考实现:docs/V2/xpackage-spec.md 与 pkgs/g/github-gh.lua
这里的
XLINGS_RES/ 镜像表解析的是软件包二进制,走资源服务器GLOBAL = github.com/xlings-res、CN = gitcode.com/xlings-res。这与「索引仓库本身」 的分发(索引即资源 / Y-asset)是同一套资源服务路径但不同资产,互不混淆。 索引机制全貌见docs/design/index-distribution.md(同步自 xlings v0.4.55 源码)。
当某个版本使用 xpm.source = "xlings-res"(历史写法为 "XLINGS_RES")时,该版本已经进入 xlings 多镜像资源服务链路。发布前必须同时满足:
https://github.com/xlings-res/<pkg> 与 https://gitcode.com/xlings-res/<pkg> 都存在同名 tag/release。.sha256 sidecar;索引版本项必须为每个受支持架构写入与 sidecar 一致的 SHA256。version-check.py --apply 缺少平台、架构、资产或 sidecar 时必须 fail closed,不得生成不完整条目。latest 仍指向应当作为最新的版本,不要因为补旧版本导致 latest 倒退。如果 GitHub RES 和 GitCode RES 任一侧缺资源、版本不一致、资产不一致,不能把该版本切到 "XLINGS_RES";先补齐镜像资源,再改包索引。
优先使用新版 API:
import("xim.libxpkg.pkginfo")import("xim.libxpkg.xvm")import("xim.libxpkg.system")(可选)import("xim.libxpkg.log")(可选)避免旧 API:
import("xim.base.runtime")import("common")import("platform")一般情况下,新增或维护 xpkg 只能使用三类能力:
package 元数据、xpm 描述和 lifecycle hooks。string/table/io/os.getenv/pcall/error 等)。xim.libxpkg.* API(例如 pkginfo/xvm/system/log/json)。不要默认引入 xmake 私有 runtime/API。除非某个既有包的兼容性约束已经证明必须使用,否则避免:
core.*、detect.*、runtime.*、xim.base.runtimecommon、platformpath.*、os.host()、is_host()、try { ... }、raise(...)测试也应默认锁定这条边界:import 只能来自 xim.libxpkg.*,路径、错误处理、文件 IO 优先用标准 Lua 或 libxpkg 可移植封装。
hook runtime 里没绑定的东西会静默毁掉安装。 已确认不可用的:
| 写法 | 现象 | 换成 |
|---|---|---|
os.exists(p) | attempt to call a nil value (field 'exists') | os.isdir(p) or os.isfile(p) |
os.arch() | 返回 nil / _RUNTIME.arch 为空 | 从 pkginfo.install_file() 推导 |
os.curdir() | attempt to call a nil value (field 'curdir') | os.cd 之后用一个已知目录回去(pkginfo.install_dir()) |
os.execv(...) | 同上 | system.exec / os.exec |
os.files(...) | 同上 | os.dirs 递归,或交给 xcopy / shell |
path.absolute(p) | 同上 | pkginfo.install_dir() 本来就是绝对路径 |
先 grep,再写。 上面五条都是同一个形状,而每一条在写下去之前都能用一条 命令排除:
grep -rn "os\.curdir" pkgs/ | wc -l # 0 → 别用整个 index 里零处使用的 sandbox API,基本可以认定它不在 runtime 里。
另外两条不在上表里,因为它们存在、只是行为和你以为的不一样:
os.cd(dir)之后system.exec不继承那个 cwd(os.exec才继承)。 照抄别的 recipe 的「cd 之后用相对路径」时,先看清它用的是哪一个 —— 命令可以拼得完全正确,然后找不到自己的文件。path.join在 Windows 上混用分隔符(保留已有的\、新加/)。"C:\Windows/System32/tar.exe"执行不了,msiexec也拒收。 凡是要交给 Windows 程序的路径都过一遍winpath()—— 包括可执行文件 本身的路径,不只是它的参数。
tar:PATH 会替你选,而两个 tar 能力不同runner 上同时存在两个 tar,它们不是同一个程序:
| 来源 | 读 zip? | C:\... 参数 | |
|---|---|---|---|
| bsdtar | %SystemRoot%\System32\tar.exe(Win10 1803+) | ✅ | 正常 |
| GNU tar | Git for Windows / MSYS2 | ❌ 完全不支持 | 当成 host:path,报 Cannot connect to C: |
.vsix / .zip 只有 bsdtar 读得了。而哪一个被选中取决于 PATH 顺序 ——
同一个 GitHub 镜像上,index 自己的 windows-test 拿到 bsdtar 通过,
mcpp 的 e2e 拿到 GNU tar 失败,recipe 一个字都没变。
所以:解 zip 时写绝对路径的 System32\tar.exe,不要写裸 tar。
钉死之后盘符问题也随之消失(那是 GNU tar 独有的),路径可以放心用绝对的。
--force-local 不是答案 —— GNU tar 认、bsdtar 拒收,是拿一个坏环境换另一个。
这不是概率判断:能用的东西早就有人用了。而代价是不对称的 —— 猜对省几秒, 猜错要等一轮 Windows CI(约 4 分钟)才知道,而且失败信息出现在 install hook 里、离你写的那一行有几层。
危险的地方在于表现形式:install hook 抛错之后,安装目录里往往只剩一个 .xpkg.lua、
没有 payload,而外层可能仍然打印 ✓ N package(s) installed。所以
install() 结尾一定要断言真正的产物存在(raise(...) 或 return os.isfile(exe)),
别只 return true;验收时也要真的去 ls 安装目录,不要只看安装命令的退出码。
注意 raise() 本身也不进汇总 —— 它不会让外层报失败;而 hook 里的 Lua 运行时错误会
浮出来([error] [pkg] failed: ...)。所以 raise 只是给读日志的人看的,不能当成保护。
xlings 在同名同版本已经装在另一个 namespace 下时,会整个跳过 install hook,并且照样
打印成功。 xim:foo@1.2.3 已装的情况下,每一次 local:foo@1.2.3 安装都是静默 no-op,
只写下 .xpkg.lua —— 看起来和 install hook 坏掉一模一样。测之前先清两边:
rm -rf ~/.xlings/data/xpkgs/{xim,local}-x-<pkg>/<version>
(这条是用 hook 里塞 io.writefile 探针确认的:日志文件根本没生成。曾因此把一个好端端的
os.mv 误判成 bug —— 实测 xlings 会重新解压、归档没了也会重新下载,os.mv 连装两次没问题。)
另外:往 local index 里放两个 package.name 相同的文件,会让整个 local repo 静默从搜索
路径消失(package 'local:foo' not found, searched repos: [xim, scode])。删掉重复文件即恢复。
对 type = "config" 且会写入用户工具配置的包(例如 Claude/LLM 配置):
package 元数据、hooks,以及必要的 xim.libxpkg.* import。core.*、detect.*、xim.base.runtime、runtime.*、is_host()、os.host()、path.*、try { ... }。error(...),不要使用 raise(...)。install() 保持轻量,默认 return true;实际配置写入放在 config()。log.info/log.warn/log.error 说明结果,敏感 token 不要明文打印。log.warn 提示继续复用旧 key 且不改 token;如果没有可复用 key,使用 log.error 后失败。install() 只负责安装动作本身:
pkginfo.install_file() 获取下载/解压后的输入路径pkginfo.install_dir() 作为目标安装目录os.tryrm(pkginfo.install_dir()) 再 os.mv(...)installed() 的含义是:payload 处于「这份 recipe 产出的状态」,
不是「这儿有个目录」。两者只在 recipe 变更时不一样,而那正是它要紧的时候。
2026-08-16 这一轮 MSVC 生态里,九层缺陷有四层是这一条:每一层的 windows-test 都是绿的,而包对使用者是坏的。
-- ❌ 目录在 ≠ 里面有东西
if os.isdir(path.join(d, "Lib", VER, "um", "x64")) then return true end
-- ✅ 查那个只有它才提供的文件
if not os.isfile(path.join(d, "Lib", VER, "um", "x64", "kernel32.lib")) then
return false
end
真实后果:少了带 kernel32.lib 的那个 MSI,目录照样存在(另外 341 个 um 库
落进去了),installed() 说 yes,而任何程序的链接都失败。
Windows 文件系统大小写不敏感,
os.isfile("kernel32.lib")照样匹配磁盘上的Kernel32.Lib。不要用"文件名大小写不确定"当作查目录的理由。
一个 payload 一条断言,而且那条断言的文件只有那个 payload 提供。 这样哪个 payload 没下来/没解开/没合并,报错就点名哪一个。
windows-sdk 的做法(8 个 payload → 7 条断言):
| 断言的文件 | 唯一提供它的 payload |
|---|---|
Include/<v>/ucrt/corecrt.h | Universal CRT |
Include/<v>/um/winnt.h | Store Apps Headers |
Include/<v>/shared/windef.h | Store Apps Headers OnecoreUap |
Lib/<v>/um/x64/kernel32.lib | Store Apps Libs |
Lib/<v>/um/x64/gdi32.lib | Desktop Libs x64 |
bin/<v>/x64/rc.exe / mt.exe | Store Apps Tools |
选 gdi32.lib 而不是随便一个库,是因为它只在 Desktop Libs 里 ——
那 365 个库和 Store Apps Libs 的 116 个完全不相交。抽样会漏,覆盖不会。
包的版本号不会因为 recipe 改了就变。所以对已经装了旧布局的机器,
installed() 是唯一能把它们拉回来的东西:
-- 新 recipe 不再安装 vctip.exe(它占着 payload 让整个包卸不掉)。
-- 只写"不装"对已经装了的机器毫无作用 —— 必须让 installed() 认出旧产物。
for _, d in ipairs(bin_arch_dirs()) do
if os.isfile(path.join(d, "vctip.exe")) then return false end
end
新增的文件靠断言"它在"就能发现;删掉的文件必须显式说"它不该在"。
local missing = {}
for _, f in ipairs(required_files()) do
if not os.isfile(f) then table.insert(missing, f) end
end
log.error("... 不在应该在的位置:\n " .. table.concat(missing, "\n "))
「wanted: <四个路径>」不算 —— 那是把清单再抄一遍,读的人还得自己比对。
config() 负责将该版本注册到 xvm(subos 隔离路由):
xvm.add("tool")xvm.add("tool", { bindir = ..., alias = ... })bindir一个 xvm 名字(程序名或 lib 名)可能被多个包提供:java 来自每个 JDK 发行版,
gcc 来自 gcc.lua 和 musl-gcc.lua,crt1.o/libc.so 来自 glibc.lua 和 musl.lua。
用裸版本号注册共享名有两种失败方式,长得完全不一样:
| 情况 | 结果 |
|---|---|
| 两个包注册同名同版本 | xvm 直接拒绝,第二个包整批 config 失败:another package already owns this exact name and version |
| 两个包注册同名不同版本 | 接受,名字变成双 owner。一次 xvm use 落到另一侧就静默改写共享 lib/ 里的符号链接 |
第二种更危险,因为安装当下一切正常。实测(musl 加入前):
crt1.o = {"active": "glibc-2.39", "installed": ["glibc-2.39", "musl-1.2.5"]}
crt1.o 一旦切到 musl 那侧,该 subos 里所有 glibc C 链接全部静默挂掉。
做法:共享名注册到 <version>-<flavor>,并在配方里把撞名集合单独列成一张表。
-- 和 glibc.lua 的 glibc_libs 求交集算出来的,不是眼估的
local SHARED_LIBS = { "crt1.o", "crti.o", "crtn.o", "Scrt1.o", "libc.a",
"libc.so", "libdl.a", "libm.a", "libpthread.a",
"librt.a", "libutil.a" }
local MUSL_ONLY_LIBS = { "ld-musl-x86_64.so.1", "rcrt1.o", "libcrypt.a",
"libresolv.a", "libxnet.a" }
local FLAVOR = "musl"
local function flavor_version() return pkginfo.version() .. "-" .. FLAVOR end
规则:
撞名集合要算,不要估。 拿对方配方里的注册表和自己 payload 里真实的目录求交集, 并用测试锁住这个集合 —— 上游改了文件集,测试要能发现。
撞名的必须带 flavor;不撞名的也一起带,这样整组能被同一次 xlings use 切换,
将来第三个包进来也是撞上约定而不是撞上一个恰好空着的版本号。
绑定根用 type = "group"。 根节点不对应任何可执行文件(没有 bin/musl),
留成默认的 program 类型会生成一个永远失败的 shim
(subos/*/bin/musl -> bin/xlings),self doctor 会把它报成 orphan
(openxlings/xlings#452)。
uninstall() 必须版本内收敛:xvm.remove(name, <stored key>)。
用裸名删会把对方包的注册一起删掉。
⚠️ xvm.add 会自己补索引命名空间前缀,xvm.remove 不会。
从 local: 或任何非 xim 的索引仓库安装时,version = "1.2.5-musl" 实际存成
local:1.2.5-musl;卸载时传裸键匹配不到,根节点删了、所有 lib 节点全留下。
CI 抓不到——posix-test.sh 的卸载后检查只看 bin/ 里残留的 shim,
而 lib 节点不产生 shim。照 glibc.lua 的 __version_key() 写:
function __stored_version()
local store = path.filename(path.directory(pkginfo.install_dir()))
local ns = store:match("^(.-)%-x%-") -- <data>/xpkgs/<ns>-x-<name>/<version>
local bare = flavor_version()
if ns and ns ~= "" and ns ~= "xim" then return ns .. ":" .. bare end
return bare
end
验收方式:装完 → 看 subos/<name>/.xlings.json 的 workspace → 卸载 → 再看一次,
必须回到 null。只跑 posix-test.sh 不足以说明卸载干净。
已有先例(新包照抄即可):jdk-temurin/corretto/zulu 的 25.0.4+7-temurin、
musl-gcc.lua 的 16.1.0-musl、musl.lua 的 1.2.5-musl。
os.exec("xvm add ...") / os.exec("xvm remove ...").bashrc / shell profileos.addenv("PATH") 或 os.setenv("PATH")apt install / brew install / pacman -S依赖请通过 xpm.<platform>.deps 声明;命令路由请通过 xvm shim 完成。
pkgs/<首字母>/<name>.lua 新增或修改包。tests/<首字母>/test_<name_with_underscore>.pyxim.libxpkg.*,只使用标准 Lua + xpkg 规范,不使用 path.*/os.host()/try {}/raise() 等 xmake 私有 API。type = "config" 包,额外锁定:install() 轻量,实际写入在 config()。详细步骤与命令见 references/testing-and-acceptance.md。
tests/ 测试文件。package = {
spec = "2",
name = "demo",
description = "demo package",
type = "package",
archs = {"x86_64"},
status = "stable",
categories = {"tools"},
keywords = {"demo"},
xvm_enable = true,
xpm = {
source = "xlings-res",
linux = {
["latest"] = { ref = "1.0.0" },
["1.0.0"] = {
sha256 = { x86_64 = "<sha256>", aarch64 = "<sha256>" },
},
},
},
}
import("xim.libxpkg.pkginfo")
import("xim.libxpkg.xvm")
function install()
os.tryrm(pkginfo.install_dir())
os.mv("demo", pkginfo.install_dir())
return true
end
function config()
xvm.add("demo")
return true
end
function uninstall()
xvm.remove("demo")
return true
end
头文件同理。 两个 libc 的 stdio.h/features.h 内容不同,散进共享
usr/include 后落地的那个会静默赢下整个 subos 的编译 —— 非 system libc 的头
要落到自己的命名空间(usr/include/musl)。