From 2fbbf8cd90cfe1c16c6f79bc86672fee56e7b2ff Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:14:47 +0900 Subject: [PATCH 01/31] feat: declare GitHub package updates and exercise the development Action --- .../2026-10-19-xpkgindex-update-design.md | 466 ++++++++++++++++++ .github/workflows/xpkgindex-update.yml | 172 +++++++ .github/xpkgindex-update.yml | 3 + pkgs/b/boost-ext.ut.lua | 7 + pkgs/c/cmp.lua | 7 + pkgs/c/compat.apriltag.lua | 7 + pkgs/c/compat.boost-align.lua | 7 + pkgs/c/compat.boost-any.lua | 7 + pkgs/c/compat.boost-asio.lua | 7 + pkgs/c/compat.boost-assert.lua | 7 + pkgs/c/compat.boost-beast.lua | 7 + pkgs/c/compat.boost-bind.lua | 7 + pkgs/c/compat.boost-circular-buffer.lua | 7 + pkgs/c/compat.boost-compat.lua | 7 + pkgs/c/compat.boost-concept-check.lua | 7 + pkgs/c/compat.boost-config.lua | 7 + pkgs/c/compat.boost-container-hash.lua | 7 + pkgs/c/compat.boost-container.lua | 7 + pkgs/c/compat.boost-core.lua | 7 + pkgs/c/compat.boost-describe.lua | 7 + pkgs/c/compat.boost-endian.lua | 7 + pkgs/c/compat.boost-intrusive.lua | 7 + pkgs/c/compat.boost-io.lua | 7 + pkgs/c/compat.boost-logic.lua | 7 + pkgs/c/compat.boost-move.lua | 7 + pkgs/c/compat.boost-mp11.lua | 7 + pkgs/c/compat.boost-optional.lua | 7 + pkgs/c/compat.boost-predef.lua | 7 + pkgs/c/compat.boost-preprocessor.lua | 7 + pkgs/c/compat.boost-smart-ptr.lua | 7 + pkgs/c/compat.boost-static-string.lua | 7 + pkgs/c/compat.boost-system.lua | 7 + pkgs/c/compat.boost-throw-exception.lua | 7 + pkgs/c/compat.boost-type-index.lua | 7 + pkgs/c/compat.boost-type-traits.lua | 7 + pkgs/c/compat.boost-utility.lua | 7 + pkgs/c/compat.boost-uuid.lua | 7 + pkgs/c/compat.boost-variant2.lua | 7 + pkgs/c/compat.boost-winapi.lua | 7 + pkgs/c/compat.brotli.lua | 7 + pkgs/c/compat.concurrentqueue.lua | 7 + pkgs/c/compat.etl.lua | 7 + pkgs/c/compat.fmt.lua | 7 + pkgs/c/compat.freetype.lua | 7 + pkgs/c/compat.ftxui.lua | 7 + pkgs/c/compat.glad.lua | 7 + pkgs/c/compat.gtest.lua | 7 + pkgs/c/compat.hiredis.lua | 7 + pkgs/c/compat.httplib.lua | 7 + pkgs/c/compat.imgui.lua | 7 + pkgs/c/compat.jwt-cpp.lua | 7 + pkgs/c/compat.libevent.lua | 7 + pkgs/c/compat.libmysqlclient.lua | 7 + pkgs/c/compat.libpng.lua | 7 + pkgs/c/compat.libuv.lua | 7 + pkgs/c/compat.md4c.lua | 7 + pkgs/c/compat.muduo.lua | 7 + pkgs/c/compat.nanodbc.lua | 7 + pkgs/c/compat.openmp.lua | 7 + pkgs/c/compat.pybind11.lua | 7 + pkgs/c/compat.quickjs-ng.lua | 7 + pkgs/c/compat.rapidcsv.lua | 7 + pkgs/c/compat.recastnavigation.lua | 7 + pkgs/c/compat.redis-plus-plus.lua | 7 + pkgs/c/compat.richdem.lua | 7 + pkgs/c/compat.safeint.lua | 7 + pkgs/c/compat.sdl3.lua | 7 + pkgs/c/compat.soci.lua | 7 + pkgs/c/compat.spdlog.lua | 7 + pkgs/c/compat.tray.lua | 7 + pkgs/c/compat.wamr.lua | 7 + pkgs/c/compat.websocket.lua | 7 + pkgs/c/compat.yyjson.lua | 7 + pkgs/e/compat.eui-neo.lua | 7 + pkgs/f/fmtlib.fmt.lua | 7 + pkgs/g/ggml-org.llamacpp.lua | 7 + pkgs/g/gzj-creator.galay.lua | 7 + pkgs/l/llamacpp.lua | 7 + pkgs/m/mpusz.mp-units.lua | 7 + pkgs/n/neargye.magic_enum.lua | 7 + pkgs/o/odygrd.quill.lua | 7 + pkgs/r/rules-cuda.lua | 7 + pkgs/t/taskflow.taskflow.lua | 7 + pkgs/t/tensorvia-cpu.lua | 7 + pkgs/y/yizhinailong.mcr.lua | 7 + tests/autoupdate/ci.py | 145 ++++++ 86 files changed, 1360 insertions(+) create mode 100644 .agents/docs/2026-10-19-xpkgindex-update-design.md create mode 100644 .github/workflows/xpkgindex-update.yml create mode 100644 .github/xpkgindex-update.yml create mode 100644 tests/autoupdate/ci.py diff --git a/.agents/docs/2026-10-19-xpkgindex-update-design.md b/.agents/docs/2026-10-19-xpkgindex-update-design.md new file mode 100644 index 00000000..a2f9606e --- /dev/null +++ b/.agents/docs/2026-10-19-xpkgindex-update-design.md @@ -0,0 +1,466 @@ +# xpkgindex-update:两个索引共用的声明式版本更新工具 + +日期:2026-10-19 +状态:实现联调中。工具开发 PR:openxlings/xpkgindex-update#1;mcpp 为首个 draft CI 接入仓库。 + +## 1. 目标与结论 + +项目使用 `openxlings/xpkgindex-update`;CLI 命名为 `xpkgindex-update`,Action 使用 +`openxlings/xpkgindex-update@v1`。它同时服务 `openxlings/xim-pkgindex` 与 +`mcpplibs/mcpp-index`,只做现有资源的版本发现、描述符更新和 PR 提交。 + +核心约定: + +- 包通过 `package.extensions.autoupdate` 显式参与;安装时忽略该扩展。 +- 工具只追加不存在的新版本;已有版本块逐字保留,包括 URL、镜像、SHA256、配置与注释。 +- 禁止 replace、覆盖、删除、重排或自动回填已有版本;只开 PR,人工决定是否合并。 +- 一个公共实现,CLI 与 Action 调用相同逻辑;两个索引用内部 adapter 处理结构差异。 +- 首版不自动 mirror,不上传、不覆盖镜像资产;新版本可以直接引用 GitHub,旧版本的镜像不动。 +- 候选版本必须得到实际验证,不能把仍然测试旧版本的绿色 CI 当成升级验证。 + +本设计承接 [mcpp-index #541](https://github.com/mcpplibs/mcpp-index/issues/541),参考 +xim-pkgindex 已有的 `package.ci`、中央扫描、版本添加和自动 PR 实现。 + +## 2. 已核对的现状 + +本次分析核对的远端版本: + +- mcpp-index:`981a4f228b1ee75251dbd14748a80d5adfc2191c`。 +- xim-pkgindex:`14c71d455db7ab623658bae454773d1c7fafb500`。 + +xim 已有: + +- [中央策略](https://github.com/openxlings/xim-pkgindex/blob/14c71d455db7ab623658bae454773d1c7fafb500/.github/xpkg-ci.yml) + 与 [扫描工具](https://github.com/openxlings/xim-pkgindex/blob/14c71d455db7ab623658bae454773d1c7fafb500/tools/xpkg_ci.py)。 +- [版本更新器](https://github.com/openxlings/xim-pkgindex/blob/14c71d455db7ab623658bae454773d1c7fafb500/.github/scripts/version-check.py) + 与 [自动 PR workflow](https://github.com/openxlings/xim-pkgindex/blob/14c71d455db7ab623658bae454773d1c7fafb500/.github/workflows/version-bump.yml)。 + +mcpp 的两个额外约束: + +- [validate.yml](https://github.com/mcpplibs/mcpp-index/blob/981a4f228b1ee75251dbd14748a80d5adfc2191c/.github/workflows/validate.yml) + 在 PR 阶段实际下载 CN 资源并核对 SHA256,引用镜像前镜像就必须可用。 +- [zlib 示例](https://github.com/mcpplibs/mcpp-index/blob/981a4f228b1ee75251dbd14748a80d5adfc2191c/tests/examples/zlib/mcpp.toml) + 固定依赖 `1.3.2`。添加新版本而不调整验证依赖,不能证明新版本可用。 + +因此复用 xim 的职责划分与测试经验,不直接复制现有调度和文本修改实现。 + +2026-10-19 对本地 checkout 的只读词法盘点发现,61 个描述符至少含一处字符串形式的直接 +GitHub URL。这不是远端最终接入数量:其中包含历史条目、混合镜像、commit 快照及特殊 tag。 +实施时必须在最新 mcpp-index 基线上重新生成逐包清单,不能把“61”当成已确认覆盖数。 + +## 3. 首版范围 + +### 3.1 支持 + +- 优先扫描 GitHub 已发布的 stable releases,排除 draft、prerelease;无匹配 release 的 + tag-only 仓库可查询 tags,按声明模板与版本语法筛选。tag 不能提供稳定性标记,报告标注发现来源。 +- 版本是至少两段的点分非负整数,例如 `1.2`、`1.2.3`、`2026.10.19.1`。 +- tag 由固定前缀、`${version}`、固定后缀组成,例如 `v${version}`。 +- 按整数分段比较版本,缺少的尾段按零比较;等价但拼写不同的候选视为歧义并报错。 +- 下载 URL 是显式模板;资源已经发布;每个安装资源使用单个字符串 SHA256。 +- 一个包已声明的平台整体更新;不同平台可使用不同 URL。 +- URL 是直连字符串,或已有 GLOBAL/CN 两个镜像的表。 +- mcpp-index 为首个真实接入仓库;目标是为直接使用 GitHub 资源的包全部补齐更新声明, + 并为每一个目标给出实际扫描结果,不用“只选容易的包”代替完整覆盖。 +- mcpp adapter 处理显式平台版本条目;xim adapter 同时处理已有 `latest.ref`。 +- xim 已有 `xpm.source` 时,仅在其运行时展开结果与更新模板一致时写入对应新版本哈希。 + +### 3.2 不支持 + +- commit、任意 tag、预发布版本、`b12345`、发行版后缀等非数字点分版本。 +- 自动回填历史 release、清理旧版本、升级依赖约束、批量联动多个包。 +- 架构分发表、per-arch SHA256、`XLINGS_RES`/`res_versioned` 等额外资源形态。 +- 需要修改 shim、sources、generated_files、安装 hook 或 build.mcpp 的更新。 +- 镜像上传、资源重打包、生成绑定、自建二进制、自动合并。 + +这些限制不阻止公共工具后续扩展,但首版不以增加 provider、脚本 hook 或复杂 DSL 解决例外。 +对于首个 mcpp 覆盖目标中发现的例外,必须逐包列出并补齐设计或实现,不能静默省略。 +commit 快照缺少版本到新 commit 的规则时,只能报告需要决策,不能擅自跟随默认分支。 +xim 的复杂包可以暂留原有工具,不要求两仓同时迁完。 + +## 4. 包字段:extensions.autoupdate + +采用复数且拼写为 `extensions`。它是维护扩展的容器,不参与运行时安装解析。 + +最小配置: + +```lua +package = { + repo = "https://github.com/madler/zlib", + + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/madler/zlib/archive/refs/tags/${tag}.tar.gz", + }, + }, + + xpm = { + -- 已发布条目仍然是安装器读取的事实来源。 + }, +} +``` + +首版只有 `tag`、`url` 两个字段,均必填;工具对 autoupdate 内的拼写错误做 lint。 +其他工具的 extensions 子字段与安装器不认识的字段仍按现有规则忽略,不扩大严格检查范围。 + +| 字段 | 含义 | +|---|---| +| `tag` | 一个且仅一个 `${version}`;其余字符按字面量匹配,不是正则或 Lua pattern | +| `url` | 所有已有平台共用的资源模板,或平台到资源模板的映射 | + +允许的占位符只有 `${version}`、`${tag}`。不认识的占位符报错。 +`${tag}` 保留 API 返回的原始拼写,`${version}` 是从 tag 提取的版本。 +默认查询 `package.repo`,首版不提供 provider/repo 覆盖字段。 + +不同平台的写法: + +```lua +extensions = { + autoupdate = { + tag = "v${version}", + url = { + linux = "https://example.org/${tag}/foo-linux.tar.gz", + macosx = "https://example.org/${tag}/foo-macos.tar.gz", + windows = "https://example.org/${tag}/foo-windows.zip", + }, + }, +} +``` + +平台值也可以是 `{ GLOBAL = "...", CN = "..." }`;所有平台共用的 GLOBAL/CN 表也可直接 +作为 `url`。解析器按键集合区分平台表与镜像表,混合写法拒绝。 +GLOBAL/CN 必须下载后哈希一致,工具不创建镜像。 + +不使用 `enabled`:无字段即手工维护;存在合法字段即参与。 +不使用 `mode`、`prerelease`、`interval`:首版固定 add、stable,调度留在仓库 workflow。 +配置不合法或形态不支持是可见错误,不伪装为未参与。 + +### 4.1 已确认的兼容性约定 + +维护者已确认其他消费者会忽略不认识的字段,extensions 不影响现有安装行为。 +采用这一约定直接设计与接入,不把修改安装器 schema 或提升客户端版本作为前置任务。 +保留解析回归 fixture,确认新增元数据前后既有版本的解析结果一致。 +不在 `mcpp` 或 `xpm` 安装段中新增维护工具专用字段。 + +## 5. 工具、Action 与仓库配置 + +### 5.1 CLI + +```bash +xpkgindex-update check --output plan.json +xpkgindex-update apply --plan plan.json --package compat:zlib +xpkgindex-update propose --plan plan.json +``` + +- `check`:本地解析、发现候选、下载校验,输出完整计划;不改描述符、不创建 PR。 +- `apply`:消费已确定计划,在本地生成最小补丁,便于维护者复现;不访问 GitHub 写接口。 +- `propose`:消费计划,按包生成补丁与分支并开 PR;不重新查询 latest。 +- 可用 `--package` 缩小范围;身份使用 namespace + name,缺省命名空间使用索引的既定规则。 + +### 5.2 Action + +同仓库根目录提供 composite Action,安装固定版本的 Python 工具并调用同一个 CLI。 +不维护第二套 Action 业务逻辑;首版不要求额外运行服务或数据库。 + +拟议调用: + +```yaml +on: + schedule: + - cron: "17 2 * * *" + workflow_dispatch: + +concurrency: + group: xpkgindex-update + cancel-in-progress: false + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: openxlings/xpkgindex-update@v1 + with: + mode: propose + token: ${{ secrets.UPDATE_TOKEN }} +``` + +这是正式发布后的接口草案,Action 尚不存在。开发联调使用开发分支的完整 commit SHA, +不提前发布或移动 v1;composite Action 从自身 GITHUB_ACTION_PATH 执行该提交的源码, +不能安装一个与开发提交无关的已发布 PyPI 版本。 +输入只有 `mode`(check/propose)、`token`(propose 必需)、可选 `package`。 +默认从当前 workspace 读取配置,上传计划和汇总到 workflow artifact/summary。 +`mode: propose` 内部先 check,再消费同一份计划;手动 CLI 可以拆开执行。 + +### 5.3 索引级配置 + +```yaml +# .github/xpkgindex-update.yml +version: 1 +adapter: mcpp-index # 或 xim-pkgindex;显式指定,不猜仓库名 +max_prs: 3 +``` + +首版只暴露这三个字段。每日调度直接写 workflow,不引入第二个 cron、包扫描周期或共享扫描状态。 +请求预算、最大下载量、超时、有限重试作为工具的固定保护值,在报告里公开。 +同一上游仓库只查一次,同一 URL 只下载一次;缓存失效不改变结果。 +选取待创建 PR 的包使用确定性顺序;已有待处理 PR 的包不占新 PR 名额。 + +## 6. 一条链路与最小计划 + +```text +读取指定测试基线或默认分支的合法 opt-in 包 + → 查询 GitHub stable releases 并按 tag 模板过滤 + → 选择严格高于当前版本的最高候选 + → 锁定原始 tag、commit 与资源 URL + → 下载、校验归档、计算 SHA256;必要时验证 CN + → 生成 plan + → adapter 原子生成补丁 + → 开发联调创建 draft PR;正式更新创建普通 PR + → 索引原有 CI 验证候选版本 + → 人工 review / merge +``` + +候选发现不能仅依赖 GitHub `releases/latest`。release/tag 查询均有界分页,并在无法确认候选时明确报告 +`discovery-incomplete`;不能用截断结果宣称已是最新。无匹配 tag 时同样给出明确诊断。 + +计划只包含必要证据: + +```json +{ + "format": 1, + "base_commit": "", + "packages": [{ + "id": "compat:zlib", + "path": "pkgs/c/compat.zlib.lua", + "current": "1.3.2", + "candidate": "1.3.3", + "tag": "v1.3.3", + "upstream_commit": "", + "assets": [{ + "platforms": ["linux", "macosx", "windows"], + "url": "https://example.org/v1.3.3.tar.gz", + "size": 123456, + "sha256": "<64 hex>" + }] + }] +} +``` + +GLOBAL/CN 情况记录两条实际 URL 和各自验证结果,不只记录展开模板。 +计划不可提供任意写入路径:apply 必须重新解析身份、确认路径在 pkgs 内,并检查基线一致。 +下载内容必须与计划哈希一致;tag 移动、资源内容变化或目标已更新时拒绝旧计划并要求重查。 +首版不创建额外的 release manifest 发布系统;计划作为 PR 附件/Actions artifact 即可。 + +## 7. 两个 adapter 的职责 + +### 7.1 公共规则 + +- 先验证所有目标平台、资源及改动位置,再一次性写文件;失败不能留下半个包的更新。 +- 平台集合以当前描述符为准;url 平台映射必须完整覆盖,禁止只更新 Linux。 +- 当前版本从已有合法版本条目确定;有 `latest.ref` 时要求其指向存在的条目且各平台一致。 +- 无 latest 时按数字点分规则确定最高版本;平台版本集合分歧则拒绝自动处理。 +- 旧版本块必须逐字不变;同版本存在时跳过,不下载后替换其 URL、哈希或 mirror。 +- 候选必须严格高于各平台当前最高版本;已有版本不自动补镜像、不修复或补齐缺失平台。 +- 候选仅存在于部分平台时报告 existing-version-conflict,交由人工处理,避免变相覆盖。 +- 既有版本块保存源码摘要;生成补丁后比较摘要与块顺序,并核对语法树仅新增目标版本。 +- 不修改 mcpp 构建字段、依赖、安装 hook、README 或工具链 pin。 +- 描述符使用能识别 Lua 字符串、长字符串和注释的语法工具读取并按源码范围插入。 + 首版仅自动编辑可静态确定的字面量结构;动态拼接、循环生成等写法报告不支持。 +- 不执行 Lua 安装脚本;不把未知复杂结构猜成可更新结构。 + +### 7.2 mcpp-index + +为各平台添加完整版本块:版本 key、显式 URL、SHA256。 +已有版本的 GLOBAL/CN 路由完全保留。新版本使用 extensions.autoupdate.url 声明的来源, +可以为 GitHub 直连字符串;不能通过复制旧版本模板误带尚不存在的 CN URL。 +禁止整表重新序列化;插入新块不能重写、重排旧版本。 +复用现有 lint、跨平台检查、镜像检查和受影响示例选择。 + +### 7.3 xim-pkgindex + +为各平台添加新版本块。latest.ref 是别名,不是版本块;是否允许工具更新这个别名留待第二轮 +review 决定,在决定前首版 mcpp 接入不依赖任何别名改写,xim apply 暂不启用。 +已有 source 模板时先证明运行时展开 URL 与计划相同,再遵循原形态写入哈希;禁止悄悄切换来源。 +首版不覆盖原更新器支持的全部历史资源形态;不支持的包保留旧工具且不得双重调度。 + +## 8. 验证候选版本:交给索引 CI,但必须补齐 + +工具不增加每包 `verify_member`,不执行描述符声明的 shell hook。 +验证入口继续由各仓现有测试组织维护。 + +mcpp 接入前增加 CI 测试辅助步骤: + +1. 比较 PR 与基线,识别新增的包版本。 +2. 选择能覆盖该包的既有示例,在临时 workspace 中将对应直接依赖固定为候选版本。 +3. 在该包支持的既有平台/工具链矩阵中编译、运行;保留正常 lint 与回归测试。 +4. 检查解析结果确认候选实际被选中;不能仅根据命令退出码判断覆盖成功。 +5. 无覆盖入口、依赖约束冲突或版本未被选中时,候选验证失败,不伪造通过。 + +xim 接入时确认包测试实际安装 `${candidate}`,不误测已安装版本或旧 latest。 +对全部 GitHub 直连目标补齐声明,测试入口缺失作为明确的 CI 覆盖缺口处理,不能偷偷漏掉包。 +新增元数据但没有真实新版本时,运行现有版本回归,并另外选择真实更新或历史基线进行候选路径测试。 +新版本验证通过是合并条件,不要求开 PR 前 +在 Action 所在的 Ubuntu runner 上完成所有平台测试。 + +## 9. PR、失败与凭证 + +### 9.1 PR + +- 默认 base 为当前索引默认分支;分支按规范化包身份和候选版本确定,避免命名空间碰撞。 +- 每个包最多一个打开的自动更新 PR;已有 PR 时报告 pending,不连续堆积多个版本 PR。 +- push 成功但 PR 创建失败时,下次补建 PR;不能仅凭分支存在就永久跳过。 +- 人工关闭且未合并的同版本 PR 不自动重开,报告 dismissed;可手动明确重试。 +- 正式更新 PR 只提交目标包文件,附原始 tag、commit、下载 URL、哈希、旧/新版本及验证入口说明。 +- 首次接入 draft PR 可以包含全部目标包的扩展声明、仓库配置及 CI 集成;与正常逐包更新 PR 区分。 +- 不自动合并;分支保护要求的 CI 和人工 review 正常生效。 + +### 9.2 状态 + +| 状态 | 含义 | 最终任务结果 | +|---|---|---| +| up-to-date | 无更高的匹配版本 | 成功 | +| update-ready | 新版本资源已完整验证 | check 成功,propose 创建 PR | +| pending / dismissed | 已有 PR / 人工关闭过该版本 | 成功并列明 | +| waiting-assets | 已发现版本,但上游或 CN 资产尚未发布 | 成功并显著列明,下次重试 | +| error | 无效声明、API 故障、哈希冲突、验证或提交失败等 | 处理其余包后最终非零 | + +只有明确的资产不存在/尚未发布归为 waiting-assets;API 403、限流、超时、下载中断不可 +伪装成等待资源。长期等待在每次 summary 中持续列出;首版不自动开 issue,避免增加写权限和噪声。 +无 autoupdate 的包完全不访问上游,不作为错误。 + +### 9.3 凭证 + +建议使用 GitHub App installation token,已有 PAT 可过渡。读取扫描与写分支/PR 分阶段, +token 仅交给所需 GitHub 请求及 git 认证;不传给任意下载域名或安装测试。 +下载仅允许公开 HTTPS 资源,拒绝私网目标和携带认证信息的 URL,限制重定向与响应大小。 +正式动作在默认分支可信 workflow 运行;开发期用维护者控制的同仓 draft PR 和固定工具提交。 +draft 的 pull_request CI 运行 check/apply 验证,不使用写凭证递归创建 PR。 +第一次 draft PR 由维护者或已有授权的提交动作创建;不用 pull_request_target 执行 PR 代码。 + +当前 GitHub 文档说明:GITHUB_TOKEN 创建/更新的相关 PR 事件可以产生等待人工批准的 CI; +GitHub App/PAT 可使其自动运行。不能沿用“GITHUB_TOKEN 创建 PR 绝不触发 CI”的绝对表述。 +参见 [GitHub workflow 触发规则](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow#triggering-a-workflow-from-a-workflow)。 + +## 10. 实现与迁移 + +拟议项目结构: + +```text +action.yml +src/xpkgindex_update/ + cli.py + github.py + manifest.py + lua_edit.py + pull_request.py + adapters/ + mcpp_index.py + xim_pkgindex.py +tests/ +``` + +不先拆多个服务、provider 插件系统或单独公共库。Python 包、CLI、Action 在一个仓库发版。 + +### 10.1 首个真实测试:mcpp-index draft PR + +1. 创建 openxlings/xpkgindex-update 项目,在 `dev/mcpp-index-integration` 分支实现工具和 Action, + 包括离线 fixture;开发阶段不发布 v1。 +2. 在 mcpp-index 建 `test/xpkgindex-update` 分支,基于最新默认分支盘点 pkgs 中安装资源。 + 覆盖清单至少包括包身份、文件、当前版本、直接 URL、上游仓库、tag 模板、测试入口、扫描结果。 +3. 为所有直接使用 GitHub 资源的目标包追加 extensions.autoupdate;只添加扩展声明,所有已有 + 版本块逐字保留。同包有历史镜像也不排除;新版本来源可明确声明为 GitHub 直连。 +4. 建立 draft PR,加入指向工具开发分支完整 SHA 的 check workflow,并接上候选版本 CI。 + workflow 必须显式匹配包文件、工具配置和集成 workflow 的路径;不能依赖只跑旧 validate 的偶然触发。 +5. draft CI 先跑全量 check、元数据 lint、旧版本不变检查和现有回归。draft 状态本身不保证 + 现有 workflow 会运行;检查事件类型、draft 条件和路径过滤,确保所需 jobs 实际执行。 +6. 对实际发现的新版本,以 plan 在临时 workspace 运行 apply、候选依赖固定及构建测试, + 并输出真实 patch 和版本解析证据。可拆为逐包 CI jobs,失败不掩盖其余目标结果。 +7. 若当前全量已最新,使用选定真实历史 release 的隔离 fixture 或历史索引基线复现追加流程。 + 不修改当前 PR 的真实旧版本、不伪造远端 release、不降级已发布版本来制造测试。 +8. 用单独的维护者触发写流程创建一个 draft 更新 PR,验证 propose 的 push、开 PR 与重试。 + 该端到端测试仅操作测试分支/draft PR,不触及 main,不启用每天批量提交。 +9. 输出逐包覆盖结果、所有例外及 CI 链接;全量声明与例外规则完成 review、候选与旧版本 + 回归通过后,再把 draft 转正式,发布工具稳定版本并启用定时 propose。 + +全量目标的识别以 xpm 安装资源中的 GitHub URL 为准,不把 package.repo、注释或其他 +文档链接算作下载来源。只要有现存直接 GitHub 版本即列入盘点;历史直连、当前镜像混合也 +必须列出。扩展的候选来源由逐包核实确定,不从任意一条历史 URL 盲猜。 +确无可更新规则的快照等也列入全量结果,明确作为待决项,不虚写无效配置冒充完成。 + +### 10.2 后续接入 + +稳定后再迁 xim;不要求首个 mcpp draft PR 同时改 xim。 +CN 资源可在以后加入“只校验已存在资源”的路径,始终不自动生产镜像。 + +xim 按包迁移:支持的包从 ci.update/url_template 迁到 extensions.autoupdate;同一次变更 +关闭该包旧更新入口,ci.mirror 等现有独立镜像行为暂时保持。不是要求所有 CI 元数据立即搬家。 +不为了统一字段一次性重写两个索引的安装 URL 结构。 + +最低测试集覆盖:模板与版本比较、stable 筛选、平台完整性、相同 URL 去重、CN 哈希冲突、 +Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本可解析,以及 push/PR 半失败恢复。 + +## 11. 多角度自审结果 + +本节是对修订方案的设计审查,不是已经完成的工具运行或线上 CI 验证。 + +| 角度 | 发现的问题 | 修订措施 / 剩余决定 | +|---|---|---| +| 已发布版本完整性 | 原版“默认 add”仍给覆盖/修复旧条目留空间 | 改为强制只追加,旧块逐字摘要与顺序检查;已有候选冲突交人工 | +| 镜像兼容性 | 强制新版本沿用旧 CN 路由会等待不存在的镜像 | 旧块不动,新块按声明使用 GitHub 直连;不创建或覆盖镜像 | +| 需求覆盖 | 仅少量数字 stable releases 无法覆盖全部 GitHub 直连目标 | 完整盘点逐包覆盖,增加 tag-only 发现;快照和特殊 tag 必须显式解决或列为待决 | +| 更新发现 | Boost 子仓等可能只有 tags;只看 releases 会漏包 | fallback tags,并公开来源和 stable 判断限制;特殊编码不猜 | +| 字段兼容 | 原 schema 前置任务超出维护者已确认范围 | 接受未知字段忽略约定,仅做解析回归,不另加安装器迁移 | +| CI 有效性 | 追加条目但依旧测试旧版本,容易假绿 | 临时 workspace 固定候选并核对解析版本,缺覆盖明确失败 | +| 开发版本一致性 | Action 可能装了 PyPI 旧版本;draft CI 可能未触发 | 固定开发提交,从 Action 自身路径运行源码;显式检查实际执行的 jobs | +| 全量提交成本 | 一次打开很多更新 PR 或一个失败中断全部 | 先一份接入 draft,全量只读/临时 apply;正式阶段才启用 max_prs | +| PR 幂等与凭证 | draft CI 自动开 PR 可递归;分支存在不等于 PR 存在 | draft CI 只读,写测试维护者触发;补建半失败 PR | +| xim 严格追加 | 更新 latest.ref 属于改既有条目,与字面“只追加”存在边界 | 不默认豁免;mcpp 先测,xim 是否允许移动别名单独确认 | + +### 11.1 尚未解决、不得隐瞒的范围冲突 + +本地盘点中存在以下真实例外,实施前必须逐包核实远端最新状态: + +- `compat.glad`、`compat.tray` 使用 commit 归档;模板无法自行决定“下一个版本”。 +- `compat.freetype` 使用 `VER-2-13-3` 一类 tag,索引版本为点分形式;现有 tag 模板缺少 + 分隔符转换语义,需要一个最小规则或专门适配,不能直接套 `v${version}`。 +- `ggml-org.llamacpp` 的历史直接资源包含 b 编号 tag,且存在历史镜像;不能覆盖历史条目, + 也不能把它误报成标准数字 release。 +- Boost 多个子包可能有固定依赖约束。可以分别追加候选,但构建若要求依赖同步,应报告失败, + 不能为了 CI 绿偷偷改既有条目或包依赖。是否引入成组更新另作决定。 + +这里不新增 enabled=false 等字段掩盖“全增加”目标。推荐先做完整盘点与接入 draft, +在同一轮 review 明确这些例外的更新规则;所有具有明确规则的目标都必须完成声明和 CI 覆盖。 + +## 12. 需要维护者 review 的决策 + +| 编号 | 建议决定 | 需要确认的细节与代价 | +|---|---|---| +| R1 | 项目/Action 为 openxlings/xpkgindex-update | 开发分支 dev/mcpp-index-integration;mcpp draft CI 固定完整开发提交,不提前发布 v1 | +| R2 | package.extensions.autoupdate 已确认 | 消费者忽略未知字段;不再把安装器 schema 修改当作前置任务 | +| R3 | tag + url 保持简洁,覆盖全部 GitHub 直连目标 | 增加 tag-only 查询;快照、分隔符和 b 编号更新规则仍需 review,不静默遗漏 | +| R4 | 强制只追加版本块 | 旧 URL、mirror、hash、注释及顺序逐字保留;同版本冲突不自动修;xim latest 别名边界另确认 | +| R5 | 一个公共工具 + 两个内置 adapter | 不统一重写 xpm、不要求两仓同时切换;会有少量维护结构差异的 adapter 代码 | +| R6 | 每日运行、默认最多三个新 PR | 无持久扫描状态、无包级周期;中央配置仅 version/adapter/max_prs,先接受轻量重复查询 | +| R7 | 首版不自动 mirror | 已有镜像不动;新版本可以追加直连,不被旧版本有 CN 这一事实阻塞 | +| R8 | mcpp-index 首测、全量声明、draft PR CI | 先全量 check/临时 apply,再验证一个真实 propose draft;候选测试与旧版本回归都必须实际运行 | +| R9 | 每包一个打开的更新 PR | 新 release 到来时不堆 PR、不自动改写维护者正在 review 的分支;关闭过的同版本不自动重开 | +| R10 | GitHub App 优先,PAT 可过渡 | Action 自己配置 git 写认证;checkout 不保留默认凭证;不把 token 暴露给包安装测试 | + +本次交付仍是设计修订与自审。下一步按上述开发分支和 draft PR 路径实现;待维护者 review 后推进。 + +## 13. 实现时核实的范围与调整 + +- 最新基线实际为 82 个含直接 GitHub 安装资源的描述符,全部追加 autoupdate 声明,旧版本不改。 +- 保持 tag/url 两字段;tag 增加 `${version:dash}`,支持 FreeType 分隔符转换;支持 b 编号和单字母稳定后缀。 +- 查询仓库优先来自显式 GitHub 下载模板,package.repo 作为回退,以覆盖下载自 GitHub 镜像但 repo 指向 GitLab 的 FreeType。 +- commit 快照报告 pinned-snapshot;独立包计数到源版本的映射报告 pinned-version-map,绝不猜测跟随分支。 +- SafeInt 历史上由 3.24 改为 3.0.28a;比较数字前同时过滤早于当前 release 的发布时间,避免回退。 +- compat.libmysqlclient 的既有 wellwei/libmysqlclient 上游 API 确认返回 404,保留真实 error 诊断与旧条目,等待明确的同源迁移地址。 +- draft CI 全量审计声明和旧版本保留,对所有 update-ready 生成平台/工具链矩阵,在临时消费者实际构建运行并核对 mcpp.lock。 +- 工具已有 Linux/macOS/Windows、Python 3.11/3.13 CI;Windows 揭示的 CRLF 哈希问题已修复并添加字节保留测试。 diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml new file mode 100644 index 00000000..39fb3f90 --- /dev/null +++ b/.github/workflows/xpkgindex-update.yml @@ -0,0 +1,172 @@ +name: xpkgindex-update + +on: + pull_request: + paths: + - 'pkgs/**/*.lua' + - 'tests/autoupdate/**' + - '.github/xpkgindex-update.yml' + - '.github/workflows/xpkgindex-update.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: xpkgindex-update-${{ github.ref }} + cancel-in-progress: true + +env: + MCPP_VERSION: '2026.10.1.2' + MCPP_INDEX_MIRROR: GLOBAL + +jobs: + scan: + runs-on: ubuntu-latest + timeout-minutes: 45 + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/checkout@v4 + with: + repository: openxlings/xpkgindex-update + ref: 989edfe335545b33feed9313a3b25c805f3315c2 + path: .updater + persist-credentials: false + - name: Use the exact development tool source + run: echo "PYTHONPATH=$PWD/.updater/src" >> "$GITHUB_ENV" + - name: All GitHub-direct descriptors are declared; published versions are unchanged + env: + BASE: ${{ github.event.pull_request.base.sha || 'origin/main' }} + run: python tests/autoupdate/ci.py audit --base "$BASE" --output coverage.json + - name: Discover and download real upstream candidates + id: scan + uses: openxlings/xpkgindex-update@989edfe335545b33feed9313a3b25c805f3315c2 + with: + mode: check + token: ${{ github.token }} + - name: Plan actual candidate build/run jobs + id: matrix + if: always() && steps.scan.outputs.plan != '' + env: + PLAN: ${{ steps.scan.outputs.plan }} + run: | + python tests/autoupdate/ci.py matrix --plan "$PLAN" --output matrix.json + echo "matrix=$(cat matrix.json)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@v4 + if: always() + with: + name: xpkgindex-update-coverage + path: | + coverage.json + matrix.json + + candidate: + name: candidate (${{ matrix.id }} ${{ matrix.candidate }}, ${{ matrix.platform }}, ${{ matrix.toolchain }}) + needs: scan + if: always() && needs.scan.outputs.matrix != '' && needs.scan.outputs.matrix != '{"include": []}' + strategy: + fail-fast: false + max-parallel: 8 + matrix: ${{ fromJSON(needs.scan.outputs.matrix) }} + runs-on: ${{ matrix.os }} + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/checkout@v4 + with: + repository: openxlings/xpkgindex-update + ref: 989edfe335545b33feed9313a3b25c805f3315c2 + path: .updater + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: xpkgindex-update-plan + path: .candidate-plan + - name: Use the development tool and a standalone consumer + shell: bash + run: | + if [[ "$RUNNER_OS" == Windows ]]; then + echo "PYTHONPATH=$(cygpath -m "$PWD/.updater/src")" >> "$GITHUB_ENV" + echo "CONSUMER=$(cygpath -m "$RUNNER_TEMP/candidate-consumer")" >> "$GITHUB_ENV" + else + echo "PYTHONPATH=$PWD/.updater/src" >> "$GITHUB_ENV" + echo "CONSUMER=$RUNNER_TEMP/candidate-consumer" >> "$GITHUB_ENV" + fi + - name: Append the candidate and prepare its existing consumer test + shell: bash + env: + GH_TOKEN: ${{ github.token }} + PACKAGE: ${{ matrix.id }} + run: | + python -m xpkgindex_update apply --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" + python tests/autoupdate/ci.py prepare --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" --output "$CONSUMER" + git diff -- pkgs + - uses: actions/cache/restore@v4 + with: + path: | + ~/.mcpp/registry + !~/.mcpp/registry/data/xim-pkgindex + !~/.mcpp/registry/data/mcpplibs + key: candidate-${{ runner.os }}-${{ matrix.toolchain }}-${{ env.MCPP_VERSION }}-${{ github.run_id }} + restore-keys: | + mcpp-registry-${{ runner.os }}-${{ matrix.toolchain }}-${{ env.MCPP_VERSION }}- + - name: Install the same mcpp release as index validation + shell: bash + env: + SUFFIX: ${{ matrix.suffix }} + EXT: ${{ matrix.ext }} + run: | + root="mcpp-${MCPP_VERSION}-${SUFFIX}" + archive="$RUNNER_TEMP/$root.$EXT" + curl -fsSL --retry 2 -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$root.$EXT" + if [[ "$EXT" == zip ]]; then + powershell -NoProfile -Command "Expand-Archive -Force -Path '$archive' -DestinationPath '$RUNNER_TEMP'" + else + tar -xzf "$archive" -C "$RUNNER_TEMP" + fi + root="$RUNNER_TEMP/$root" + mkdir -p "$HOME/.mcpp/registry" + cp -a "$root/registry/." "$HOME/.mcpp/registry/" + echo "MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV" + if [[ "$RUNNER_OS" == Windows ]]; then + echo "MCPP=$(cygpath -m "$root/bin/mcpp.exe")" >> "$GITHUB_ENV" + else + echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" + fi + - name: Resolve, build and run the actual candidate + shell: bash + env: + TOOLCHAIN: ${{ matrix.toolchain }} + run: | + "$MCPP" index update + args=() + if [[ "$TOOLCHAIN" != default ]]; then args+=(--toolchain "$TOOLCHAIN"); fi + cd "$CONSUMER" + "$MCPP" test "${args[@]}" 2>&1 | tee candidate.log + - name: The lockfile proves the candidate was selected + shell: bash + env: + PACKAGE: ${{ matrix.id }} + run: python tests/autoupdate/ci.py verify --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" --consumer "$CONSUMER" + - uses: actions/upload-artifact@v4 + if: always() + with: + name: candidate-${{ strategy.job-index }} + path: | + ${{ env.CONSUMER }}/candidate.log + ${{ env.CONSUMER }}/mcpp.lock + ${{ env.CONSUMER }}/mcpp.toml diff --git a/.github/xpkgindex-update.yml b/.github/xpkgindex-update.yml new file mode 100644 index 00000000..20192d3d --- /dev/null +++ b/.github/xpkgindex-update.yml @@ -0,0 +1,3 @@ +version: 1 +adapter: mcpp-index +max_prs: 3 diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index f1b8b448..69d3db42 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -86,6 +86,13 @@ package = { repo = "https://github.com/boost-ext/ut", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/boost-ext/ut/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.3.1"] = { diff --git a/pkgs/c/cmp.lua b/pkgs/c/cmp.lua index 1387916a..d49a122c 100644 --- a/pkgs/c/cmp.lua +++ b/pkgs/c/cmp.lua @@ -9,6 +9,13 @@ package = { repo = "https://github.com/mcpplibs/cmp", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/cmp/releases/download/${tag}/cmp-${version}.tar.gz", + }, + }, + xpm = { linux = { ["0.1.0"] = { diff --git a/pkgs/c/compat.apriltag.lua b/pkgs/c/compat.apriltag.lua index 37d2988e..3a1f76b8 100644 --- a/pkgs/c/compat.apriltag.lua +++ b/pkgs/c/compat.apriltag.lua @@ -16,6 +16,13 @@ package = { repo = "https://github.com/AprilRobotics/apriltag", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/AprilRobotics/apriltag/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["3.4.5"] = { diff --git a/pkgs/c/compat.boost-align.lua b/pkgs/c/compat.boost-align.lua index dd85a09d..f85c0178 100644 --- a/pkgs/c/compat.boost-align.lua +++ b/pkgs/c/compat.boost-align.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/align", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/align/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-any.lua b/pkgs/c/compat.boost-any.lua index f8a5d430..b01e523a 100644 --- a/pkgs/c/compat.boost-any.lua +++ b/pkgs/c/compat.boost-any.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/boostorg/any", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/any/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-asio.lua b/pkgs/c/compat.boost-asio.lua index 958ac664..bbac69d2 100644 --- a/pkgs/c/compat.boost-asio.lua +++ b/pkgs/c/compat.boost-asio.lua @@ -46,6 +46,13 @@ package = { repo = "https://github.com/boostorg/asio", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/asio/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-assert.lua b/pkgs/c/compat.boost-assert.lua index 405b2443..d7207eb6 100644 --- a/pkgs/c/compat.boost-assert.lua +++ b/pkgs/c/compat.boost-assert.lua @@ -19,6 +19,13 @@ package = { repo = "https://github.com/boostorg/assert", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/assert/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-beast.lua b/pkgs/c/compat.boost-beast.lua index bd21bf36..dae085bb 100644 --- a/pkgs/c/compat.boost-beast.lua +++ b/pkgs/c/compat.boost-beast.lua @@ -47,6 +47,13 @@ package = { repo = "https://github.com/boostorg/beast", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/beast/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-bind.lua b/pkgs/c/compat.boost-bind.lua index 9866740d..a93cb326 100644 --- a/pkgs/c/compat.boost-bind.lua +++ b/pkgs/c/compat.boost-bind.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/bind", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/bind/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-circular-buffer.lua b/pkgs/c/compat.boost-circular-buffer.lua index eaa411c6..fcd8a46a 100644 --- a/pkgs/c/compat.boost-circular-buffer.lua +++ b/pkgs/c/compat.boost-circular-buffer.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/boostorg/circular_buffer", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/circular_buffer/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-compat.lua b/pkgs/c/compat.boost-compat.lua index b1866942..50aff4b9 100644 --- a/pkgs/c/compat.boost-compat.lua +++ b/pkgs/c/compat.boost-compat.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/compat", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/compat/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-concept-check.lua b/pkgs/c/compat.boost-concept-check.lua index 4934af84..d46b1d4a 100644 --- a/pkgs/c/compat.boost-concept-check.lua +++ b/pkgs/c/compat.boost-concept-check.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/boostorg/concept_check", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/concept_check/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-config.lua b/pkgs/c/compat.boost-config.lua index 70dd66e5..0c6105f1 100644 --- a/pkgs/c/compat.boost-config.lua +++ b/pkgs/c/compat.boost-config.lua @@ -34,6 +34,13 @@ package = { repo = "https://github.com/boostorg/config", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/config/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-container-hash.lua b/pkgs/c/compat.boost-container-hash.lua index fc6f9c8f..5f3690b6 100644 --- a/pkgs/c/compat.boost-container-hash.lua +++ b/pkgs/c/compat.boost-container-hash.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/container_hash", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/container_hash/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-container.lua b/pkgs/c/compat.boost-container.lua index adc8dde2..c3c8514b 100644 --- a/pkgs/c/compat.boost-container.lua +++ b/pkgs/c/compat.boost-container.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/container", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/container/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-core.lua b/pkgs/c/compat.boost-core.lua index 3c5e3d8a..037d55ed 100644 --- a/pkgs/c/compat.boost-core.lua +++ b/pkgs/c/compat.boost-core.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/core", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/core/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-describe.lua b/pkgs/c/compat.boost-describe.lua index 7bbaff52..a9cbbd00 100644 --- a/pkgs/c/compat.boost-describe.lua +++ b/pkgs/c/compat.boost-describe.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/describe", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/describe/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-endian.lua b/pkgs/c/compat.boost-endian.lua index 1260661d..467ddebd 100644 --- a/pkgs/c/compat.boost-endian.lua +++ b/pkgs/c/compat.boost-endian.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/endian", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/endian/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-intrusive.lua b/pkgs/c/compat.boost-intrusive.lua index 114401fa..9cfe1813 100644 --- a/pkgs/c/compat.boost-intrusive.lua +++ b/pkgs/c/compat.boost-intrusive.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/intrusive", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/intrusive/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-io.lua b/pkgs/c/compat.boost-io.lua index bb5d68a2..3daa0153 100644 --- a/pkgs/c/compat.boost-io.lua +++ b/pkgs/c/compat.boost-io.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/io", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/io/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-logic.lua b/pkgs/c/compat.boost-logic.lua index 2ffe8c85..e23af295 100644 --- a/pkgs/c/compat.boost-logic.lua +++ b/pkgs/c/compat.boost-logic.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/logic", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/logic/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-move.lua b/pkgs/c/compat.boost-move.lua index 276b3c9d..8da53305 100644 --- a/pkgs/c/compat.boost-move.lua +++ b/pkgs/c/compat.boost-move.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/move", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/move/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-mp11.lua b/pkgs/c/compat.boost-mp11.lua index 48c1228c..a687cfa8 100644 --- a/pkgs/c/compat.boost-mp11.lua +++ b/pkgs/c/compat.boost-mp11.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/mp11", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/mp11/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-optional.lua b/pkgs/c/compat.boost-optional.lua index 4313d4bf..0dec78fa 100644 --- a/pkgs/c/compat.boost-optional.lua +++ b/pkgs/c/compat.boost-optional.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/optional", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/optional/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-predef.lua b/pkgs/c/compat.boost-predef.lua index 4363af8b..59b7df35 100644 --- a/pkgs/c/compat.boost-predef.lua +++ b/pkgs/c/compat.boost-predef.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/predef", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/predef/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-preprocessor.lua b/pkgs/c/compat.boost-preprocessor.lua index a59d6a01..c7427045 100644 --- a/pkgs/c/compat.boost-preprocessor.lua +++ b/pkgs/c/compat.boost-preprocessor.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/preprocessor", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/preprocessor/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-smart-ptr.lua b/pkgs/c/compat.boost-smart-ptr.lua index 615a8e12..67139aca 100644 --- a/pkgs/c/compat.boost-smart-ptr.lua +++ b/pkgs/c/compat.boost-smart-ptr.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/smart_ptr", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/smart_ptr/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-static-string.lua b/pkgs/c/compat.boost-static-string.lua index ad32ed2d..f144aa56 100644 --- a/pkgs/c/compat.boost-static-string.lua +++ b/pkgs/c/compat.boost-static-string.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/static_string", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/static_string/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-system.lua b/pkgs/c/compat.boost-system.lua index 344ef706..d0e636b3 100644 --- a/pkgs/c/compat.boost-system.lua +++ b/pkgs/c/compat.boost-system.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/system", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/system/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-throw-exception.lua b/pkgs/c/compat.boost-throw-exception.lua index ca038c3a..f0086df7 100644 --- a/pkgs/c/compat.boost-throw-exception.lua +++ b/pkgs/c/compat.boost-throw-exception.lua @@ -18,6 +18,13 @@ package = { repo = "https://github.com/boostorg/throw_exception", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/throw_exception/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-type-index.lua b/pkgs/c/compat.boost-type-index.lua index d5623111..87d3654f 100644 --- a/pkgs/c/compat.boost-type-index.lua +++ b/pkgs/c/compat.boost-type-index.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/type_index", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/type_index/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-type-traits.lua b/pkgs/c/compat.boost-type-traits.lua index ed6aca6e..3becf001 100644 --- a/pkgs/c/compat.boost-type-traits.lua +++ b/pkgs/c/compat.boost-type-traits.lua @@ -20,6 +20,13 @@ package = { repo = "https://github.com/boostorg/type_traits", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/type_traits/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-utility.lua b/pkgs/c/compat.boost-utility.lua index 09bea92e..5b7a40bb 100644 --- a/pkgs/c/compat.boost-utility.lua +++ b/pkgs/c/compat.boost-utility.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/utility", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/utility/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-uuid.lua b/pkgs/c/compat.boost-uuid.lua index c7550d05..0f4dd09e 100644 --- a/pkgs/c/compat.boost-uuid.lua +++ b/pkgs/c/compat.boost-uuid.lua @@ -31,6 +31,13 @@ package = { repo = "https://github.com/boostorg/uuid", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/uuid/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-variant2.lua b/pkgs/c/compat.boost-variant2.lua index 5ffcb875..80cd299c 100644 --- a/pkgs/c/compat.boost-variant2.lua +++ b/pkgs/c/compat.boost-variant2.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/variant2", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/variant2/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.boost-winapi.lua b/pkgs/c/compat.boost-winapi.lua index 5c2c0bd7..8eab21cd 100644 --- a/pkgs/c/compat.boost-winapi.lua +++ b/pkgs/c/compat.boost-winapi.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/boostorg/winapi", type = "package", + extensions = { + autoupdate = { + tag = "boost-${version}", + url = "https://github.com/boostorg/winapi/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.92.0"] = { diff --git a/pkgs/c/compat.brotli.lua b/pkgs/c/compat.brotli.lua index 0a6ad5d3..b39102ec 100644 --- a/pkgs/c/compat.brotli.lua +++ b/pkgs/c/compat.brotli.lua @@ -11,6 +11,13 @@ package = { repo = "https://github.com/google/brotli", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/google/brotli/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.2.0"] = { diff --git a/pkgs/c/compat.concurrentqueue.lua b/pkgs/c/compat.concurrentqueue.lua index 812cf938..55be08db 100644 --- a/pkgs/c/compat.concurrentqueue.lua +++ b/pkgs/c/compat.concurrentqueue.lua @@ -42,6 +42,13 @@ package = { repo = "https://github.com/cameron314/concurrentqueue", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/cameron314/concurrentqueue/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.0.5"] = { diff --git a/pkgs/c/compat.etl.lua b/pkgs/c/compat.etl.lua index e0a5d1ea..6c73956a 100644 --- a/pkgs/c/compat.etl.lua +++ b/pkgs/c/compat.etl.lua @@ -18,6 +18,13 @@ package = { repo = "https://github.com/ETLCPP/etl", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/ETLCPP/etl/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["20.49.0"] = { diff --git a/pkgs/c/compat.fmt.lua b/pkgs/c/compat.fmt.lua index 14ee0058..1ba4152c 100644 --- a/pkgs/c/compat.fmt.lua +++ b/pkgs/c/compat.fmt.lua @@ -63,6 +63,13 @@ package = { repo = "https://github.com/fmtlib/fmt", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/fmtlib/fmt/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["12.2.0"] = { diff --git a/pkgs/c/compat.freetype.lua b/pkgs/c/compat.freetype.lua index 28b5b4f4..b69ffc08 100644 --- a/pkgs/c/compat.freetype.lua +++ b/pkgs/c/compat.freetype.lua @@ -16,6 +16,13 @@ package = { repo = "https://gitlab.freedesktop.org/freetype/freetype", type = "package", + extensions = { + autoupdate = { + tag = "VER-${version:dash}", + url = "https://github.com/freetype/freetype/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.13.3"] = { diff --git a/pkgs/c/compat.ftxui.lua b/pkgs/c/compat.ftxui.lua index 4b312323..a08a746f 100644 --- a/pkgs/c/compat.ftxui.lua +++ b/pkgs/c/compat.ftxui.lua @@ -99,6 +99,13 @@ package = { repo = "https://github.com/ArthurSonzogni/FTXUI", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/ArthurSonzogni/FTXUI/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["6.1.9"] = { diff --git a/pkgs/c/compat.glad.lua b/pkgs/c/compat.glad.lua index ee2132c2..d40ccfb0 100644 --- a/pkgs/c/compat.glad.lua +++ b/pkgs/c/compat.glad.lua @@ -13,6 +13,13 @@ package = { repo = "https://github.com/libigl/libigl-glad", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/libigl/libigl-glad/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.0.0-651a425"] = { diff --git a/pkgs/c/compat.gtest.lua b/pkgs/c/compat.gtest.lua index 5b468e8e..8295a0b6 100644 --- a/pkgs/c/compat.gtest.lua +++ b/pkgs/c/compat.gtest.lua @@ -12,6 +12,13 @@ package = { repo = "https://github.com/google/googletest", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/google/googletest/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.17.0"] = { diff --git a/pkgs/c/compat.hiredis.lua b/pkgs/c/compat.hiredis.lua index 92cd1f7d..21faf0c9 100644 --- a/pkgs/c/compat.hiredis.lua +++ b/pkgs/c/compat.hiredis.lua @@ -34,6 +34,13 @@ package = { repo = "https://github.com/redis/hiredis", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/redis/hiredis/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.2.0"] = { diff --git a/pkgs/c/compat.httplib.lua b/pkgs/c/compat.httplib.lua index f11223ad..8a38e64c 100644 --- a/pkgs/c/compat.httplib.lua +++ b/pkgs/c/compat.httplib.lua @@ -9,6 +9,13 @@ package = { repo = "https://github.com/yhirose/cpp-httplib", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/yhirose/cpp-httplib/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { -- No mcpp-res credentials are available in this environment. Keep the -- GLOBAL URL as a plain string until a byte-identical CN asset exists. diff --git a/pkgs/c/compat.imgui.lua b/pkgs/c/compat.imgui.lua index 3495f565..2e723e5a 100644 --- a/pkgs/c/compat.imgui.lua +++ b/pkgs/c/compat.imgui.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/ocornut/imgui", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/ocornut/imgui/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { -- No CN mirror credentials: use upstream URLs for the new releases. diff --git a/pkgs/c/compat.jwt-cpp.lua b/pkgs/c/compat.jwt-cpp.lua index dbe2c4af..48a99950 100644 --- a/pkgs/c/compat.jwt-cpp.lua +++ b/pkgs/c/compat.jwt-cpp.lua @@ -37,6 +37,13 @@ package = { repo = "https://github.com/Thalhammer/jwt-cpp", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/Thalhammer/jwt-cpp/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.7.2"] = { diff --git a/pkgs/c/compat.libevent.lua b/pkgs/c/compat.libevent.lua index 5a48ccf3..d61252ca 100644 --- a/pkgs/c/compat.libevent.lua +++ b/pkgs/c/compat.libevent.lua @@ -9,6 +9,13 @@ package = { repo = "https://github.com/libevent/libevent", type = "package", + extensions = { + autoupdate = { + tag = "release-${version}-stable", + url = "https://github.com/libevent/libevent/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { deps = { "xim:cmake@latest" }, diff --git a/pkgs/c/compat.libmysqlclient.lua b/pkgs/c/compat.libmysqlclient.lua index 308b798a..e69a306e 100644 --- a/pkgs/c/compat.libmysqlclient.lua +++ b/pkgs/c/compat.libmysqlclient.lua @@ -8,6 +8,13 @@ package = { repo = "https://github.com/wellwei/libmysqlclient", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/wellwei/libmysqlclient/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["8.4.6"] = { diff --git a/pkgs/c/compat.libpng.lua b/pkgs/c/compat.libpng.lua index 2cdb4ef6..09e89f76 100644 --- a/pkgs/c/compat.libpng.lua +++ b/pkgs/c/compat.libpng.lua @@ -17,6 +17,13 @@ package = { repo = "https://github.com/pnggroup/libpng", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/pnggroup/libpng/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.6.43"] = { diff --git a/pkgs/c/compat.libuv.lua b/pkgs/c/compat.libuv.lua index d7707a7a..a0c8c228 100644 --- a/pkgs/c/compat.libuv.lua +++ b/pkgs/c/compat.libuv.lua @@ -26,6 +26,13 @@ package = { repo = "https://github.com/libuv/libuv", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/libuv/libuv/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.48.0"] = { diff --git a/pkgs/c/compat.md4c.lua b/pkgs/c/compat.md4c.lua index 53e428f5..fde12bd5 100644 --- a/pkgs/c/compat.md4c.lua +++ b/pkgs/c/compat.md4c.lua @@ -13,6 +13,13 @@ package = { repo = "https://github.com/mity/md4c", type = "package", + extensions = { + autoupdate = { + tag = "release-${version}", + url = "https://github.com/mity/md4c/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.5.3"] = { diff --git a/pkgs/c/compat.muduo.lua b/pkgs/c/compat.muduo.lua index 956cbd1a..29696ee0 100644 --- a/pkgs/c/compat.muduo.lua +++ b/pkgs/c/compat.muduo.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/chenshuo/muduo", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/chenshuo/muduo/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.0.3"] = { diff --git a/pkgs/c/compat.nanodbc.lua b/pkgs/c/compat.nanodbc.lua index 04360e2d..3d642b89 100644 --- a/pkgs/c/compat.nanodbc.lua +++ b/pkgs/c/compat.nanodbc.lua @@ -40,6 +40,13 @@ package = { repo = "https://github.com/nanodbc/nanodbc", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/nanodbc/nanodbc/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.14.0"] = { diff --git a/pkgs/c/compat.openmp.lua b/pkgs/c/compat.openmp.lua index 77ef8822..1956dbd8 100644 --- a/pkgs/c/compat.openmp.lua +++ b/pkgs/c/compat.openmp.lua @@ -10,6 +10,13 @@ package = { licenses = {"Apache-2.0 WITH LLVM-exception"}, repo = "https://github.com/llvm/llvm-project/tree/main/openmp", type = "package", + extensions = { + autoupdate = { + tag = "llvmorg-${version}", + url = "https://github.com/llvm/llvm-project/releases/download/${tag}/llvm-project-${version}.src.tar.xz", + }, + }, + xpm = { linux = { deps = { "xim:cmake@latest", "xim:python@3.13.12" }, diff --git a/pkgs/c/compat.pybind11.lua b/pkgs/c/compat.pybind11.lua index 4b2a6cb4..a220d737 100644 --- a/pkgs/c/compat.pybind11.lua +++ b/pkgs/c/compat.pybind11.lua @@ -12,6 +12,13 @@ package = { repo = "https://github.com/pybind/pybind11", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/pybind/pybind11/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["3.2.0-dev.6254086"] = { diff --git a/pkgs/c/compat.quickjs-ng.lua b/pkgs/c/compat.quickjs-ng.lua index 6d6773c7..abb02b49 100644 --- a/pkgs/c/compat.quickjs-ng.lua +++ b/pkgs/c/compat.quickjs-ng.lua @@ -68,6 +68,13 @@ package = { repo = "https://github.com/quickjs-ng/quickjs", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/quickjs-ng/quickjs/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.17.0"] = { diff --git a/pkgs/c/compat.rapidcsv.lua b/pkgs/c/compat.rapidcsv.lua index 90a1a9df..10f0f73c 100644 --- a/pkgs/c/compat.rapidcsv.lua +++ b/pkgs/c/compat.rapidcsv.lua @@ -30,6 +30,13 @@ package = { repo = "https://github.com/d99kris/rapidcsv", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/d99kris/rapidcsv/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["9.07"] = { diff --git a/pkgs/c/compat.recastnavigation.lua b/pkgs/c/compat.recastnavigation.lua index dc663de0..c4f795f5 100644 --- a/pkgs/c/compat.recastnavigation.lua +++ b/pkgs/c/compat.recastnavigation.lua @@ -58,6 +58,13 @@ package = { repo = "https://github.com/recastnavigation/recastnavigation", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/recastnavigation/recastnavigation/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.6.0"] = { diff --git a/pkgs/c/compat.redis-plus-plus.lua b/pkgs/c/compat.redis-plus-plus.lua index 7006b8aa..b1c56c32 100644 --- a/pkgs/c/compat.redis-plus-plus.lua +++ b/pkgs/c/compat.redis-plus-plus.lua @@ -50,6 +50,13 @@ package = { repo = "https://github.com/sewenew/redis-plus-plus", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.3.3"] = { diff --git a/pkgs/c/compat.richdem.lua b/pkgs/c/compat.richdem.lua index b2c87575..fb63e61d 100644 --- a/pkgs/c/compat.richdem.lua +++ b/pkgs/c/compat.richdem.lua @@ -13,6 +13,13 @@ package = { repo = "https://github.com/r-barnes/richdem", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/r-barnes/richdem/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.2.11"] = { diff --git a/pkgs/c/compat.safeint.lua b/pkgs/c/compat.safeint.lua index a49108dc..b4ac7306 100644 --- a/pkgs/c/compat.safeint.lua +++ b/pkgs/c/compat.safeint.lua @@ -23,6 +23,13 @@ package = { repo = "https://github.com/dcleblanc/SafeInt", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/dcleblanc/SafeInt/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["3.0.28a"] = { diff --git a/pkgs/c/compat.sdl3.lua b/pkgs/c/compat.sdl3.lua index 926ea7bd..ee2576c0 100644 --- a/pkgs/c/compat.sdl3.lua +++ b/pkgs/c/compat.sdl3.lua @@ -161,6 +161,13 @@ package = { repo = "https://github.com/libsdl-org/SDL", type = "package", + extensions = { + autoupdate = { + tag = "release-${version}", + url = "https://github.com/libsdl-org/SDL/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["3.4.2"] = { diff --git a/pkgs/c/compat.soci.lua b/pkgs/c/compat.soci.lua index 221e9037..dd73a60d 100644 --- a/pkgs/c/compat.soci.lua +++ b/pkgs/c/compat.soci.lua @@ -94,6 +94,13 @@ package = { repo = "https://github.com/SOCI/soci", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/SOCI/soci/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["4.1.4"] = { diff --git a/pkgs/c/compat.spdlog.lua b/pkgs/c/compat.spdlog.lua index 16e2eb0e..7a504370 100644 --- a/pkgs/c/compat.spdlog.lua +++ b/pkgs/c/compat.spdlog.lua @@ -49,6 +49,13 @@ package = { repo = "https://github.com/gabime/spdlog", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/gabime/spdlog/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["1.17.0"] = { diff --git a/pkgs/c/compat.tray.lua b/pkgs/c/compat.tray.lua index e3c595ba..d01581d6 100644 --- a/pkgs/c/compat.tray.lua +++ b/pkgs/c/compat.tray.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/zserge/tray", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/zserge/tray/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.0.0-8dd1358"] = { diff --git a/pkgs/c/compat.wamr.lua b/pkgs/c/compat.wamr.lua index 7e335abb..81160729 100644 --- a/pkgs/c/compat.wamr.lua +++ b/pkgs/c/compat.wamr.lua @@ -100,6 +100,13 @@ package = { repo = "https://github.com/bytecodealliance/wasm-micro-runtime", type = "package", + extensions = { + autoupdate = { + tag = "WAMR-${version}", + url = "https://github.com/bytecodealliance/wasm-micro-runtime/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.4.5"] = { diff --git a/pkgs/c/compat.websocket.lua b/pkgs/c/compat.websocket.lua index 054b5f59..4a230eff 100644 --- a/pkgs/c/compat.websocket.lua +++ b/pkgs/c/compat.websocket.lua @@ -45,6 +45,13 @@ package = { repo = "https://github.com/machinezone/IXWebSocket", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/machinezone/IXWebSocket/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { -- CN mirror not published yet (no mcpp-res write access here); -- plain-string url keeps lint green and lets CN users fall back to diff --git a/pkgs/c/compat.yyjson.lua b/pkgs/c/compat.yyjson.lua index 75289bd0..595a03ee 100644 --- a/pkgs/c/compat.yyjson.lua +++ b/pkgs/c/compat.yyjson.lua @@ -13,6 +13,13 @@ package = { repo = "https://github.com/ibireme/yyjson", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/ibireme/yyjson/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.12.0"] = { diff --git a/pkgs/e/compat.eui-neo.lua b/pkgs/e/compat.eui-neo.lua index a5d1c28a..ac2354c4 100644 --- a/pkgs/e/compat.eui-neo.lua +++ b/pkgs/e/compat.eui-neo.lua @@ -142,6 +142,13 @@ package = { repo = "https://github.com/sudoevolve/EUI-NEO", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/sudoevolve/EUI-NEO/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { -- What the SNI tray needs, and where each half comes from. diff --git a/pkgs/f/fmtlib.fmt.lua b/pkgs/f/fmtlib.fmt.lua index c478fd2d..cced83b4 100644 --- a/pkgs/f/fmtlib.fmt.lua +++ b/pkgs/f/fmtlib.fmt.lua @@ -48,6 +48,13 @@ package = { repo = "https://github.com/fmtlib/fmt", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/fmtlib/fmt/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["12.2.0"] = { diff --git a/pkgs/g/ggml-org.llamacpp.lua b/pkgs/g/ggml-org.llamacpp.lua index 387f6d2d..d0c87e39 100644 --- a/pkgs/g/ggml-org.llamacpp.lua +++ b/pkgs/g/ggml-org.llamacpp.lua @@ -22,6 +22,13 @@ package = { repo = "https://github.com/mcpplibs/llama.cpp-m", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/mcpplibs/llama.cpp-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { -- b10069.1 ADDS `backend-vulkan`, AND ITS BUILD PROGRAM NEEDS mcpp diff --git a/pkgs/g/gzj-creator.galay.lua b/pkgs/g/gzj-creator.galay.lua index c9c3d01d..641b2469 100644 --- a/pkgs/g/gzj-creator.galay.lua +++ b/pkgs/g/gzj-creator.galay.lua @@ -15,6 +15,13 @@ package = { repo = "https://github.com/gzj-creator/galay", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/gzj-creator/galay/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["5.0.2"] = { diff --git a/pkgs/l/llamacpp.lua b/pkgs/l/llamacpp.lua index 98da8022..de324001 100644 --- a/pkgs/l/llamacpp.lua +++ b/pkgs/l/llamacpp.lua @@ -23,6 +23,13 @@ package = { repo = "https://github.com/mcpplibs/llama.cpp-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/llama.cpp-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.1.0"] = { diff --git a/pkgs/m/mpusz.mp-units.lua b/pkgs/m/mpusz.mp-units.lua index da7f57c4..7a8096fe 100644 --- a/pkgs/m/mpusz.mp-units.lua +++ b/pkgs/m/mpusz.mp-units.lua @@ -6,6 +6,13 @@ package = { licenses = { "MIT" }, repo = "https://github.com/mpusz/mp-units", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mpusz/mp-units/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["2.5.0"] = { diff --git a/pkgs/n/neargye.magic_enum.lua b/pkgs/n/neargye.magic_enum.lua index e40a9c0b..839b6f8a 100644 --- a/pkgs/n/neargye.magic_enum.lua +++ b/pkgs/n/neargye.magic_enum.lua @@ -21,6 +21,13 @@ package = { repo = "https://github.com/Neargye/magic_enum", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/Neargye/magic_enum/releases/download/${tag}/magic_enum-${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.9.8"] = { diff --git a/pkgs/o/odygrd.quill.lua b/pkgs/o/odygrd.quill.lua index 520cd35e..bbbefd2f 100644 --- a/pkgs/o/odygrd.quill.lua +++ b/pkgs/o/odygrd.quill.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/odygrd/quill", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/odygrd/quill/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["13.0.0"] = { diff --git a/pkgs/r/rules-cuda.lua b/pkgs/r/rules-cuda.lua index 0a482b35..7136295c 100644 --- a/pkgs/r/rules-cuda.lua +++ b/pkgs/r/rules-cuda.lua @@ -35,6 +35,13 @@ package = { repo = "https://github.com/mcpp-community/mcpp", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpp-community/mcpp/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.1.0"] = { diff --git a/pkgs/t/taskflow.taskflow.lua b/pkgs/t/taskflow.taskflow.lua index fe5fd17e..4bc9f5a8 100644 --- a/pkgs/t/taskflow.taskflow.lua +++ b/pkgs/t/taskflow.taskflow.lua @@ -7,6 +7,13 @@ package = { repo = "https://github.com/taskflow/taskflow", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/taskflow/taskflow/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["4.1.0"] = { diff --git a/pkgs/t/tensorvia-cpu.lua b/pkgs/t/tensorvia-cpu.lua index bb2e8404..22f580a5 100644 --- a/pkgs/t/tensorvia-cpu.lua +++ b/pkgs/t/tensorvia-cpu.lua @@ -10,6 +10,13 @@ package = { namespace = "aimol", maintainers = {"Aimol-l"}, + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/Aimol-l/Tensorvia-cpu/releases/download/${tag}/tensorvia-cpu-${version}.tar.gz", + }, + }, + xpm = { linux = { ['0.2.3'] = { url = "https://github.com/Aimol-l/Tensorvia-cpu/releases/download/v0.2.3/tensorvia-cpu-0.2.3.tar.gz", sha256 = "8476d2fa27eb47a8e9d50b3ef3b974cad8923f4fdebd4d3f6d45355cc6a534f7" }, diff --git a/pkgs/y/yizhinailong.mcr.lua b/pkgs/y/yizhinailong.mcr.lua index aa64787a..af9d59e4 100644 --- a/pkgs/y/yizhinailong.mcr.lua +++ b/pkgs/y/yizhinailong.mcr.lua @@ -26,6 +26,13 @@ package = { repo = "https://github.com/yizhinailong/mcr", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/yizhinailong/mcr/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { ["0.1.5"] = { diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py new file mode 100644 index 00000000..8a1199f7 --- /dev/null +++ b/tests/autoupdate/ci.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Real updater integration: full declaration coverage and candidate consumers.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import tomllib +from pathlib import Path + +from xpkgindex_update.manifest import assert_preserved, inventory + +ROOT = Path(__file__).resolve().parents[2] + + +def audit(base: str, output: Path): + records = [] + for info in inventory(ROOT): + before = subprocess.check_output(["git", "show", f"{base}:{info['path']}"], cwd=ROOT).decode() + assert_preserved(before, info["text"]) + if info["direct"]: + if info["autoupdate"] is None: + raise RuntimeError(f"missing autoupdate: {info['id']}") + records.append({k: info[k] for k in ("id", "path", "direct", "autoupdate")}) + output.write_text(json.dumps({"covered": len(records), "packages": records}, indent=2)) + print(f"All {len(records)} GitHub-direct descriptors declared; all existing versions unchanged.") + + +def direct_member(identity: str) -> str | None: + namespace, name = identity.split(":", 1) + candidates = [] + for path in (ROOT / "tests/examples").glob("*/mcpp.toml"): + text = path.read_text() + # Metadata-only selection; prepare() and the lock assertion verify actual resolution. + if re.search(rf"(?m)^\s*{re.escape(name)}\s*=", text) and ( + f"dependencies.{namespace}]" in text or f"dependencies.{namespace}." in text + ): + candidates.append(path.parent.name) + if not candidates: + return None + return min(candidates, key=lambda value: (len(value), value)) + + +def matrix(plan: Path, output: Path): + jobs = [] + for record in json.loads(plan.read_text())["packages"]: + if record["status"] != "update-ready": + continue + member = direct_member(record["id"]) + if member is None and record["id"] not in ("compat:hiredis", "compat:libuv"): + raise RuntimeError(f"no candidate consumer: {record['id']}") + for platform in [asset["platform"] for asset in record["assets"]]: + rows = { + "linux": ("ubuntu-latest", "linux-x86_64", "tar.gz"), + "macosx": ("macos-latest", "macos-arm64", "tar.gz"), + "windows": ("windows-latest", "windows-x86_64", "zip"), + } + runner, suffix, ext = rows[platform] + for toolchain in (["default", "llvm"] if platform == "linux" else ["default"]): + jobs.append({"id": record["id"], "candidate": record["candidate"], + "member": member or "probe", "platform": platform, + "os": runner, "suffix": suffix, "ext": ext, "toolchain": toolchain}) + output.write_text(json.dumps({"include": jobs})) + print(f"Candidate matrix: {len(jobs)} real build/run jobs.") + + +def prepare(plan: Path, identity: str, output: Path): + record = next(r for r in json.loads(plan.read_text())["packages"] if r["id"] == identity) + namespace, name = identity.split(":", 1) + version = record["candidate"] + member = direct_member(identity) + output.mkdir(parents=True, exist_ok=True) + if member: + source = ROOT / "tests/examples" / member + shutil.copytree(source, output, dirs_exist_ok=True, + ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock")) + text = (output / "mcpp.toml").read_text() + text, replacements = re.subn( + rf'(?m)^(\s*{re.escape(name)}\s*=\s*(?:\{{\s*version\s*=\s*)?")[^"]+(".*)$', + lambda m: m[1] + version + m[2], text) + if not replacements: + raise RuntimeError("candidate dependency was not replaced") + # Standalone consumer: no inherited workspace or stale relative index redirects. + text = re.sub(r'(?ms)^\[indices(?:\.[^\]]+)?\]\s*\n.*?(?=^\[|\Z)', '', text) + else: + text = f'[package]\nname = "candidate-{name}"\nversion = "0.1.0"\n\n[dependencies.{namespace}]\n{name} = "{version}"\n' + (output / "tests").mkdir(exist_ok=True) + probes = { + "hiredis": '#include \nint main() { auto *r = redisReaderCreate(); if (!r) return 1; redisReaderFree(r); }\n', + "libuv": '#include \nint main() { uv_loop_t loop; if (uv_loop_init(&loop)) return 1; return uv_loop_close(&loop); }\n', + } + (output / "tests/probe.cpp").write_text(probes[name]) + if name == "nanodbc": + # The existing Linux-only diagnostic test cannot prove non-Linux candidate resolution. + text = f'[package]\nname = "candidate-nanodbc"\nversion = "0.1.0"\n\n[dependencies.compat]\nnanodbc = "{version}"\n' + for path in (output / "tests").glob("*.cpp"): + path.unlink() + (output / "tests/connection.cpp").write_text( + '#include \nint main() { nanodbc::connection c; return c.connected() ? 1 : 0; }\n') + text += f'\n[indices]\n"{namespace}" = {{ path = {json.dumps(str(ROOT).replace(chr(92), "/"))} }}\n' + (output / "mcpp.toml").write_text(text) + print(f"Prepared {identity}@{version} in {output}") + + +def verify(plan: Path, identity: str, consumer: Path): + record = next(r for r in json.loads(plan.read_text())["packages"] if r["id"] == identity) + namespace, name = identity.split(":", 1) + lock = tomllib.loads((consumer / "mcpp.lock").read_text()) + matches = [value for key, value in lock.get("package", {}).items() + if value.get("namespace") == namespace and key in (name, identity, f"{namespace}.{name}")] + if not matches or any(v["version"] != record["candidate"] for v in matches): + raise RuntimeError(f"lockfile does not prove {identity}@{record['candidate']}: {matches}") + print(f"PROVEN: build resolved {identity}@{record['candidate']}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify")) + parser.add_argument("--base") + parser.add_argument("--plan", type=Path) + parser.add_argument("--package") + parser.add_argument("--output", type=Path) + parser.add_argument("--consumer", type=Path) + args = parser.parse_args() + if args.command == "audit": + audit(args.base, args.output) + elif args.command == "matrix": + matrix(args.plan, args.output) + elif args.command == "prepare": + prepare(args.plan, args.package, args.output) + else: + verify(args.plan, args.package, args.consumer) + + +if __name__ == "__main__": + try: + main() + except (RuntimeError, OSError, ValueError) as exc: + print(str(exc), file=sys.stderr) + sys.exit(1) From 186333175db76f06cf82ea644f6aff1c157c5dc7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:16:03 +0900 Subject: [PATCH 02/31] fix: quote the empty candidate matrix condition --- .github/workflows/xpkgindex-update.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 39fb3f90..0ad6c61b 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -71,7 +71,8 @@ jobs: candidate: name: candidate (${{ matrix.id }} ${{ matrix.candidate }}, ${{ matrix.platform }}, ${{ matrix.toolchain }}) needs: scan - if: always() && needs.scan.outputs.matrix != '' && needs.scan.outputs.matrix != '{"include": []}' + if: >- + always() && needs.scan.outputs.matrix != '' && needs.scan.outputs.matrix != '{"include": []}' strategy: fail-fast: false max-parallel: 8 From abebd8951a3e18d4073935b906e3443f790f298e Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:16:16 +0900 Subject: [PATCH 03/31] fix: use the declared hiredis include root in its candidate probe --- tests/autoupdate/ci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 8a1199f7..d4c4f9d9 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -91,7 +91,7 @@ def prepare(plan: Path, identity: str, output: Path): text = f'[package]\nname = "candidate-{name}"\nversion = "0.1.0"\n\n[dependencies.{namespace}]\n{name} = "{version}"\n' (output / "tests").mkdir(exist_ok=True) probes = { - "hiredis": '#include \nint main() { auto *r = redisReaderCreate(); if (!r) return 1; redisReaderFree(r); }\n', + "hiredis": '#include \nint main() { auto *r = redisReaderCreate(); if (!r) return 1; redisReaderFree(r); }\n', "libuv": '#include \nint main() { uv_loop_t loop; if (uv_loop_init(&loop)) return 1; return uv_loop_close(&loop); }\n', } (output / "tests/probe.cpp").write_text(probes[name]) From d9b1a4b214612099a20606a9455e12dd10c3a4bc Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:18:38 +0900 Subject: [PATCH 04/31] test: verify added PR versions and pin network retry fixes --- .github/workflows/xpkgindex-update.yml | 29 +++++++++++----- tests/autoupdate/ci.py | 46 ++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 0ad6c61b..b2a6afb0 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -37,29 +37,40 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 989edfe335545b33feed9313a3b25c805f3315c2 + ref: c7bea7e8c469ae0d57c9cd96debd771960374284 path: .updater persist-credentials: false - name: Use the exact development tool source run: echo "PYTHONPATH=$PWD/.updater/src" >> "$GITHUB_ENV" - name: All GitHub-direct descriptors are declared; published versions are unchanged + id: coverage env: BASE: ${{ github.event.pull_request.base.sha || 'origin/main' }} - run: python tests/autoupdate/ci.py audit --base "$BASE" --output coverage.json + run: | + python tests/autoupdate/ci.py audit --base "$BASE" --output coverage.json + python tests/autoupdate/ci.py selection --base "$BASE" --output selected-package.txt + echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" - name: Discover and download real upstream candidates id: scan - uses: openxlings/xpkgindex-update@989edfe335545b33feed9313a3b25c805f3315c2 + uses: openxlings/xpkgindex-update@c7bea7e8c469ae0d57c9cd96debd771960374284 with: mode: check token: ${{ github.token }} + package: ${{ steps.coverage.outputs.package }} - name: Plan actual candidate build/run jobs id: matrix if: always() && steps.scan.outputs.plan != '' env: PLAN: ${{ steps.scan.outputs.plan }} + BASE: ${{ github.event.pull_request.base.sha || 'origin/main' }} run: | - python tests/autoupdate/ci.py matrix --plan "$PLAN" --output matrix.json + python tests/autoupdate/ci.py matrix --plan "$PLAN" --base "$BASE" --output matrix.json echo "matrix=$(cat matrix.json)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@v4 + if: always() + with: + name: xpkgindex-update-build-plan + path: build-plan.json - uses: actions/upload-artifact@v4 if: always() with: @@ -90,12 +101,12 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 989edfe335545b33feed9313a3b25c805f3315c2 + ref: c7bea7e8c469ae0d57c9cd96debd771960374284 path: .updater persist-credentials: false - uses: actions/download-artifact@v4 with: - name: xpkgindex-update-plan + name: xpkgindex-update-build-plan path: .candidate-plan - name: Use the development tool and a standalone consumer shell: bash @@ -113,8 +124,8 @@ jobs: GH_TOKEN: ${{ github.token }} PACKAGE: ${{ matrix.id }} run: | - python -m xpkgindex_update apply --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" - python tests/autoupdate/ci.py prepare --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" --output "$CONSUMER" + python -m xpkgindex_update apply --plan .candidate-plan/build-plan.json --package "$PACKAGE" + python tests/autoupdate/ci.py prepare --plan .candidate-plan/build-plan.json --package "$PACKAGE" --output "$CONSUMER" git diff -- pkgs - uses: actions/cache/restore@v4 with: @@ -162,7 +173,7 @@ jobs: shell: bash env: PACKAGE: ${{ matrix.id }} - run: python tests/autoupdate/ci.py verify --plan .candidate-plan/xpkgindex-update-plan.json --package "$PACKAGE" --consumer "$CONSUMER" + run: python tests/autoupdate/ci.py verify --plan .candidate-plan/build-plan.json --package "$PACKAGE" --consumer "$CONSUMER" - uses: actions/upload-artifact@v4 if: always() with: diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index d4c4f9d9..c0b360de 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -5,7 +5,6 @@ import argparse import json -import os import re import shutil import subprocess @@ -13,7 +12,7 @@ import tomllib from pathlib import Path -from xpkgindex_update.manifest import assert_preserved, inventory +from xpkgindex_update.manifest import assert_preserved, inventory, version_blocks ROOT = Path(__file__).resolve().parents[2] @@ -31,6 +30,12 @@ def audit(base: str, output: Path): print(f"All {len(records)} GitHub-direct descriptors declared; all existing versions unchanged.") +def selection(base: str, output: Path): + changed = subprocess.check_output(["git", "diff", "--name-only", base, "HEAD", "--", "pkgs"], cwd=ROOT, text=True).splitlines() + selected = [info["id"] for info in inventory(ROOT) if info["path"] in changed and info["autoupdate"]] + output.write_text(selected[0] if len(selected) == 1 else "") + + def direct_member(identity: str) -> str | None: namespace, name = identity.split(":", 1) candidates = [] @@ -46,10 +51,34 @@ def direct_member(identity: str) -> str | None: return min(candidates, key=lambda value: (len(value), value)) -def matrix(plan: Path, output: Path): +def matrix(plan: Path, output: Path, base: str): + build_plan = json.loads(plan.read_text()) + # A normal bump PR already contains its candidate, so discovery says up-to-date. + # Derive added versions from the actual diff and test those exact published entries. + if base: + for info in inventory(ROOT): + old_text = subprocess.check_output(["git", "show", f"{base}:{info['path']}"], cwd=ROOT).decode() + old = version_blocks(old_text) + new = version_blocks(info["text"]) + added = {v for plat, entries in new.items() for v in entries if v not in old.get(plat, {})} + if not added: + continue + if len(added) != 1: + raise RuntimeError(f"expected one added candidate for {info['id']}: {added}") + candidate = added.pop() + assets = [] + for plat, platform in info["platforms"].items(): + if candidate not in platform["entries"]: + raise RuntimeError("candidate missing on a platform") + entry = platform["entries"][candidate].plain() + urls = entry["url"] if isinstance(entry["url"], dict) else {"GLOBAL": entry["url"]} + assets.append({"platform": plat, "resources": {k: {"url": v, "sha256": entry["sha256"]} for k, v in urls.items()}}) + build_plan["packages"] = [r for r in build_plan["packages"] if r["id"] != info["id"]] + build_plan["packages"].append({"id": info["id"], "path": info["path"], + "candidate": candidate, "status": "existing-candidate", "assets": assets}) jobs = [] - for record in json.loads(plan.read_text())["packages"]: - if record["status"] != "update-ready": + for record in build_plan["packages"]: + if record["status"] not in ("update-ready", "existing-candidate"): continue member = direct_member(record["id"]) if member is None and record["id"] not in ("compat:hiredis", "compat:libuv"): @@ -66,6 +95,7 @@ def matrix(plan: Path, output: Path): "member": member or "probe", "platform": platform, "os": runner, "suffix": suffix, "ext": ext, "toolchain": toolchain}) output.write_text(json.dumps({"include": jobs})) + output.with_name("build-plan.json").write_text(json.dumps(build_plan, indent=2)) print(f"Candidate matrix: {len(jobs)} real build/run jobs.") @@ -120,7 +150,7 @@ def verify(plan: Path, identity: str, consumer: Path): def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify")) + parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection")) parser.add_argument("--base") parser.add_argument("--plan", type=Path) parser.add_argument("--package") @@ -129,8 +159,10 @@ def main(): args = parser.parse_args() if args.command == "audit": audit(args.base, args.output) + elif args.command == "selection": + selection(args.base, args.output) elif args.command == "matrix": - matrix(args.plan, args.output) + matrix(args.plan, args.output, args.base) elif args.command == "prepare": prepare(args.plan, args.package, args.output) else: From ed80f4c52160768d9dcefe06e545a5aa78c11d5e Mon Sep 17 00:00:00 2001 From: "xpkgindex-update[bot]" Date: Tue, 20 Oct 2026 00:19:20 +0900 Subject: [PATCH 05/31] chore: append compat:hiredis 1.5.0 --- pkgs/c/compat.hiredis.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkgs/c/compat.hiredis.lua b/pkgs/c/compat.hiredis.lua index 21faf0c9..f4cdfc5a 100644 --- a/pkgs/c/compat.hiredis.lua +++ b/pkgs/c/compat.hiredis.lua @@ -47,18 +47,30 @@ package = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, + ["1.5.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", + sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", + }, }, macosx = { ["1.2.0"] = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, + ["1.5.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", + sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", + }, }, windows = { ["1.2.0"] = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, + ["1.5.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", + sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", + }, }, }, From e065c8f252cfb2138e7ba8e06f7895c015a261c2 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:20:31 +0900 Subject: [PATCH 06/31] fix: include hiredis public API in the real candidate probe --- tests/autoupdate/ci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index c0b360de..ab0813a2 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -121,7 +121,7 @@ def prepare(plan: Path, identity: str, output: Path): text = f'[package]\nname = "candidate-{name}"\nversion = "0.1.0"\n\n[dependencies.{namespace}]\n{name} = "{version}"\n' (output / "tests").mkdir(exist_ok=True) probes = { - "hiredis": '#include \nint main() { auto *r = redisReaderCreate(); if (!r) return 1; redisReaderFree(r); }\n', + "hiredis": '#include \nint main() { auto *r = redisReaderCreate(); if (!r) return 1; redisReaderFree(r); }\n', "libuv": '#include \nint main() { uv_loop_t loop; if (uv_loop_init(&loop)) return 1; return uv_loop_close(&loop); }\n', } (output / "tests/probe.cpp").write_text(probes[name]) From be2b21f7180406dc563402c8a204c167b39087e3 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:22:49 +0900 Subject: [PATCH 07/31] fix: match the index runner and macOS archive naming --- tests/autoupdate/ci.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index ab0813a2..46c0ad21 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -86,8 +86,8 @@ def matrix(plan: Path, output: Path, base: str): for platform in [asset["platform"] for asset in record["assets"]]: rows = { "linux": ("ubuntu-latest", "linux-x86_64", "tar.gz"), - "macosx": ("macos-latest", "macos-arm64", "tar.gz"), - "windows": ("windows-latest", "windows-x86_64", "zip"), + "macosx": ("macos-15", "macosx-arm64", "tar.gz"), + "windows": ("windows-2022", "windows-x86_64", "zip"), } runner, suffix, ext = rows[platform] for toolchain in (["default", "llvm"] if platform == "linux" else ["default"]): From ef0f6bb72608c098a325536db737b87add970f04 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:24:59 +0900 Subject: [PATCH 08/31] fix: align candidate toolchain setup and Windows UTF-8 handling --- .github/workflows/xpkgindex-update.yml | 15 +++++++++------ tests/autoupdate/ci.py | 12 ++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index b2a6afb0..e4ecc71c 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -17,6 +17,7 @@ concurrency: cancel-in-progress: true env: + PYTHONUTF8: '1' MCPP_VERSION: '2026.10.1.2' MCPP_INDEX_MIRROR: GLOBAL @@ -37,7 +38,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: c7bea7e8c469ae0d57c9cd96debd771960374284 + ref: 0e574b1b67016d6c89c41c952f023453f2f1939e path: .updater persist-credentials: false - name: Use the exact development tool source @@ -52,7 +53,7 @@ jobs: echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" - name: Discover and download real upstream candidates id: scan - uses: openxlings/xpkgindex-update@c7bea7e8c469ae0d57c9cd96debd771960374284 + uses: openxlings/xpkgindex-update@0e574b1b67016d6c89c41c952f023453f2f1939e with: mode: check token: ${{ github.token }} @@ -101,7 +102,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: c7bea7e8c469ae0d57c9cd96debd771960374284 + ref: 0e574b1b67016d6c89c41c952f023453f2f1939e path: .updater persist-credentials: false - uses: actions/download-artifact@v4 @@ -165,10 +166,12 @@ jobs: TOOLCHAIN: ${{ matrix.toolchain }} run: | "$MCPP" index update - args=() - if [[ "$TOOLCHAIN" != default ]]; then args+=(--toolchain "$TOOLCHAIN"); fi + if [[ "$TOOLCHAIN" != default ]]; then + "$MCPP" toolchain install "$TOOLCHAIN" + "$MCPP" toolchain default "$TOOLCHAIN" + fi cd "$CONSUMER" - "$MCPP" test "${args[@]}" 2>&1 | tee candidate.log + "$MCPP" test 2>&1 | tee candidate.log - name: The lockfile proves the candidate was selected shell: bash env: diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 46c0ad21..dca65ac5 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -40,7 +40,7 @@ def direct_member(identity: str) -> str | None: namespace, name = identity.split(":", 1) candidates = [] for path in (ROOT / "tests/examples").glob("*/mcpp.toml"): - text = path.read_text() + text = path.read_text(encoding="utf-8") # Metadata-only selection; prepare() and the lock assertion verify actual resolution. if re.search(rf"(?m)^\s*{re.escape(name)}\s*=", text) and ( f"dependencies.{namespace}]" in text or f"dependencies.{namespace}." in text @@ -52,7 +52,7 @@ def direct_member(identity: str) -> str | None: def matrix(plan: Path, output: Path, base: str): - build_plan = json.loads(plan.read_text()) + build_plan = json.loads(plan.read_text(encoding="utf-8")) # A normal bump PR already contains its candidate, so discovery says up-to-date. # Derive added versions from the actual diff and test those exact published entries. if base: @@ -100,7 +100,7 @@ def matrix(plan: Path, output: Path, base: str): def prepare(plan: Path, identity: str, output: Path): - record = next(r for r in json.loads(plan.read_text())["packages"] if r["id"] == identity) + record = next(r for r in json.loads(plan.read_text(encoding="utf-8"))["packages"] if r["id"] == identity) namespace, name = identity.split(":", 1) version = record["candidate"] member = direct_member(identity) @@ -109,7 +109,7 @@ def prepare(plan: Path, identity: str, output: Path): source = ROOT / "tests/examples" / member shutil.copytree(source, output, dirs_exist_ok=True, ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock")) - text = (output / "mcpp.toml").read_text() + text = (output / "mcpp.toml").read_text(encoding="utf-8") text, replacements = re.subn( rf'(?m)^(\s*{re.escape(name)}\s*=\s*(?:\{{\s*version\s*=\s*)?")[^"]+(".*)$', lambda m: m[1] + version + m[2], text) @@ -138,9 +138,9 @@ def prepare(plan: Path, identity: str, output: Path): def verify(plan: Path, identity: str, consumer: Path): - record = next(r for r in json.loads(plan.read_text())["packages"] if r["id"] == identity) + record = next(r for r in json.loads(plan.read_text(encoding="utf-8"))["packages"] if r["id"] == identity) namespace, name = identity.split(":", 1) - lock = tomllib.loads((consumer / "mcpp.lock").read_text()) + lock = tomllib.loads((consumer / "mcpp.lock").read_text(encoding="utf-8")) matches = [value for key, value in lock.get("package", {}).items() if value.get("namespace") == namespace and key in (name, identity, f"{namespace}.{name}")] if not matches or any(v["version"] != record["candidate"] for v in matches): From 1eb56e8c1d51c4dcd8d4f92c41fb837f253c56fe Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:37:32 +0900 Subject: [PATCH 09/31] fix: support FreeType HVF and verify cross-platform update plans --- .../docs/2026-10-19-xpkgindex-update-design.md | 4 ++++ .github/workflows/xpkgindex-update.yml | 18 ++++++++++++++---- pkgs/c/compat.freetype.lua | 3 +++ tests/autoupdate/ci.py | 18 +++++++++++++++++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.agents/docs/2026-10-19-xpkgindex-update-design.md b/.agents/docs/2026-10-19-xpkgindex-update-design.md index a2f9606e..5e8527e6 100644 --- a/.agents/docs/2026-10-19-xpkgindex-update-design.md +++ b/.agents/docs/2026-10-19-xpkgindex-update-design.md @@ -464,3 +464,7 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 - compat.libmysqlclient 的既有 wellwei/libmysqlclient 上游 API 确认返回 404,保留真实 error 诊断与旧条目,等待明确的同源迁移地址。 - draft CI 全量审计声明和旧版本保留,对所有 update-ready 生成平台/工具链矩阵,在临时消费者实际构建运行并核对 mcpp.lock。 - 工具已有 Linux/macOS/Windows、Python 3.11/3.13 CI;Windows 揭示的 CRLF 哈希问题已修复并添加字节保留测试。 + +- 实际候选 CI 发现 FreeType 2.15 新增 HVF 驱动;人工追加 hvf.c union glob,2.15 与旧 2.13.3 均已本地构建运行通过,旧版本块不变。 +- Linux 生成的计划与 Windows CRLF checkout 必须可互用:仅描述符计划摘要按 LF 规范化,实际追加仍逐字保留 checkout 的旧版本块;资源 SHA256 永不规范化。 +- 候选 CI 保留 verbose 编译、link response 与 CDB 证据,用于分辨安装资源、包配置和工具链问题。 diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index e4ecc71c..15091f8f 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 0e574b1b67016d6c89c41c952f023453f2f1939e + ref: 597f2558d06eb144d2c80e1407eaaca6749e6afe path: .updater persist-credentials: false - name: Use the exact development tool source @@ -53,7 +53,7 @@ jobs: echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" - name: Discover and download real upstream candidates id: scan - uses: openxlings/xpkgindex-update@0e574b1b67016d6c89c41c952f023453f2f1939e + uses: openxlings/xpkgindex-update@597f2558d06eb144d2c80e1407eaaca6749e6afe with: mode: check token: ${{ github.token }} @@ -102,7 +102,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 0e574b1b67016d6c89c41c952f023453f2f1939e + ref: 597f2558d06eb144d2c80e1407eaaca6749e6afe path: .updater persist-credentials: false - uses: actions/download-artifact@v4 @@ -171,7 +171,7 @@ jobs: "$MCPP" toolchain default "$TOOLCHAIN" fi cd "$CONSUMER" - "$MCPP" test 2>&1 | tee candidate.log + "$MCPP" test --verbose 2>&1 | tee candidate.log - name: The lockfile proves the candidate was selected shell: bash env: @@ -185,3 +185,13 @@ jobs: ${{ env.CONSUMER }}/candidate.log ${{ env.CONSUMER }}/mcpp.lock ${{ env.CONSUMER }}/mcpp.toml + - name: Collect compiler and linker evidence + if: always() + shell: bash + run: python tests/autoupdate/ci.py diagnostics --consumer "$CONSUMER" --output candidate-diagnostics + - uses: actions/upload-artifact@v4 + if: always() + with: + name: candidate-diagnostics-${{ strategy.job-index }} + path: candidate-diagnostics + if-no-files-found: ignore diff --git a/pkgs/c/compat.freetype.lua b/pkgs/c/compat.freetype.lua index b69ffc08..56706faa 100644 --- a/pkgs/c/compat.freetype.lua +++ b/pkgs/c/compat.freetype.lua @@ -76,6 +76,9 @@ package = { "*/src/cid/type1cid.c", "*/src/cache/ftcache.c", "*/src/gzip/ftgzip.c", + -- 2.15 registers the HVF driver in ftmodule.h; this aggregate is + -- absent (and the glob unmatched) on the preserved 2.13.3 release. + "*/src/hvf/hvf.c", "*/src/lzw/ftlzw.c", "*/src/pcf/pcf.c", "*/src/pfr/pfr.c", diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index dca65ac5..00632cba 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -5,6 +5,7 @@ import argparse import json +import os import re import shutil import subprocess @@ -148,9 +149,22 @@ def verify(plan: Path, identity: str, consumer: Path): print(f"PROVEN: build resolved {identity}@{record['candidate']}") +def diagnostics(consumer: Path, output: Path): + output.mkdir(parents=True, exist_ok=True) + for directory, dirs, files in os.walk(consumer): + dirs[:] = [name for name in dirs if name not in (".xlings", ".git")] + for name in files: + if name in ("compile_commands.json", "build.ninja") or name.endswith(".rsp"): + path = Path(directory) / name + if path.stat().st_size <= 8 * 1024 * 1024: + destination = output / path.relative_to(consumer) + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(path, destination) + + def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection")) + parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection", "diagnostics")) parser.add_argument("--base") parser.add_argument("--plan", type=Path) parser.add_argument("--package") @@ -165,6 +179,8 @@ def main(): matrix(args.plan, args.output, args.base) elif args.command == "prepare": prepare(args.plan, args.package, args.output) + elif args.command == "diagnostics": + diagnostics(args.consumer, args.output) else: verify(args.plan, args.package, args.consumer) From 49099f9009d5c9bcb00c95997fc71c1f78966bbd Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:46:49 +0900 Subject: [PATCH 10/31] test: isolate Windows candidate failures from the legacy client --- .github/workflows/xpkgindex-update.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 15091f8f..1b28c61a 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -177,6 +177,17 @@ jobs: env: PACKAGE: ${{ matrix.id }} run: python tests/autoupdate/ci.py verify --plan .candidate-plan/build-plan.json --package "$PACKAGE" --consumer "$CONSUMER" + - name: Diagnose a Windows failure with the current released client + if: failure() && runner.os == 'Windows' && env.MCPP != '' + shell: bash + run: | + # Diagnostic only: the original failing step still makes the job fail. + version=2026.10.19.1 + root="$RUNNER_TEMP/mcpp-${version}-windows-x86_64" + curl -fsSL --retry 2 -o "$root.zip" "https://github.com/mcpp-community/mcpp/releases/download/v${version}/mcpp-${version}-windows-x86_64.zip" + powershell -NoProfile -Command "Expand-Archive -Force -Path '$root.zip' -DestinationPath '$RUNNER_TEMP'" + cd "$CONSUMER" + "$root/bin/mcpp.exe" test --verbose 2>&1 | tee modern-client.log - uses: actions/upload-artifact@v4 if: always() with: @@ -185,6 +196,7 @@ jobs: ${{ env.CONSUMER }}/candidate.log ${{ env.CONSUMER }}/mcpp.lock ${{ env.CONSUMER }}/mcpp.toml + ${{ env.CONSUMER }}/modern-client.log - name: Collect compiler and linker evidence if: always() shell: bash From 2559443e898c93f2df9f0f386b076e692495a84f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 00:59:52 +0900 Subject: [PATCH 11/31] test: record Windows payload and source-glob evidence --- .github/workflows/xpkgindex-update.yml | 4 ++++ tests/autoupdate/ci.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 1b28c61a..9eab9df5 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -164,8 +164,12 @@ jobs: shell: bash env: TOOLCHAIN: ${{ matrix.toolchain }} + PACKAGE: ${{ matrix.id }} run: | "$MCPP" index update + if [[ "$PACKAGE" == compat:hiredis ]]; then + "$MCPP" xpkg parse pkgs/c/compat.hiredis.lua --all-os + fi if [[ "$TOOLCHAIN" != default ]]; then "$MCPP" toolchain install "$TOOLCHAIN" "$MCPP" toolchain default "$TOOLCHAIN" diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 00632cba..c6bf253b 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -160,6 +160,14 @@ def diagnostics(consumer: Path, output: Path): destination = output / path.relative_to(consumer) destination.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(path, destination) + payload = consumer / ".mcpp/.xlings/data/xpkgs/compat-x-hiredis/1.5.0" + if payload.exists(): + evidence = {"path": str(payload), "resolved": str(payload.resolve()), + "children": [str(p.relative_to(payload)) for p in payload.iterdir()], + "c_files": [{"path": str(p), "resolved": str(p.resolve()), "size": p.stat().st_size} + for p in payload.glob("*/*.c")], + "alloc_matches": [str(p) for p in payload.glob("*/alloc.c")]} + (output / "hiredis-payload.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8") def main(): From 94c18cfa5b42f5a4227dfb1125404bfa733bffa2 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:08:40 +0900 Subject: [PATCH 12/31] test: preserve the installed descriptor in candidate diagnostics --- tests/autoupdate/ci.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index c6bf253b..a37cc001 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -168,6 +168,9 @@ def diagnostics(consumer: Path, output: Path): for p in payload.glob("*/*.c")], "alloc_matches": [str(p) for p in payload.glob("*/alloc.c")]} (output / "hiredis-payload.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8") + for name in (".xpkg.lua", ".xpkg-install.json"): + if (payload / name).is_file(): + shutil.copyfile(payload / name, output / ("hiredis-" + name.lstrip("."))) def main(): From 30eb4a59c89106fc6fb38be947661b61a740c075 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:10:11 +0900 Subject: [PATCH 13/31] test: isolate Windows source planning with bounded layout probes --- .github/workflows/xpkgindex-update.yml | 11 +++++++++++ tests/autoupdate/ci.py | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 9eab9df5..62ac3d77 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -205,6 +205,17 @@ jobs: if: always() shell: bash run: python tests/autoupdate/ci.py diagnostics --consumer "$CONSUMER" --output candidate-diagnostics + - name: Isolate Windows cache and workspace planning + if: failure() && runner.os == 'Windows' && matrix.id == 'compat:hiredis' + shell: bash + run: python tests/autoupdate/ci.py experiment --consumer "$CONSUMER" --output candidate-experiment + - uses: actions/upload-artifact@v4 + if: always() && runner.os == 'Windows' && matrix.id == 'compat:hiredis' + with: + name: candidate-experiment-${{ strategy.job-index }} + path: | + candidate-experiment/*.log + if-no-files-found: ignore - uses: actions/upload-artifact@v4 if: always() with: diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index a37cc001..73d4f3a5 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -173,9 +173,27 @@ def diagnostics(consumer: Path, output: Path): shutil.copyfile(payload / name, output / ("hiredis-" + name.lstrip("."))) +def experiment(consumer: Path, output: Path): + """Keep the primary failure; isolate cache and test-layout variables.""" + output.mkdir(parents=True, exist_ok=True) + mcpp = os.environ["MCPP"] + with (output / "no-cache.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, + stdout=log, stderr=subprocess.STDOUT) + print("standalone without cache:", result.returncode) + workspace = output / "workspace" + shutil.copytree(consumer, workspace / "consumer", dirs_exist_ok=True, + ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock", "*.log")) + (workspace / "mcpp.toml").write_text('[workspace]\nmembers = ["consumer"]\n', encoding="utf-8") + with (output / "workspace.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "-p", "consumer", "--verbose", "--cache", "off"], + cwd=workspace, stdout=log, stderr=subprocess.STDOUT) + print("workspace without cache:", result.returncode) + + def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection", "diagnostics")) + parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection", "diagnostics", "experiment")) parser.add_argument("--base") parser.add_argument("--plan", type=Path) parser.add_argument("--package") @@ -192,6 +210,8 @@ def main(): prepare(args.plan, args.package, args.output) elif args.command == "diagnostics": diagnostics(args.consumer, args.output) + elif args.command == "experiment": + experiment(args.consumer, args.output) else: verify(args.plan, args.package, args.consumer) From c438861925f77fcaa9797565e7ca847228cd792b Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:25:38 +0900 Subject: [PATCH 14/31] fix(ci): adapt OpenMP runtime build and isolate Windows source discovery --- pkgs/c/compat.openmp.lua | 10 +++++++--- tests/autoupdate/ci.py | 40 +++++++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/pkgs/c/compat.openmp.lua b/pkgs/c/compat.openmp.lua index 1956dbd8..b1877c8a 100644 --- a/pkgs/c/compat.openmp.lua +++ b/pkgs/c/compat.openmp.lua @@ -128,9 +128,13 @@ function install() options = options .. " -DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++" .. " -DCMAKE_ASM_COMPILER=/usr/bin/cc -DCMAKE_MAKE_PROGRAM=/usr/bin/make" end - local configure = quote(cmake) .. " -S " .. quote(path.join(prefix, "openmp")) + -- LLVM 23 removed openmp's legacy standalone entry point. The runtimes + -- entry point also supports the preserved LLVM 22 source release. + local configure = quote(cmake) .. " -S " .. quote(path.join(prefix, "runtimes")) .. " -B " .. quote(build) .. " -DCMAKE_BUILD_TYPE=Release" .. " -DCMAKE_INSTALL_PREFIX=" .. quote(output) + .. " -DLLVM_ENABLE_RUNTIMES=openmp -DLLVM_INCLUDE_TESTS=OFF" + .. " -DOPENMP_INSTALL_LIBDIR=lib" .. " -DPython3_EXECUTABLE=" .. quote(python) .. " -DOPENMP_ENABLE_LIBOMPTARGET=OFF -DOPENMP_ENABLE_OMPT_TOOLS=OFF" .. " -DLIBOMP_OMPT_SUPPORT=OFF -DLIBOMP_USE_ITT_NOTIFY=OFF" .. options @@ -140,8 +144,8 @@ function install() -- The Visual Studio generator can omit the archive of upstream's -- source-less ompimp target. The DLL link already produced an import -- library for these exact exports; give install() its expected name. - local expected = path.join(build, "runtime", "src", "Release", "libomp.lib") - local linked = path.join(build, "runtime", "src", "Release", "libomp.dll.lib") + local expected = path.join(build, "openmp", "runtime", "src", "Release", "libomp.lib") + local linked = path.join(build, "openmp", "runtime", "src", "Release", "libomp.dll.lib") if not os.isfile(expected) and os.isfile(linked) then os.cp(linked, expected) end diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 73d4f3a5..157b6d60 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -174,21 +174,35 @@ def diagnostics(consumer: Path, output: Path): def experiment(consumer: Path, output: Path): - """Keep the primary failure; isolate cache and test-layout variables.""" + """Keep the primary failure; isolate source discovery in the job checkout.""" output.mkdir(parents=True, exist_ok=True) mcpp = os.environ["MCPP"] - with (output / "no-cache.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, - stdout=log, stderr=subprocess.STDOUT) - print("standalone without cache:", result.returncode) - workspace = output / "workspace" - shutil.copytree(consumer, workspace / "consumer", dirs_exist_ok=True, - ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock", "*.log")) - (workspace / "mcpp.toml").write_text('[workspace]\nmembers = ["consumer"]\n', encoding="utf-8") - with (output / "workspace.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "-p", "consumer", "--verbose", "--cache", "off"], - cwd=workspace, stdout=log, stderr=subprocess.STDOUT) - print("workspace without cache:", result.returncode) + descriptor = ROOT / "pkgs/c/compat.hiredis.lua" + original = descriptor.read_bytes() + try: + # Temporary diagnostic only: leave all version blocks untouched and + # restore the descriptor even when the experiment fails. + changed = original + for name in ("alloc", "async", "hiredis", "net", "read", "sds", "sockcompat"): + changed = changed.replace(f'"*/{name}.c"'.encode(), + f'"hiredis-1.5.0/{name}.c"'.encode()) + descriptor.write_bytes(changed) + with (output / "literal-sources.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, + stdout=log, stderr=subprocess.STDOUT) + print("literal sources:", result.returncode) + finally: + descriptor.write_bytes(original) + probe = consumer / "tests/probe.cpp" + source = probe.read_bytes() + try: + probe.write_bytes(b"import std;\n" + source) + with (output / "import-std.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, + stdout=log, stderr=subprocess.STDOUT) + print("root imports std (diagnostic only):", result.returncode) + finally: + probe.write_bytes(source) def main(): From 3384f86226dbfda2ee0502c9fb95ae5fe073361f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:27:45 +0900 Subject: [PATCH 15/31] fix(ci): supply nanodbc macOS ODBC development headers --- .github/workflows/xpkgindex-update.yml | 3 +++ pkgs/c/compat.nanodbc.lua | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 62ac3d77..f53215f0 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -160,6 +160,9 @@ jobs: else echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" fi + - name: Install macOS ODBC development headers + if: runner.os == 'macOS' && matrix.id == 'compat:nanodbc' + run: brew install libiodbc - name: Resolve, build and run the actual candidate shell: bash env: diff --git a/pkgs/c/compat.nanodbc.lua b/pkgs/c/compat.nanodbc.lua index 3d642b89..a6b3576d 100644 --- a/pkgs/c/compat.nanodbc.lua +++ b/pkgs/c/compat.nanodbc.lua @@ -10,7 +10,8 @@ -- ODBC driver manager and cannot run without one: -- -- * windows — the SDK's odbc32 is always there; just link it. --- * macOS — iODBC is part of the OS; just link it. +-- * macOS — link the OS iODBC runtime. Its development headers are no +-- longer in recent SDKs; install Homebrew libiodbc for those headers. -- * linux — the manager (unixODBC) is a third-party library, and mcpp's -- runtime closure rejects binaries whose NEEDED libodbc.so.2 only exists -- on the host. compat.unixodbc therefore builds the manager STATICALLY @@ -149,7 +150,12 @@ struct char_traits { cxxflags = { "-fpermissive" }, }, macosx = { - -- iODBC driver manager, part of the OS. + -- iODBC runtime is in the OS, but recent SDKs omit sql.h. + -- Homebrew's keg-only development headers (Apple Silicon/Intel). + include_dirs = { + "/opt/homebrew/opt/libiodbc/include", + "/usr/local/opt/libiodbc/include", + }, ldflags = { "-liodbc" }, }, windows = { From f35f466c2c88e91e96cb64ec0176336b71d00a16 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:29:22 +0900 Subject: [PATCH 16/31] test(ci): compare registry and path loading of identical hiredis payload --- tests/autoupdate/ci.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 157b6d60..e7ca3566 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -203,6 +203,30 @@ def experiment(consumer: Path, output: Path): print("root imports std (diagnostic only):", result.returncode) finally: probe.write_bytes(source) + # A path manifest over the very same installed files distinguishes Lua + # descriptor acquisition from the scanner/backend, without another download. + payload = consumer / ".mcpp/.xlings/data/xpkgs/compat-x-hiredis/1.5.0" + manifest = payload / "mcpp.toml" + root_manifest = consumer / "mcpp.toml" + root_source = root_manifest.read_bytes() + try: + manifest.write_text( + '[package]\nname = "hiredis"\nnamespace = "compat"\nversion = "1.5.0"\n' + '[build]\ninclude_dirs = ["*", "mcpp_generated/include"]\n' + 'sources = ["*/alloc.c", "*/async.c", "*/hiredis.c", "*/net.c", ' + '"*/read.c", "*/sds.c", "*/sockcompat.c"]\n' + 'cflags = ["-D_CRT_SECURE_NO_WARNINGS", "-DWIN32_LEAN_AND_MEAN"]\n' + 'ldflags = ["-lws2_32", "-lcrypt32"]\n', encoding="utf-8") + text = root_source.decode().replace('hiredis = "1.5.0"', + 'hiredis = { path = ' + json.dumps(payload.as_posix()) + ' }') + root_manifest.write_text(text, encoding="utf-8") + with (output / "path-manifest.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, + stdout=log, stderr=subprocess.STDOUT) + print("same payload through a path manifest:", result.returncode) + finally: + root_manifest.write_bytes(root_source) + manifest.unlink(missing_ok=True) def main(): From 358792e278e1c81c9794f7f436081b29bba99834 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:31:06 +0900 Subject: [PATCH 17/31] docs: reconcile updater design with real integration evidence --- .../2026-10-19-xpkgindex-update-design.md | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.agents/docs/2026-10-19-xpkgindex-update-design.md b/.agents/docs/2026-10-19-xpkgindex-update-design.md index 5e8527e6..36fb7d05 100644 --- a/.agents/docs/2026-10-19-xpkgindex-update-design.md +++ b/.agents/docs/2026-10-19-xpkgindex-update-design.md @@ -54,7 +54,7 @@ GitHub URL。这不是远端最终接入数量:其中包含历史条目、混 - 优先扫描 GitHub 已发布的 stable releases,排除 draft、prerelease;无匹配 release 的 tag-only 仓库可查询 tags,按声明模板与版本语法筛选。tag 不能提供稳定性标记,报告标注发现来源。 -- 版本是至少两段的点分非负整数,例如 `1.2`、`1.2.3`、`2026.10.19.1`。 +- 版本支持数字点分(包括 calver)、b 编号,以及明确排序的单字母稳定后缀;不猜测任意字符串的版本关系。 - tag 由固定前缀、`${version}`、固定后缀组成,例如 `v${version}`。 - 按整数分段比较版本,缺少的尾段按零比较;等价但拼写不同的候选视为歧义并报错。 - 下载 URL 是显式模板;资源已经发布;每个安装资源使用单个字符串 SHA256。 @@ -67,7 +67,7 @@ GitHub URL。这不是远端最终接入数量:其中包含历史条目、混 ### 3.2 不支持 -- commit、任意 tag、预发布版本、`b12345`、发行版后缀等非数字点分版本。 +- 自动推进 commit 快照、任意 tag、预发布版本和无明确排序的发行版后缀。 - 自动回填历史 release、清理旧版本、升级依赖约束、批量联动多个包。 - 架构分发表、per-arch SHA256、`XLINGS_RES`/`res_versioned` 等额外资源形态。 - 需要修改 shim、sources、generated_files、安装 hook 或 build.mcpp 的更新。 @@ -109,9 +109,9 @@ package = { | `tag` | 一个且仅一个 `${version}`;其余字符按字面量匹配,不是正则或 Lua pattern | | `url` | 所有已有平台共用的资源模板,或平台到资源模板的映射 | -允许的占位符只有 `${version}`、`${tag}`。不认识的占位符报错。 +允许的占位符为 `${version}`、`${version:dash}`、`${tag}`。dash 只把点分版本转换为横线分隔,用于 FreeType。其他占位符报错。 `${tag}` 保留 API 返回的原始拼写,`${version}` 是从 tag 提取的版本。 -默认查询 `package.repo`,首版不提供 provider/repo 覆盖字段。 +优先从显式 GitHub 下载模板确定查询仓库;模板不指定 GitHub 仓库时回退 `package.repo`。首版不增加 provider/repo 覆盖字段。 不同平台的写法: @@ -190,7 +190,7 @@ jobs: token: ${{ secrets.UPDATE_TOKEN }} ``` -这是正式发布后的接口草案,Action 尚不存在。开发联调使用开发分支的完整 commit SHA, +这是正式发布后的接口草案。开发 Action 已在 draft PR 实现,开发联调使用完整 commit SHA, 不提前发布或移动 v1;composite Action 从自身 GITHUB_ACTION_PATH 执行该提交的源码, 不能安装一个与开发提交无关的已发布 PyPI 版本。 输入只有 `mode`(check/propose)、`token`(propose 必需)、可选 `package`。 @@ -452,7 +452,7 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 | R9 | 每包一个打开的更新 PR | 新 release 到来时不堆 PR、不自动改写维护者正在 review 的分支;关闭过的同版本不自动重开 | | R10 | GitHub App 优先,PAT 可过渡 | Action 自己配置 git 写认证;checkout 不保留默认凭证;不把 token 暴露给包安装测试 | -本次交付仍是设计修订与自审。下一步按上述开发分支和 draft PR 路径实现;待维护者 review 后推进。 +设计约束与执行证据分开记录;下节列出实际进度,不能用设计自审代替 CI 通过。 ## 13. 实现时核实的范围与调整 @@ -468,3 +468,13 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 - 实际候选 CI 发现 FreeType 2.15 新增 HVF 驱动;人工追加 hvf.c union glob,2.15 与旧 2.13.3 均已本地构建运行通过,旧版本块不变。 - Linux 生成的计划与 Windows CRLF checkout 必须可互用:仅描述符计划摘要按 LF 规范化,实际追加仍逐字保留 checkout 的旧版本块;资源 SHA256 永不规范化。 - 候选 CI 保留 verbose 编译、link response 与 CDB 证据,用于分辨安装资源、包配置和工具链问题。 + +### 13.1 当前真实验证进度(2026-10-20 JST) + +- 工具 draft:[openxlings/xpkgindex-update #1](https://github.com/openxlings/xpkgindex-update/pull/1);标准 src 布局、简约 README、45 项回归测试与六种 OS/Python CI 组合。 +- 接入 draft:[mcpp-index #543](https://github.com/mcpplibs/mcpp-index/pull/543);真实 propose draft:[mcpp-index #544](https://github.com/mcpplibs/mcpp-index/pull/544),hiredis 1.5.0,重复 propose 返回 pending。 +- OpenMP 23 移除 standalone 配置入口,人工将构建配方改为 runtimes;新 23.1.3 与旧 22.1.8 均完成本地静态库构建、安装和并行归约运行测试;仍需远端全平台验证。 +- nanodbc 的 macOS 候选暴露 SDK 缺少 sql.h;明确使用 Homebrew libiodbc 开发头文件,CI 安装该前置依赖,等待实际重跑结果。 +- Windows hiredis/libuv 候选存在真实链接失败;禁用缓存、独立/虚拟 workspace、显式源码路径及 import std 均未解决,不据此降级测试或声称通过。继续比较同一 payload 的 registry 与 path manifest 加载。 +- 旧版本保留与 82 包声明覆盖审计通过;扫描的 libmysqlclient 404 仍是可见错误,需要同源迁移信息才能恢复更新。 +- 开发期不发布稳定标签、不启用定时写入、不合并 PR;最终验收仍要求所有实际候选矩阵和既有 CI gates 通过。 From 7e485069a3dc9cba3ae5f6d53e59e0c4617603bf Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:33:24 +0900 Subject: [PATCH 18/31] ci: pin tested updater commit and stop candidate jobs on cancellation --- .github/workflows/xpkgindex-update.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index f53215f0..9a51a60e 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 597f2558d06eb144d2c80e1407eaaca6749e6afe + ref: 416a954c67dba4cdf52a033a71ee88fdec0acf1d path: .updater persist-credentials: false - name: Use the exact development tool source @@ -53,7 +53,7 @@ jobs: echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" - name: Discover and download real upstream candidates id: scan - uses: openxlings/xpkgindex-update@597f2558d06eb144d2c80e1407eaaca6749e6afe + uses: openxlings/xpkgindex-update@416a954c67dba4cdf52a033a71ee88fdec0acf1d with: mode: check token: ${{ github.token }} @@ -84,7 +84,7 @@ jobs: name: candidate (${{ matrix.id }} ${{ matrix.candidate }}, ${{ matrix.platform }}, ${{ matrix.toolchain }}) needs: scan if: >- - always() && needs.scan.outputs.matrix != '' && needs.scan.outputs.matrix != '{"include": []}' + always() && !cancelled() && needs.scan.outputs.matrix != '' && needs.scan.outputs.matrix != '{"include": []}' strategy: fail-fast: false max-parallel: 8 @@ -102,7 +102,7 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 597f2558d06eb144d2c80e1407eaaca6749e6afe + ref: 416a954c67dba4cdf52a033a71ee88fdec0acf1d path: .updater persist-credentials: false - uses: actions/download-artifact@v4 From 8684ade64fd18425adcb9ee6750890d77163e2c8 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:34:55 +0900 Subject: [PATCH 19/31] test(nanodbc): exercise preserved version on macOS and Windows --- .github/workflows/validate.yml | 4 ++++ tests/examples/nanodbc/mcpp.toml | 8 +++----- tests/examples/nanodbc/tests/error_path.cpp | 9 ++++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 936d40f3..c1ed8a20 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1499,6 +1499,10 @@ jobs: run: | "$MCPP" index update + - name: Install nanodbc macOS development headers + if: runner.os == 'macOS' && contains(format(' {0} ', env.MEMBERS), ' nanodbc ') + run: brew install libiodbc + - name: mcpp test (workspace or affected members) shell: bash env: diff --git a/tests/examples/nanodbc/mcpp.toml b/tests/examples/nanodbc/mcpp.toml index 3ec54a5d..84e049dd 100644 --- a/tests/examples/nanodbc/mcpp.toml +++ b/tests/examples/nanodbc/mcpp.toml @@ -5,15 +5,13 @@ # not exist must throw nanodbc::database_error carrying the manager's own # diagnostics. # -# Gated to linux: windows (-lodbc32, Windows SDK) and macOS (-liodbc) are -# declared in the descriptor for consumers but are not exercised by this -# index's CI -- the linux leg is the one where the system driver manager is -# guaranteed present. +# Windows uses the SDK driver manager. macOS uses the system iODBC runtime +# and requires libiodbc development headers (brew install libiodbc). [package] name = "nanodbc-tests" version = "0.1.0" -[target.'cfg(linux)'.dependencies.compat] +[dependencies.compat] nanodbc = "2.14.0" [target.'cfg(linux)'.build] diff --git a/tests/examples/nanodbc/tests/error_path.cpp b/tests/examples/nanodbc/tests/error_path.cpp index f25e7da0..d5d01a1f 100644 --- a/tests/examples/nanodbc/tests/error_path.cpp +++ b/tests/examples/nanodbc/tests/error_path.cpp @@ -56,6 +56,13 @@ int main() { #else -int main() { return 0; } // non-linux: dependency is cfg-gated out, nothing to test +#include + +// Also exercise real allocation, linking and destruction on macOS/Windows. +// No database driver or running database is needed for an idle connection. +int main() { + nanodbc::connection connection; + return connection.connected() ? 1 : 0; +} #endif From 0468e191235a0aa5ff70564a8837b0093aac325b Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:35:49 +0900 Subject: [PATCH 20/31] test(ci): isolate source glob expansion with absolute paths --- tests/autoupdate/ci.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index e7ca3566..e805b9e5 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -224,6 +224,15 @@ def experiment(consumer: Path, output: Path): result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, stdout=log, stderr=subprocess.STDOUT) print("same payload through a path manifest:", result.returncode) + text = manifest.read_text(encoding="utf-8") + for name in ("alloc", "async", "hiredis", "net", "read", "sds", "sockcompat"): + text = text.replace(f'"*/{name}.c"', + json.dumps((payload / "hiredis-1.5.0" / f"{name}.c").as_posix())) + manifest.write_text(text, encoding="utf-8") + with (output / "absolute-sources.log").open("w", encoding="utf-8") as log: + result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, + stdout=log, stderr=subprocess.STDOUT) + print("absolute sources bypassing glob expansion:", result.returncode) finally: root_manifest.write_bytes(root_source) manifest.unlink(missing_ok=True) From 853cf4eee020bc41352b350602a6b986afe051ff Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:43:40 +0900 Subject: [PATCH 21/31] test(ci): inspect Windows native and generic source traversal --- tests/autoupdate/ci.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index e805b9e5..f5868a06 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -229,12 +229,31 @@ def experiment(consumer: Path, output: Path): text = text.replace(f'"*/{name}.c"', json.dumps((payload / "hiredis-1.5.0" / f"{name}.c").as_posix())) manifest.write_text(text, encoding="utf-8") + probe.write_text('''#include +#include +int main() { + namespace fs = std::filesystem; + fs::path generic(ROOT_PATH); + auto native = generic; + native.make_preferred(); + for (const auto& root : {generic, native}) { + std::cout << "ROOT " << root << " canonical=" << fs::canonical(root) << '\\n'; + for (const auto& entry : fs::recursive_directory_iterator(root)) { + if (!entry.is_regular_file() || entry.path().extension() != ".c") continue; + auto relative = entry.path().lexically_relative(root); + std::cout << "FILE " << entry.path() << " relative=" << relative + << " generic=" << relative.generic_string() << '\\n'; + } + } +} +'''.replace('ROOT_PATH', json.dumps(payload.as_posix())), encoding="utf-8") with (output / "absolute-sources.log").open("w", encoding="utf-8") as log: result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, stdout=log, stderr=subprocess.STDOUT) print("absolute sources bypassing glob expansion:", result.returncode) finally: root_manifest.write_bytes(root_source) + probe.write_bytes(source) manifest.unlink(missing_ok=True) From 9aa17f50abf4cb825ce18783fcdb9ad98891e4a7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:48:19 +0900 Subject: [PATCH 22/31] test(ci): report filesystem traversal errors without buffered streams --- tests/autoupdate/ci.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index f5868a06..2048c3ce 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -230,20 +230,33 @@ def experiment(consumer: Path, output: Path): json.dumps((payload / "hiredis-1.5.0" / f"{name}.c").as_posix())) manifest.write_text(text, encoding="utf-8") probe.write_text('''#include -#include +#include +#include int main() { namespace fs = std::filesystem; - fs::path generic(ROOT_PATH); - auto native = generic; - native.make_preferred(); - for (const auto& root : {generic, native}) { - std::cout << "ROOT " << root << " canonical=" << fs::canonical(root) << '\\n'; - for (const auto& entry : fs::recursive_directory_iterator(root)) { - if (!entry.is_regular_file() || entry.path().extension() != ".c") continue; - auto relative = entry.path().lexically_relative(root); - std::cout << "FILE " << entry.path() << " relative=" << relative - << " generic=" << relative.generic_string() << '\\n'; + try { + fs::path generic(ROOT_PATH); + auto native = generic; + native.make_preferred(); + for (const auto& root : {generic, native}) { + std::printf("ROOT %s\\n", root.string().c_str()); + std::fflush(stdout); + std::error_code ec, entry_error; + fs::recursive_directory_iterator it(root, fs::directory_options::follow_directory_symlink, ec), end; + for (; !ec && it != end; it.increment(ec)) { + auto regular = it->is_regular_file(entry_error); + if (entry_error) std::printf("ENTRY ERROR %s: %s\\n", it->path().string().c_str(), entry_error.message().c_str()); + if (!regular || it->path().extension() != ".c") continue; + auto relative = it->path().lexically_relative(root); + std::printf("FILE %s relative=%s generic=%s\\n", it->path().string().c_str(), relative.string().c_str(), relative.generic_string().c_str()); + std::fflush(stdout); + } + std::printf("WALK ERROR %d %s\\n", ec.value(), ec.message().c_str()); + std::fflush(stdout); } + } catch (const std::exception& error) { + std::fprintf(stderr, "EXCEPTION %s\\n", error.what()); + return 1; } } '''.replace('ROOT_PATH', json.dumps(payload.as_posix())), encoding="utf-8") From 1e723e0618789b5cb092850d0f637e7cab5306f9 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:58:10 +0900 Subject: [PATCH 23/31] fix(packages): retain source discovery and current toolchain compatibility --- pkgs/c/compat.hiredis.lua | 24 +++++++++++++----------- pkgs/c/compat.libuv.lua | 4 +++- pkgs/c/compat.nanodbc.lua | 1 + pkgs/o/odygrd.quill.lua | 9 +++++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/pkgs/c/compat.hiredis.lua b/pkgs/c/compat.hiredis.lua index 21faf0c9..705fe1e2 100644 --- a/pkgs/c/compat.hiredis.lua +++ b/pkgs/c/compat.hiredis.lua @@ -72,17 +72,12 @@ package = { -- `mcpp_generated/include` carries the `hiredis/`-prefixed wrappers. include_dirs = { "*", "mcpp_generated/include" }, - -- Upstream CMakeLists' `hiredis_sources`, verbatim — identical across - -- every 1.x release. - sources = { - "*/alloc.c", - "*/async.c", - "*/hiredis.c", - "*/net.c", - "*/read.c", - "*/sds.c", - "*/sockcompat.c", - }, + -- One wrapper per upstream TU preserves the original seven-source + -- build while bounding discovery to this generated directory. + -- hiredis 1.5 ships a .claude documentation symlink that Windows + -- cannot traverse; walking the whole archive stops before its C files. + -- No upstream file, installed metadata or archive is rewritten. + sources = { "mcpp_generated/src/*.c" }, targets = { ["hiredis"] = { kind = "lib" } }, deps = {}, @@ -93,6 +88,13 @@ package = { -- skip the wrapper's own directory and land on the real headers via -- the `*` include dir (the flat `"..."` form would self-include). generated_files = { + ["mcpp_generated/src/alloc.c"] = "#include \n", + ["mcpp_generated/src/async.c"] = "#include \n", + ["mcpp_generated/src/hiredis.c"] = "#include \n", + ["mcpp_generated/src/net.c"] = "#include \n", + ["mcpp_generated/src/read.c"] = "#include \n", + ["mcpp_generated/src/sds.c"] = "#include \n", + ["mcpp_generated/src/sockcompat.c"] = "#include \n", ["mcpp_generated/include/hiredis/hiredis.h"] = "#pragma once\n#include \n", ["mcpp_generated/include/hiredis/async.h"] = "#pragma once\n#include \n", -- redis-plus-plus's async build (event_loop.cpp) includes diff --git a/pkgs/c/compat.libuv.lua b/pkgs/c/compat.libuv.lua index a0c8c228..57e05993 100644 --- a/pkgs/c/compat.libuv.lua +++ b/pkgs/c/compat.libuv.lua @@ -116,7 +116,9 @@ package = { -- it. Unfixable package-side otherwise without shadowing -- event_loop.cpp. Regression: PR #195 windows workspace leg. cflags = { "-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0602", "-D_CRT_DECLARE_NONSTDC_NAMES=0", "-DNDEBUG" }, - ldflags = { "-lpsapi", "-luser32", "-ladvapi32", "-liphlpapi", "-luserenv", "-lws2_32", "-ldbghelp", "-lole32", "-lshell32" }, + -- libuv 1.53 uses WaitOnAddress/WakeByAddressSingle; match its + -- upstream synchronization.lib dependency. Also safe for 1.48. + ldflags = { "-lpsapi", "-luser32", "-ladvapi32", "-liphlpapi", "-luserenv", "-lws2_32", "-ldbghelp", "-lole32", "-lshell32", "-lsynchronization" }, }, }, } diff --git a/pkgs/c/compat.nanodbc.lua b/pkgs/c/compat.nanodbc.lua index a6b3576d..31128eb5 100644 --- a/pkgs/c/compat.nanodbc.lua +++ b/pkgs/c/compat.nanodbc.lua @@ -90,6 +90,7 @@ package = { #define MCPP_NANODBC_CHAR_TRAITS_H #include #include +#include // nanodbc 2.14 uses wstring_convert; codecvt no longer includes locale in libc++ 23. #include #if defined(_LIBCPP_VERSION) namespace std { diff --git a/pkgs/o/odygrd.quill.lua b/pkgs/o/odygrd.quill.lua index bbbefd2f..c25c70ec 100644 --- a/pkgs/o/odygrd.quill.lua +++ b/pkgs/o/odygrd.quill.lua @@ -67,6 +67,15 @@ function install() content = patched end + -- This descriptor builds Quill's header-backed module (QUILL_IMPORT_STD + -- is not defined). 13.1 adds an inactive conditional import, which mcpp's + -- source scanner refuses before preprocessing. Remove that known inactive + -- block from the generated interface; keep the downloaded source intact. + local optional_std = "#ifdef QUILL_IMPORT_STD\nimport std;\n #define FMTQUILL_IMPORT_STD\n#endif\n" + if content:find(optional_std, 1, true) then + patch(optional_std, "") + end + -- Clang 的资源目录在 ARM 上也含 x86 头,存在性检查不能代替目标架构判断 patch("#if !defined(__INTEL_COMPILER)\n", "#if !defined(__INTEL_COMPILER) && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))\n") From aab0adbab2ab502d6ce39cf961f0a88345e1f79c Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 01:58:21 +0900 Subject: [PATCH 24/31] test(ci): remove temporary filesystem probe after identifying hiredis failure --- tests/autoupdate/ci.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 2048c3ce..e805b9e5 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -229,44 +229,12 @@ def experiment(consumer: Path, output: Path): text = text.replace(f'"*/{name}.c"', json.dumps((payload / "hiredis-1.5.0" / f"{name}.c").as_posix())) manifest.write_text(text, encoding="utf-8") - probe.write_text('''#include -#include -#include -int main() { - namespace fs = std::filesystem; - try { - fs::path generic(ROOT_PATH); - auto native = generic; - native.make_preferred(); - for (const auto& root : {generic, native}) { - std::printf("ROOT %s\\n", root.string().c_str()); - std::fflush(stdout); - std::error_code ec, entry_error; - fs::recursive_directory_iterator it(root, fs::directory_options::follow_directory_symlink, ec), end; - for (; !ec && it != end; it.increment(ec)) { - auto regular = it->is_regular_file(entry_error); - if (entry_error) std::printf("ENTRY ERROR %s: %s\\n", it->path().string().c_str(), entry_error.message().c_str()); - if (!regular || it->path().extension() != ".c") continue; - auto relative = it->path().lexically_relative(root); - std::printf("FILE %s relative=%s generic=%s\\n", it->path().string().c_str(), relative.string().c_str(), relative.generic_string().c_str()); - std::fflush(stdout); - } - std::printf("WALK ERROR %d %s\\n", ec.value(), ec.message().c_str()); - std::fflush(stdout); - } - } catch (const std::exception& error) { - std::fprintf(stderr, "EXCEPTION %s\\n", error.what()); - return 1; - } -} -'''.replace('ROOT_PATH', json.dumps(payload.as_posix())), encoding="utf-8") with (output / "absolute-sources.log").open("w", encoding="utf-8") as log: result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, stdout=log, stderr=subprocess.STDOUT) print("absolute sources bypassing glob expansion:", result.returncode) finally: root_manifest.write_bytes(root_source) - probe.write_bytes(source) manifest.unlink(missing_ok=True) From 94c9d355c36b40d8ebfa28d22f72a453145f99e6 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 02:14:00 +0900 Subject: [PATCH 25/31] fix: adapt mcr upgrade and legacy LLVM builds; report approved source outage --- .../2026-10-19-xpkgindex-update-design.md | 15 ++-- .github/workflows/validate.yml | 4 + .github/workflows/xpkgindex-update.yml | 33 ++----- pkgs/c/compat.muduo.lua | 3 + pkgs/g/ggml-org.llamacpp.lua | 22 +++++ pkgs/y/yizhinailong.mcr.lua | 48 +++++++++- tests/autoupdate/ci.py | 89 +++---------------- tests/autoupdate/source_outage.py | 55 ++++++++++++ tests/examples/mcr/tests/api.cpp | 39 +++++++- 9 files changed, 191 insertions(+), 117 deletions(-) create mode 100644 tests/autoupdate/source_outage.py diff --git a/.agents/docs/2026-10-19-xpkgindex-update-design.md b/.agents/docs/2026-10-19-xpkgindex-update-design.md index 36fb7d05..9d2f57f8 100644 --- a/.agents/docs/2026-10-19-xpkgindex-update-design.md +++ b/.agents/docs/2026-10-19-xpkgindex-update-design.md @@ -461,7 +461,7 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 - 查询仓库优先来自显式 GitHub 下载模板,package.repo 作为回退,以覆盖下载自 GitHub 镜像但 repo 指向 GitLab 的 FreeType。 - commit 快照报告 pinned-snapshot;独立包计数到源版本的映射报告 pinned-version-map,绝不猜测跟随分支。 - SafeInt 历史上由 3.24 改为 3.0.28a;比较数字前同时过滤早于当前 release 的发布时间,避免回退。 -- compat.libmysqlclient 的既有 wellwei/libmysqlclient 上游 API 确认返回 404,保留真实 error 诊断与旧条目,等待明确的同源迁移地址。 +- compat.libmysqlclient 的既有 wellwei/libmysqlclient 上游 API 确认返回 404,保留真实 error 诊断与旧条目;用户允许本轮暂时忽略该上游不可用,CI 只豁免此仓库的明确 404,不豁免其他错误。 - draft CI 全量审计声明和旧版本保留,对所有 update-ready 生成平台/工具链矩阵,在临时消费者实际构建运行并核对 mcpp.lock。 - 工具已有 Linux/macOS/Windows、Python 3.11/3.13 CI;Windows 揭示的 CRLF 哈希问题已修复并添加字节保留测试。 @@ -473,8 +473,11 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 - 工具 draft:[openxlings/xpkgindex-update #1](https://github.com/openxlings/xpkgindex-update/pull/1);标准 src 布局、简约 README、45 项回归测试与六种 OS/Python CI 组合。 - 接入 draft:[mcpp-index #543](https://github.com/mcpplibs/mcpp-index/pull/543);真实 propose draft:[mcpp-index #544](https://github.com/mcpplibs/mcpp-index/pull/544),hiredis 1.5.0,重复 propose 返回 pending。 -- OpenMP 23 移除 standalone 配置入口,人工将构建配方改为 runtimes;新 23.1.3 与旧 22.1.8 均完成本地静态库构建、安装和并行归约运行测试;仍需远端全平台验证。 -- nanodbc 的 macOS 候选暴露 SDK 缺少 sql.h;明确使用 Homebrew libiodbc 开发头文件,CI 安装该前置依赖,等待实际重跑结果。 -- Windows hiredis/libuv 候选存在真实链接失败;禁用缓存、独立/虚拟 workspace、显式源码路径及 import std 均未解决,不据此降级测试或声称通过。继续比较同一 payload 的 registry 与 path manifest 加载。 -- 旧版本保留与 82 包声明覆盖审计通过;扫描的 libmysqlclient 404 仍是可见错误,需要同源迁移信息才能恢复更新。 -- 开发期不发布稳定标签、不启用定时写入、不合并 PR;最终验收仍要求所有实际候选矩阵和既有 CI gates 通过。 +- OpenMP 23 的 runtimes 入口修复已在候选四个平台/工具链组合实际通过,新旧版本本地测试通过。 +- Windows hiredis 的失败根因是上游 AI 配置中的 symlink 导致源码遍历 Access denied;生成七个独立 C 包装翻译单元后,真实 hiredis draft PR 的四个候选构建/运行 jobs 全通过。libuv 1.53 另需 synchronization 链接库,正在全量 CI 验证。 +- Quill 13.1 的安装适配、新旧 nanodbc 的 locale/ODBC 头修复已有本地验证;全量 CI 继续验证各平台。 +- 用户要求继续逐库适配升级:mcr 0.2 移除了 JSON API,测试保留旧版 JSON 断言并对新版验证 typed response/body/options;安装配方仅对 0.2.0 回补 libc++20 缺少的 join_with/enumerate,已有本地实际测试。 +- 旧版 muduo/llama.cpp 在 LLVM23 缺少标准头,人工补充编译参数,本地实际测试通过。工具本身仍只追加版本,不自动修改构建配方。 +- 用户明确允许暂时忽略 wellwei/libmysqlclient 的源不可用:原始扫描 error 留在 plan,单独强制 gate 仅接受该包/仓库/路径的确切 GitHub 404;libmysqlclient 和依赖它的 mysql-connector-cpp 消费者在 summary 明示 NOT TESTED,不冒充构建通过。其他错误仍失败;上游恢复后删除此临时策略。 +- 旧版本保留与 82 包声明覆盖审计通过;工具最新提交 416a954 的 14 个 checks 全部成功。接入及端到端 PR 的最终完整回归仍在进行。 +- 开发期不发布稳定标签、不启用定时写入、不合并 PR;最终验收要求除上述用户批准的源不可用豁免外,所有实际候选矩阵和既有 CI gates 通过。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c1ed8a20..c7ed8ed2 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1499,6 +1499,10 @@ jobs: run: | "$MCPP" index update + - name: Report temporarily untestable consumers of the unavailable MySQL source + shell: bash + run: python3 tests/autoupdate/source_outage.py members + - name: Install nanodbc macOS development headers if: runner.os == 'macOS' && contains(format(' {0} ', env.MEMBERS), ' nanodbc ') run: brew install libiodbc diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 9a51a60e..b71a481c 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -53,11 +53,18 @@ jobs: echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" - name: Discover and download real upstream candidates id: scan + continue-on-error: true # The mandatory gate below accepts only the approved source outage. uses: openxlings/xpkgindex-update@416a954c67dba4cdf52a033a71ee88fdec0acf1d with: mode: check token: ${{ github.token }} package: ${{ steps.coverage.outputs.package }} + - name: Reject all scan failures except the approved unavailable source + if: always() + env: + PLAN: ${{ steps.scan.outputs.plan }} + OUTCOME: ${{ steps.scan.outcome }} + run: python tests/autoupdate/source_outage.py scan-gate --plan "$PLAN" --outcome "$OUTCOME" - name: Plan actual candidate build/run jobs id: matrix if: always() && steps.scan.outputs.plan != '' @@ -170,9 +177,6 @@ jobs: PACKAGE: ${{ matrix.id }} run: | "$MCPP" index update - if [[ "$PACKAGE" == compat:hiredis ]]; then - "$MCPP" xpkg parse pkgs/c/compat.hiredis.lua --all-os - fi if [[ "$TOOLCHAIN" != default ]]; then "$MCPP" toolchain install "$TOOLCHAIN" "$MCPP" toolchain default "$TOOLCHAIN" @@ -184,17 +188,6 @@ jobs: env: PACKAGE: ${{ matrix.id }} run: python tests/autoupdate/ci.py verify --plan .candidate-plan/build-plan.json --package "$PACKAGE" --consumer "$CONSUMER" - - name: Diagnose a Windows failure with the current released client - if: failure() && runner.os == 'Windows' && env.MCPP != '' - shell: bash - run: | - # Diagnostic only: the original failing step still makes the job fail. - version=2026.10.19.1 - root="$RUNNER_TEMP/mcpp-${version}-windows-x86_64" - curl -fsSL --retry 2 -o "$root.zip" "https://github.com/mcpp-community/mcpp/releases/download/v${version}/mcpp-${version}-windows-x86_64.zip" - powershell -NoProfile -Command "Expand-Archive -Force -Path '$root.zip' -DestinationPath '$RUNNER_TEMP'" - cd "$CONSUMER" - "$root/bin/mcpp.exe" test --verbose 2>&1 | tee modern-client.log - uses: actions/upload-artifact@v4 if: always() with: @@ -203,22 +196,10 @@ jobs: ${{ env.CONSUMER }}/candidate.log ${{ env.CONSUMER }}/mcpp.lock ${{ env.CONSUMER }}/mcpp.toml - ${{ env.CONSUMER }}/modern-client.log - name: Collect compiler and linker evidence if: always() shell: bash run: python tests/autoupdate/ci.py diagnostics --consumer "$CONSUMER" --output candidate-diagnostics - - name: Isolate Windows cache and workspace planning - if: failure() && runner.os == 'Windows' && matrix.id == 'compat:hiredis' - shell: bash - run: python tests/autoupdate/ci.py experiment --consumer "$CONSUMER" --output candidate-experiment - - uses: actions/upload-artifact@v4 - if: always() && runner.os == 'Windows' && matrix.id == 'compat:hiredis' - with: - name: candidate-experiment-${{ strategy.job-index }} - path: | - candidate-experiment/*.log - if-no-files-found: ignore - uses: actions/upload-artifact@v4 if: always() with: diff --git a/pkgs/c/compat.muduo.lua b/pkgs/c/compat.muduo.lua index 29696ee0..d4c24d84 100644 --- a/pkgs/c/compat.muduo.lua +++ b/pkgs/c/compat.muduo.lua @@ -26,6 +26,9 @@ package = { mcpp = { language = "c++23", import_std = false, + -- Frozen upstream uses abort() without declaring it. Recent libc++ + -- no longer provides that declaration through unrelated headers. + cxxflags = { "-include", "cstdlib" }, include_dirs = { "*" }, sources = { "*/muduo/base/*.cc", diff --git a/pkgs/g/ggml-org.llamacpp.lua b/pkgs/g/ggml-org.llamacpp.lua index d0c87e39..cde32d3e 100644 --- a/pkgs/g/ggml-org.llamacpp.lua +++ b/pkgs/g/ggml-org.llamacpp.lua @@ -130,3 +130,25 @@ package = { -- (无 `mcpp` 字段 —— 默认查找会命中 /*/mcpp.toml) } + +import("xim.libxpkg.pkginfo") + +function install() + local wrap = "llama.cpp-m-" .. pkginfo.version() + -- This checkpoint uses getenv/atoi/strtol/free without their header. + -- Scope the explicit include to its ordinary implementation sources; + -- the exported module and the downloaded source files remain untouched. + if pkginfo.version() == "b10069.2" then + local manifest = path.join(wrap, "mcpp.toml") + local content = assert(io.readfile(manifest)):gsub("\r\n", "\n") + local patched, count = content:gsub("\nflags%s*=%s*%[", + '\nflags = [\n { glob = "third_party/llama.cpp/**/*.cpp", cxxflags = ["-include", "cstdlib", "-include", "cerrno"] },', 1) + assert(count == 1, "llamacpp: expected the native build flags table") + io.writefile(manifest, patched) + end + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mkdir(prefix) + os.mv(wrap, path.join(prefix, wrap)) + return true +end diff --git a/pkgs/y/yizhinailong.mcr.lua b/pkgs/y/yizhinailong.mcr.lua index af9d59e4..7efa85b7 100644 --- a/pkgs/y/yizhinailong.mcr.lua +++ b/pkgs/y/yizhinailong.mcr.lua @@ -11,8 +11,8 @@ -- All three platforms share the one source archive. TLS is not inlined here -- because mcr's OWN manifest already branches it — Schannel on Windows, -- `compat.openssl` on linux/macosx — and Form A means upstream keeps owning --- that decision. Its dependencies (compat.curl 8.21.0, nlohmann.json 3.12.0) --- are both already in this index. +-- that decision. Its dependencies are already in this index; 0.2 removes +-- the JSON API and its nlohmann.json dependency. -- -- No mcpp-res mirror is configured locally, so the plain-string GLOBAL url is -- the documented fallback for CN consumers (docs/cn-mirror.md) until a @@ -21,7 +21,7 @@ package = { spec = "1", namespace = "yizhinailong", name = "mcr", - description = "Modular C++23 HTTP client on libcurl — sync, async, coroutine and batch requests with JSON support", + description = "Modular C++23 HTTP client on libcurl — sync, async, coroutine and batch requests with typed responses", licenses = {"MIT"}, repo = "https://github.com/yizhinailong/mcr", type = "package", @@ -56,3 +56,45 @@ package = { mcpp = "*/mcpp.toml", } + +import("xim.libxpkg.pkginfo") + +function install() + local wrap = "mcr-" .. pkginfo.version() + -- libc++ 20 does not implement views::join_with. Keep the two 0.2 + -- formatting operations usable on the index's macOS baseline, preserving + -- empty elements, input order and the exact comma-space separator. + if pkginfo.version() == "0.2.0" then + local source = path.join(wrap, "src/options/http.cppm") + local content = assert(io.readfile(source), "mcr: cannot read " .. source):gsub("\r\n", "\n") + local function patch(before, after) + local pattern = before:gsub("(%W)", "%%%1") + local result, count = content:gsub(pattern, function() return after end) + assert(count == 1, "mcr: expected exactly one formatting expression") + content = result + end + local function joined(collection, item) + return "std::string joined; bool first = true;\n" + .. " for (auto const& value : " .. collection .. ") {\n" + .. " if (!first) joined += \", \"; first = false;\n" + .. " joined += " .. item .. ";\n" + .. " }\n return joined;" + end + patch('return m_methods | std::views::join_with(std::string_view{ ", " }) | std::ranges::to();', + joined("m_methods", "value")) + patch('return m_ranges | std::views::transform(&Range::Str) | std::views::join_with(std::string_view{ ", " }) | std::ranges::to();', + joined("m_ranges", "value.Str()")) + io.writefile(source, content) + source = path.join(wrap, "src/session/session.cppm") + content = assert(io.readfile(source), "mcr: cannot read " .. source):gsub("\r\n", "\n") + patch("for (auto const& [index, entry] : m_sessions | std::views::enumerate) {", + "for (std::size_t index = 0; index < m_sessions.size(); ++index) {\n" + .. " auto const& entry = m_sessions[index];") + io.writefile(source, content) + end + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mkdir(prefix) + os.mv(wrap, path.join(prefix, wrap)) + return true +end diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index e805b9e5..dc58a40a 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -133,6 +133,15 @@ def prepare(plan: Path, identity: str, output: Path): path.unlink() (output / "tests/connection.cpp").write_text( '#include \nint main() { nanodbc::connection c; return c.connected() ? 1 : 0; }\n') + if identity == "yizhinailong:mcr": + parts = version.split(".") + if len(parts) != 3 or not all(part.isdecimal() for part in parts): + raise RuntimeError("mcr version assertions require three numeric parts") + api = output / "tests/api.cpp" + defines = f'#define MCR_EXPECT_VERSION "{version}"\n' + defines += ''.join(f'#define MCR_EXPECT_{key} {int(value)}\n' + for key, value in zip(("MAJOR", "MINOR", "PATCH"), parts)) + api.write_text(defines + api.read_text(encoding="utf-8"), encoding="utf-8") text += f'\n[indices]\n"{namespace}" = {{ path = {json.dumps(str(ROOT).replace(chr(92), "/"))} }}\n' (output / "mcpp.toml").write_text(text) print(f"Prepared {identity}@{version} in {output}") @@ -160,87 +169,11 @@ def diagnostics(consumer: Path, output: Path): destination = output / path.relative_to(consumer) destination.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(path, destination) - payload = consumer / ".mcpp/.xlings/data/xpkgs/compat-x-hiredis/1.5.0" - if payload.exists(): - evidence = {"path": str(payload), "resolved": str(payload.resolve()), - "children": [str(p.relative_to(payload)) for p in payload.iterdir()], - "c_files": [{"path": str(p), "resolved": str(p.resolve()), "size": p.stat().st_size} - for p in payload.glob("*/*.c")], - "alloc_matches": [str(p) for p in payload.glob("*/alloc.c")]} - (output / "hiredis-payload.json").write_text(json.dumps(evidence, indent=2), encoding="utf-8") - for name in (".xpkg.lua", ".xpkg-install.json"): - if (payload / name).is_file(): - shutil.copyfile(payload / name, output / ("hiredis-" + name.lstrip("."))) - - -def experiment(consumer: Path, output: Path): - """Keep the primary failure; isolate source discovery in the job checkout.""" - output.mkdir(parents=True, exist_ok=True) - mcpp = os.environ["MCPP"] - descriptor = ROOT / "pkgs/c/compat.hiredis.lua" - original = descriptor.read_bytes() - try: - # Temporary diagnostic only: leave all version blocks untouched and - # restore the descriptor even when the experiment fails. - changed = original - for name in ("alloc", "async", "hiredis", "net", "read", "sds", "sockcompat"): - changed = changed.replace(f'"*/{name}.c"'.encode(), - f'"hiredis-1.5.0/{name}.c"'.encode()) - descriptor.write_bytes(changed) - with (output / "literal-sources.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, - stdout=log, stderr=subprocess.STDOUT) - print("literal sources:", result.returncode) - finally: - descriptor.write_bytes(original) - probe = consumer / "tests/probe.cpp" - source = probe.read_bytes() - try: - probe.write_bytes(b"import std;\n" + source) - with (output / "import-std.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, - stdout=log, stderr=subprocess.STDOUT) - print("root imports std (diagnostic only):", result.returncode) - finally: - probe.write_bytes(source) - # A path manifest over the very same installed files distinguishes Lua - # descriptor acquisition from the scanner/backend, without another download. - payload = consumer / ".mcpp/.xlings/data/xpkgs/compat-x-hiredis/1.5.0" - manifest = payload / "mcpp.toml" - root_manifest = consumer / "mcpp.toml" - root_source = root_manifest.read_bytes() - try: - manifest.write_text( - '[package]\nname = "hiredis"\nnamespace = "compat"\nversion = "1.5.0"\n' - '[build]\ninclude_dirs = ["*", "mcpp_generated/include"]\n' - 'sources = ["*/alloc.c", "*/async.c", "*/hiredis.c", "*/net.c", ' - '"*/read.c", "*/sds.c", "*/sockcompat.c"]\n' - 'cflags = ["-D_CRT_SECURE_NO_WARNINGS", "-DWIN32_LEAN_AND_MEAN"]\n' - 'ldflags = ["-lws2_32", "-lcrypt32"]\n', encoding="utf-8") - text = root_source.decode().replace('hiredis = "1.5.0"', - 'hiredis = { path = ' + json.dumps(payload.as_posix()) + ' }') - root_manifest.write_text(text, encoding="utf-8") - with (output / "path-manifest.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, - stdout=log, stderr=subprocess.STDOUT) - print("same payload through a path manifest:", result.returncode) - text = manifest.read_text(encoding="utf-8") - for name in ("alloc", "async", "hiredis", "net", "read", "sds", "sockcompat"): - text = text.replace(f'"*/{name}.c"', - json.dumps((payload / "hiredis-1.5.0" / f"{name}.c").as_posix())) - manifest.write_text(text, encoding="utf-8") - with (output / "absolute-sources.log").open("w", encoding="utf-8") as log: - result = subprocess.run([mcpp, "test", "--verbose", "--cache", "off"], cwd=consumer, - stdout=log, stderr=subprocess.STDOUT) - print("absolute sources bypassing glob expansion:", result.returncode) - finally: - root_manifest.write_bytes(root_source) - manifest.unlink(missing_ok=True) def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection", "diagnostics", "experiment")) + parser.add_argument("command", choices=("audit", "matrix", "prepare", "verify", "selection", "diagnostics")) parser.add_argument("--base") parser.add_argument("--plan", type=Path) parser.add_argument("--package") @@ -257,8 +190,6 @@ def main(): prepare(args.plan, args.package, args.output) elif args.command == "diagnostics": diagnostics(args.consumer, args.output) - elif args.command == "experiment": - experiment(args.consumer, args.output) else: verify(args.plan, args.package, args.consumer) diff --git a/tests/autoupdate/source_outage.py b/tests/autoupdate/source_outage.py new file mode 100644 index 00000000..abbc951d --- /dev/null +++ b/tests/autoupdate/source_outage.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Temporary, explicitly approved exception for an unavailable upstream source. + +Remove this policy once wellwei/libmysqlclient is accessible or migrated. +The updater plan keeps its error; excluded consumers are reported as untested. +""" +import argparse +import json +import os +from pathlib import Path + +REASON = "User-approved temporary exception: wellwei/libmysqlclient is unavailable (GitHub 404); existing versions retained." +MEMBERS = {"libmysqlclient", "mysql-connector-cpp"} + + +def gate(plan, outcome): + errors = [record for record in plan["packages"] if record["status"] == "error"] + allowed = [record for record in errors if ( + record.get("id") == "compat:libmysqlclient" + and record.get("repo") == "wellwei/libmysqlclient" + and record.get("path") == "pkgs/c/compat.libmysqlclient.lua" + and record.get("reason") == "HTTP Error 404: Not Found" + )] + if outcome not in {"success", "failure"} or len(errors) != len(allowed) or (outcome == "failure" and not allowed): + raise RuntimeError("Unapproved scan failure: " + json.dumps(errors)) + if allowed: + report(REASON + " Scan error remains in the plan; this is not a successful source check.") + + +def report(message): + print("::warning::" + message) + if os.environ.get("GITHUB_STEP_SUMMARY"): + with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as stream: + stream.write("\n- **NOT TESTED — source outage:** " + message + "\n") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("scan-gate", "members")) + parser.add_argument("--plan", type=Path) + parser.add_argument("--outcome") + args = parser.parse_args() + if args.command == "scan-gate": + gate(json.loads(args.plan.read_text(encoding="utf-8")), args.outcome) + else: + selected = os.environ["MEMBERS"].split() + skipped = [member for member in selected if member in MEMBERS] + if skipped: + report(REASON + " Untested consumers: " + ", ".join(skipped)) + with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as stream: + stream.write("MEMBERS=" + " ".join(member for member in selected if member not in MEMBERS) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/examples/mcr/tests/api.cpp b/tests/examples/mcr/tests/api.cpp index 5ac5af85..4550afcd 100644 --- a/tests/examples/mcr/tests/api.cpp +++ b/tests/examples/mcr/tests/api.cpp @@ -8,12 +8,19 @@ import std; import mcr; +#ifndef MCR_EXPECT_VERSION +#define MCR_EXPECT_VERSION "0.1.5" +#define MCR_EXPECT_MAJOR 0 +#define MCR_EXPECT_MINOR 1 +#define MCR_EXPECT_PATCH 5 +#endif + int main() { // mcr.version is generated by the package's own build.mcpp, read out of // its mcpp.toml. Asserting on it proves the build hook ran and the // generated module unit reached the library target. - if (mcr::VERSION != "0.1.5") return 1; - if (mcr::VERSION_MAJOR != 0 || mcr::VERSION_MINOR != 1 || mcr::VERSION_PATCH != 5) return 2; + if (mcr::VERSION != MCR_EXPECT_VERSION) return 1; + if (mcr::VERSION_MAJOR != MCR_EXPECT_MAJOR || mcr::VERSION_MINOR != MCR_EXPECT_MINOR || mcr::VERSION_PATCH != MCR_EXPECT_PATCH) return 2; // Status classification is constexpr and independent of libcurl. static_assert(mcr::status::is_informational(100)); @@ -34,7 +41,8 @@ int main() { if (header.find("content-type") == header.end()) return 6; if (header.at("CONTENT-TYPE") != "application/json") return 7; - // JsonBody owns the compact serialization of a value, taken at construction. +#if MCR_EXPECT_MAJOR == 0 && MCR_EXPECT_MINOR < 2 + // The preserved 0.1 API owns JSON serialization and parsing. mcr::Json const value{ { "name", "Alice" }, { "enabled", true } }; mcr::JsonBody const body{ value }; if (mcr::Json::parse(body.Str()) != value) return 8; @@ -60,6 +68,31 @@ int main() { threw = true; } if (!threw) return 13; +#else + // 0.2 deliberately removes the JSON dependency: body and response carry + // bytes verbatim, including embedded nulls. Test the replacement contract. + std::string const bytes{ "a\0b", 3 }; + mcr::Body const body{ bytes }; + if (body.Str() != bytes) return 8; + + mcr::Response response; + response.status_code = mcr::status::HTTP_OK; + response.text = bytes; + if (response.text != bytes || response.error) return 9; + + // Invalid local input must produce a typed error, without a network call. + auto invalid = mcr::Response::FromCurl(nullptr, "", ""); + if (invalid) return 10; + if (invalid.error().code != mcr::ErrorCode::BAD_FUNCTION_ARGUMENT) return 11; + if (invalid.error().message.empty()) return 12; + + mcr::options::AcceptEncoding const encodings{ std::string{}, std::string{"gzip"} }; + auto const encoding = encodings.GetString(); + if (encoding != ", gzip" && encoding != "gzip, ") return 13; + mcr::options::MultiRange const ranges{mcr::options::Range{0, 1}, mcr::options::Range{2, 3}}; + if (ranges.Str() != "0-1, 2-3") return 14; + if (!mcr::options::MultiRange{}.Str().empty()) return 15; +#endif return 0; } From 9252858c396035ee0ce9fec3a9c95fd50716a0fe Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 02:14:50 +0900 Subject: [PATCH 26/31] ci: use the runner Python command for source outage reporting --- .github/workflows/validate.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c7ed8ed2..3614cce5 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1501,7 +1501,12 @@ jobs: - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash - run: python3 tests/autoupdate/source_outage.py members + run: | + if [[ "$RUNNER_OS" == Windows ]]; then + python tests/autoupdate/source_outage.py members + else + python3 tests/autoupdate/source_outage.py members + fi - name: Install nanodbc macOS development headers if: runner.os == 'macOS' && contains(format(' {0} ', env.MEMBERS), ' nanodbc ') From 1da7f3869d17eee8aa151ecd9dc5bc31d185981d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 02:36:10 +0900 Subject: [PATCH 27/31] fix(openmp): select the real Windows Python payload interpreter --- pkgs/c/compat.openmp.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/c/compat.openmp.lua b/pkgs/c/compat.openmp.lua index b1877c8a..cacb94ed 100644 --- a/pkgs/c/compat.openmp.lua +++ b/pkgs/c/compat.openmp.lua @@ -74,6 +74,7 @@ local function tool(dep, name) if info then for _, candidate in ipairs({ path.join(info.bin or info.path or "", name), + path.join(info.path or "", name), -- Windows Python lives at the payload root. path.join(info.path or "", "CMake.app", "Contents", "bin", name), }) do if os.isfile(candidate) then return candidate end From 24422a88cd09c8fae13a9f89999c1aae31eaeb6a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 02:42:24 +0900 Subject: [PATCH 28/31] ci: accept new descriptors and reject removal of published packages --- .github/workflows/xpkgindex-update.yml | 1 + tests/autoupdate/ci.py | 21 ++++++++++-- tests/autoupdate/test_ci.py | 47 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 tests/autoupdate/test_ci.py diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index b71a481c..aee1691b 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -48,6 +48,7 @@ jobs: env: BASE: ${{ github.event.pull_request.base.sha || 'origin/main' }} run: | + python -m unittest discover -s tests/autoupdate -p 'test_*.py' python tests/autoupdate/ci.py audit --base "$BASE" --output coverage.json python tests/autoupdate/ci.py selection --base "$BASE" --output selected-package.txt echo "package=$(cat selected-package.txt)" >> "$GITHUB_OUTPUT" diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index dc58a40a..65d226d4 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -18,11 +18,23 @@ ROOT = Path(__file__).resolve().parents[2] +def base_descriptors(base: str) -> set[str]: + files = subprocess.check_output( + ["git", "ls-tree", "-r", "--name-only", base, "--", "pkgs"], cwd=ROOT, text=True) + return {name for name in files.splitlines() if name.endswith(".lua")} + + def audit(base: str, output: Path): records = [] - for info in inventory(ROOT): - before = subprocess.check_output(["git", "show", f"{base}:{info['path']}"], cwd=ROOT).decode() - assert_preserved(before, info["text"]) + previous = base_descriptors(base) + infos = inventory(ROOT) + removed = previous - {info["path"] for info in infos} + if removed: + raise RuntimeError(f"existing descriptors removed: {sorted(removed)}") + for info in infos: + if info["path"] in previous: + before = subprocess.check_output(["git", "show", f"{base}:{info['path']}"], cwd=ROOT).decode() + assert_preserved(before, info["text"]) if info["direct"]: if info["autoupdate"] is None: raise RuntimeError(f"missing autoupdate: {info['id']}") @@ -57,7 +69,10 @@ def matrix(plan: Path, output: Path, base: str): # A normal bump PR already contains its candidate, so discovery says up-to-date. # Derive added versions from the actual diff and test those exact published entries. if base: + previous = base_descriptors(base) for info in inventory(ROOT): + if info["path"] not in previous: + continue # New descriptors are covered by ordinary consumer validation. old_text = subprocess.check_output(["git", "show", f"{base}:{info['path']}"], cwd=ROOT).decode() old = version_blocks(old_text) new = version_blocks(info["text"]) diff --git a/tests/autoupdate/test_ci.py b/tests/autoupdate/test_ci.py new file mode 100644 index 00000000..a2ce72b8 --- /dev/null +++ b/tests/autoupdate/test_ci.py @@ -0,0 +1,47 @@ +"""Exercise CI audit boundaries with real Git baselines and Lua descriptors.""" +import json +import subprocess +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +import ci + + +def descriptor(name): + return '''package = { + spec = "1", namespace = "compat", name = "%s", + extensions = { autoupdate = { + tag = "v${version}", url = "https://github.com/acme/%s/archive/refs/tags/${tag}.tar.gz", + } }, + xpm = { linux = { ["1.0.0"] = { + url = "https://github.com/acme/%s/archive/refs/tags/v1.0.0.tar.gz", + sha256 = "%s", + } } }, +} +''' % (name, name, name, "a" * 64) + + +class BaselineTests(unittest.TestCase): + def test_added_descriptor_and_removed_historical_descriptor(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + pkgs = root / "pkgs/f" + pkgs.mkdir(parents=True) + old = pkgs / "compat.old.lua" + old.write_text(descriptor("old")) + for args in (("init",), ("add", "."), + ("-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "baseline")): + subprocess.run(["git", *args], cwd=root, check=True, capture_output=True) + (pkgs / "compat.new.lua").write_text(descriptor("new")) + plan = root / "plan.json" + plan.write_text(json.dumps({"packages": []})) + with patch.object(ci, "ROOT", root): + ci.audit("HEAD", root / "coverage.json") + self.assertEqual(json.loads((root / "coverage.json").read_text())["covered"], 2) + ci.matrix(plan, root / "matrix.json", "HEAD") + self.assertEqual(json.loads((root / "matrix.json").read_text()), {"include": []}) + old.unlink() + with self.assertRaisesRegex(RuntimeError, "existing descriptors removed"): + ci.audit("HEAD", root / "coverage.json") From 582fc31a51a95984207bf4076bf8049ded349956 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 02:43:56 +0900 Subject: [PATCH 29/31] fix(llamacpp): scope libc++ header compatibility to Linux sources --- pkgs/g/ggml-org.llamacpp.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/g/ggml-org.llamacpp.lua b/pkgs/g/ggml-org.llamacpp.lua index cde32d3e..a7c82f7a 100644 --- a/pkgs/g/ggml-org.llamacpp.lua +++ b/pkgs/g/ggml-org.llamacpp.lua @@ -135,15 +135,15 @@ import("xim.libxpkg.pkginfo") function install() local wrap = "llama.cpp-m-" .. pkginfo.version() - -- This checkpoint uses getenv/atoi/strtol/free without their header. - -- Scope the explicit include to its ordinary implementation sources; + -- libc++ 23 exposes missing standard headers in this Linux checkpoint. + -- Scope the explicit includes to ordinary Linux implementation sources; -- the exported module and the downloaded source files remain untouched. if pkginfo.version() == "b10069.2" then local manifest = path.join(wrap, "mcpp.toml") local content = assert(io.readfile(manifest)):gsub("\r\n", "\n") - local patched, count = content:gsub("\nflags%s*=%s*%[", - '\nflags = [\n { glob = "third_party/llama.cpp/**/*.cpp", cxxflags = ["-include", "cstdlib", "-include", "cerrno"] },', 1) - assert(count == 1, "llamacpp: expected the native build flags table") + local patched, count = content:gsub("\n%[target%.linux%.build%]\n", + '\n[target.linux.build]\nflags = [{ glob = "third_party/llama.cpp/**/*.cpp", cxxflags = ["-include", "cstdlib", "-include", "cerrno"] }]\n', 1) + assert(count == 1, "llamacpp: expected the native Linux build table") io.writefile(manifest, patched) end local prefix = pkginfo.install_dir() From 45884cfea34baa354c386c06b7937ac4b21e8729 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 03:40:44 +0900 Subject: [PATCH 30/31] ci: validate shared version resources with the merged updater --- .../2026-10-19-xpkgindex-update-design.md | 89 ++++++++++++++++--- .github/workflows/xpkgindex-update.yml | 17 ++-- tests/autoupdate/test_ci.py | 17 ++++ 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/.agents/docs/2026-10-19-xpkgindex-update-design.md b/.agents/docs/2026-10-19-xpkgindex-update-design.md index 9d2f57f8..7febaefb 100644 --- a/.agents/docs/2026-10-19-xpkgindex-update-design.md +++ b/.agents/docs/2026-10-19-xpkgindex-update-design.md @@ -469,15 +469,80 @@ Lua 注释/长字符串保留、修改原子性、实际候选选择、旧版本 - Linux 生成的计划与 Windows CRLF checkout 必须可互用:仅描述符计划摘要按 LF 规范化,实际追加仍逐字保留 checkout 的旧版本块;资源 SHA256 永不规范化。 - 候选 CI 保留 verbose 编译、link response 与 CDB 证据,用于分辨安装资源、包配置和工具链问题。 -### 13.1 当前真实验证进度(2026-10-20 JST) - -- 工具 draft:[openxlings/xpkgindex-update #1](https://github.com/openxlings/xpkgindex-update/pull/1);标准 src 布局、简约 README、45 项回归测试与六种 OS/Python CI 组合。 -- 接入 draft:[mcpp-index #543](https://github.com/mcpplibs/mcpp-index/pull/543);真实 propose draft:[mcpp-index #544](https://github.com/mcpplibs/mcpp-index/pull/544),hiredis 1.5.0,重复 propose 返回 pending。 -- OpenMP 23 的 runtimes 入口修复已在候选四个平台/工具链组合实际通过,新旧版本本地测试通过。 -- Windows hiredis 的失败根因是上游 AI 配置中的 symlink 导致源码遍历 Access denied;生成七个独立 C 包装翻译单元后,真实 hiredis draft PR 的四个候选构建/运行 jobs 全通过。libuv 1.53 另需 synchronization 链接库,正在全量 CI 验证。 -- Quill 13.1 的安装适配、新旧 nanodbc 的 locale/ODBC 头修复已有本地验证;全量 CI 继续验证各平台。 -- 用户要求继续逐库适配升级:mcr 0.2 移除了 JSON API,测试保留旧版 JSON 断言并对新版验证 typed response/body/options;安装配方仅对 0.2.0 回补 libc++20 缺少的 join_with/enumerate,已有本地实际测试。 -- 旧版 muduo/llama.cpp 在 LLVM23 缺少标准头,人工补充编译参数,本地实际测试通过。工具本身仍只追加版本,不自动修改构建配方。 -- 用户明确允许暂时忽略 wellwei/libmysqlclient 的源不可用:原始扫描 error 留在 plan,单独强制 gate 仅接受该包/仓库/路径的确切 GitHub 404;libmysqlclient 和依赖它的 mysql-connector-cpp 消费者在 summary 明示 NOT TESTED,不冒充构建通过。其他错误仍失败;上游恢复后删除此临时策略。 -- 旧版本保留与 82 包声明覆盖审计通过;工具最新提交 416a954 的 14 个 checks 全部成功。接入及端到端 PR 的最终完整回归仍在进行。 -- 开发期不发布稳定标签、不启用定时写入、不合并 PR;最终验收要求除上述用户批准的源不可用豁免外,所有实际候选矩阵和既有 CI gates 通过。 +### 13.1 最终验证记录(2026-10-20 JST) + +- 工具 draft:[openxlings/xpkgindex-update #1](https://github.com/openxlings/xpkgindex-update/pull/1),开发分支 `dev/mcpp-index-integration`,提交 `416a954c67dba4cdf52a033a71ee88fdec0acf1d`。45 项测试,Linux/macOS/Windows × Python 3.11/3.13,打包、CLI 和 Action smoke;push/PR 合计 14 checks 成功。 +- 接入 draft:[mcpp-index #543](https://github.com/mcpplibs/mcpp-index/pull/543),分支 `test/xpkgindex-update`,最终提交 `582fc31a51a95984207bf4076bf8049ded349956`。74 个执行的 checks 成功;两个既有的条件性通知/发布任务按原规则跳过。 +- [候选 CI](https://github.com/mcpplibs/mcpp-index/actions/runs/39296649657):扫描 + 52/52 真实候选构建、运行及 mcpp.lock 版本确认成功;52 份候选日志/锁文件 artifact 齐全。 +- [完整回归 CI](https://github.com/mcpplibs/mcpp-index/actions/runs/39296649642):19 个执行 jobs 成功,覆盖 13 个现有消费者回归分片(Linux 默认/GCC 三份、LLVM 六份、macOS 两份、Windows 两份),以及 lint、镜像、选择、图形副作用、计时与失败归属检查。 +- 真实 propose draft:[mcpp-index #544](https://github.com/mcpplibs/mcpp-index/pull/544),最终提交 `8e72ea8011cd6bc794880aba2c33522bd404b465`。20 个执行 checks 成功;相对测试基线仅追加 hiredis 1.5.0 的三平台版本块,共 12 行。重复 propose 返回 pending,不重复创建 PR。 +- [端到端候选 CI](https://github.com/mcpplibs/mcpp-index/actions/runs/39296667503) 和 [消费者 CI](https://github.com/mcpplibs/mcpp-index/actions/runs/39296667523) 均成功;候选固定为 1.5.0,四种平台/工具链实际构建运行通过。 +- 82 个 GitHub 直连描述符全部声明;独立 guard 对 254 个已有描述符的历史版本条目保留检查通过。扫描结果:63 up-to-date、13 update-ready、4 pinned-snapshot、1 pinned-version-map、1 已批准源故障。 + +人工兼容适配与自动追加逻辑分开: + +| 包 | 最终适配 | 真实验证 | +|---|---|---| +| FreeType | 追加 HVF 驱动源码的兼容 glob | 新 2.15 四种组合成功;旧 2.13.3 消费回归成功 | +| OpenMP | LLVM 23 改用 runtimes CMake 入口;工具定位支持 Windows Python 位于 payload 根目录 | 新 23.1.3 四种组合成功;旧 22.1.8 各支持平台回归成功,Windows 实际构建及运行通过 | +| hiredis | 七个独立 C 包装翻译单元,避免 Windows 遍历上游 AI 配置 symlink 时 Access denied;不删改上游资源 | 新 1.5 四种组合和端到端 CI 成功;旧 1.2 本地构建运行成功 | +| libuv | Windows 加 synchronization 链接库 | 新 1.53 四种组合成功 | +| nanodbc | 标准 locale 头;macOS 明确使用 libiodbc 头,旧版测试不再空跑 | 新 3.0.7 四种组合成功;旧 2.14 的 Linux LLVM23/macOS/Windows 消费回归成功 | +| Quill | 生成模块时移除未启用的 QUILL_IMPORT_STD 块,避免扫描器把 inactive import std 视为入口 | 新 13.1 四种组合成功;旧 13.0 回归成功 | +| mcr | 0.2 移除 JSON API,保留旧版 JSON 断言,新增 typed response/body/options 断言;仅对 0.2.0 回补 libc++20 不具备的 join_with/enumerate | 新 0.2 四种组合成功;旧 0.1.5 回归成功 | +| muduo / llama.cpp | 显式补标准头;llama.cpp 仅在 Linux 普通实现源注入 cstdlib/cerrno,避免改变 Mac/Windows 编译环境 | 旧版本全平台现有消费者回归成功;llama.cpp Windows 与 LLVM23 日志均输出 PASS | + +临时源故障豁免:用户明确允许暂时忽略 wellwei/libmysqlclient 不可用。原 error 留在 plan;强制 gate 仅接受该包/仓库/路径的确切 HTTP 404。libmysqlclient 和依赖它的 mysql-connector-cpp 在 summary 标为 NOT TESTED,不计作成功构建。其他扫描或构建失败仍失败;上游恢复/迁移后删除 `tests/autoupdate/source_outage.py` 及对应 workflow 步骤。 + +补充自审:CI 对新增描述符不读取不存在的基线文件;新增包由普通消费者验证,若有更新候选仍执行候选 CI;删除任何已有描述符直接失败。真实临时 Git 仓库回归已验证新增包可审计、初始版本不误判为 bump、历史描述符不能删除,且已加入扫描 CI。 + +本轮未迁移 xim-pkgindex 的真实包;共用静态版本追加逻辑与 adapter 边界已有 fixture 验证,带 latest.ref 的变更明确拒绝,等待独立策略。自动更新可验证版本/资源/构建,不能保证 API 向后兼容;mcr 0.2 是实际例子,始终保留人工 review/merge。 + +三个 PR 均保持 draft;不发布稳定标签、不启用定时写入、不合并。运行中的旧提交已取消,失败历史及修复依据保留;最终结论仅依据以上最终提交的完整 CI。 + +## 14. 共享资源优化及合并验证(2026-10-20) + +用户授权 CI 成功后 squash 合并工具与索引 PR,并实际触发更新验证、合入成功更新。 + +### 14.1 一份资源,多平台引用 + +```lua +local versions = { + ["1.5.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", + sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", + }, +} + +package = { + -- 身份、构建配置与 extensions 保持原结构 + xpm = { + linux = { -- 已有版本块保持原文 + ["1.5.0"] = versions["1.5.0"], + }, + macosx = { ["1.5.0"] = versions["1.5.0"] }, + windows = { ["1.5.0"] = versions["1.5.0"] }, + }, +} +``` + +- 所有参与平台的完整资源相同时,共用一份前置版本定义;URL 不同,即便 SHA 相同,也不混用来源。 +- 后续版本追加到同一个共享表;旧的内联条目不迁移、不覆盖、不重排。名称与现有局部变量冲突时选择新名称。 +- 支持字面量局部表、字面量字符串索引引用和已有整个平台共享表。不执行 Lua,不猜测动态表达式。 +- 版本保留检查同时保护引用源码及引用指向的资源定义;仅保留引用、修改背后的 URL/SHA 同样失败。 +- 拒绝共享表重绑、直接变更、外部调用和后续 package 访问;无法静态解析的历史版本资源明确报错,不能从版本比较中静默消失。 +- 所有平台与资源先验证;生成后再次静态解析,确认每个平台的新条目确实指向已验证 URL/SHA,最后原子写入。 + +### 14.2 自审与实施证据 + +- 工具回归增至 59 项;覆盖连续追加、共享定义被改、变量重绑/变更、平台资源差异、名称冲突、不可解析前缀、动态历史资源等。 +- 13 个真实候选生成结果均只有一份 URL/SHA,三平台引用同一资源;旧版本保留和 Lua 平台一致性检查通过。固定客户端 2026.10.1.2 对全部候选的三平台解析均成功。 +- 索引 CI 的真实 Git 基线回归同时覆盖:新增描述符不误判为版本 bump、历史描述符不能删除、共享版本引用能生成精确候选构建矩阵。 +- README 精简为正式项目入口,详细 CLI/config/shared-table 内容放在 docs/usage.md。 +- 工具 PR #1 已 squash 合并为 `23c5e04948c55e606bc56ee2c649ddd4849b7ec6`;索引 Action 固定到此真实合并提交。 + +### 14.3 合并后的真实更新验证 + +先等待索引候选与现有消费者 CI 全部成功,再 squash 合并 #543。将 #544 同步到 main 基线,以工具重新生成共享格式并验证后合入。随后手动触发 main 上的 Action,使用可选 package 输入限定一个真实更新包;从 artifact 取回同一份固定计划,由已合并的 CLI 创建针对 main 的更新 PR,等待实际构建、运行、锁文件及旧版本保留检查成功后 squash 合入。 + +写 PR 使用本地已有授权,不新建长期 token secret。现有 libmysqlclient 源故障豁免继续明确保留,不改变其历史版本。此节是实施时的计划及已核实证据,最终合并与 CI 结果另见汇报和 PR 记录。 diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index aee1691b..524fc007 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -8,6 +8,11 @@ on: - '.github/xpkgindex-update.yml' - '.github/workflows/xpkgindex-update.yml' workflow_dispatch: + inputs: + package: + description: "Optional namespace:name; empty scans all declared packages" + type: string + required: false permissions: contents: read @@ -38,10 +43,10 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 416a954c67dba4cdf52a033a71ee88fdec0acf1d + ref: 23c5e04948c55e606bc56ee2c649ddd4849b7ec6 path: .updater persist-credentials: false - - name: Use the exact development tool source + - name: Use the pinned updater source run: echo "PYTHONPATH=$PWD/.updater/src" >> "$GITHUB_ENV" - name: All GitHub-direct descriptors are declared; published versions are unchanged id: coverage @@ -55,11 +60,11 @@ jobs: - name: Discover and download real upstream candidates id: scan continue-on-error: true # The mandatory gate below accepts only the approved source outage. - uses: openxlings/xpkgindex-update@416a954c67dba4cdf52a033a71ee88fdec0acf1d + uses: openxlings/xpkgindex-update@23c5e04948c55e606bc56ee2c649ddd4849b7ec6 with: mode: check token: ${{ github.token }} - package: ${{ steps.coverage.outputs.package }} + package: ${{ inputs.package || steps.coverage.outputs.package }} - name: Reject all scan failures except the approved unavailable source if: always() env: @@ -110,14 +115,14 @@ jobs: - uses: actions/checkout@v4 with: repository: openxlings/xpkgindex-update - ref: 416a954c67dba4cdf52a033a71ee88fdec0acf1d + ref: 23c5e04948c55e606bc56ee2c649ddd4849b7ec6 path: .updater persist-credentials: false - uses: actions/download-artifact@v4 with: name: xpkgindex-update-build-plan path: .candidate-plan - - name: Use the development tool and a standalone consumer + - name: Use the pinned updater and a standalone consumer shell: bash run: | if [[ "$RUNNER_OS" == Windows ]]; then diff --git a/tests/autoupdate/test_ci.py b/tests/autoupdate/test_ci.py index a2ce72b8..52226bb8 100644 --- a/tests/autoupdate/test_ci.py +++ b/tests/autoupdate/test_ci.py @@ -42,6 +42,23 @@ def test_added_descriptor_and_removed_historical_descriptor(self): self.assertEqual(json.loads((root / "coverage.json").read_text())["covered"], 2) ci.matrix(plan, root / "matrix.json", "HEAD") self.assertEqual(json.loads((root / "matrix.json").read_text()), {"include": []}) + # A normal bump is already present when discovery runs. Shared + # definitions must still produce jobs for that exact version. + resource = '{ url = "https://github.com/acme/old/archive/refs/tags/v1.1.0.tar.gz", sha256 = "' + "b" * 64 + '" }' + old.write_text('local versions = { ["1.1.0"] = ' + resource + ' }\n' + + old.read_text().replace('xpm = { linux = {', + 'xpm = { linux = { ["1.1.0"] = versions["1.1.0"],')) + consumer = root / "tests/examples/old" + consumer.mkdir(parents=True) + (consumer / "mcpp.toml").write_text('[dependencies.compat]\nold = "1.1.0"\n') + ci.audit("HEAD", root / "coverage.json") + ci.matrix(plan, root / "matrix.json", "HEAD") + jobs = json.loads((root / "matrix.json").read_text())["include"] + self.assertEqual(len(jobs), 2) + self.assertTrue(all(job["candidate"] == "1.1.0" for job in jobs)) + generated = json.loads((root / "build-plan.json").read_text())["packages"][0] + self.assertEqual(generated["status"], "existing-candidate") + self.assertEqual(generated["assets"][0]["resources"]["GLOBAL"]["sha256"], "b" * 64) old.unlink() with self.assertRaisesRegex(RuntimeError, "existing descriptors removed"): ci.audit("HEAD", root / "coverage.json") From 6ebea26b24e5350e76d806601e24684928b462d0 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 20 Oct 2026 03:41:40 +0900 Subject: [PATCH 31/31] chore: share the hiredis 1.5 resource across platforms --- pkgs/c/compat.hiredis.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pkgs/c/compat.hiredis.lua b/pkgs/c/compat.hiredis.lua index 12c92863..99e46b9f 100644 --- a/pkgs/c/compat.hiredis.lua +++ b/pkgs/c/compat.hiredis.lua @@ -1,3 +1,10 @@ +local versions = { + ["1.5.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", + sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", + }, +} + -- compat.hiredis — hiredis 1.2.0, minimal C client for Redis. -- -- Shape A (C-source compat), same as compat.cjson / compat.zlib: the seven @@ -47,30 +54,21 @@ package = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, - ["1.5.0"] = { - url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", - sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", - }, + ["1.5.0"] = versions["1.5.0"], }, macosx = { ["1.2.0"] = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, - ["1.5.0"] = { - url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", - sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", - }, + ["1.5.0"] = versions["1.5.0"], }, windows = { ["1.2.0"] = { url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", }, - ["1.5.0"] = { - url = "https://github.com/redis/hiredis/archive/refs/tags/v1.5.0.tar.gz", - sha256 = "eec7ef8641d221e144a58d5ef8d733c8c75c7fd487097a233aacb4fa33981cfb", - }, + ["1.5.0"] = versions["1.5.0"], }, },