From c5c95e06364458cb95a51ef355ab27fdb36d9784 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 03:28:51 +0900 Subject: [PATCH 01/25] feat: checkpoint the ONNX Runtime CPU SDK provider --- .gitignore | 6 +++ pkgs/c/compat.onnxruntime.lua | 59 +++++++++++++++++++++++ tests/mwrap/onnxruntime-sdk/mcpp.toml | 10 ++++ tests/mwrap/onnxruntime-sdk/tests/sdk.cpp | 14 ++++++ 4 files changed, 89 insertions(+) create mode 100644 pkgs/c/compat.onnxruntime.lua create mode 100644 tests/mwrap/onnxruntime-sdk/mcpp.toml create mode 100644 tests/mwrap/onnxruntime-sdk/tests/sdk.cpp diff --git a/.gitignore b/.gitignore index 9f58afa2..ad886326 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,9 @@ tests/examples/*/imgui.ini __pycache__/ *.pyc tests/openkal-work/ + +# mwrap consumer build/fetch artifacts +tests/mwrap/*/target/ +tests/mwrap/*/.mcpp/ +tests/mwrap/*/compile_commands.json +tests/mwrap/*/mcpp.lock diff --git a/pkgs/c/compat.onnxruntime.lua b/pkgs/c/compat.onnxruntime.lua new file mode 100644 index 00000000..cd7f214a --- /dev/null +++ b/pkgs/c/compat.onnxruntime.lua @@ -0,0 +1,59 @@ +-- Official ONNX Runtime CPU SDK. The module interface is owned by +-- mcpplibs.mwrap.onnxruntime; this package owns the headers and shared runtime. +local version = "1.25.1" +local function archive(name, sha256) + return { + url = "https://github.com/microsoft/onnxruntime/releases/download/v" .. version .. "/" .. name, + sha256 = sha256, + } +end + +package = { + spec = "1", + namespace = "compat", + name = "onnxruntime", + description = "ONNX Runtime 1.25.1 official CPU C/C++ SDK", + licenses = { "MIT" }, + repo = "https://github.com/microsoft/onnxruntime", + type = "package", + xpm = { + linux = { ["1.25.1"] = archive("onnxruntime-linux-x64-1.25.1.tgz", + "eb566a49cfc49ef0642f809b69340b5bb656c7c4905ba873526d226f2c005816") }, + macosx = { ["1.25.1"] = archive("onnxruntime-osx-arm64-1.25.1.tgz", + "18987ec3187b5f29ba798109750f6135060560ad4e0a52678fcc753ee8fb3091") }, + windows = { ["1.25.1"] = archive("onnxruntime-win-x64-1.25.1.zip", + "33f2e8a63774811f99a5fc224cac32f4eed8c27643d46c6cc685319fa8f18019") }, + }, + mcpp = { + language = "c++23", + import_std = false, + include_dirs = { "*/include" }, + sources = { "mcpp_generated/sdk.cpp" }, + generated_files = { ["mcpp_generated/sdk.cpp"] = [=[ +#include +static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1"); +#if defined(__APPLE__) +#if !defined(__aarch64__) && !defined(_M_ARM64) +#error "This ONNX Runtime SDK package supplies macOS arm64 only" +#endif +#elif !defined(__x86_64__) && !defined(_M_X64) +#error "This ONNX Runtime SDK package supplies Linux/Windows x86_64 only" +#endif +]=] }, + linux = { runtime = { + libraries = { "onnxruntime" }, + link_library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, + library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, + } }, + macosx = { runtime = { + libraries = { "onnxruntime" }, + link_library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, + library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, + } }, + windows = { runtime = { + libraries = { "onnxruntime" }, + link_library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, + library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, + } }, + }, +} diff --git a/tests/mwrap/onnxruntime-sdk/mcpp.toml b/tests/mwrap/onnxruntime-sdk/mcpp.toml new file mode 100644 index 00000000..ee3a14b8 --- /dev/null +++ b/tests/mwrap/onnxruntime-sdk/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "onnxruntime-sdk" +version = "1.25.1" +description = "Exercise the official ONNX Runtime SDK through the local index" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +onnxruntime = "1.25.1" diff --git a/tests/mwrap/onnxruntime-sdk/tests/sdk.cpp b/tests/mwrap/onnxruntime-sdk/tests/sdk.cpp new file mode 100644 index 00000000..0ebea475 --- /dev/null +++ b/tests/mwrap/onnxruntime-sdk/tests/sdk.cpp @@ -0,0 +1,14 @@ +#include +#include + +int main() { + const auto* api = OrtGetApiBase(); + if (!api || std::strcmp(api->GetVersionString(), "1.25.1") != 0) return 1; + Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "mwrap-sdk-test"); + Ort::SessionOptions options; + options.SetIntraOpNumThreads(1); + const auto providers = Ort::GetAvailableProviders(); + for (const auto& provider : providers) + if (provider == "CPUExecutionProvider") return 0; + return 2; +} From 3067e52abc0165b453ea3acac4022895ebf7d9bc Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 05:29:54 +0900 Subject: [PATCH 02/25] fix: declare the ONNX Runtime SDK Linux runtime closure --- pkgs/c/compat.onnxruntime.lua | 70 +++++++++++++++++++++++++-- tests/mwrap/onnxruntime-sdk/mcpp.toml | 3 ++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/pkgs/c/compat.onnxruntime.lua b/pkgs/c/compat.onnxruntime.lua index cd7f214a..bbb9ff7e 100644 --- a/pkgs/c/compat.onnxruntime.lua +++ b/pkgs/c/compat.onnxruntime.lua @@ -17,8 +17,11 @@ package = { repo = "https://github.com/microsoft/onnxruntime", type = "package", xpm = { - linux = { ["1.25.1"] = archive("onnxruntime-linux-x64-1.25.1.tgz", - "eb566a49cfc49ef0642f809b69340b5bb656c7c4905ba873526d226f2c005816") }, + linux = { + deps = { "xim:gcc-runtime@15.1.0", "xim:glibc@2.44.3" }, + ["1.25.1"] = archive("onnxruntime-linux-x64-1.25.1.tgz", + "eb566a49cfc49ef0642f809b69340b5bb656c7c4905ba873526d226f2c005816"), + }, macosx = { ["1.25.1"] = archive("onnxruntime-osx-arm64-1.25.1.tgz", "18987ec3187b5f29ba798109750f6135060560ad4e0a52678fcc753ee8fb3091") }, windows = { ["1.25.1"] = archive("onnxruntime-win-x64-1.25.1.zip", @@ -39,9 +42,15 @@ static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1 #elif !defined(__x86_64__) && !defined(_M_X64) #error "This ONNX Runtime SDK package supplies Linux/Windows x86_64 only" #endif +#if defined(__linux__) && !defined(__GLIBC__) +#error "The official ONNX Runtime Linux SDK requires glibc; musl needs a separate build" +#endif +#if defined(_WIN32) && !defined(_MSC_VER) +#error "The official ONNX Runtime Windows SDK requires the MSVC ABI" +#endif ]=] }, linux = { runtime = { - libraries = { "onnxruntime" }, + libraries = { "onnxruntime", "stdc++" }, link_library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, } }, @@ -57,3 +66,58 @@ static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1 } }, }, } + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function quote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +function install() + local platform = os.host() + local wrap = platform == "linux" and "onnxruntime-linux-x64-1.25.1" + or platform == "macosx" and "onnxruntime-osx-arm64-1.25.1" + or "onnxruntime-win-x64-1.25.1" + local prefix = pkginfo.install_dir() + os.mkdir(prefix) + os.mv(wrap, path.join(prefix, wrap)) + if platform == "linux" then + -- ORT carries RUNPATH=$ORIGIN: inherited executable RPATH cannot + -- supply its libstdc++ dependency. Use the declared SDK runtime, + -- never an undeclared host library or a scan of arbitrary stores. + local runtime = pkginfo.dep_install_dir("xim:gcc-runtime", "15.1.0") + if not runtime then + log.error("compat.onnxruntime: declared gcc-runtime@15.1.0 is unavailable") + return false + end + local directory = path.join(prefix, wrap, "lib") + for _, name in ipairs({ "libstdc++.so.6", "libgcc_s.so.1" }) do + local source = path.join(runtime, "lib64", name) + if not os.isfile(source) then + log.error("compat.onnxruntime: missing declared runtime file %s", source) + return false + end + os.exec("ln -sfn " .. quote(source) .. " " .. quote(path.join(directory, name))) + end + -- Explicitly link this shared standard library before the driver's + -- implicit static one, so GCC consumers load one implementation. + os.exec("ln -sfn libstdc++.so.6 " .. quote(path.join(directory, "libstdc++.so"))) + -- These are the empty compatibility stubs merged into libc since + -- glibc 2.34. Do not copy libc, libm or a second ELF loader. + local libc = pkginfo.dep_install_dir("xim:glibc", "2.44.3") + if not libc then + log.error("compat.onnxruntime: declared glibc compatibility stubs are unavailable") + return false + end + for _, name in ipairs({ "libdl.so.2", "librt.so.1", "libpthread.so.0" }) do + local source = path.join(libc, "lib64", name) + if not os.isfile(source) then + log.error("compat.onnxruntime: missing compatibility stub %s", source) + return false + end + os.exec("ln -sfn " .. quote(source) .. " " .. quote(path.join(directory, name))) + end + end + return true +end diff --git a/tests/mwrap/onnxruntime-sdk/mcpp.toml b/tests/mwrap/onnxruntime-sdk/mcpp.toml index ee3a14b8..be25297a 100644 --- a/tests/mwrap/onnxruntime-sdk/mcpp.toml +++ b/tests/mwrap/onnxruntime-sdk/mcpp.toml @@ -8,3 +8,6 @@ compat = { path = "../../.." } [dependencies.compat] onnxruntime = "1.25.1" + +[target.x86_64-linux-gnu] +cxx_runtime = "toolchain-coupled" From 1d6bc23218eaba29fcba4b767d786c6a5aaf77d6 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 05:36:49 +0900 Subject: [PATCH 03/25] feat: register mwrap consumers and document wrapper package contracts --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 11 ++ .../skills/add-mcpp-index-package/SKILL.md | 50 +++--- .github/workflows/openkal-compat.yml | 2 +- .github/workflows/validate.yml | 27 +-- .github/workflows/xpkgindex-update.yml | 2 +- docs/mwrap.md | 156 ++++++++++++++++++ docs/repository-and-schema.md | 6 +- docs/zh/mwrap.md | 106 ++++++++++++ docs/zh/repository-and-schema.md | 6 +- index.toml | 2 +- mcpp.toml | 6 +- tests/openkal/pins.toml | 6 +- tests/plan_shards.lua | 11 +- tests/run_members.sh | 6 +- 14 files changed, 339 insertions(+), 58 deletions(-) create mode 100644 .agents/docs/2026-10-24-add-mwrap-and-ort-plan.md create mode 100644 docs/mwrap.md create mode 100644 docs/zh/mwrap.md diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md new file mode 100644 index 00000000..306f0606 --- /dev/null +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -0,0 +1,11 @@ +# mwrap category and ONNX Runtime SDK implementation + +The new mwrap category is flat under pkgs/mwrap. Existing clients scan one category level and the publishing script copies pkgs unchanged; no staging conversion or mcpp engine change is required. Consumers live under tests/mwrap and participate in the same selection/shard/timing entry points as existing examples. + +compat.onnxruntime owns the official MIT-licensed 1.25.1 CPU SDK, including exact platform checksums. The module wrapper remains in mcpplibs/onnxruntime-m and exposes upstream Ort/C names. The provider supports Linux glibc x86_64, macOS arm64 and Windows x86_64 MSVC ABI. Unsupported architectures/libcs fail explicitly. Linux runtime closure declares gcc-runtime and glibc; it never farms libc or an ELF loader from the host. + +Local checks: strict descriptor parse, cross-package references, duplicate/platform version lint and shard inclusion passed. Isolated SDK installation and real inference/header coexistence passed with LLVM 23.1.3; GCC 16.1.0 also passed, followed by actual template creation/build/inference. Native wrapper CI pins SDK provider commit 3067e52abc0165b453ea3acac4022895ebf7d9bc. + +The formal English/Chinese mwrap specification records upstream[.N] versions, modules versus API namespaces, macros, optional features and executable quickstart templates. The package skill links this common specification. Autoupdate is not enabled for the SDK yet: updating it requires changing archive directory names, API inventory and ABI verification, rather than only replacing a URL version. + +Checkpoint state: SDK provider and category infrastructure first. The opencv/onnxruntime wrapper descriptors must be added only after immutable source archives and checksums are verified and install/template consumers execute through this index. No placeholder published versions, release tags or merges are performed here. diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 907c4bd8..3a3b273d 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -1,6 +1,6 @@ --- name: add-mcpp-index-package -description: Use when adding a new third-party library/package to the mcpp-index repo — writing a pkgs/*/*.lua descriptor, setting up a GitCode CN mirror via gtc, adding a minimal example, and opening a green PR. Covers the four package shapes (C-source compat / header-only / C++23 module / external Form-A module repo), the GLOBAL+CN mirror table, lint rules, the feature (sources-only) gate, and local + CI verification. +description: Use when adding a new third-party library/package to the mcpp-index repo — writing a descriptor, registering and running consumers, and opening a green PR. Includes traditional compat packages and mcpplibs.mwrap module adapters, source/runtime feature dependencies, runnable project templates, mirrors, and local plus CI verification. --- # 向 mcpp-index 新增一个库(标准作业流程) @@ -19,6 +19,7 @@ description: Use when adding a new third-party library/package to the mcpp-index 配套参考文档位于仓库 `docs/` 目录(英文为默认版本,中文版在 `docs/zh/`,内容对等),供人工与 agent 共同使用, 可按需查阅(下列链接指向中文版): +- [mwrap 规范](../../../docs/zh/mwrap.md) —— 为传统头文件库增加模块入口时读取;涵盖命名、上游版本尾号、目录、宏、feature 与必须运行的快捷模板。 - [docs/zh/package-types.md](../../../docs/zh/package-types.md) —— 四种库形态的描述符模板与样例路径。 - [docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md) —— CN 镜像闭环,含无 `mcpp-res` 权限时的回退方案。 - [docs/zh/automatic-updates.md](../../../docs/zh/automatic-updates.md) —— 自动更新适用条件、声明、每日 bot PR 和候选 CI。 @@ -56,13 +57,13 @@ description: Use when adding a new third-party library/package to the mcpp-index 以保证字节一致(sha 相同)。**在不具备 `mcpp-res` 写权限时**,不应构造镜像表,而应使用纯字符串形式 `url = ""`(lint 允许此形式,CN 用户将回退至上游源),镜像由维护者后续补充。详见 [docs/zh/cn-mirror.md](../../../docs/zh/cn-mirror.md)。 -4. **编写描述符** `pkgs//.lua`。 +4. **编写描述符**:传统包使用 `pkgs//.lua`,`mcpplibs.mwrap` 包使用 `pkgs/mwrap/<完整包身份>.lua`。 - 目录 `` **取完整包名首字母**(`compat.eigen` 对应 `pkgs/c/`,`nlohmann.json` 对应 `pkgs/n/`), - 而非短名。放置错误将导致本地 path index 报 `not found in local index`。 + 而非短名。mwrap 是明确的分类例外,文件平铺于 pkgs/mwrap;当前客户端 fallback 按声明身份扫描一级分类目录,不要求目录名为单字母。不增加 pkgs/mwrap/o 等额外层级,也不转换发布路径。 - `xpm` 须覆盖三平台(linux/macosx/windows),每个版本包含 `url = { GLOBAL=…, CN=… }` 与 `sha256`。 - 版本号采用**裸版本**(如 `"1.2.3"`),不含前导 `v`;下载 URL 中可保留上游的 `…/v1.2.3.tar.gz` 形式。 -5. **识别可门控的可选组件并实现 feature**(参见下文“feature 机制”)。feature 仅能门控 **sources**。 -6. **编写测试工程(workspace 成员)** `tests/examples//`(`short` 为包名去除 `compat.`/`mcpplibs.` +5. **识别可门控组件并实现 feature**(见下文)。核对 pinned mcpp 的 sources、defines、flags、implies、依赖转发和 feature-deps;不要沿用 0.0.68 的 sources-only 限制。 +6. **编写测试工程(workspace 成员)**:mwrap 使用 `tests/mwrap//`,传统包使用 `tests/examples//`(`short` 为包名去除 `compat.`/`mcpplibs.` 前缀后的结果;公开模块包用 `-module`),并把它加进仓根 `mcpp.toml` 的 `[workspace].members`。 - 包含 `mcpp.toml` 与 `tests/*.cpp`,后者须包含**可失败的有效断言**(`return ok ? 0 : 1`)。 - 成员声明**恰好一条** `[indices] = { path = "../../.." }`,把所消费命名空间重定向到 @@ -100,30 +101,25 @@ description: Use when adding a new third-party library/package to the mcpp-index 用户明确要求整理文档或说明确有变化时才改入口文档。新增包通常也不改 `.xpkgindex.json`。 - 详细字段、例外和认证机制见 [自动更新文档](../../../docs/zh/automatic-updates.md)。 -## feature 机制 +## feature 与模板 -mcpp **0.0.68** 的包描述符 `features` 表**仅能门控 `sources`**;其余子字段会被解析器忽略(经 `manifest.cppm` 的 -features 解析逻辑确认)。 +当前 manifest feature 支持 sources、defines、flags、implies、forward、requires/provides;可选依赖写在 +`[feature-deps.]`,不将 deps 当成 manifest feature table 的有效成员。索引内联描述符的同类依赖 +仍使用其 Lua schema 的 deps 字段。以 CI 固定 mcpp 的 parser 为准,先运行 `mcpp xpkg parse`。 -```lua -mcpp = { - sources = { "*/core.c" }, -- 默认始终编译 - features = { - ["extra"] = { sources = { "*/extra.c" } }, -- 默认排除;请求 features=["extra"] 时编入同一 lib 目标 - }, -} -``` +feature 只增加能力,依赖图请求取并集。依赖声明支持 `default-features = false`;显式 features 仍激活, +其他边的默认请求不会被取消。根项目没有 `--no-default-features` CLI。可选大依赖使用命名 feature 门控。 +后端命名沿用上游模块和正式供应者名,如 highgui-gtk3、videoio-ffmpeg、dnn-onnxruntime。 +capability 选择不会自动移除别的 provider 对象,不能用它掩盖两份同符号运行库。 + +须验证启用后的真实行为,以及不启用时不编译/获取可选依赖。只看到符号常量或配置名称不证明后端可用。 +Linux 本地执行;Windows/macOS 用原生 CI,缺少真实设备时明确报告 build-only/未执行项。 -- 消费侧声明:`dep = { version = "…", features = ["extra"] }`。 -- 既有实例:`compat.gtest` 的 `main`(gtest_main.cc)、`compat.cjson` 的 `utils`(cJSON_Utils.c)、`compat.eigen` - 的 `blas`(`*/blas/*.cpp` 与 `*/blas/f2c/*.c`)。 -- 判定某可选组件能否实现为 feature 的准则:该组件是否为**额外的可编译源码**。若是,则可门控。纯头文件(如 Eigen 的 - `unsupported/`,与核心共享 include 根,无法隐藏)不可门控;编译期 **define**(如 `EIGEN_MPL2_ONLY`、 - `EIGEN_USE_BLAS`)当前无法由 feature 表携带,因而不可门控,应在描述符中加注释说明,待 mcpp 支持 define/cflags - 后再实现。 -- glob 规则:支持 `*`(段内匹配)与 `**`(跨段匹配),因此 `*/blas/*.cpp`、`*/foo/**/*.c` 均合法。 -- **须进行负向验证**:在不启用 feature 时,对应的符号或源码应**确实缺失**(表现为链接期 `undefined reference` - 或编译期找不到),以此证明门控确实生效,而非默认即被编入。 +每个新建或重构的 -m 库至少提供一个默认 mcpp 快捷模板。实际执行 `mcpp new --template <完整包身份>@<版本>`, +再 build/run 生成工程。当前 `{{self.name}}` 是完整包选择器,应写 `"{{self.name}}"` 为单个依赖 key, +不能再放到另一层 namespace 表中;版本用 self.version,所需 feature 显式声明。 +未发布候选可用隔离的已安装包 fixture,但必须先检查渲染身份和版本,再施加本地依赖覆盖。 +参见 [mwrap 规范](../../../docs/zh/mwrap.md)。 ## 本地验证(与 CI 同版本) @@ -150,7 +146,7 @@ rm -rf "tests/examples//target" "tests/examples//.mcpp" # 冷 | 错误做法 | 正确做法 | |----------|----------| -| 将 `compat.foo.lua` 置于 `pkgs/f/` | 置于 `pkgs/c/`,目录取完整包名首字母 | +| 将 mwrap 也强制放到首字母桶,或增加 pkgs/mwrap/o 层级 | mwrap 平铺于 pkgs/mwrap;传统包仍按完整包名首字母分桶 | | 版本写作 `"v1.2.3"` | 采用裸版本 `"1.2.3"`,lint 会拦截前导 v | | CN 镜像上传经改动的包(与 GLOBAL 不一致) | 上传与 GLOBAL 相同的 tarball,sha 一致,以维持 GLOBAL/CN 一致性 | | 仅执行 `mcpp build` 而不 `run`,或示例缺少有效断言 | 实际运行,并以 `return ok ? 0 : 1` 断言 | diff --git a/.github/workflows/openkal-compat.yml b/.github/workflows/openkal-compat.yml index 606683c1..c9b9de84 100644 --- a/.github/workflows/openkal-compat.yml +++ b/.github/workflows/openkal-compat.yml @@ -42,7 +42,7 @@ env: # run if this drifts from pins.toml. See the comment beside `mcpp` in # pins.toml for why this pin, not just validate.yml's, is gated by the # index floor. - MCPP_VERSION: "2026.9.26.1" + MCPP_VERSION: "2026.10.21.1" # CROSS-VALIDATION BEFORE A RELEASE, NOT AFTER IT. # # Empty in an ordinary run, so this measurement keeps using the RELEASED diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 2b9592c0..91384afc 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -431,7 +431,7 @@ env: # one module map of what it reaches; every unit of an ELF target is compiled # position-independent; `mcpp pack` states its build; `[build] jobs` bounds # `test` and `pack` (#751, #753). - MCPP_VERSION: "2026.10.1.2" + MCPP_VERSION: "2026.10.21.1" jobs: lint: @@ -837,27 +837,28 @@ jobs: # members list, so that alone must NOT force a full run: # select the added members; anything else in this file # (indices, settings) affects everyone → full. - if ! diff -q <(git show "$base:mcpp.toml" | grep -v 'tests/examples/') \ - <(grep -v 'tests/examples/' mcpp.toml) >/dev/null; then + if ! diff -q <(git show "$base:mcpp.toml" | grep -vE 'tests/(examples|mwrap)/') \ + <(grep -vE 'tests/(examples|mwrap)/' mcpp.toml) >/dev/null; then widen "mcpp.toml non-member change" fi - for p in $(comm -13 <(git show "$base:mcpp.toml" | grep -o 'tests/examples/[A-Za-z0-9._-]*' | sort -u) \ - <(grep -o 'tests/examples/[A-Za-z0-9._-]*' mcpp.toml | sort -u)); do - add "${p#tests/examples/}" + for p in $(comm -13 <(git show "$base:mcpp.toml" | grep -oE 'tests/(examples|mwrap)/[A-Za-z0-9._-]+' | sort -u) \ + <(grep -oE 'tests/(examples|mwrap)/[A-Za-z0-9._-]+' mcpp.toml | sort -u)); do + add "${p##*/}" done ;; - tests/examples/*) - m=${f#tests/examples/}; m=${m%%/*} + tests/examples/*|tests/mwrap/*) + m=${f#tests/}; m=${m#*/}; m=${m%%/*} # A deleted/renamed member dir implies a mcpp.toml edit, # which already forces a full run above. - [ -d "tests/examples/$m" ] && add "$m" ;; + if [ -d "tests/examples/$m" ] || [ -d "tests/mwrap/$m" ]; then add "$m"; fi ;; pkgs/*.lua|pkgs/*/*.lua) # A DELETED descriptor is in the diff but not on disk, and its # CN urls are gone with it — nothing left to reach. if [ -f "$f" ]; then pkgsel="$pkgsel $f"; fi - lib=$(basename "$f" .lua); lib=${lib#compat.} + lib=$(basename "$f" .lua); lib=${lib##*.} hit=0 - for mt in tests/examples/*/mcpp.toml; do - if grep -q "$lib" "$mt"; then add "$(basename "$(dirname "$mt")")"; hit=1; fi + for mt in tests/examples/*/mcpp.toml tests/mwrap/*/mcpp.toml; do + [ -f "$mt" ] || continue + if grep -Fq "$lib" "$mt"; then add "$(basename "$(dirname "$mt")")"; hit=1; fi done [ "$hit" = 1 ] || echo "note: no workspace member exercises $f" ;; # tools/ holds OFFLINE descriptor-generation and publishing @@ -1607,7 +1608,7 @@ jobs: echo "::group::$log" tail -80 "$log" echo "::endgroup::" - done < <(find tests/examples "$HOME/.mcpp/registry" -name 'mcpp_*_build.log' 2>/dev/null) + done < <(find tests/examples tests/mwrap "$HOME/.mcpp/registry" -name 'mcpp_*_build.log' 2>/dev/null) [ "$found" = 1 ] || echo "no install() build logs found" # ── Where the time went ─────────────────────────────────────────────── diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 9dededda..9b6aaeef 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -25,7 +25,7 @@ concurrency: env: PYTHONUTF8: '1' - MCPP_VERSION: '2026.10.1.2' + MCPP_VERSION: '2026.10.21.1' MCPP_INDEX_MIRROR: GLOBAL jobs: diff --git a/docs/mwrap.md b/docs/mwrap.md new file mode 100644 index 00000000..b8db9076 --- /dev/null +++ b/docs/mwrap.md @@ -0,0 +1,156 @@ +# Module wrappers: mcpplibs.mwrap + +`mcpplibs.mwrap` contains C++ module interfaces for existing C and C++ header +libraries. Native module projects remain directly under their own namespace. +A wrapper changes how a library is imported while preserving its upstream +public names, namespaces, type identities, overloads and behavior. + +[简体中文](zh/mwrap.md) + +## Names and ownership + +| Artifact | Convention | Example | +|---|---|---| +| Repository | `-m` | `mcpplibs/opencv-m` | +| Package namespace | `mcpplibs.mwrap` | same for C and C++ wrappers | +| Package short name | upstream library name, one atomic segment | `opencv` | +| Root module | complete package identity | `mcpplibs.mwrap.opencv` | +| Submodule | upstream module grouping | `mcpplibs.mwrap.opencv.core` | +| Public API | original upstream spelling | `cv::Mat`, `Ort::Session` | +| Macro mapping module | `.macros` | `mcpplibs.mwrap.opencv.macros` | + +Do not add `-m` to package/module names or relocate ordinary upstream entities +into a new C++ namespace. Preserve legacy published package identities and bytes; +publish a new mwrap identity rather than rewriting old releases. + +Use `compat.` for an independently reusable traditional implementation. +A wrapper should depend on it when its configuration and ABI are sufficient. +Complex implementations may instead vendor pinned sources under `upstream/`. +Each implementation has one owner in a program: do not link both a vendored copy +and a compat copy of the same library. + +## Repository layout + +```text +-m/ + mcpp.toml + build.mcpp # only when generation/adaptation needs it + .xlings.json # exact tested mcpp release + upstream/ # source pins, checksums, licenses; sources if vendored + patches/ # minimal reproducible patches and their reasons + port/ # compatibility implementation, headers and config templates + src/.cppm # public lib root + src/macros.cppm + generated/ # reproducible stable outputs, if needed + templates// # mcpp quickstart templates + tests/ # API, behavior, header/module and installed consumers + tools/ # upstream import, generation and auditing +``` + +Separate upstream code from wrapper and platform adaptations. Record source +URL, tag/commit, SHA256, pruning and licenses. Keep feature/target-specific +generated files in the build output directory, so concurrent configurations +cannot overwrite shared source files. Build-time downloads belong to package +providers, not module compilation or an undeclared build script. + +## Versions + +Package versions follow the upstream version, with at most one numeric wrapper +revision appended: `5.0.0`, `5.0.0.1`, `5.0.0.2`. A new upstream `5.0.1` starts +again at `5.0.1`. Revisions start at **1**: never publish both `5.0.0` and +`5.0.0.0`, which compare equally. Do not use an unrelated wrapper `0.x` version +or change the upstream patch number to describe a wrapper fix. + +Record upstream identity separately in `[package.metadata.mwrap]` and source +pins. A wrapper revision does not excuse undocumented breaking interface changes. +Non-numeric/prerelease upstream schemes need an explicit mapping preserving +their meaning. Build metadata (`+...`) is not a sortable wrapper revision. + +## Public interfaces and macros + +Prefer exporting the upstream entities with `export using`. Each entity has one +export owner; the root re-exports the enabled public module closure. Audit public +C/C++ headers, including templates and overloads. Language-binding annotations +alone are not a complete C++ API inventory. Record unsupported/internal-linkage +adaptations and test their semantics instead of silently pruning failed exports. + +Named modules cannot export preprocessing macros. A `.macros` module may map +object constants to `inline constexpr` and suitable computation macros to +functions. Put these additions in +`mcpplibs::mwrap::::macros`, retaining the original macro name. +Token concatenation, stringification, conditional compilation and control-flow +macros stay in `/macros.hpp>`, included before imports. +Preserve argument evaluation and call-site behavior; an assertion macro is not +automatically equivalent to a function. Active macros expand even in qualified +names, so test and document raw-header versus mapped-constant usage. + +## Features + +Use lowercase kebab-case and recognizable upstream vocabulary: + +- Modules: `dnn`, `highgui`, `stitching`. +- Implementations: `-`, such as `highgui-gtk3`, + `videoio-ffmpeg`, `dnn-onnxruntime`. +- Formats: `imgcodecs-png`, `imgcodecs-tiff`. +- Cross-module dependencies: `opencl`, `tbb`, `lapack`. +- Extras: `contrib-`. +- A module-only aggregate: `all-modules`; explicitly state its expansion. + +Features are additive and unify across dependency edges. Use `implies`, +dependency feature forwarding, `[feature-deps.]` and target conditions. +Consumers may use `default-features = false` on a dependency edge. Explicit +features still activate, and another edge may request defaults again. Keep +heavyweight or replaceable providers behind named features. A module API and its runtime backend are different +capabilities. Reject unavailable targets and incompatible provider combinations +explicitly; do not silently skip a requested backend. + +Capabilities select and validate providers; they do **not** remove another +provider's object files from the link. Avoid duplicate implementations before +they enter the graph. Record ABI, runtime, CPU baseline and SDK constraints. + +## Every -m package ships a runnable template + +At least one default `templates//template.toml` and project skeleton is +required. A default template runs without a GPU, camera, desktop session or a +separate model/data download. Advanced templates may declare those requirements. + +```sh +mcpp new vision --template mcpplibs.mwrap.opencv@5.0.0 +mcpp new --list-templates mcpplibs.mwrap.opencv@5.0.0 +mcpp new viewer --template mcpplibs.mwrap.opencv@5.0.0:window +``` + +Templates are data, without execution hooks. With the pinned current mcpp, +`{{self.name}}` is the **qualified package selector**, not merely its short name: + +```toml +[dependencies] +"{{self.name}}" = { version = "{{self.version}}", features = ["imgproc", "imgcodecs-png"] } +``` + +Quote the selector as one key; do not place it under another namespace table. +Declare required features explicitly and keep the library version tied to +`{{self.version}}`. Test actual `mcpp new`, then build and run the generated +project. A source candidate may use an isolated preseeded installed-package +fixture, but must verify the rendered identity/version before applying local +dependency overrides. Also test the release archive includes the templates. + +## Index and verification + +Descriptors live directly in `pkgs/mwrap/.lua`; consumers live in +`tests/mwrap//`. Do not add an extra alphabetic directory level. The +existing client scans immediate subdirectories of `pkgs` by declared identity, +and publication copies `pkgs` unchanged: no staging path conversion is needed. +Register consumers as workspace members and cover them in selection/sharding, +lint, mirrors, search and the site. Traditional providers remain under compat. + +Use the latest tested stable mcpp release, pinned consistently across manifests, +CI, examples and templates. Check current remote main before relying on source. +Run Linux tests locally and repeat release gates in CI; cover Windows/macOS in +native CI. Distinguish build-only checks from executed GUI, provider and hardware +tests. Validate cold index consumption and runtime deployment, including libraries +loaded dynamically rather than appearing on a link line. + +Each repository's PR retains checkpoint commits. Record commands and actual +results, inspect the final dependency/ABI/API graph, and wait for every relevant +CI check before reporting merge/release readiness. diff --git a/docs/repository-and-schema.md b/docs/repository-and-schema.md index 5128da34..9499191e 100644 --- a/docs/repository-and-schema.md +++ b/docs/repository-and-schema.md @@ -2,11 +2,13 @@ **English** | [简体中文](zh/repository-and-schema.md) +Module wrapper descriptors use `pkgs/mwrap/.lua` and their consumers use `tests/mwrap//`; see [the mwrap specification](mwrap.md). The category is flat and published unchanged. The workspace test source uses the current pinned mcpp; `index.toml` separately states the descriptor compatibility floor. + ## Repository layout ``` pkgs//.lua descriptors. is the initial of the full package name (compat.* → c, nlohmann.json → n, imgui → i) -mcpp.toml workspace manifest (the members list) + the root-level [indices] compat = { path = "." }, +mcpp.toml workspace manifest (the members list) + the root-level [workspace.indices] compat = { path = "." }, inherited by members (relative paths resolve against the workspace root, mcpp >= 0.0.97) tests/examples// one test project per library (a workspace member; is the package name minus its mcpp.toml prefix, or -module for module packages). Members consuming compat write no @@ -108,7 +110,7 @@ resolves. What the test surface has to validate is **the descriptors in the checkout**, not the published remote index, and `[indices]` is what redirects a namespace into this repository to make that happen. -**Root-level inheritance**: the workspace root's `mcpp.toml` declares `[indices] compat = { path = "." }`, whose +**Root-level inheritance**: the workspace root's `mcpp.toml` declares `[workspace.indices] compat = { path = "." }`, whose relative path resolves against the **workspace root** (mcpp >= 0.0.97, [mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)), and members inherit it directly instead of each writing their own `path = "../../.."`. diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md new file mode 100644 index 00000000..95526a60 --- /dev/null +++ b/docs/zh/mwrap.md @@ -0,0 +1,106 @@ +# 模块化封装:mcpplibs.mwrap + +`mcpplibs.mwrap` 为已有 C/C++ 头文件库提供模块入口;原生模块项目仍位于各自的直接命名空间下。 +封装保持上游 API 名称、namespace、类型身份、重载与行为。这里不重新设计上游 API。 + +[English](../mwrap.md) + +## 命名与实现归属 + +| 对象 | 约定 | 示例 | +|---|---|---| +| 仓库 | `<上游库名>-m` | mcpplibs/opencv-m | +| 包命名空间 | mcpplibs.mwrap | C/C++ 封装统一分类 | +| 包短名 | 上游库名,单个原子段 | opencv | +| 根模块 | 完整包身份 | mcpplibs.mwrap.opencv | +| 子模块 | 沿用上游功能分组 | mcpplibs.mwrap.opencv.core | +| API | 保留上游 | cv::Mat、Ort::Session | +| 宏映射模块 | `<根模块>.macros` | mcpplibs.mwrap.opencv.macros | + +包和模块名不带 -m,不创建另一套普通 API namespace。已发布旧身份、版本和内容保持不可变, +通过新 mwrap 身份迁移。简单实现优先依赖 compat.xxx;复杂库可以 vendor 到 upstream/。 +同一程序的同一实现只能有一个所有者,不能把 compat 与 vendored 实现重复链接。 + +## 仓库分层 + +```text +<库名>-m/ + mcpp.toml / build.mcpp / .xlings.json + upstream/ 来源、版本、校验和、许可证;需要时含原始源码 + patches/ 最小可重放补丁与原因 + port/ 适配实现、兼容头、配置模板 + src/<库名>.cppm 公共根模块 + src/macros.cppm + generated/ 必须提交的稳定可复现生成物 + templates/<名称>/ 快捷模板 + tests/ API、行为、混用与安装后消费 + tools/ 同步、生成与审计 +``` + +上游与适配分开,记录 tag/commit、SHA256、裁剪与许可。按 feature/target 变化的生成文件 +写入构建输出目录,不能覆盖共享源码。获取依赖由 provider 负责,不在模块编译时偷偷下载。 + +## 版本 + +版本与上游一致,最多追加一个数字封装修订段,例如 5.0.0、5.0.0.1、5.0.0.2。 +上游升级到 5.0.1 后从 5.0.1 重新开始;修订从 1 开始,不发布排序等同原版本的 .0。 +不使用独立 0.x 封装版本,不修改上游 patch 号冒充封装修复,不使用 +build 进行升级排序。 +metadata 与上游锁文件仍明确记录来源。破坏性模块/API 变化必须附迁移说明。 +非数字或预发布上游版本需要明确映射规则,保持原版本含义。 + +## 导出与宏 + +优先 export using 上游实体,每个实体仅有一个导出归属,根模块聚合已启用的公共模块闭包。 +以公共 C/C++ 头文件审计模板、重载和函数,不把绑定标记当完整清单,不静默 prune 失败导出。 +内部链接实体确需替换时列出清单并验证语义。 + +模块不能传递预处理宏。常量可映射为 constexpr,可保持语义的计算宏可映射为函数; +映射置于 mcpplibs::mwrap::<库名>::macros,保留原宏名。字符串化、token 拼接、条件编译、 +控制流宏通过 `/macros.hpp>` 保留,在 import 前 include。 +限定名仍会被宏展开;原始宏与映射常量的用法必须分别测试,断言函数不能默认代替断言宏。 + +## feature + +采用小写 kebab-case,复用上游术语:模块 dnn/highgui/stitching;后端 highgui-gtk3、 +videoio-ffmpeg、dnn-onnxruntime;格式 imgcodecs-png/imgcodecs-tiff;跨模块依赖 opencl/tbb/lapack; +扩展 contrib-<上游模块名>;模块全集 all-modules 必须给出展开清单。 + +feature 为加法并集,通过 implies、依赖转发、feature-deps 与 target 条件表达。 +消费者可通过依赖边的 default-features=false 关闭该边的默认集合,显式 feature 仍激活;其他边仍可能要求默认项。 +可替换/大体积后端使用明确命名的 feature 门控。 +模块 API 与实际运行后端分开说明,缺失目标/SDK 与冲突组合明确报错,不能静默跳过。 +capability 选择不会自动移除另一份实现对象文件,必须先防止重复运行库进入图。 +记录 ABI、CPU baseline、C++ runtime 和 SDK 约束。 + +## 每个 -m 包必须有可运行快捷模板 + +至少提供一个默认 templates/<名称>/template.toml 与项目骨架。默认模板不要求 GPU、摄像头、 +桌面会话或另行下载模型/数据;高级模板单独说明前提。 + +```sh +mcpp new vision --template mcpplibs.mwrap.opencv@5.0.0 +mcpp new --list-templates mcpplibs.mwrap.opencv@5.0.0 +mcpp new viewer --template mcpplibs.mwrap.opencv@5.0.0:window +``` + +模板是纯数据,不执行 hook。当前 mcpp 的 self.name 为完整包选择器,应作为一个带引号的 key: + +```toml +[dependencies] +"{{self.name}}" = { version = "{{self.version}}", features = ["imgproc", "imgcodecs-png"] } +``` + +不能再把 self.name 放入另一层 namespace 表。版本与 self.version 绑定,明确所需 feature。 +测试必须执行实际 mcpp new 后 build/run;未发布候选可使用隔离的已安装包 fixture, +先确认渲染的身份与版本,再添加本地依赖覆盖。发布归档也必须携带模板。 + +## 索引与验收 + +描述符平铺于 pkgs/mwrap/<完整身份>.lua,消费者置于 tests/mwrap/<成员>/,不再增加字母子目录。 +客户端按声明身份扫描 pkgs 的一级分类目录,发布原样复制,无需 staging 转换。 +测试注册 workspace,并接入选择器、分片、lint、镜像、搜索与站点;传统供应者仍归 compat。 + +使用最新已测试稳定 mcpp,在清单、CI、模板与例子中精确固定。核查源码前更新 remote main。 +Linux 本地执行,CI 重跑发布门槛;Windows/macOS 原生 CI。区分编译验证与实际 GUI/设备/GPU 运行, +覆盖冷索引消费和动态加载库的运行时分发。每仓库一个 PR,保留 checkpoint commits; +逐项核查最终 API/ABI/依赖图,并等待相关 CI 全通过后报告合入与发布就绪。 diff --git a/docs/zh/repository-and-schema.md b/docs/zh/repository-and-schema.md index 58e2ff08..85375f2c 100644 --- a/docs/zh/repository-and-schema.md +++ b/docs/zh/repository-and-schema.md @@ -2,11 +2,13 @@ [English](../repository-and-schema.md) | **简体中文** +模块封装描述符使用 `pkgs/mwrap/<完整身份>.lua`,消费者使用 `tests/mwrap//`,见 [mwrap 规范](mwrap.md)。分类目录平铺并原样发布。workspace 测试源码使用当前固定的 mcpp;`index.toml` 单独声明描述符兼容下限。 + ## 仓库布局 ``` pkgs//.lua 描述符。 取完整包名首字母(compat.* → c,nlohmann.json → n,imgui → i) -mcpp.toml workspace 清单(members 列表)+ 根级 [indices] compat = { path = "." }, +mcpp.toml workspace 清单(members 列表)+ 根级 [workspace.indices] compat = { path = "." }, 由成员继承(相对路径按 workspace 根解析,mcpp ≥ 0.0.97) tests/examples// 每库测试工程(workspace 成员; 为包名去前缀,模块包为 mcpp.toml -module)。消费 compat 的成员不写 [indices];消费其他 @@ -91,7 +93,7 @@ namespace = "mcpplibs", name = "capi.lua" -- 短名仍带点 测试面要验证的是 **checkout 里的描述符**,而不是已发布的远程索引,这靠 `[indices]` 把命名空间重定向到本仓完成。 -**根级继承**:workspace 根的 `mcpp.toml` 声明 `[indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 +**根级继承**:workspace 根的 `mcpp.toml` 声明 `[workspace.indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 **为什么只有一条,而且是 `compat`**: diff --git a/index.toml b/index.toml index 30cadd5e..c121b4f7 100644 --- a/index.toml +++ b/index.toml @@ -177,4 +177,4 @@ [index] spec = "1" min_mcpp = "2026.9.18.3" -latest_mcpp = "2026.10.1.2" +latest_mcpp = "2026.10.21.1" diff --git a/mcpp.toml b/mcpp.toml index 9d9ea35e..4b952ead 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -5,8 +5,12 @@ # `mcpp test --workspace` (or `mcpp test -p `). "Eat our own dog food." # See .agents/docs/2026-06-29-mcpp-native-workspace-ci-design.md and # mcpp .agents/docs/2026-06-30-workspace-test-and-zero-shell-index-design.md. +[workspace.package] +mcpp = ">=2026.10.21.1" + [workspace] members = [ + "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", "tests/examples/argparse", @@ -236,5 +240,5 @@ members = [ # asio itself comes from the published index while its `ssl` feature dep, # compat.openssl, resolves from this checkout — which is what lets an # unmerged compat descriptor be tested through a feature that activates it. -[indices] +[workspace.indices] compat = { path = "." } diff --git a/tests/openkal/pins.toml b/tests/openkal/pins.toml index a446ca5d..f8f8f0b2 100644 --- a/tests/openkal/pins.toml +++ b/tests/openkal/pins.toml @@ -18,10 +18,10 @@ toolchain = "llvm@22.1.8" # index requires mcpp >= 2026.9.18.1 but this is mcpp 2026.9.17.3 [E0006]" # before a single line of the member's own source is read. # -# 2026-09-26 (fourth move): `runtime` -> 0.15.2 and `mcpp` -> 2026.9.26.1, +# 2026-09-26 (fourth move): `runtime` -> 0.15.2 and `mcpp` -> 2026.10.21.1, # AND THE GRAPH'S OWN LINUX TARGETS JOIN THE MEASUREMENT. # -# mcpp 2026.9.26.1 links a program over a graph-supplied C library with no +# mcpp 2026.10.21.1 links a program over a graph-supplied C library with no # host library directory in its search (mcpp-community/mcpp#696). Before it, # clang derived `/usr/lib/x86_64-linux-gnu` and its siblings for such a link, # so a member's `-lm` on x86_64-linux-musl linked the HOST's glibc objects into @@ -136,7 +136,7 @@ toolchain = "llvm@22.1.8" # openkal-musl 0.16.0 and openkal-llvm-runtime 0.12.0), because a pin that # moves onto an unreachable or divergent asset fails every member at once and # reports it as a compatibility result. -mcpp = "2026.9.26.1" +mcpp = "2026.10.21.1" # The Windows target is named for the C environment the graph presents there, # which is musl's (openkal-musl declares `[c-abi] presents = "posix"`), not diff --git a/tests/plan_shards.lua b/tests/plan_shards.lua index 67663135..863a8496 100644 --- a/tests/plan_shards.lua +++ b/tests/plan_shards.lua @@ -67,10 +67,12 @@ end if #members == 0 then local toml = read_file("mcpp.toml") or error("cannot read mcpp.toml") local seen = {} - for name in toml:gmatch("tests/examples/([A-Za-z0-9._%-]+)") do - if name ~= "" and not seen[name] then - local probe = io.open("tests/examples/" .. name .. "/mcpp.toml", "r") - if probe then probe:close(); seen[name] = true; members[#members + 1] = name end + for _, category in ipairs({"examples", "mwrap"}) do + for name in toml:gmatch("tests/" .. category .. "/([A-Za-z0-9._%-]+)") do + if name ~= "" and not seen[name] then + local probe = io.open("tests/" .. category .. "/" .. name .. "/mcpp.toml", "r") + if probe then probe:close(); seen[name] = true; members[#members + 1] = name end + end end end table.sort(members) @@ -172,6 +174,7 @@ end -- ── dependency signature, for affinity ──────────────────────────────────── local function deps_of(member) local toml = read_file("tests/examples/" .. member .. "/mcpp.toml") + or read_file("tests/mwrap/" .. member .. "/mcpp.toml") if not toml then return {} end local set = {} -- The package names a member depends on. Deliberately crude: exact diff --git a/tests/run_members.sh b/tests/run_members.sh index a6c36de3..a724c96c 100755 --- a/tests/run_members.sh +++ b/tests/run_members.sh @@ -66,10 +66,10 @@ done if [ "$all" = 1 ]; then while IFS= read -r m; do [ -n "$m" ] || continue - [ -d "tests/examples/$m" ] || continue + if [ ! -d "tests/examples/$m" ] && [ ! -d "tests/mwrap/$m" ]; then continue; fi members+=("$m") - done < <(grep -o 'tests/examples/[A-Za-z0-9._-]*' mcpp.toml \ - | sed 's|tests/examples/||' | sort -u) + done < <(grep -oE 'tests/(examples|mwrap)/[A-Za-z0-9._-]+' mcpp.toml \ + | sed -E 's|tests/(examples|mwrap)/||' | sort -u) fi if [ "${#members[@]}" -eq 0 ]; then From 587e6eb3dd0a88461a89d75c75030f86c0c9528a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 05:41:22 +0900 Subject: [PATCH 04/25] fix: preserve per-member index replacement semantics --- .agents/docs/2026-10-24-add-mwrap-and-ort-plan.md | 2 ++ docs/repository-and-schema.md | 4 ++-- docs/zh/repository-and-schema.md | 4 ++-- mcpp.toml | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 306f0606..5414b3c8 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -9,3 +9,5 @@ Local checks: strict descriptor parse, cross-package references, duplicate/platf The formal English/Chinese mwrap specification records upstream[.N] versions, modules versus API namespaces, macros, optional features and executable quickstart templates. The package skill links this common specification. Autoupdate is not enabled for the SDK yet: updating it requires changing archive directory names, API inventory and ABI verification, rather than only replacing a URL version. Checkpoint state: SDK provider and category infrastructure first. The opencv/onnxruntime wrapper descriptors must be added only after immutable source archives and checksums are verified and install/template consumers execute through this index. No placeholder published versions, release tags or merges are performed here. + +CI caught a semantic difference in migrating root [indices] to [workspace.indices]: the latter merges namespace entries into members, registering the same physical index twice for non-compat members and triggering xlings ambiguity. Keep the existing root [indices] replacement semantics deliberately; this is supported compatibility behavior in the pinned client. No engine patch or per-member mass rewrite is needed. Reproduction: graphics side-effects job 120643941884 failed before installing any graphics package. diff --git a/docs/repository-and-schema.md b/docs/repository-and-schema.md index 9499191e..cbf19352 100644 --- a/docs/repository-and-schema.md +++ b/docs/repository-and-schema.md @@ -8,7 +8,7 @@ Module wrapper descriptors use `pkgs/mwrap/.lua` and their consum ``` pkgs//.lua descriptors. is the initial of the full package name (compat.* → c, nlohmann.json → n, imgui → i) -mcpp.toml workspace manifest (the members list) + the root-level [workspace.indices] compat = { path = "." }, +mcpp.toml workspace manifest (the members list) + the root-level [indices] compat = { path = "." }, inherited by members (relative paths resolve against the workspace root, mcpp >= 0.0.97) tests/examples// one test project per library (a workspace member; is the package name minus its mcpp.toml prefix, or -module for module packages). Members consuming compat write no @@ -110,7 +110,7 @@ resolves. What the test surface has to validate is **the descriptors in the checkout**, not the published remote index, and `[indices]` is what redirects a namespace into this repository to make that happen. -**Root-level inheritance**: the workspace root's `mcpp.toml` declares `[workspace.indices] compat = { path = "." }`, whose +**Root-level inheritance**: the workspace root's `mcpp.toml` declares `[indices] compat = { path = "." }`, whose relative path resolves against the **workspace root** (mcpp >= 0.0.97, [mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)), and members inherit it directly instead of each writing their own `path = "../../.."`. diff --git a/docs/zh/repository-and-schema.md b/docs/zh/repository-and-schema.md index 85375f2c..05abaa3a 100644 --- a/docs/zh/repository-and-schema.md +++ b/docs/zh/repository-and-schema.md @@ -8,7 +8,7 @@ ``` pkgs//.lua 描述符。 取完整包名首字母(compat.* → c,nlohmann.json → n,imgui → i) -mcpp.toml workspace 清单(members 列表)+ 根级 [workspace.indices] compat = { path = "." }, +mcpp.toml workspace 清单(members 列表)+ 根级 [indices] compat = { path = "." }, 由成员继承(相对路径按 workspace 根解析,mcpp ≥ 0.0.97) tests/examples// 每库测试工程(workspace 成员; 为包名去前缀,模块包为 mcpp.toml -module)。消费 compat 的成员不写 [indices];消费其他 @@ -93,7 +93,7 @@ namespace = "mcpplibs", name = "capi.lua" -- 短名仍带点 测试面要验证的是 **checkout 里的描述符**,而不是已发布的远程索引,这靠 `[indices]` 把命名空间重定向到本仓完成。 -**根级继承**:workspace 根的 `mcpp.toml` 声明 `[workspace.indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 +**根级继承**:workspace 根的 `mcpp.toml` 声明 `[indices] compat = { path = "." }`,相对路径按 **workspace 根**解析(mcpp ≥ 0.0.97,[mcpp#224](https://github.com/mcpp-community/mcpp/issues/224)),成员直接继承,不必各写一份 `path = "../../.."`。 **为什么只有一条,而且是 `compat`**: diff --git a/mcpp.toml b/mcpp.toml index 4b952ead..a9fd06fb 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -240,5 +240,5 @@ members = [ # asio itself comes from the published index while its `ssl` feature dep, # compat.openssl, resolves from this checkout — which is what lets an # unmerged compat descriptor be tested through a feature that activates it. -[workspace.indices] +[indices] compat = { path = "." } From d418997b235ec5d3918df134bb75d7992601e250 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 06:06:09 +0900 Subject: [PATCH 05/25] feat: validate the indexed ORT archive and unmodified quickstart consumer --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 2 + .../skills/add-mcpp-index-package/SKILL.md | 9 ++-- .github/workflows/validate.yml | 5 ++ .gitignore | 1 + docs/mwrap.md | 6 +++ docs/zh/mwrap.md | 10 ++++ mcpp.toml | 1 + pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua | 32 ++++++++++++ tests/mwrap/check_installed_template.py | 51 +++++++++++++++++++ tests/mwrap/onnxruntime-cpu/mcpp.toml | 14 +++++ .../mwrap/onnxruntime-cpu/tests/inference.cpp | 25 +++++++++ tests/mwrap/onnxruntime-cpu/tests/model.hpp | 2 + .../onnxruntime-cpu/tests/public_surface.cpp | 22 ++++++++ tests/prepare_mwrap_indices.py | 44 ++++++++++++++++ tests/run_members.sh | 6 +++ 15 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua create mode 100644 tests/mwrap/check_installed_template.py create mode 100644 tests/mwrap/onnxruntime-cpu/mcpp.toml create mode 100644 tests/mwrap/onnxruntime-cpu/tests/inference.cpp create mode 100644 tests/mwrap/onnxruntime-cpu/tests/model.hpp create mode 100644 tests/mwrap/onnxruntime-cpu/tests/public_surface.cpp create mode 100644 tests/prepare_mwrap_indices.py diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 5414b3c8..2d220a36 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -11,3 +11,5 @@ The formal English/Chinese mwrap specification records upstream[.N] versions, mo Checkpoint state: SDK provider and category infrastructure first. The opencv/onnxruntime wrapper descriptors must be added only after immutable source archives and checksums are verified and install/template consumers execute through this index. No placeholder published versions, release tags or merges are performed here. CI caught a semantic difference in migrating root [indices] to [workspace.indices]: the latter merges namespace entries into members, registering the same physical index twice for non-compat members and triggering xlings ambiguity. Keep the existing root [indices] replacement semantics deliberately; this is supported compatibility behavior in the pinned client. No engine patch or per-member mass rewrite is needed. Reproduction: graphics side-effects job 120643941884 failed before installing any graphics package. + +The real wrapper archive de3a0a9 was downloaded twice and pinned as SHA256 24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251. Its CPU inference and public SDK helper consumer passed through index installation. Testing the wrapper and compat SDK together confirmed both namespace-routing constraints on the current client; tests/prepare_mwrap_indices.py now provides unchanged namespace-scoped test views, while check_installed_template.py independently tests the complete unchanged published layout with no source pre-seeding or dependency rewriting. diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 3a3b273d..44dbf2ee 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -66,9 +66,12 @@ description: Use when adding a new third-party library/package to the mcpp-index 6. **编写测试工程(workspace 成员)**:mwrap 使用 `tests/mwrap//`,传统包使用 `tests/examples//`(`short` 为包名去除 `compat.`/`mcpplibs.` 前缀后的结果;公开模块包用 `-module`),并把它加进仓根 `mcpp.toml` 的 `[workspace].members`。 - 包含 `mcpp.toml` 与 `tests/*.cpp`,后者须包含**可失败的有效断言**(`return ok ? 0 : 1`)。 - - 成员声明**恰好一条** `[indices] = { path = "../../.." }`,把所消费命名空间重定向到 - 本 checkout(公开模块包用 `default`,即内建 mcpplibs 命名空间;mcpp ≥ 0.0.97)。 - 单条是硬约束:xlings 对 >1 个项目级 index repo 静默失败(mcpp#238);根级集中化待其修复。 + - 普通成员用一条 `[indices] = { path = "../../.." }` 将所消费命名空间重定向到 checkout。 + 不把同一个完整索引路径注册成多个 namespace:当前 xlings 会将同一实体报为歧义。 + mwrap wrapper + compat SDK 的组合测试先运行 `python3 tests/prepare_mwrap_indices.py`, + 再将两条 namespace 路由指向 `tests/mwrap/.indices/` 中字节不变的测试视图。 + `tests/run_members.sh` 自动准备这些视图;它们不提交、不发布,也不改变 pkgs/mwrap 的正式布局。 + 正常发布布局另由 `tests/mwrap/check_installed_template.py` 在独立 MCPP_HOME 中使用单一完整索引验收。 - 非全平台包按 `[target.'cfg(...)'.dependencies]` 门控依赖,测试源码在被门控掉的平台上 编译为 no-op `main`(`#ifdef __linux__ … #else int main(){return 0;} #endif` 模式)。 - 如需测试 feature,依赖采用长式声明 `name = { version = "…", features = ["…"] }`。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 91384afc..a5a7c13b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1518,6 +1518,11 @@ jobs: run: | "$MCPP" index update + - name: Installed mwrap archive and actual project template + if: contains(format(' {0} ', env.MEMBERS), ' onnxruntime-cpu ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" + - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash run: | diff --git a/.gitignore b/.gitignore index ad886326..69704682 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ tests/mwrap/*/target/ tests/mwrap/*/.mcpp/ tests/mwrap/*/compile_commands.json tests/mwrap/*/mcpp.lock +tests/mwrap/.indices/ diff --git a/docs/mwrap.md b/docs/mwrap.md index b8db9076..c750fbf4 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -154,3 +154,9 @@ loaded dynamically rather than appearing on a link line. Each repository's PR retains checkpoint commits. Record commands and actual results, inspect the final dependency/ABI/API graph, and wait for every relevant CI check before reporting merge/release readiness. + +## Candidate index tests across namespaces + +The pinned client routes each requested namespace independently. Registering the same full checkout twice makes xlings report duplicate project candidates. Local wrapper/SDK tests therefore call tests/prepare_mwrap_indices.py and route each namespace to a test-only view under tests/mwrap/.indices. Every descriptor is copied unchanged and its source SHA256 is recorded; no aliases share a descriptor. These views are ignored build outputs, not a publication format. + +Normal installed consumers are tested independently by tests/mwrap/check_installed_template.py: it copies the complete pkgs/index.toml publication layout into one isolated built-in index, downloads the real checksummed source archive, runs mcpp new and builds/runs the generated manifest without a path substitution. The Git and published pkgs/mwrap directory layout remains identical. diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index 95526a60..e7df4bf3 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -104,3 +104,13 @@ mcpp new viewer --template mcpplibs.mwrap.opencv@5.0.0:window Linux 本地执行,CI 重跑发布门槛;Windows/macOS 原生 CI。区分编译验证与实际 GUI/设备/GPU 运行, 覆盖冷索引消费和动态加载库的运行时分发。每仓库一个 PR,保留 checkpoint commits; 逐项核查最终 API/ABI/依赖图,并等待相关 CI 全通过后报告合入与发布就绪。 + +## 候选索引的跨 namespace 测试 + +固定客户端按请求 namespace 分别路由;同一个完整 checkout 注册两次会让 xlings 报项目候选歧义。 +本地 wrapper/SDK 组合测试先执行 tests/prepare_mwrap_indices.py,再分别指向 tests/mwrap/.indices 中的测试视图。 +各描述符字节不变并记录源 SHA256,视图之间不重复登记实体。它们是不提交、不发布的构建输出,不是新的发布格式。 + +正常安装由 tests/mwrap/check_installed_template.py 独立验证:把完整 pkgs/index.toml 发布布局放进独立 MCPP_HOME 的单一内置索引, +下载真实且带校验的源码归档,执行 mcpp new,再原样构建/运行生成清单,不替换为 path 依赖。 +Git 与发布的 pkgs/mwrap 目录始终一致。 diff --git a/mcpp.toml b/mcpp.toml index a9fd06fb..f52e16a6 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -10,6 +10,7 @@ mcpp = ">=2026.10.21.1" [workspace] members = [ + "tests/mwrap/onnxruntime-cpu", "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", diff --git a/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua new file mode 100644 index 00000000..51d4c2e0 --- /dev/null +++ b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua @@ -0,0 +1,32 @@ +-- Immutable candidate source archive; the CPU SDK is owned by compat.onnxruntime. +-- No release tag is created by this descriptor. Updates require a verified source +-- revision and archive checksum, not a URL-only automatic version substitution. +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "onnxruntime", + description = "ONNX Runtime 1.25.1 CPU C/C++ APIs through upstream-preserving modules", + licenses = { "MIT" }, + repo = "https://github.com/mcpplibs/onnxruntime-m", + type = "package", + xpm = { + linux = { + ["1.25.1"] = { + url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", + sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + }, + }, + macosx = { + ["1.25.1"] = { + url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", + sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + }, + }, + windows = { + ["1.25.1"] = { + url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", + sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + }, + }, + }, +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py new file mode 100644 index 00000000..569e5dd0 --- /dev/null +++ b/tests/mwrap/check_installed_template.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Install real archives from the unchanged candidate index and run a template.""" +import argparse +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import tomllib + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--mcpp", default="mcpp") + parser.add_argument("--toolchain", default="llvm@23.1.3") + parser.add_argument("--package", default="mcpplibs.mwrap.onnxruntime@1.25.1") + args = parser.parse_args() + root = Path(__file__).resolve().parents[2] + identity, version = args.package.rsplit("@", 1) + with tempfile.TemporaryDirectory(prefix="mwrap-installed-template-") as directory: + temporary = Path(directory) + home = temporary / "home" + index = home / "indices/mcpplibs" + index.mkdir(parents=True) + # Exactly the publication layout, with all namespaces in one built-in + # index. No namespace views, source pre-seeding or dependency rewriting. + shutil.copytree(root / "pkgs", index / "pkgs") + shutil.copyfile(root / "index.toml", index / "index.toml") + env = dict(os.environ, MCPP_HOME=str(home)) + commands = [[args.mcpp, "self", "config", "--mirror", "GLOBAL"], + [args.mcpp, "new", "--list-templates", args.package], + [args.mcpp, "new", "installed-quickstart", "--template", args.package]] + for command in commands: + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=temporary, env=env, check=True) + project = temporary / "installed-quickstart" + manifest = tomllib.loads((project / "mcpp.toml").read_text()) + dependency = manifest["dependencies"][identity] + actual_version = dependency if isinstance(dependency, str) else dependency["version"] + assert actual_version == version, dependency + if isinstance(dependency, dict): + assert "path" not in dependency and "git" not in dependency, dependency + for command in ([args.mcpp, "build", "--strict", "--toolchain", args.toolchain], + [args.mcpp, "run", "--toolchain", args.toolchain]): + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=project, env=env, check=True) + print("Verified real indexed archive installation and unmodified template consumer", flush=True) + + +if __name__ == "__main__": + main() diff --git a/tests/mwrap/onnxruntime-cpu/mcpp.toml b/tests/mwrap/onnxruntime-cpu/mcpp.toml new file mode 100644 index 00000000..383d4e14 --- /dev/null +++ b/tests/mwrap/onnxruntime-cpu/mcpp.toml @@ -0,0 +1,14 @@ +[package] +name = "onnxruntime-cpu" +version = "1.25.1" +description = "Install the immutable mwrap archive and execute CPU inference" + +[indices] +"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } +compat = { path = "../.indices/compat" } + +[dependencies."mcpplibs.mwrap"] +onnxruntime = "1.25.1" + +[target.x86_64-linux-gnu] +cxx_runtime = "toolchain-coupled" diff --git a/tests/mwrap/onnxruntime-cpu/tests/inference.cpp b/tests/mwrap/onnxruntime-cpu/tests/inference.cpp new file mode 100644 index 00000000..b747b8da --- /dev/null +++ b/tests/mwrap/onnxruntime-cpu/tests/inference.cpp @@ -0,0 +1,25 @@ +#include "model.hpp" +import std; +import mcpplibs.mwrap.onnxruntime; + +int main() { + if (Ort::GetVersionString() != "1.25.1") return 1; + static_assert(mcpplibs::mwrap::onnxruntime::macros::ORT_API_VERSION == 25); + Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "mwrap-inference"); + Ort::SessionOptions options; + options.SetIntraOpNumThreads(1); + Ort::Session session(env, identity_model, sizeof(identity_model), options); + const char* names[]{"x"}; + const char* outputs[]{"y"}; + std::array input{0.5f, -4.0f, 8.0f}; + std::array shape{3}; + auto memory = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); + auto value = Ort::Value::CreateTensor(memory, input.data(), input.size(), shape.data(), shape.size()); + auto result = session.Run(Ort::RunOptions{nullptr}, names, &value, 1, outputs, 1); + if (result.size() != 1 || !result[0].IsTensor()) return 2; + auto dimensions = result[0].GetTensorTypeAndShapeInfo().GetShape(); + if (dimensions != std::vector{3}) return 3; + const float* actual = result[0].GetTensorData(); + for (std::size_t i = 0; i < input.size(); ++i) + if (actual[i] != input[i]) return 4; +} diff --git a/tests/mwrap/onnxruntime-cpu/tests/model.hpp b/tests/mwrap/onnxruntime-cpu/tests/model.hpp new file mode 100644 index 00000000..e7932940 --- /dev/null +++ b/tests/mwrap/onnxruntime-cpu/tests/model.hpp @@ -0,0 +1,2 @@ +#pragma once +inline constexpr unsigned char identity_model[] = {0x08, 0x08, 0x12, 0x0d, 0x6d, 0x77, 0x72, 0x61, 0x70, 0x2d, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x3a, 0x44, 0x0a, 0x10, 0x0a, 0x01, 0x78, 0x12, 0x01, 0x79, 0x22, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x6d, 0x77, 0x72, 0x61, 0x70, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5a, 0x0f, 0x0a, 0x01, 0x78, 0x12, 0x0a, 0x0a, 0x08, 0x08, 0x01, 0x12, 0x04, 0x0a, 0x02, 0x08, 0x03, 0x62, 0x0f, 0x0a, 0x01, 0x79, 0x12, 0x0a, 0x0a, 0x08, 0x08, 0x01, 0x12, 0x04, 0x0a, 0x02, 0x08, 0x03, 0x42, 0x02, 0x10, 0x0d}; diff --git a/tests/mwrap/onnxruntime-cpu/tests/public_surface.cpp b/tests/mwrap/onnxruntime-cpu/tests/public_surface.cpp new file mode 100644 index 00000000..c6efd3bb --- /dev/null +++ b/tests/mwrap/onnxruntime-cpu/tests/public_surface.cpp @@ -0,0 +1,22 @@ +import std; +import mcpplibs.mwrap.onnxruntime; +import mcpplibs.mwrap.onnxruntime.config; + +int main() { + static_assert(sizeof(OrtEpApi) > 0); + static_assert(std::is_pointer_v); + static_assert(std::is_class_v>); + static_assert(std::is_same_v); + static_assert(cpu_resource_offset == 0); + CustomOpContext context; + onnxruntime::ProviderOptions provider_options{{"example", "value"}}; + if (provider_options.at("example") != "value") return 1; + Ort::SessionOptions options; + if (std::string_view(kOrtSessionOptionsConfigDisablePrepacking) != "session.disable_prepacking") return 3; + options.AddConfigEntry(kOrtSessionOptionsConfigDisablePrepacking, "1"); + OrtStatus* status = OrtSessionOptionsAppendExecutionProvider_CPU(options, 1); + if (status) { + Ort::GetApi().ReleaseStatus(status); + return 2; + } +} diff --git a/tests/prepare_mwrap_indices.py b/tests/prepare_mwrap_indices.py new file mode 100644 index 00000000..19630176 --- /dev/null +++ b/tests/prepare_mwrap_indices.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Create test-only namespace views without changing the published index layout. + +The pinned client routes descriptors by namespace, while xlings rejects two +project aliases of one physical full index as ambiguous. Each view contains +unchanged descriptor bytes for just its namespace. Normal published consumers +use one complete built-in index instead. +""" +import hashlib +import json +from pathlib import Path +import re +import shutil + + +def main(): + root = Path(__file__).resolve().parents[1] + namespaces = {name: [] for name in ("compat", "mcpplibs.mwrap")} + for source in sorted((root / "pkgs").glob("*/*.lua")): + match = re.search(r'^\s*namespace\s*=\s*[\'"]([^\'"]+)[\'"]', source.read_text(), re.M) + if match and match[1] in namespaces: + namespaces[match[1]].append(source) + for namespace, sources in namespaces.items(): + view = root / "tests/mwrap/.indices" / namespace + payload = view / "pkgs/view" + payload.mkdir(parents=True, exist_ok=True) + expected = {source.name for source in sources} + for stale in payload.glob("*.lua"): + if stale.name not in expected: + stale.unlink() + provenance = {} + for source in sources: + dest = payload / source.name + shutil.copyfile(source, dest) + digest = hashlib.sha256(source.read_bytes()).hexdigest() + assert hashlib.sha256(dest.read_bytes()).hexdigest() == digest + provenance[source.relative_to(root).as_posix()] = digest + shutil.copyfile(root / "index.toml", view / "index.toml") + (view / "source-manifest.json").write_text(json.dumps(provenance, indent=2) + "\n") + print(f"Test view {namespace}: {len(sources)} unchanged descriptors") + + +if __name__ == "__main__": + main() diff --git a/tests/run_members.sh b/tests/run_members.sh index a724c96c..961cd9bb 100755 --- a/tests/run_members.sh +++ b/tests/run_members.sh @@ -112,6 +112,12 @@ if [ -n "$shard" ]; then fi [ -n "$cache" ] && export MCPP_BUILD_CACHE="$cache" +for m in "${members[@]}"; do + if [ -d "tests/mwrap/$m" ]; then + python3 tests/prepare_mwrap_indices.py || exit $? + break + fi +done echo "cache mode: ${MCPP_BUILD_CACHE:-global (default)}" rows=$(mktemp) From e38774bb71e1581ae364b1f336b3a049345bb673 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 06:15:17 +0900 Subject: [PATCH 06/25] fix: audit immutable wrapper snapshots without inventing release updates --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 2 + docs/automatic-updates.md | 3 ++ docs/zh/automatic-updates.md | 3 ++ tests/autoupdate/ci.py | 49 ++++++++++++++---- tests/autoupdate/test_ci.py | 51 +++++++++++++++++++ 5 files changed, 97 insertions(+), 11 deletions(-) diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 2d220a36..552a2a2e 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -13,3 +13,5 @@ Checkpoint state: SDK provider and category infrastructure first. The opencv/onn CI caught a semantic difference in migrating root [indices] to [workspace.indices]: the latter merges namespace entries into members, registering the same physical index twice for non-compat members and triggering xlings ambiguity. Keep the existing root [indices] replacement semantics deliberately; this is supported compatibility behavior in the pinned client. No engine patch or per-member mass rewrite is needed. Reproduction: graphics side-effects job 120643941884 failed before installing any graphics package. The real wrapper archive de3a0a9 was downloaded twice and pinned as SHA256 24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251. Its CPU inference and public SDK helper consumer passed through index installation. Testing the wrapper and compat SDK together confirmed both namespace-routing constraints on the current client; tests/prepare_mwrap_indices.py now provides unchanged namespace-scoped test views, while check_installed_template.py independently tests the complete unchanged published layout with no source pre-seeding or dependency rewriting. + +The unchanged full-index installed-template flow passed in a fresh home: real wrapper/SDK archives and LLVM were installed, mcpp new rendered the default inference project, and its original dependency resolved and ran Identity inference. The updater audit now implements the documented immutable-commit exception rather than demanding a fake release template. All 14 updater adapter tests and the full real audit passed (85 automatic declarations, one manual snapshot), retaining historical-byte guards. Candidate selection/preparation also recognizes tests/mwrap and fully qualified dependency keys. diff --git a/docs/automatic-updates.md b/docs/automatic-updates.md index a0b7f575..4819ca96 100644 --- a/docs/automatic-updates.md +++ b/docs/automatic-updates.md @@ -42,6 +42,9 @@ Use `package.repo` as a fallback when the URL does not identify a GitHub repo. Commit snapshots, independent source/package version mappings, prereleases, non-GitHub providers and upgrades requiring recipe changes need manual handling. Do not invent a rule to opt them in; explain the exception in the PR instead. +The CI audit records descriptors whose resources all use full 40/64-digit commit +archive URLs as manual snapshots. Their historical version/resource guards still +apply; an ordinary release/tag descriptor without an update declaration still fails. The current xim adapter does not advance `latest.ref`. ## Verify a declaration diff --git a/docs/zh/automatic-updates.md b/docs/zh/automatic-updates.md index 1aa704d2..5a28b324 100644 --- a/docs/zh/automatic-updates.md +++ b/docs/zh/automatic-updates.md @@ -87,3 +87,6 @@ GitHub 自动产生的原生 PR 审批请求无需批准;验证使用显式 di 一般只改描述符、消费测试和必要的 workspace 登记,符合条件就补上自动更新声明; 不适用时在 PR 中说明。站点会从描述符发现包,普通包 PR 不向两份根 README 追加包介绍、 示例或更新记录,也不修改站点配置。只有文档整理或用户明确要求时才调整 README。 + +固定 commit 归档的 CI 审计按完整 40/64 位提交 URL 识别,并在报告中记录为人工更新; +已有版本和资源的不可静默改写检查仍然执行。普通 release/tag 描述符缺少自动更新声明仍会失败。 diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 5ac9b733..2f1d472c 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -24,8 +24,28 @@ def base_descriptors(base: str) -> set[str]: return {name for name in files.splitlines() if name.endswith(".lua")} +def immutable_commit_snapshot(info: dict) -> bool: + """Commit archives have no release-version substitution to automate.""" + urls = [] + for platform in info["platforms"].values(): + for value in platform["entries"].values(): + entry = value.plain() + if "ref" in entry: + continue + url = entry.get("url") + if isinstance(url, dict): + url = url.get("GLOBAL") + if not isinstance(url, str): + return False + urls.append(url) + return bool(urls) and all(re.fullmatch( + r"https://github\.com/[^/]+/[^/]+/archive/[0-9a-fA-F]{40}(?:[0-9a-fA-F]{24})?\.(?:tar\.gz|zip)", + url) for url in urls) + + def audit(base: str, output: Path): records = [] + manual = [] previous = base_descriptors(base) infos = inventory(ROOT) removed = previous - {info["path"] for info in infos} @@ -68,10 +88,15 @@ def audit(base: str, output: Path): f"{info['id']} {plat}/{version}") if info["direct"]: if info["autoupdate"] is None: + if immutable_commit_snapshot(info): + manual.append({"id": info["id"], "path": info["path"], + "reason": "immutable commit archive; update requires a reviewed source revision"}) + continue 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 preserved.") + output.write_text(json.dumps({"covered": len(records), "packages": records, "manual": manual}, indent=2)) + print(f"All {len(records)} updateable GitHub-direct descriptors declared; " + f"{len(manual)} immutable snapshots require manual updates; all existing versions preserved.") def _literal_field(source: str, name: str) -> int | None: @@ -89,13 +114,14 @@ def selection(base: str, output: Path): 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(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 - ): - candidates.append(path.parent.name) + for category in ("examples", "mwrap"): + for path in (ROOT / "tests" / category).glob("*/mcpp.toml"): + deps = tomllib.loads(path.read_text(encoding="utf-8")).get("dependencies", {}) + nested = deps + for part in namespace.split("."): + nested = nested.get(part, {}) if isinstance(nested, dict) else {} + if (f"{namespace}.{name}" in deps or name in deps.get(namespace, {}) or name in nested): + candidates.append(path.parent.name) if not candidates: return None return min(candidates, key=lambda value: (len(value), value)) @@ -159,12 +185,13 @@ def prepare(plan: Path, identity: str, output: Path): member = direct_member(identity) output.mkdir(parents=True, exist_ok=True) if member: - source = ROOT / "tests/examples" / member + paths = [ROOT / "tests" / category / member for category in ("examples", "mwrap")] + source = next(path for path in paths if (path / "mcpp.toml").is_file()) shutil.copytree(source, output, dirs_exist_ok=True, ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock")) 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*)?")[^"]+(".*)$', + rf'(?m)^(\s*(?:{re.escape(name)}|"{re.escape(namespace)}\.{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") diff --git a/tests/autoupdate/test_ci.py b/tests/autoupdate/test_ci.py index 0b8da9e2..a4ae4147 100644 --- a/tests/autoupdate/test_ci.py +++ b/tests/autoupdate/test_ci.py @@ -2,6 +2,7 @@ import json import subprocess import tempfile +import tomllib import unittest from pathlib import Path from unittest.mock import patch @@ -24,6 +25,56 @@ def descriptor(name): class BaselineTests(unittest.TestCase): + def test_commit_snapshot_is_manual_but_historical_bytes_are_still_guarded(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + path = root / "pkgs/mwrap/mcpplibs.mwrap.example.lua" + path.parent.mkdir(parents=True) + original = '''package = { +spec="1", namespace="mcpplibs.mwrap", name="example", +xpm={linux={["1.0.0"]={ +url="https://github.com/acme/example/archive/1111111111111111111111111111111111111111.tar.gz", +sha256="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +}}}, +}''' + path.write_text(original) + 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) + with patch.object(ci, "ROOT", root): + report = root / "coverage.json" + ci.audit("HEAD", report) + self.assertEqual(len(json.loads(report.read_text())["manual"]), 1) + path.write_text(original.replace("a" * 64, "b" * 64)) + with self.assertRaisesRegex(RuntimeError, "without a revision bump"): + ci.audit("HEAD", report) + path.write_text(original) + release = path.with_name("mcpplibs.mwrap.release.lua") + release.write_text(original.replace('name="example"', 'name="release"').replace( + 'archive/1111111111111111111111111111111111111111', 'archive/refs/tags/v1.0.0')) + with self.assertRaisesRegex(RuntimeError, "missing autoupdate"): + ci.audit("HEAD", report) + + def test_mwrap_candidate_uses_its_actual_member_and_qualified_dependency(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "tests/mwrap/example" + (source / "tests").mkdir(parents=True) + (source / "mcpp.toml").write_text( + '[package]\nname="consumer"\nversion="0.1.0"\n' + '[dependencies]\n"mcpplibs.mwrap.example"="1.0.0"\n') + (source / "tests/probe.cpp").write_text('int main() { return 0; }\n') + plan = root / "plan.json" + plan.write_text(json.dumps({"packages": [ + {"id": "mcpplibs.mwrap:example", "candidate": "1.0.1"}]})) + with patch.object(ci, "ROOT", root): + self.assertEqual(ci.direct_member("mcpplibs.mwrap:example"), "example") + dest = root / "candidate" + ci.prepare(plan, "mcpplibs.mwrap:example", dest) + result = tomllib.loads((dest / "mcpp.toml").read_text()) + self.assertEqual(result["dependencies"]["mcpplibs.mwrap.example"], "1.0.1") + self.assertEqual((dest / "tests/probe.cpp").read_text(), 'int main() { return 0; }\n') + def test_added_descriptor_and_removed_historical_descriptor(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) From a4130c75a49c6e9df03690743467696ef81d3a19 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 06:23:16 +0900 Subject: [PATCH 07/25] fix: preserve UTF-8 views and share cross-image runtime adapters --- .agents/docs/2026-10-24-add-mwrap-and-ort-plan.md | 2 ++ pkgs/c/compat.vulkan-runtime.lua | 5 ++++- pkgs/c/compat.xorgproto.lua | 4 +++- tests/mwrap/check_installed_template.py | 2 +- tests/prepare_mwrap_indices.py | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 552a2a2e..c7ac79f2 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -15,3 +15,5 @@ CI caught a semantic difference in migrating root [indices] to [workspace.indice The real wrapper archive de3a0a9 was downloaded twice and pinned as SHA256 24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251. Its CPU inference and public SDK helper consumer passed through index installation. Testing the wrapper and compat SDK together confirmed both namespace-routing constraints on the current client; tests/prepare_mwrap_indices.py now provides unchanged namespace-scoped test views, while check_installed_template.py independently tests the complete unchanged published layout with no source pre-seeding or dependency rewriting. The unchanged full-index installed-template flow passed in a fresh home: real wrapper/SDK archives and LLVM were installed, mcpp new rendered the default inference project, and its original dependency resolved and ran Identity inference. The updater audit now implements the documented immutable-commit exception rather than demanding a fake release template. All 14 updater adapter tests and the full real audit passed (85 automatic declarations, one manual snapshot), retaining historical-byte guards. Candidate selection/preparation also recognizes tests/mwrap and fully qualified dependency keys. + +Cross-platform CI correction: namespace-view metadata is explicitly decoded as UTF-8 on Windows. The ecosystem Vulkan check also exposed two header/runtime adapter anchors defaulting to static while reachable from several shared images; their providers now default to shared form, with explicit packaging revision 1 on current entries and unchanged source URLs/hashes. The consumer can still choose linkage. Llama Vulkan CI pins this candidate index commit rather than requiring the PR to merge first. diff --git a/pkgs/c/compat.vulkan-runtime.lua b/pkgs/c/compat.vulkan-runtime.lua index a85a6f67..0b527ae6 100644 --- a/pkgs/c/compat.vulkan-runtime.lua +++ b/pkgs/c/compat.vulkan-runtime.lua @@ -121,6 +121,7 @@ package = { -- A new key because install() output is baked into the installed -- payload. ["2026.09.11"] = { + revision = 1, url = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Loader/vulkan-sdk-1.4.357.0/README.md", sha256 = "21ec0987a05bd680ecd11f8be747e27744d7558f7318736f6cb8a5c5ec1b8ba8", }, @@ -175,7 +176,9 @@ package = { import_std = false, c_standard = "c11", sources = { "mcpp_generated/vulkan_runtime_empty.c" }, - targets = { ["vulkan_runtime"] = { kind = "lib" } }, + -- Both the loader and its program reach this adapter. One shared + -- form avoids placing a static provider in several runtime images. + targets = { ["vulkan_runtime"] = { kind = "lib", linkage = "shared" } }, deps = {}, runtime = { library_dirs = { "mcpp_generated/vulkan_runtime/lib" }, diff --git a/pkgs/c/compat.xorgproto.lua b/pkgs/c/compat.xorgproto.lua index fe538ce3..199161ae 100644 --- a/pkgs/c/compat.xorgproto.lua +++ b/pkgs/c/compat.xorgproto.lua @@ -10,6 +10,7 @@ package = { xpm = { linux = { ["2025.1"] = { + revision = 1, url = { GLOBAL = "https://xorg.freedesktop.org/releases/individual/proto/xorgproto-2025.1.tar.gz", CN = "https://gitcode.com/mcpp-res/xorgproto/releases/download/2025.1/xorgproto-2025.1.tar.gz", @@ -29,7 +30,8 @@ package = { ["mcpp_generated/xorgproto_empty.c"] = "int mcpp_compat_xorgproto_headers_anchor(void) { return 0; }\n", }, sources = {"mcpp_generated/xorgproto_empty.c"}, - targets = { ["xorgproto"] = { kind = "lib" } }, + -- X11, Xau, Xdmcp and their consumers can be separate shared images. + targets = { ["xorgproto"] = { kind = "lib", linkage = "shared" } }, deps = {}, }, } diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 569e5dd0..f2ee9d5a 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -34,7 +34,7 @@ def main(): print("+", " ".join(command), flush=True) subprocess.run(command, cwd=temporary, env=env, check=True) project = temporary / "installed-quickstart" - manifest = tomllib.loads((project / "mcpp.toml").read_text()) + manifest = tomllib.loads((project / "mcpp.toml").read_text(encoding="utf-8")) dependency = manifest["dependencies"][identity] actual_version = dependency if isinstance(dependency, str) else dependency["version"] assert actual_version == version, dependency diff --git a/tests/prepare_mwrap_indices.py b/tests/prepare_mwrap_indices.py index 19630176..a65979b4 100644 --- a/tests/prepare_mwrap_indices.py +++ b/tests/prepare_mwrap_indices.py @@ -17,7 +17,7 @@ def main(): root = Path(__file__).resolve().parents[1] namespaces = {name: [] for name in ("compat", "mcpplibs.mwrap")} for source in sorted((root / "pkgs").glob("*/*.lua")): - match = re.search(r'^\s*namespace\s*=\s*[\'"]([^\'"]+)[\'"]', source.read_text(), re.M) + match = re.search(r'^\s*namespace\s*=\s*[\'"]([^\'"]+)[\'"]', source.read_text(encoding="utf-8"), re.M) if match and match[1] in namespaces: namespaces[match[1]].append(source) for namespace, sources in namespaces.items(): From 8d0f3cb90b8184397e4d9b8eee0a3b1a49e28aa4 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 07:05:08 +0900 Subject: [PATCH 08/25] fix: deploy ORT notices and verify relocated inference bundles --- .github/workflows/validate.yml | 4 +- pkgs/c/compat.onnxruntime.lua | 12 ++++++ tests/mwrap/check_installed_template.py | 57 +++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a5a7c13b..4d36f79c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1518,10 +1518,10 @@ jobs: run: | "$MCPP" index update - - name: Installed mwrap archive and actual project template + - name: Installed mwrap archive, project template and relocated deployment if: contains(format(' {0} ', env.MEMBERS), ' onnxruntime-cpu ') && matrix.toolchain == 'default' shell: bash - run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --deploy - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash diff --git a/pkgs/c/compat.onnxruntime.lua b/pkgs/c/compat.onnxruntime.lua index bbb9ff7e..a86483af 100644 --- a/pkgs/c/compat.onnxruntime.lua +++ b/pkgs/c/compat.onnxruntime.lua @@ -50,16 +50,28 @@ static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1 #endif ]=] }, linux = { runtime = { + deploy = { + { from = "onnxruntime-linux-x64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, + { from = "onnxruntime-linux-x64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + }, libraries = { "onnxruntime", "stdc++" }, link_library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, } }, macosx = { runtime = { + deploy = { + { from = "onnxruntime-osx-arm64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, + { from = "onnxruntime-osx-arm64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + }, libraries = { "onnxruntime" }, link_library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, } }, windows = { runtime = { + deploy = { + { from = "onnxruntime-win-x64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, + { from = "onnxruntime-win-x64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + }, libraries = { "onnxruntime" }, link_library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index f2ee9d5a..1382f2ab 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 """Install real archives from the unchanged candidate index and run a template.""" import argparse +import hashlib +import json import os from pathlib import Path import shutil import subprocess +import sys import tempfile import tomllib @@ -14,6 +17,7 @@ def main(): parser.add_argument("--mcpp", default="mcpp") parser.add_argument("--toolchain", default="llvm@23.1.3") parser.add_argument("--package", default="mcpplibs.mwrap.onnxruntime@1.25.1") + parser.add_argument("--deploy", action="store_true", help="pack, relocate and run the consumer; verify SDK notices") args = parser.parse_args() root = Path(__file__).resolve().parents[2] identity, version = args.package.rsplit("@", 1) @@ -45,6 +49,59 @@ def main(): print("+", " ".join(command), flush=True) subprocess.run(command, cwd=project, env=env, check=True) print("Verified real indexed archive installation and unmodified template consumer", flush=True) + if args.deploy: + mode = "self-contained" if sys.platform.startswith("linux") else "vendored" + command = [args.mcpp, "pack", "--toolchain", args.toolchain, "--mode", mode, + "--format", "dir", "--output", "deployment-audit"] + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=project, env=env, check=True) + bundle = project / "target/dist/deployment-audit" + relocated = temporary / "relocated/bundle" + shutil.copytree(bundle, relocated, symlinks=True) + notices = list(relocated.rglob("licenses/onnxruntime/ThirdPartyNotices.txt")) + assert len(notices) == 1 and notices[0].stat().st_size, notices + installed = [] + for entry in json.loads((project / "compile_commands.json").read_text(encoding="utf-8")): + arguments = entry["arguments"] + for number, argument in enumerate(arguments): + directory = (arguments[number + 1] if argument in ("-I", "/I") else + argument[2:] if argument.startswith(("-I", "/I")) else "") + include = Path(directory) + if directory and (include / "onnxruntime_c_api.h").is_file(): + installed.append(include.parent / "ThirdPartyNotices.txt") + expected = hashlib.sha256(notices[0].read_bytes()).hexdigest() + assert any(p.is_file() and hashlib.sha256(p.read_bytes()).hexdigest() == expected + for p in installed), installed + runtime_env = {key: value for key, value in os.environ.items() + if key not in {"MCPP_HOME", "XLINGS_HOME", "LD_LIBRARY_PATH", "LD_PRELOAD", + "DYLD_LIBRARY_PATH", "DYLD_FALLBACK_LIBRARY_PATH"}} + empty_home = temporary / "empty-home" + empty_home.mkdir() + runtime_env.update(HOME=str(empty_home), USERPROFILE=str(empty_home)) + if sys.platform.startswith("linux"): + runtime_env["PATH"] = "/usr/bin:/bin" + launcher = relocated / "run.sh" + else: + filename = "installed-quickstart" + (".exe" if os.name == "nt" else "") + launcher = relocated / "bin" / filename + if not launcher.exists(): + launcher = relocated / filename + if os.name == "nt": + system_root = os.environ.get("SystemRoot", r"C:\Windows") + runtime_env["PATH"] = os.pathsep.join([system_root, str(Path(system_root) / "System32")]) + subprocess.run([str(launcher)], cwd=empty_home, env=runtime_env, check=True) + if sys.platform.startswith("linux"): + loader = relocated / "lib/ld-linux-x86-64.so.2" + listing = subprocess.run([str(loader), "--library-path", str(relocated / "lib"), + "--list", str(relocated / "bin/installed-quickstart")], + cwd=empty_home, env=runtime_env, text=True, capture_output=True, check=True) + for line in listing.stdout.splitlines(): + if "=>" not in line: + continue + resolved = line.split("=>", 1)[1].strip().split(" ", 1)[0] + if resolved.startswith("/"): + assert Path(resolved).resolve().is_relative_to(relocated.resolve()), line + print("Verified relocated inference bundle and byte-identical SDK notices", flush=True) if __name__ == "__main__": From 7d8b3ffe99bcbc76e69b244eac39326d96254a36 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 07:07:38 +0900 Subject: [PATCH 09/25] fix: declare the relocatable macOS ORT loader search path --- pkgs/c/compat.onnxruntime.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/c/compat.onnxruntime.lua b/pkgs/c/compat.onnxruntime.lua index a86483af..a6f4cd67 100644 --- a/pkgs/c/compat.onnxruntime.lua +++ b/pkgs/c/compat.onnxruntime.lua @@ -58,7 +58,10 @@ static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1 link_library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, } }, - macosx = { runtime = { + -- The SDK install name is @rpath/libonnxruntime.1.25.1.dylib. + -- Consumers must also search beside their executable so pack can + -- place the dylib there and the relocated program can load it. + macosx = { ldflags = { "-Wl,-rpath,@loader_path" }, runtime = { deploy = { { from = "onnxruntime-osx-arm64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, { from = "onnxruntime-osx-arm64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, From 4018413f17742a038a6386751039f86f9d9495b8 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 07:41:54 +0900 Subject: [PATCH 10/25] feat: index runnable llama CPU templates and validate the latest mcpp release --- .../skills/add-mcpp-index-package/SKILL.md | 4 +++ .github/workflows/openkal-compat.yml | 2 +- .github/workflows/validate.yml | 7 +++- .github/workflows/xpkgindex-update.yml | 2 +- docs/mwrap.md | 4 +++ docs/zh/mwrap.md | 5 ++- index.toml | 2 +- mcpp.toml | 1 + pkgs/g/ggml-org.llamacpp.lua | 18 ++++++++++ pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua | 12 +++---- tests/mwrap/check_installed_template.py | 28 +++++++++++++-- tests/mwrap/llamacpp-cpu/mcpp.toml | 10 ++++++ tests/mwrap/llamacpp-cpu/tests/api.cpp | 35 +++++++++++++++++++ 13 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 tests/mwrap/llamacpp-cpu/mcpp.toml create mode 100644 tests/mwrap/llamacpp-cpu/tests/api.cpp diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 44dbf2ee..0b2dad03 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -118,6 +118,10 @@ capability 选择不会自动移除别的 provider 对象,不能用它掩盖 须验证启用后的真实行为,以及不启用时不编译/获取可选依赖。只看到符号常量或配置名称不证明后端可用。 Linux 本地执行;Windows/macOS 用原生 CI,缺少真实设备时明确报告 build-only/未执行项。 +共享 SDK 还须验收 `mcpp pack` 后的搬迁运行与运行库闭包;显式通过 `runtime.deploy` 部署 +LICENSE 和 ThirdPartyNotices 等通知,并与解析到的 SDK 原文件逐字节比较。 +不能把安装目录中的运行成功当作可分发包已验收,也不能靠修改 mcpp 引擎绕过库的部署声明。 + 每个新建或重构的 -m 库至少提供一个默认 mcpp 快捷模板。实际执行 `mcpp new --template <完整包身份>@<版本>`, 再 build/run 生成工程。当前 `{{self.name}}` 是完整包选择器,应写 `"{{self.name}}"` 为单个依赖 key, 不能再放到另一层 namespace 表中;版本用 self.version,所需 feature 显式声明。 diff --git a/.github/workflows/openkal-compat.yml b/.github/workflows/openkal-compat.yml index c9b9de84..ca077376 100644 --- a/.github/workflows/openkal-compat.yml +++ b/.github/workflows/openkal-compat.yml @@ -42,7 +42,7 @@ env: # run if this drifts from pins.toml. See the comment beside `mcpp` in # pins.toml for why this pin, not just validate.yml's, is gated by the # index floor. - MCPP_VERSION: "2026.10.21.1" + MCPP_VERSION: "2026.10.24.1" # CROSS-VALIDATION BEFORE A RELEASE, NOT AFTER IT. # # Empty in an ordinary run, so this measurement keeps using the RELEASED diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 4d36f79c..a68ea4d6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -431,7 +431,7 @@ env: # one module map of what it reaches; every unit of an ELF target is compiled # position-independent; `mcpp pack` states its build; `[build] jobs` bounds # `test` and `pack` (#751, #753). - MCPP_VERSION: "2026.10.21.1" + MCPP_VERSION: "2026.10.24.1" jobs: lint: @@ -1523,6 +1523,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --deploy + - name: Installed llama.cpp-m archive and actual CPU project template + if: contains(format(' {0} ', env.MEMBERS), ' llamacpp-cpu ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package ggml-org.llamacpp@b10069.3 --legacy-exact-key + - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash run: | diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 9b6aaeef..33bd380b 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -25,7 +25,7 @@ concurrency: env: PYTHONUTF8: '1' - MCPP_VERSION: '2026.10.21.1' + MCPP_VERSION: '2026.10.24.1' MCPP_INDEX_MIRROR: GLOBAL jobs: diff --git a/docs/mwrap.md b/docs/mwrap.md index c750fbf4..4a14f62e 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -150,6 +150,10 @@ Run Linux tests locally and repeat release gates in CI; cover Windows/macOS in native CI. Distinguish build-only checks from executed GUI, provider and hardware tests. Validate cold index consumption and runtime deployment, including libraries loaded dynamically rather than appearing on a link line. +When a runtime SDK ships LICENSE and third-party notices, declare their deployment +alongside the runtime files. Verify those bytes in a relocated consumer bundle; +automatic license discovery may copy LICENSE while omitting additional notices. +Use `runtime.deploy` for explicit relative destinations without changing the engine. Each repository's PR retains checkpoint commits. Record commands and actual results, inspect the final dependency/ABI/API graph, and wait for every relevant diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index e7df4bf3..86222c99 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -102,7 +102,10 @@ mcpp new viewer --template mcpplibs.mwrap.opencv@5.0.0:window 使用最新已测试稳定 mcpp,在清单、CI、模板与例子中精确固定。核查源码前更新 remote main。 Linux 本地执行,CI 重跑发布门槛;Windows/macOS 原生 CI。区分编译验证与实际 GUI/设备/GPU 运行, -覆盖冷索引消费和动态加载库的运行时分发。每仓库一个 PR,保留 checkpoint commits; +覆盖冷索引消费和动态加载库的运行时分发。SDK 附带 LICENSE 与第三方通知时,通过 +`runtime.deploy` 显式声明它们的相对部署位置,并在搬迁后的消费方分发包中校验字节; +自动许可证收集可能只复制 LICENSE 而遗漏其他通知,无需为此修改引擎。 +每仓库一个 PR,保留 checkpoint commits; 逐项核查最终 API/ABI/依赖图,并等待相关 CI 全通过后报告合入与发布就绪。 ## 候选索引的跨 namespace 测试 diff --git a/index.toml b/index.toml index c121b4f7..d107789e 100644 --- a/index.toml +++ b/index.toml @@ -177,4 +177,4 @@ [index] spec = "1" min_mcpp = "2026.9.18.3" -latest_mcpp = "2026.10.21.1" +latest_mcpp = "2026.10.24.1" diff --git a/mcpp.toml b/mcpp.toml index f52e16a6..53d483c8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -11,6 +11,7 @@ mcpp = ">=2026.10.21.1" [workspace] members = [ "tests/mwrap/onnxruntime-cpu", + "tests/mwrap/llamacpp-cpu", "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", diff --git a/pkgs/g/ggml-org.llamacpp.lua b/pkgs/g/ggml-org.llamacpp.lua index a7c82f7a..c09f3b81 100644 --- a/pkgs/g/ggml-org.llamacpp.lua +++ b/pkgs/g/ggml-org.llamacpp.lua @@ -67,6 +67,12 @@ package = { -- consumer wanting a feature must be able to name the revision that -- has it, and one that cannot use the newer engine must still find a -- revision it can build. + -- Candidate b10069.3 adds runnable templates; immutable source + -- bytes are verified before the release tag exists. + ["b10069.3"] = { + url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", + sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + }, ["b10069.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/llama.cpp-m/archive/refs/tags/b10069.2.tar.gz", @@ -87,6 +93,12 @@ package = { }, }, macosx = { + -- Candidate b10069.3 adds runnable templates; immutable source + -- bytes are verified before the release tag exists. + ["b10069.3"] = { + url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", + sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + }, ["b10069.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/llama.cpp-m/archive/refs/tags/b10069.2.tar.gz", @@ -107,6 +119,12 @@ package = { }, }, windows = { + -- Candidate b10069.3 adds runnable templates; immutable source + -- bytes are verified before the release tag exists. + ["b10069.3"] = { + url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", + sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + }, ["b10069.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/llama.cpp-m/archive/refs/tags/b10069.2.tar.gz", diff --git a/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua index 51d4c2e0..693fe9ac 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", - sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", + sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", }, }, macosx = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", - sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", + sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", }, }, windows = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/de3a0a9da143f4ae52eb5e1a0302ba3fdf623b6d.tar.gz", - sha256 = "24d4bc26d2f426e3643f21d289d92e7a23b3ba54524a099a1079c1c490915251", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", + sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", }, }, }, diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 1382f2ab..cc072a19 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -18,6 +18,9 @@ def main(): parser.add_argument("--toolchain", default="llvm@23.1.3") parser.add_argument("--package", default="mcpplibs.mwrap.onnxruntime@1.25.1") parser.add_argument("--deploy", action="store_true", help="pack, relocate and run the consumer; verify SDK notices") + parser.add_argument("--run-arg", action="append", default=[]) + parser.add_argument("--legacy-exact-key", action="store_true", + help="allow only mcpp's nonnumeric exact-version diagnostic; fail other build warnings") args = parser.parse_args() root = Path(__file__).resolve().parents[2] identity, version = args.package.rsplit("@", 1) @@ -44,10 +47,29 @@ def main(): assert actual_version == version, dependency if isinstance(dependency, dict): assert "path" not in dependency and "git" not in dependency, dependency - for command in ([args.mcpp, "build", "--strict", "--toolchain", args.toolchain], - [args.mcpp, "run", "--toolchain", args.toolchain]): + build = [args.mcpp, "build", "--toolchain", args.toolchain] + if not args.legacy_exact_key: + build.append("--strict") + else: + assert not version[0].isdigit(), "This exception is only for upstream nonnumeric release keys" + for command in (build, [args.mcpp, "run", "--toolchain", args.toolchain] + + (["--", *args.run_arg] if args.run_arg else [])): print("+", " ".join(command), flush=True) - subprocess.run(command, cwd=project, env=env, check=True) + if args.legacy_exact_key and command is build: + # This is a supported exact index key, not a numeric range. + # --strict promotes its planner warning to an error. Preserve + # that upstream identity, but reject every other warning, + # including module/ABI compatibility degradations. + result = subprocess.run(command, cwd=project, env=env, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + print(result.stdout, end="", flush=True) + result.check_returncode() + allowed = f"[dependencies] {identity} = '{version}' is not a version RANGE " + for line in result.stdout.splitlines(): + if "warning:" in line: + assert line.split("warning:", 1)[1].strip().startswith(allowed), line + else: + subprocess.run(command, cwd=project, env=env, check=True) print("Verified real indexed archive installation and unmodified template consumer", flush=True) if args.deploy: mode = "self-contained" if sys.platform.startswith("linux") else "vendored" diff --git a/tests/mwrap/llamacpp-cpu/mcpp.toml b/tests/mwrap/llamacpp-cpu/mcpp.toml new file mode 100644 index 00000000..320539b2 --- /dev/null +++ b/tests/mwrap/llamacpp-cpu/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "llamacpp-cpu" +version = "b10069.3" +description = "Install the immutable llama.cpp-m archive and exercise the CPU API" + +[indices] +ggml-org = { path = "../../.." } + +[dependencies.ggml-org] +llamacpp = "b10069.3" diff --git a/tests/mwrap/llamacpp-cpu/tests/api.cpp b/tests/mwrap/llamacpp-cpu/tests/api.cpp new file mode 100644 index 00000000..074b1f5a --- /dev/null +++ b/tests/mwrap/llamacpp-cpu/tests/api.cpp @@ -0,0 +1,35 @@ +import std; +import llamacpp; + +int main() { + static_assert(LLAMA_DEFAULT_SEED == 0xFFFFFFFFu); + static_assert(LLAMA_TOKEN_NULL == llama_token{-1}); + + llama_backend_init(); + + const auto model_params = llama_model_default_params(); + const bool gpu_offload_available = llama_supports_gpu_offload(); + llama_sampler * chain = llama_sampler_chain_init( + llama_sampler_chain_default_params() + ); + llama_sampler * greedy = llama_sampler_init_greedy(); + + if (!chain || !greedy) { + if (chain) { + llama_sampler_free(chain); + } else if (greedy) { + llama_sampler_free(greedy); + } + llama_backend_free(); + return 1; + } + + llama_sampler_chain_add(chain, greedy); + llama_sampler_free(chain); + llama_backend_free(); + + std::cout << "gpu_offload_available=" << gpu_offload_available + << " default_gpu_layers=" << model_params.n_gpu_layers << '\n' + << "LLAMACPP_MWRAP_CPU_INDEX_TEST=PASS\n"; + return 0; +} From 11dde87f6e7fde4ad10906540d75ade269f7ae70 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 07:45:33 +0900 Subject: [PATCH 11/25] fix: align the OpenKAL measurement client pin with CI --- tests/openkal/pins.toml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/openkal/pins.toml b/tests/openkal/pins.toml index f8f8f0b2..797e9c37 100644 --- a/tests/openkal/pins.toml +++ b/tests/openkal/pins.toml @@ -6,10 +6,10 @@ # version names the whole graph. runtime = "0.15.2" toolchain = "llvm@22.1.8" -# `mcpp` here moves together with index.toml's `min_mcpp` and -# .github/workflows/openkal-compat.yml's own MCPP_VERSION (that workflow -# checks the two agree, and fails loudly if they do not) -- not because this -# is the same pin as validate.yml's, but because compat.py's `prepare()` +# `mcpp` here must match .github/workflows/openkal-compat.yml's MCPP_VERSION +# (the workflow checks equality), and must satisfy index.toml's min_mcpp. +# Advancing the test engine does not itself raise that client floor. +# The floor still matters because compat.py's `prepare()` # opens THIS repository as a live index (`[indices] compat = { path = ... }`) # to measure against uncommitted descriptor changes, so index.toml's # min_mcpp gates this measurement exactly as it gates every other client. @@ -18,6 +18,9 @@ toolchain = "llvm@22.1.8" # index requires mcpp >= 2026.9.18.1 but this is mcpp 2026.9.17.3 [E0006]" # before a single line of the member's own source is read. # +# 2026-10-24: measurement engine updated to 2026.10.24.1; runtime and +# toolchain pins below remain unchanged. The index client floor is unchanged. +# # 2026-09-26 (fourth move): `runtime` -> 0.15.2 and `mcpp` -> 2026.10.21.1, # AND THE GRAPH'S OWN LINUX TARGETS JOIN THE MEASUREMENT. # @@ -136,7 +139,7 @@ toolchain = "llvm@22.1.8" # openkal-musl 0.16.0 and openkal-llvm-runtime 0.12.0), because a pin that # moves onto an unreachable or divergent asset fails every member at once and # reports it as a compatibility result. -mcpp = "2026.10.21.1" +mcpp = "2026.10.24.1" # The Windows target is named for the C environment the graph presents there, # which is musl's (openkal-musl declares `[c-abi] presents = "posix"`), not From f0f8e48c396dad2ac67315f2ac54a197772e6555 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 08:25:48 +0900 Subject: [PATCH 12/25] test: validate current llama CPU and Metal consumers against revision 3 --- tests/examples/llamacpp-metal/mcpp.toml | 2 +- tests/examples/llamacpp/mcpp.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/examples/llamacpp-metal/mcpp.toml b/tests/examples/llamacpp-metal/mcpp.toml index 4a34299d..e85c2f90 100644 --- a/tests/examples/llamacpp-metal/mcpp.toml +++ b/tests/examples/llamacpp-metal/mcpp.toml @@ -7,4 +7,4 @@ name = "llamacpp-metal-tests" version = "0.1.0" [target.'cfg(macos)'.dependencies.ggml-org] -llamacpp = { version = "b10069.2", features = ["backend-metal"] } +llamacpp = { version = "b10069.3", features = ["backend-metal"] } diff --git a/tests/examples/llamacpp/mcpp.toml b/tests/examples/llamacpp/mcpp.toml index b5e9e0cb..12e0758d 100644 --- a/tests/examples/llamacpp/mcpp.toml +++ b/tests/examples/llamacpp/mcpp.toml @@ -7,10 +7,10 @@ name = "llamacpp-tests" version = "0.1.0" [target.'cfg(linux)'.dependencies.ggml-org] -llamacpp = "b10069.2" +llamacpp = "b10069.3" [target.'cfg(macos)'.dependencies.ggml-org] -llamacpp = "b10069.2" +llamacpp = "b10069.3" [target.'cfg(windows)'.dependencies.ggml-org] -llamacpp = "b10069.2" +llamacpp = "b10069.3" From b22a9a8a436beabffe92982da7c08266476042a6 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 10:34:30 +0900 Subject: [PATCH 13/25] feat: index runnable legacy wrapper revisions and enforce mwrap repository norms --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 19 +++++++++ .../skills/add-mcpp-index-package/SKILL.md | 14 +++++++ .github/workflows/validate.yml | 42 +++++++++++++++++++ docs/mwrap.md | 31 ++++++++++++++ docs/zh/mwrap.md | 23 ++++++++++ mcpp.toml | 3 ++ pkgs/f/ffmpeg.ffmpeg.lua | 21 ++++++++++ pkgs/g/ggml-org.llamacpp.lua | 12 +++--- pkgs/g/grpc-plugin.lua | 22 ++++++++++ pkgs/g/grpc.lua | 17 ++++++++ pkgs/g/grpcgen.lua | 22 ++++++++++ pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua | 12 +++--- pkgs/o/ocornut.imgui.lua | 22 ++++++++++ tests/mwrap/ffmpeg-cpu/mcpp.toml | 10 +++++ tests/mwrap/ffmpeg-cpu/tests/behavior.cpp | 32 ++++++++++++++ tests/mwrap/grpc-cpu/mcpp.toml | 13 ++++++ tests/mwrap/grpc-cpu/tests/behavior.cpp | 13 ++++++ tests/mwrap/imgui-headless/mcpp.toml | 10 +++++ tests/mwrap/imgui-headless/tests/behavior.cpp | 28 +++++++++++++ 19 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 tests/mwrap/ffmpeg-cpu/mcpp.toml create mode 100644 tests/mwrap/ffmpeg-cpu/tests/behavior.cpp create mode 100644 tests/mwrap/grpc-cpu/mcpp.toml create mode 100644 tests/mwrap/grpc-cpu/tests/behavior.cpp create mode 100644 tests/mwrap/imgui-headless/mcpp.toml create mode 100644 tests/mwrap/imgui-headless/tests/behavior.cpp diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index c7ac79f2..db7f59a1 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -17,3 +17,22 @@ The real wrapper archive de3a0a9 was downloaded twice and pinned as SHA256 24d4b The unchanged full-index installed-template flow passed in a fresh home: real wrapper/SDK archives and LLVM were installed, mcpp new rendered the default inference project, and its original dependency resolved and ran Identity inference. The updater audit now implements the documented immutable-commit exception rather than demanding a fake release template. All 14 updater adapter tests and the full real audit passed (85 automatic declarations, one manual snapshot), retaining historical-byte guards. Candidate selection/preparation also recognizes tests/mwrap and fully qualified dependency keys. Cross-platform CI correction: namespace-view metadata is explicitly decoded as UTF-8 on Windows. The ecosystem Vulkan check also exposed two header/runtime adapter anchors defaulting to static while reachable from several shared images; their providers now default to shared form, with explicit packaging revision 1 on current entries and unchanged source URLs/hashes. The consumer can still choose linkage. Llama Vulkan CI pins this candidate index commit rather than requiring the PR to merge first. + +## Existing-wrapper template archive checkpoint + +- All six -m repositories now use concise root READMEs (normally <=100 lines per language), with detailed usage under docs/. The bilingual mwrap spec and authoring skill record the shared structure and the local aggregate resource limit. +- Added immutable candidate versions for FFmpeg 8.1.2.1, ImGui 1.92.8.1 and gRPC/runtime-plugin-rule 1.83.0.1. Published older version blocks are unchanged. Existing package/module identities remain compatible. ImGui and gRPC now declare tag-based automatic update URLs; coupled gRPC manifests are still version-checked by source CI. +- CPU/headless workspace consumers live in tests/mwrap/{ffmpeg-cpu,imgui-headless,grpc-cpu}/tests, so mcpp test executes their behavior checks rather than merely building an application. Windows gRPC explicitly remains unavailable with the declared OpenSSL provider. +- Each candidate's full-commit archive was downloaded twice with identical SHA256. Updated the unpublished ORT/llama candidates to include the reviewed README revisions. Archive references at this checkpoint: + +- onnxruntime-m: `576c780bfcbd792c7f4194942c90324f183a48aa`, SHA256 `3654811f65b98ade75b5e46fe59a0c4479d815aa7fa9aceff9f9cc3fffd8ddc1`. +- llama.cpp-m: `5028a1aa4f741db4dbd86d6ebd1928ef21027147`, SHA256 `9b21c35c17e801ceee1165960eefbf7280ad50e14aaf3ea5603fbf2c4ab911de`. +- imgui-m: `ffebf83be99ba0eec2677cc197dea3316a72b501`, SHA256 `9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f`. +- grpc-m: `839b61e94fff14892a1dfb2c211a4a8e66342239`, SHA256 `b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4`. +- ffmpeg-m: `1e8bce6ee12c50e43058245df42515ffbabfc78f`, SHA256 `595b5bcca166583dd082ae911f7e9ed279a3e8899e9564f0901e084737c89ab2`. + +Local evidence: OpenCV LLVM/GCC all-module strict tests each pass fourteen binaries; the GCC ORT adapter fix also passes actual CPU-profile inference. FFmpeg's managed-NASM default template strictly builds and decodes three generated frames. The full index audit covers 90 automatic declarations plus one manual snapshot and preserves all historical version blocks; fourteen updater regressions and changed-descriptor all-platform grammar/name/syntax checks pass. + +The new complete-index ImGui archive/template check is running in a fresh MCPP_HOME. Final fresh-archive checks for changed source revisions, the new workspace test entries and native CI are not yet complete. gRPC's cold installed-template check has its own Linux/macOS jobs with a 210-minute limit; it does not consume a workspace shard's existing 90-minute allowance. No release readiness is inferred from source CI or archive checksums alone. + +Local heavy commands run serially in mwrap-tests.slice with CPUQuota=1600%, MemoryHigh=28 GiB, MemoryMax=32 GiB, TasksMax=512, taskset 0-15 and MCPP_JOBS=6 on this 32-logical-CPU/62-GiB host. Check current available resources and disk before each check; retain at least 6 GiB free during builds. No engine change, merge or tag is included. diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 0b2dad03..1d512247 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -104,6 +104,20 @@ description: Use when adding a new third-party library/package to the mcpp-index 用户明确要求整理文档或说明确有变化时才改入口文档。新增包通常也不改 `.xpkgindex.json`。 - 详细字段、例外和认证机制见 [自动更新文档](../../../docs/zh/automatic-updates.md)。 +## -m 仓库 README + +新增或重构 `<库名>-m` 时,README 使用统一简洁风格,原则上每种语言不超过 100 行。 +顺序为一句定位、快捷模板、依赖与最小 import 用法、关键平台/能力边界、详细文档与许可证链接。 +架构、完整矩阵、后端细节、排错与验证历史进入 docs/;不向 README 追加 checkpoint、长表格或徽章墙。 +保留候选版本状态、必要 SDK/设备前提与关键限制,不靠超长行压缩,不为此增加用户审批。 +检查行数、示例身份/版本和文档链接;已有翻译保持结构对应。完整规则见 docs/zh/mwrap.md。 + +## 本地测试资源 + +所有本地测试及子进程合计不得超过本机有效 CPU/内存的 80%,默认预留更多余量。 +先检查负载,限制构建并发和重型任务数量;使用 MCPP_JOBS,必要时用共享 cgroup/slice 约束整个进程树。 +不能为各并发任务分别分配 80%;接近上限时减少本任务并发,不停止用户其他进程,不跳过验证。 + ## feature 与模板 当前 manifest feature 支持 sources、defines、flags、implies、forward、requires/provides;可选依赖写在 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a68ea4d6..951ebc79 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1221,6 +1221,38 @@ jobs: "$MCPP" self config --mirror GLOBAL 2>/dev/null || true bash tests/check_graphics_install_side_effects.sh + grpc-installed-template: + name: installed gRPC template (${{ matrix.platform }}) + needs: select + if: needs.select.outputs.members == '__ALL__' || contains(format(' {0} ', needs.select.outputs.members), ' grpc-cpu ') + runs-on: ${{ matrix.os }} + # A fresh SDK + protoc/plugin + gRPC build is independent of the timed + # workspace shards. It must not consume their shared 90-minute allowance. + timeout-minutes: 210 + strategy: + fail-fast: false + matrix: + include: + - platform: linux + os: ubuntu-latest + suffix: linux-x86_64 + toolchain: gcc@16.1.0 + - platform: macos + os: macos-15 + suffix: macosx-arm64 + toolchain: llvm@23.1.3 + steps: + - uses: actions/checkout@v4 + - name: Install pinned mcpp + shell: bash + run: | + archive="mcpp-${MCPP_VERSION}-${{ matrix.suffix }}.tar.gz" + curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive" + tar -xzf "$archive" + echo "MCPP=$PWD/mcpp-${MCPP_VERSION}-${{ matrix.suffix }}/bin/mcpp" >> "$GITHUB_ENV" + - name: Immutable archive, default scaffold, strict build and loopback RPC + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package grpc.grpc@1.83.0.1 --toolchain "${{ matrix.toolchain }}" + workspace: # The shard suffix appears only when the platform is actually split, and # the toolchain only when it is not mcpp's default — so the existing job @@ -1528,6 +1560,16 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package ggml-org.llamacpp@b10069.3 --legacy-exact-key + - name: Installed FFmpeg wrapper archive and actual default project template + if: contains(format(' {0} ', env.MEMBERS), ' ffmpeg-cpu ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package ffmpeg.ffmpeg@8.1.2.1 + + - name: Installed ImGui wrapper archive and actual headless default template + if: contains(format(' {0} ', env.MEMBERS), ' imgui-headless ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package ocornut.imgui@1.92.8.1 + - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash run: | diff --git a/docs/mwrap.md b/docs/mwrap.md index 4a14f62e..5ccc3725 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -108,6 +108,24 @@ Capabilities select and validate providers; they do **not** remove another provider's object files from the link. Avoid duplicate implementations before they enter the graph. Record ABI, runtime, CPU baseline and SDK constraints. +## README style + +Every `-m` repository uses a consistent, concise entry page, normally +**no more than 100 lines**. Existing translations follow the same structure and +length target and stay aligned. + +Use this order: project name and one-sentence purpose; quickstart template; +existing-project dependency and minimal import example; essential capability and +platform boundaries; links to detailed docs, examples and the license. Show full +package identities and directly runnable commands first. + +Move architecture, complete feature/API matrices, backend setup, troubleshooting, +validation evidence and history into `docs/`. Avoid badge walls, repeated +navigation, large tables, implementation essays and appended checkpoint logs. +Do not meet the target with excessively long lines or by removing prerequisites. +State unpublished-candidate status and important device/SDK requirements briefly. +The line target is an editorial guideline, not an additional user approval gate. + ## Every -m package ships a runnable template At least one default `templates//template.toml` and project skeleton is @@ -135,6 +153,19 @@ project. A source candidate may use an isolated preseeded installed-package fixture, but must verify the rendered identity/version before applying local dependency overrides. Also test the release archive includes the templates. +## Local test resource budget + +Local tests must collectively stay below **80%** of the machine's effective CPU +and memory resources, accounting for affinity and container/cgroup limits. +Reserve additional desktop headroom by default. This covers concurrent tests, +dependency builds, code generation, compilers and AST subprocesses together. + +Check load and available memory before heavy work, limit build jobs and concurrent +heavy tasks, and monitor usage. A task-specific cgroup/systemd slice can enforce +aggregate limits on Linux; MCPP_JOBS controls build concurrency. Reduce this +task's concurrency near the budget, without stopping unrelated user processes +or dropping validation gates. + ## Index and verification Descriptors live directly in `pkgs/mwrap/.lua`; consumers live in diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index 86222c99..18d9956a 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -72,6 +72,19 @@ feature 为加法并集,通过 implies、依赖转发、feature-deps 与 targe capability 选择不会自动移除另一份实现对象文件,必须先防止重复运行库进入图。 记录 ABI、CPU baseline、C++ runtime 和 SDK 约束。 +## README 风格 + +所有 `<库名>-m` 仓库使用统一、简洁的入口风格,README 原则上不超过 **100 行**。 +已有多语言 README 各自遵守同一结构和长度目标,内容保持对应。 + +推荐顺序:项目名与一句定位 → 快捷模板 → 已有项目接入与最小 import 示例 → +关键能力/平台边界 → 详细文档、示例与许可证入口。快速开始优先展示完整包身份和可直接执行的命令。 + +只保留影响首次使用的必要信息;架构、完整 feature/API 矩阵、后端配置、排错、验证记录 +及版本历史放入 `docs/`,从 README 链接。避免徽章墙、重复导航、大表格、长篇实现解释、 +checkpoint 日志及“后面继续追加”的更新记录,也不要用超长行或删掉必要前提来凑行数。 +未发布候选、设备/SDK 前提和重要使用限制仍须简短说明;约 100 行是编辑目标,不新增用户审批环节。 + ## 每个 -m 包必须有可运行快捷模板 至少提供一个默认 templates/<名称>/template.toml 与项目骨架。默认模板不要求 GPU、摄像头、 @@ -94,6 +107,16 @@ mcpp new viewer --template mcpplibs.mwrap.opencv@5.0.0:window 测试必须执行实际 mcpp new 后 build/run;未发布候选可使用隔离的已安装包 fixture, 先确认渲染的身份与版本,再添加本地依赖覆盖。发布归档也必须携带模板。 +## 本地测试资源预算 + +本地测试合计 CPU 与内存占用不得超过本机有效资源的 **80%**;按物理资源、CPU affinity +与容器/cgroup 限额取实际可用值,默认预留更多桌面余量。上限覆盖全部并发测试及其依赖构建、 +代码生成、编译器和 AST 子进程,不能让每个任务各占 80%。 + +启动前检查负载与可用内存,限制构建并发和同时运行的重型任务;运行中监测资源。 +Linux 可使用任务专属 cgroup/systemd slice 设置 CPU/内存共享上限,并用 MCPP_JOBS 控制构建并发。 +接近预算时降低并发或等待本任务的构建结束,不停止用户的其他进程;资源限制不替代验证门槛。 + ## 索引与验收 描述符平铺于 pkgs/mwrap/<完整身份>.lua,消费者置于 tests/mwrap/<成员>/,不再增加字母子目录。 diff --git a/mcpp.toml b/mcpp.toml index 53d483c8..cbbed95d 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -12,6 +12,9 @@ mcpp = ">=2026.10.21.1" members = [ "tests/mwrap/onnxruntime-cpu", "tests/mwrap/llamacpp-cpu", + "tests/mwrap/ffmpeg-cpu", + "tests/mwrap/imgui-headless", + "tests/mwrap/grpc-cpu", "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", diff --git a/pkgs/f/ffmpeg.ffmpeg.lua b/pkgs/f/ffmpeg.ffmpeg.lua index cb1f31aa..a8ffb0d0 100644 --- a/pkgs/f/ffmpeg.ffmpeg.lua +++ b/pkgs/f/ffmpeg.ffmpeg.lua @@ -15,9 +15,20 @@ package = { licenses = {"MIT"}, repo = "https://github.com/mcpplibs/ffmpeg-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/ffmpeg-m/archive/refs/tags/${tag}.tar.gz", + }, + }, xpm = { linux = { + -- Immutable candidate: wrapper/template revision; upstream SDK stays 8.1.2. + ["8.1.2.1"] = { + url = "https://github.com/mcpplibs/ffmpeg-m/archive/1e8bce6ee12c50e43058245df42515ffbabfc78f.tar.gz", + sha256 = "595b5bcca166583dd082ae911f7e9ed279a3e8899e9564f0901e084737c89ab2", + }, ["8.1.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/ffmpeg-m/archive/refs/tags/v8.1.2.tar.gz", @@ -27,6 +38,11 @@ package = { }, }, macosx = { + -- Immutable candidate: wrapper/template revision; upstream SDK stays 8.1.2. + ["8.1.2.1"] = { + url = "https://github.com/mcpplibs/ffmpeg-m/archive/1e8bce6ee12c50e43058245df42515ffbabfc78f.tar.gz", + sha256 = "595b5bcca166583dd082ae911f7e9ed279a3e8899e9564f0901e084737c89ab2", + }, ["8.1.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/ffmpeg-m/archive/refs/tags/v8.1.2.tar.gz", @@ -36,6 +52,11 @@ package = { }, }, windows = { + -- Immutable candidate: wrapper/template revision; upstream SDK stays 8.1.2. + ["8.1.2.1"] = { + url = "https://github.com/mcpplibs/ffmpeg-m/archive/1e8bce6ee12c50e43058245df42515ffbabfc78f.tar.gz", + sha256 = "595b5bcca166583dd082ae911f7e9ed279a3e8899e9564f0901e084737c89ab2", + }, ["8.1.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/ffmpeg-m/archive/refs/tags/v8.1.2.tar.gz", diff --git a/pkgs/g/ggml-org.llamacpp.lua b/pkgs/g/ggml-org.llamacpp.lua index c09f3b81..7b3c3775 100644 --- a/pkgs/g/ggml-org.llamacpp.lua +++ b/pkgs/g/ggml-org.llamacpp.lua @@ -70,8 +70,8 @@ package = { -- Candidate b10069.3 adds runnable templates; immutable source -- bytes are verified before the release tag exists. ["b10069.3"] = { - url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", - sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + url = "https://github.com/mcpplibs/llama.cpp-m/archive/5028a1aa4f741db4dbd86d6ebd1928ef21027147.tar.gz", + sha256 = "9b21c35c17e801ceee1165960eefbf7280ad50e14aaf3ea5603fbf2c4ab911de", }, ["b10069.2"] = { url = { @@ -96,8 +96,8 @@ package = { -- Candidate b10069.3 adds runnable templates; immutable source -- bytes are verified before the release tag exists. ["b10069.3"] = { - url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", - sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + url = "https://github.com/mcpplibs/llama.cpp-m/archive/5028a1aa4f741db4dbd86d6ebd1928ef21027147.tar.gz", + sha256 = "9b21c35c17e801ceee1165960eefbf7280ad50e14aaf3ea5603fbf2c4ab911de", }, ["b10069.2"] = { url = { @@ -122,8 +122,8 @@ package = { -- Candidate b10069.3 adds runnable templates; immutable source -- bytes are verified before the release tag exists. ["b10069.3"] = { - url = "https://github.com/mcpplibs/llama.cpp-m/archive/f5a046c332a0929281692a102c5819bc2d40f172.tar.gz", - sha256 = "543c19018aa05a7e6813f692a3535b6c5953842fdd70041e63266b5864435ddd", + url = "https://github.com/mcpplibs/llama.cpp-m/archive/5028a1aa4f741db4dbd86d6ebd1928ef21027147.tar.gz", + sha256 = "9b21c35c17e801ceee1165960eefbf7280ad50e14aaf3ea5603fbf2c4ab911de", }, ["b10069.2"] = { url = { diff --git a/pkgs/g/grpc-plugin.lua b/pkgs/g/grpc-plugin.lua index 7cc49c64..4e40633e 100644 --- a/pkgs/g/grpc-plugin.lua +++ b/pkgs/g/grpc-plugin.lua @@ -27,8 +27,20 @@ package = { repo = "https://github.com/mcpplibs/grpc-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", @@ -38,6 +50,11 @@ package = { }, }, macosx = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", @@ -47,6 +64,11 @@ package = { }, }, windows = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", diff --git a/pkgs/g/grpc.lua b/pkgs/g/grpc.lua index c33d7b9c..165a1404 100644 --- a/pkgs/g/grpc.lua +++ b/pkgs/g/grpc.lua @@ -55,8 +55,20 @@ package = { repo = "https://github.com/mcpplibs/grpc-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", @@ -66,6 +78,11 @@ package = { }, }, macosx = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", diff --git a/pkgs/g/grpcgen.lua b/pkgs/g/grpcgen.lua index be94ded1..b8be840b 100644 --- a/pkgs/g/grpcgen.lua +++ b/pkgs/g/grpcgen.lua @@ -27,8 +27,20 @@ package = { repo = "https://github.com/mcpplibs/grpc-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", @@ -38,6 +50,11 @@ package = { }, }, macosx = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", @@ -47,6 +64,11 @@ package = { }, }, windows = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.83.0.1"] = { + url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", + sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + }, ["1.83.0"] = { url = { GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz", diff --git a/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua index 693fe9ac..b4b5c88b 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.onnxruntime.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", - sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/576c780bfcbd792c7f4194942c90324f183a48aa.tar.gz", + sha256 = "3654811f65b98ade75b5e46fe59a0c4479d815aa7fa9aceff9f9cc3fffd8ddc1", }, }, macosx = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", - sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/576c780bfcbd792c7f4194942c90324f183a48aa.tar.gz", + sha256 = "3654811f65b98ade75b5e46fe59a0c4479d815aa7fa9aceff9f9cc3fffd8ddc1", }, }, windows = { ["1.25.1"] = { - url = "https://github.com/mcpplibs/onnxruntime-m/archive/2c3495fb2f6a108404871aaee996b1b104635e0a.tar.gz", - sha256 = "26a89e349d3eb01e339d3c091ff0371ccc4401026f94698bef8e607ad27bc407", + url = "https://github.com/mcpplibs/onnxruntime-m/archive/576c780bfcbd792c7f4194942c90324f183a48aa.tar.gz", + sha256 = "3654811f65b98ade75b5e46fe59a0c4479d815aa7fa9aceff9f9cc3fffd8ddc1", }, }, }, diff --git a/pkgs/o/ocornut.imgui.lua b/pkgs/o/ocornut.imgui.lua index 2156bfe0..feb05437 100644 --- a/pkgs/o/ocornut.imgui.lua +++ b/pkgs/o/ocornut.imgui.lua @@ -19,8 +19,20 @@ package = { repo = "https://github.com/mcpplibs/imgui-m", type = "package", + extensions = { + autoupdate = { + tag = "${version}", + url = "https://github.com/mcpplibs/imgui-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.92.8.1"] = { + url = "https://github.com/mcpplibs/imgui-m/archive/ffebf83be99ba0eec2677cc197dea3316a72b501.tar.gz", + sha256 = "9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f", + }, ["1.92.8"] = { url = { GLOBAL = "https://github.com/mcpplibs/imgui-m/archive/refs/tags/1.92.8.tar.gz", @@ -30,6 +42,11 @@ package = { }, }, macosx = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.92.8.1"] = { + url = "https://github.com/mcpplibs/imgui-m/archive/ffebf83be99ba0eec2677cc197dea3316a72b501.tar.gz", + sha256 = "9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f", + }, ["1.92.8"] = { url = { GLOBAL = "https://github.com/mcpplibs/imgui-m/archive/refs/tags/1.92.8.tar.gz", @@ -39,6 +56,11 @@ package = { }, }, windows = { + -- Immutable candidate; published upstream versions remain unchanged. + ["1.92.8.1"] = { + url = "https://github.com/mcpplibs/imgui-m/archive/ffebf83be99ba0eec2677cc197dea3316a72b501.tar.gz", + sha256 = "9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f", + }, ["1.92.8"] = { url = { GLOBAL = "https://github.com/mcpplibs/imgui-m/archive/refs/tags/1.92.8.tar.gz", diff --git a/tests/mwrap/ffmpeg-cpu/mcpp.toml b/tests/mwrap/ffmpeg-cpu/mcpp.toml new file mode 100644 index 00000000..a001508d --- /dev/null +++ b/tests/mwrap/ffmpeg-cpu/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "ffmpeg-cpu" +version = "8.1.2.1" +description = "Consume the immutable FFmpeg wrapper revision and decode a real raw frame" + +[indices] +ffmpeg = { path = "../../.." } + +[dependencies] +"ffmpeg.ffmpeg" = "8.1.2.1" diff --git a/tests/mwrap/ffmpeg-cpu/tests/behavior.cpp b/tests/mwrap/ffmpeg-cpu/tests/behavior.cpp new file mode 100644 index 00000000..fbaf8adf --- /dev/null +++ b/tests/mwrap/ffmpeg-cpu/tests/behavior.cpp @@ -0,0 +1,32 @@ +import std; +import ffmpeg.av; + +int main() { + if (std::string_view(av_version_info()) != "8.1.2") return 1; + const auto* codec = avcodec_find_decoder(AV_CODEC_ID_RAWVIDEO); + if (!codec) return 2; + struct Decoder { + AVCodecContext* context = nullptr; + AVPacket* packet = nullptr; + AVFrame* frame = nullptr; + ~Decoder() { av_frame_free(&frame); av_packet_free(&packet); avcodec_free_context(&context); } + } decoder; + decoder.context = avcodec_alloc_context3(codec); + decoder.packet = av_packet_alloc(); + decoder.frame = av_frame_alloc(); + if (!decoder.context || !decoder.packet || !decoder.frame) return 3; + decoder.context->width = 16; + decoder.context->height = 16; + decoder.context->pix_fmt = AV_PIX_FMT_YUV420P; + if (avcodec_open2(decoder.context, codec, nullptr) < 0) return 4; + if (av_new_packet(decoder.packet, 16 * 16 * 3 / 2) < 0) return 5; + std::fill_n(decoder.packet->data, 16 * 16, 64); + std::fill_n(decoder.packet->data + 16 * 16, 2 * 8 * 8, 128); + if (avcodec_send_packet(decoder.context, decoder.packet) < 0 || + avcodec_receive_frame(decoder.context, decoder.frame) < 0) return 6; + if (decoder.frame->width != 16 || decoder.frame->height != 16 || + decoder.frame->data[0][0] != 64 || decoder.frame->data[1][0] != 128 || + decoder.frame->data[2][0] != 128) return 7; + std::println("FFmpeg {} decoded a 16x16 YUV420 frame", av_version_info()); + return 0; +} diff --git a/tests/mwrap/grpc-cpu/mcpp.toml b/tests/mwrap/grpc-cpu/mcpp.toml new file mode 100644 index 00000000..885f3ac6 --- /dev/null +++ b/tests/mwrap/grpc-cpu/mcpp.toml @@ -0,0 +1,13 @@ +[package] +name = "grpc-cpu" +version = "1.83.0.1" +description = "Consume the immutable gRPC wrapper revision and check module status semantics" + +[indices] +grpc = { path = "../../.." } + +[target.'cfg(unix)'.dependencies] +"grpc.grpc" = "1.83.0.1" + +[target.'cfg(unix)'.build] +defines = ["MCPP_TEST_GRPC"] diff --git a/tests/mwrap/grpc-cpu/tests/behavior.cpp b/tests/mwrap/grpc-cpu/tests/behavior.cpp new file mode 100644 index 00000000..b8b93ef3 --- /dev/null +++ b/tests/mwrap/grpc-cpu/tests/behavior.cpp @@ -0,0 +1,13 @@ +#if defined(MCPP_TEST_GRPC) +import grpc; +int main() { + if (!grpc::Status::OK.ok()) return 1; + const grpc::Status error(grpc::StatusCode::INVALID_ARGUMENT, "indexed module"); + if (error.ok() || error.error_code() != grpc::StatusCode::INVALID_ARGUMENT || + error.error_message() != "indexed module") return 2; + auto credentials = grpc::InsecureChannelCredentials(); + return credentials ? 0 : 3; +} +#else +int main() { return 0; } // Windows: declared OpenSSL provider is unavailable. +#endif diff --git a/tests/mwrap/imgui-headless/mcpp.toml b/tests/mwrap/imgui-headless/mcpp.toml new file mode 100644 index 00000000..6b5311cb --- /dev/null +++ b/tests/mwrap/imgui-headless/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "imgui-headless" +version = "1.92.8.1" +description = "Consume the immutable ImGui wrapper and render actual headless draw data" + +[indices] +ocornut = { path = "../../.." } + +[dependencies] +"ocornut.imgui" = "1.92.8.1" diff --git a/tests/mwrap/imgui-headless/tests/behavior.cpp b/tests/mwrap/imgui-headless/tests/behavior.cpp new file mode 100644 index 00000000..937ece7a --- /dev/null +++ b/tests/mwrap/imgui-headless/tests/behavior.cpp @@ -0,0 +1,28 @@ +import std; +import imgui.core; + +int main() { + struct Context { + ImGuiContext* value = ImGui::CreateContext(); + ~Context() { if (value) ImGui::DestroyContext(value); } + } context; + if (!context.value) return 1; + ImGui::SetCurrentContext(context.value); + auto& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(320, 240); + unsigned char* pixels = nullptr; + int width = 0, height = 0; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + if (!pixels || width <= 0 || height <= 0) return 2; + for (int frame = 0; frame < 3; ++frame) { + ImGui::NewFrame(); + ImGui::Begin("Indexed ImGui"); + ImGui::TextUnformatted("Hello from import imgui.core"); + ImGui::End(); + ImGui::Render(); + } + const auto* draw = ImGui::GetDrawData(); + if (!draw || draw->CmdListsCount <= 0 || draw->TotalVtxCount <= 0) return 3; + std::println("Dear ImGui {}: {} vertices generated without a window", ImGui::GetVersion(), draw->TotalVtxCount); + return 0; +} From ba9a72e5b91c319763b24139e43654ae11f75352 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 10:42:48 +0900 Subject: [PATCH 14/25] fix: validate target and host-tool wrapper consumers in candidate CI --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 8 +++++ mcpp.toml | 1 + tests/autoupdate/ci.py | 28 +++++++++++----- tests/autoupdate/test_ci.py | 33 +++++++++++++++++++ tests/mwrap/grpc-tools/build.mcpp | 19 +++++++++++ tests/mwrap/grpc-tools/mcpp.toml | 14 ++++++++ tests/mwrap/grpc-tools/tests/host_binary.cpp | 10 ++++++ tests/prepare_mwrap_indices.py | 2 +- 8 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 tests/mwrap/grpc-tools/build.mcpp create mode 100644 tests/mwrap/grpc-tools/mcpp.toml create mode 100644 tests/mwrap/grpc-tools/tests/host_binary.cpp diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index db7f59a1..541ab0c9 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -36,3 +36,11 @@ Local evidence: OpenCV LLVM/GCC all-module strict tests each pass fourteen binar The new complete-index ImGui archive/template check is running in a fresh MCPP_HOME. Final fresh-archive checks for changed source revisions, the new workspace test entries and native CI are not yet complete. gRPC's cold installed-template check has its own Linux/macOS jobs with a 210-minute limit; it does not consume a workspace shard's existing 90-minute allowance. No release readiness is inferred from source CI or archive checksums alone. Local heavy commands run serially in mwrap-tests.slice with CPUQuota=1600%, MemoryHigh=28 GiB, MemoryMax=32 GiB, TasksMax=512, taskset 0-15 and MCPP_JOBS=6 on this 32-logical-CPU/62-GiB host. Check current available resources and disk before each check; retain at least 6 GiB free during builds. No engine change, merge or tag is included. + +## Candidate scanner and host-tool consumer correction + +The b22a9a8 candidate scan stopped with `no candidate consumer: grpc:grpc-plugin`. Added a dedicated grpc-tools consumer for the indexed plugin and grpcgen host module; it resolves both exact 1.83.0.1 archives through separate unchanged namespace views, builds the host binary and verifies its path plus the rule's generation metadata. This is host-tool build/availability coverage, including planned native Windows checks, not a Windows runtime/codegen support claim. Actual service generation/RPC remains covered by the default gRPC templates on supported Linux/macOS targets. + +The scanner now finds normal and build dependencies inside target tables, and prefers dedicated tests/mwrap consumers over older platform-gated examples. Two regressions cover these boundaries, including build-dependency version replacement. All sixteen updater tests pass; an actual full candidate-matrix reproduction produces 23 build/run jobs and selects grpc-cpu, grpc-tools, imgui-headless, ffmpeg-cpu and llamacpp-cpu as intended. The full audit still preserves historical versions and reports 90 automatic descriptors plus one manual snapshot. + +Local strict actual index tests now pass all four new members: ffmpeg-cpu (real 16x16 frame), imgui-headless (positive draw vertices), grpc-cpu (status/credentials) and grpc-tools (built host plugin plus imported rule). A fresh complete-index ImGui archive/default-template installation also passes with positive draw data and no display. These results supplement the source/template CI; all new native matrix jobs remain required before release readiness. diff --git a/mcpp.toml b/mcpp.toml index cbbed95d..4b51e535 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -15,6 +15,7 @@ members = [ "tests/mwrap/ffmpeg-cpu", "tests/mwrap/imgui-headless", "tests/mwrap/grpc-cpu", + "tests/mwrap/grpc-tools", "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index 2f1d472c..d6cbb6b5 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -114,17 +114,29 @@ def selection(base: str, output: Path): def direct_member(identity: str) -> str | None: namespace, name = identity.split(":", 1) candidates = [] + def dependency_tables(table): + for key, value in table.items(): + if not isinstance(value, dict): + continue + if key in {"dependencies", "build-dependencies"}: + yield value + else: + yield from dependency_tables(value) for category in ("examples", "mwrap"): for path in (ROOT / "tests" / category).glob("*/mcpp.toml"): - deps = tomllib.loads(path.read_text(encoding="utf-8")).get("dependencies", {}) - nested = deps - for part in namespace.split("."): - nested = nested.get(part, {}) if isinstance(nested, dict) else {} - if (f"{namespace}.{name}" in deps or name in deps.get(namespace, {}) or name in nested): - candidates.append(path.parent.name) + manifest = tomllib.loads(path.read_text(encoding="utf-8")) + for deps in dependency_tables(manifest): + nested = deps + for part in namespace.split("."): + nested = nested.get(part, {}) if isinstance(nested, dict) else {} + if (f"{namespace}.{name}" in deps or name in deps.get(namespace, {}) or name in nested): + candidates.append((category, path.parent.name)) + break if not candidates: return None - return min(candidates, key=lambda value: (len(value), value)) + # A wrapper's dedicated consumer covers its current declared platforms; + # old examples may intentionally be gated to just one OS or feature. + return min(candidates, key=lambda value: (value[0] != "mwrap", len(value[1]), value[1]))[1] def matrix(plan: Path, output: Path, base: str): @@ -185,7 +197,7 @@ def prepare(plan: Path, identity: str, output: Path): member = direct_member(identity) output.mkdir(parents=True, exist_ok=True) if member: - paths = [ROOT / "tests" / category / member for category in ("examples", "mwrap")] + paths = [ROOT / "tests" / category / member for category in ("mwrap", "examples")] source = next(path for path in paths if (path / "mcpp.toml").is_file()) shutil.copytree(source, output, dirs_exist_ok=True, ignore=shutil.ignore_patterns("target", ".mcpp", "mcpp.lock")) diff --git a/tests/autoupdate/test_ci.py b/tests/autoupdate/test_ci.py index a4ae4147..e4e3dfb9 100644 --- a/tests/autoupdate/test_ci.py +++ b/tests/autoupdate/test_ci.py @@ -75,6 +75,39 @@ def test_mwrap_candidate_uses_its_actual_member_and_qualified_dependency(self): self.assertEqual(result["dependencies"]["mcpplibs.mwrap.example"], "1.0.1") self.assertEqual((dest / "tests/probe.cpp").read_text(), 'int main() { return 0; }\n') + def test_candidate_discovers_target_and_build_dependencies(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "tests/mwrap/tools" + source.mkdir(parents=True) + (source / "mcpp.toml").write_text( + '[package]\nname="tools"\nversion="0.1.0"\n' + '[target.\'cfg(unix)\'.dependencies.grpc]\ngrpc="1.83.0"\n' + '[build-dependencies]\n"mcpplibs.grpcgen"={version="1.83.0",host-module=true}\n') + with patch.object(ci, "ROOT", root): + self.assertEqual(ci.direct_member("grpc:grpc"), "tools") + self.assertEqual(ci.direct_member("mcpplibs:grpcgen"), "tools") + self.assertIsNone(ci.direct_member("grpc:grpc-plugin")) + plan = root / "plan.json" + plan.write_text(json.dumps({"packages": [ + {"id": "mcpplibs:grpcgen", "candidate": "1.83.0.1"}]})) + dest = root / "candidate" + ci.prepare(plan, "mcpplibs:grpcgen", dest) + result = tomllib.loads((dest / "mcpp.toml").read_text()) + self.assertEqual(result["build-dependencies"]["mcpplibs.grpcgen"]["version"], "1.83.0.1") + + def test_dedicated_mwrap_consumer_precedes_shorter_legacy_example(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for category, name in (("examples", "old"), ("mwrap", "headless")): + source = root / "tests" / category / name + source.mkdir(parents=True) + (source / "mcpp.toml").write_text( + '[package]\nname="consumer"\nversion="0.1.0"\n' + '[dependencies]\n"ocornut.imgui"="1.92.8.1"\n') + with patch.object(ci, "ROOT", root): + self.assertEqual(ci.direct_member("ocornut:imgui"), "headless") + def test_added_descriptor_and_removed_historical_descriptor(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) diff --git a/tests/mwrap/grpc-tools/build.mcpp b/tests/mwrap/grpc-tools/build.mcpp new file mode 100644 index 00000000..09c071a9 --- /dev/null +++ b/tests/mwrap/grpc-tools/build.mcpp @@ -0,0 +1,19 @@ +import std; +import mcpp; +import grpcgen; + +int main() { + const auto plugin = grpcgen::cpp(); + if (plugin.name != "grpc" || plugin.binary.empty() || + !std::filesystem::is_regular_file(plugin.binary) || + plugin.suffixes != std::vector{".grpc.pb.cc", ".grpc.pb.h"}) return 1; + const std::filesystem::path out(mcpp::out_dir()); + std::filesystem::create_directories(out); + std::ofstream header(out / "plugin_info.hpp"); + header << "#pragma once\n#include \n" + << "inline constexpr std::string_view plugin_binary = " + << std::quoted(plugin.binary) << ";\n"; + if (!header) return 2; + mcpp::include_dir(out.string().c_str()); + return 0; +} diff --git a/tests/mwrap/grpc-tools/mcpp.toml b/tests/mwrap/grpc-tools/mcpp.toml new file mode 100644 index 00000000..95f3bb71 --- /dev/null +++ b/tests/mwrap/grpc-tools/mcpp.toml @@ -0,0 +1,14 @@ +[package] +name = "grpc-tools" +version = "1.83.0.1" +description = "Resolve the indexed gRPC plugin and host rule, and verify the built host binary" + +[indices] +grpc = { path = "../.indices/grpc" } +mcpplibs = { path = "../.indices/mcpplibs" } + +[dependencies] +"grpc.grpc-plugin" = { version = "1.83.0.1", tools = ["grpc_cpp_plugin"] } + +[build-dependencies] +"mcpplibs.grpcgen" = { version = "1.83.0.1", host-module = true } diff --git a/tests/mwrap/grpc-tools/tests/host_binary.cpp b/tests/mwrap/grpc-tools/tests/host_binary.cpp new file mode 100644 index 00000000..b7d6f114 --- /dev/null +++ b/tests/mwrap/grpc-tools/tests/host_binary.cpp @@ -0,0 +1,10 @@ +#include "plugin_info.hpp" +import std; + +int main() { + const std::filesystem::path binary(plugin_binary); + if (!std::filesystem::is_regular_file(binary)) return 1; + if (std::filesystem::file_size(binary) == 0) return 2; + std::println("Indexed gRPC host plugin built: {}", binary.string()); + return 0; +} diff --git a/tests/prepare_mwrap_indices.py b/tests/prepare_mwrap_indices.py index a65979b4..65572146 100644 --- a/tests/prepare_mwrap_indices.py +++ b/tests/prepare_mwrap_indices.py @@ -15,7 +15,7 @@ def main(): root = Path(__file__).resolve().parents[1] - namespaces = {name: [] for name in ("compat", "mcpplibs.mwrap")} + namespaces = {name: [] for name in ("compat", "mcpplibs.mwrap", "grpc", "mcpplibs")} for source in sorted((root / "pkgs").glob("*/*.lua")): match = re.search(r'^\s*namespace\s*=\s*[\'"]([^\'"]+)[\'"]', source.read_text(encoding="utf-8"), re.M) if match and match[1] in namespaces: From 815c647b1647657e73df294b12c9012473dc681e Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 12:52:56 +0900 Subject: [PATCH 15/25] feat: index OpenCV and Godot wrappers with complete candidate index fixtures --- .../docs/2026-10-24-add-mwrap-and-ort-plan.md | 24 +++++++++ .../skills/add-mcpp-index-package/SKILL.md | 3 ++ .github/workflows/validate.yml | 15 ++++++ .github/workflows/xpkgindex-update.yml | 6 ++- docs/mwrap.md | 5 ++ docs/zh/mwrap.md | 3 ++ mcpp.toml | 2 + pkgs/g/godotengine.godot-cpp-m.lua | 22 ++++++++ pkgs/g/grpc-plugin.lua | 12 ++--- pkgs/g/grpc.lua | 8 +-- pkgs/g/grpcgen.lua | 12 ++--- pkgs/mwrap/mcpplibs.mwrap.opencv.lua | 32 +++++++++++ tests/autoupdate/ci.py | 14 +++-- tests/autoupdate/test_ci.py | 31 +++++++++++ tests/mwrap/check_installed_template.py | 54 +++++++++++++------ tests/mwrap/godot-math/mcpp.toml | 10 ++++ tests/mwrap/godot-math/tests/math.cpp | 11 ++++ tests/mwrap/grpc-cpu/tests/behavior.cpp | 1 + tests/mwrap/grpc-tools/mcpp.toml | 4 +- tests/mwrap/opencv-cpu/mcpp.toml | 10 ++++ tests/mwrap/opencv-cpu/tests/pipeline.cpp | 28 ++++++++++ tests/prepare_builtin_index.py | 32 +++++++++++ 22 files changed, 300 insertions(+), 39 deletions(-) create mode 100644 pkgs/mwrap/mcpplibs.mwrap.opencv.lua create mode 100644 tests/mwrap/godot-math/mcpp.toml create mode 100644 tests/mwrap/godot-math/tests/math.cpp create mode 100644 tests/mwrap/opencv-cpu/mcpp.toml create mode 100644 tests/mwrap/opencv-cpu/tests/pipeline.cpp create mode 100644 tests/prepare_builtin_index.py diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 541ab0c9..6610c7a9 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -44,3 +44,27 @@ The b22a9a8 candidate scan stopped with `no candidate consumer: grpc:grpc-plugin The scanner now finds normal and build dependencies inside target tables, and prefers dedicated tests/mwrap consumers over older platform-gated examples. Two regressions cover these boundaries, including build-dependency version replacement. All sixteen updater tests pass; an actual full candidate-matrix reproduction produces 23 build/run jobs and selects grpc-cpu, grpc-tools, imgui-headless, ffmpeg-cpu and llamacpp-cpu as intended. The full audit still preserves historical versions and reports 90 automatic descriptors plus one manual snapshot. Local strict actual index tests now pass all four new members: ffmpeg-cpu (real 16x16 frame), imgui-headless (positive draw vertices), grpc-cpu (status/credentials) and grpc-tools (built host plugin plus imported rule). A fresh complete-index ImGui archive/default-template installation also passes with positive draw data and no display. These results supplement the source/template CI; all new native matrix jobs remain required before release readiness. + +## Complete-index fixture, seven-wrapper and OpenCV archive checkpoint + +The ba9a72e native candidate jobs exposed two real test-fixture problems: preparing only the namespace under test hid coupled unpublished plugin/rule versions, and registering full candidate views through multiple project aliases caused ambiguous candidates in fresh CI. The new prepare_builtin_index.py installs the complete unchanged pkgs/index.toml layout into one isolated test home's built-in index. Updater candidate preparation uses it for all namespaces; grpc-tools uses an empty member [indices] table to override the legacy workspace redirect. Publication layout remains unchanged, with no staging/path conversion or engine change. + +A fresh standalone strict plugin/rule consumer passed in 201.08 seconds and its lock proves both exact 1.83.0.1 identities. A separate fresh actual workspace grpc-tools invocation passed its host-binary behavior in 184.46 seconds; workspace lock verification proves both identities too. The existing workspace-root [indices] deprecation remains a documented baseline, so the actual workspace command follows CI's non-strict mode; standalone candidate consumers stay strict. The first workspace diagnostic harness looked for a member lock instead of the root lock after the successful test; postprocessing was corrected and the root lock was independently verified. The grpc-cpu consumer now includes the standard string header explicitly after GCC CI exposed missing string comparison declarations. All seventeen updater regressions pass. + +The organization audit includes seven -m repositories: Godot was omitted from the initial six-repository inventory. Godot 10.0.0-rc1.1 is now indexed with a standalone math behavior consumer and an actual installed-template CI check. Its source head 40f72d5 has Linux/macOS/Windows CI green, including the explicit one-symbol Windows extension export check; actual loading by a Godot process remains separate scope. All seven root READMEs are 42–52 lines per language, with details under docs/. + +Added the new canonical mcpplibs.mwrap.opencv@5.0.0.1 descriptor under pkgs/mwrap and its tests/mwrap/opencv-cpu consumer. The first immutable 52f4c8f archive passed a real strict installed image/scalar consumer in 67.22 seconds: exact grayscale pixels, PNG/JPEG roundtrips, saturation/rounding and generic min/max/makePtr. The actual default image template also installed and built strictly in a fresh home. Its release packaging audit then correctly failed because the old source manifest deployed no OpenCV license tree. Source checkpoint 5da7fc8 now explicitly deploys all fourteen retained wrapper/upstream license/copyright files with original paths. The installed-template helper checks each byte and executes the relocated application with a clean HOME/runtime environment, verifies its 64x64 PNG and, on Linux, confirms loader dependencies stay inside the bundle. The fixed archive/deployment rerun is in progress and is a native CI gate; no release readiness is claimed yet. + +The gRPC native source workflow completed its advanced program but failed a stale unscoped generated-source assertion. New source 0d11b22 checks common/types.pb.cc beside the real mock output, finds the actual generation graph by its edge rather than filesystem order, and removes the expired pre-cache-fix local-cache workaround. A local LLVM advanced generation/RPC rerun passed in 3m40s and all generated outputs/edge metadata were checked; latest native source CI is running. + +Each candidate below was downloaded twice with matching SHA256. Existing published archive versions are unchanged. The full audit reports 91 automatic declarations and two reviewed immutable snapshots; the five current new/updated archive descriptors pass resolver grammar and name/namespace/mirror/version checks. No merge, tag or release has been performed. + +| Repository | Exact source | SHA256 | +|---|---|---| +| opencv-m | `5da7fc8abedc700ce1ed91a1f57067ad552c74fc` | `5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c` | +| onnxruntime-m | `576c780bfcbd792c7f4194942c90324f183a48aa` | `3654811f65b98ade75b5e46fe59a0c4479d815aa7fa9aceff9f9cc3fffd8ddc1` | +| llama.cpp-m | `5028a1aa4f741db4dbd86d6ebd1928ef21027147` | `9b21c35c17e801ceee1165960eefbf7280ad50e14aaf3ea5603fbf2c4ab911de` | +| ffmpeg-m | `1e8bce6ee12c50e43058245df42515ffbabfc78f` | `595b5bcca166583dd082ae911f7e9ed279a3e8899e9564f0901e084737c89ab2` | +| grpc-m | `0d11b22399875f2847f04609fd4fccb50fda5410` | `8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a` | +| imgui-m | `ffebf83be99ba0eec2677cc197dea3316a72b501` | `9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f` | +| godot-cpp-m | `40f72d5533a9bc29b17e62ae8a6c84b3dc593071` | `79f15b4f70b0ef07e8b751c928f9e3fd7a6b32703f09b177ad02cdfd676a7f33` | diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 1d512247..4a337224 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -72,6 +72,9 @@ description: Use when adding a new third-party library/package to the mcpp-index 再将两条 namespace 路由指向 `tests/mwrap/.indices/` 中字节不变的测试视图。 `tests/run_members.sh` 自动准备这些视图;它们不提交、不发布,也不改变 pkgs/mwrap 的正式布局。 正常发布布局另由 `tests/mwrap/check_installed_template.py` 在独立 MCPP_HOME 中使用单一完整索引验收。 + 运行库/插件/host-module 多 namespace 耦合时,优先用 `tests/prepare_builtin_index.py --home <独立测试 home>` + 安装唯一完整内置索引,成员以空 `[indices]` 覆盖旧 workspace 路由。自动更新候选 CI 同样采用此方式, + 避免项目别名缓存相互污染;这是测试准备,不是发布 staging 或目录转换。 - 非全平台包按 `[target.'cfg(...)'.dependencies]` 门控依赖,测试源码在被门控掉的平台上 编译为 no-op `main`(`#ifdef __linux__ … #else int main(){return 0;} #endif` 模式)。 - 如需测试 feature,依赖采用长式声明 `name = { version = "…", features = ["…"] }`。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 951ebc79..f34694f7 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1550,6 +1550,16 @@ jobs: run: | "$MCPP" index update + - name: Complete candidate index for the cross-namespace host-tool consumer + if: contains(format(' {0} ', env.MEMBERS), ' grpc-tools ') + shell: bash + run: python3 tests/prepare_builtin_index.py --home "$MCPP_HOME" + + - name: Installed OpenCV archive, image template and relocated deployment + if: contains(format(' {0} ', env.MEMBERS), ' opencv-cpu ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.opencv@5.0.0.1 --deploy + - name: Installed mwrap archive, project template and relocated deployment if: contains(format(' {0} ', env.MEMBERS), ' onnxruntime-cpu ') && matrix.toolchain == 'default' shell: bash @@ -1570,6 +1580,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package ocornut.imgui@1.92.8.1 + - name: Installed Godot wrapper archive and actual standalone math template + if: contains(format(' {0} ', env.MEMBERS), ' godot-math ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package godotengine.godot-cpp-m@10.0.0-rc1.1 + - name: Report temporarily untestable consumers of the unavailable MySQL source shell: bash run: | diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 33bd380b..2aff6a0c 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -156,9 +156,11 @@ jobs: if [[ "$RUNNER_OS" == Windows ]]; then echo "PYTHONPATH=$(cygpath -m "$PWD/.updater/src")" >> "$GITHUB_ENV" echo "CONSUMER=$(cygpath -m "$RUNNER_TEMP/candidate-consumer")" >> "$GITHUB_ENV" + echo "CANDIDATE_MCPP_HOME=$(cygpath -m "$HOME/.mcpp")" >> "$GITHUB_ENV" else echo "PYTHONPATH=$PWD/.updater/src" >> "$GITHUB_ENV" echo "CONSUMER=$RUNNER_TEMP/candidate-consumer" >> "$GITHUB_ENV" + echo "CANDIDATE_MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV" fi - name: Append the candidate and prepare its existing consumer test shell: bash @@ -167,7 +169,7 @@ jobs: PACKAGE: ${{ matrix.id }} run: | 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" + python tests/autoupdate/ci.py prepare --plan .candidate-plan/build-plan.json --package "$PACKAGE" --output "$CONSUMER" --home "$CANDIDATE_MCPP_HOME" git diff -- pkgs - uses: actions/cache/restore@v4 with: @@ -195,7 +197,7 @@ jobs: root="$RUNNER_TEMP/$root" mkdir -p "$HOME/.mcpp/registry" cp -a "$root/registry/." "$HOME/.mcpp/registry/" - echo "MCPP_HOME=$HOME/.mcpp" >> "$GITHUB_ENV" + echo "MCPP_HOME=$CANDIDATE_MCPP_HOME" >> "$GITHUB_ENV" if [[ "$RUNNER_OS" == Windows ]]; then echo "MCPP=$(cygpath -m "$root/bin/mcpp.exe")" >> "$GITHUB_ENV" else diff --git a/docs/mwrap.md b/docs/mwrap.md index 5ccc3725..3d618711 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -185,6 +185,9 @@ When a runtime SDK ships LICENSE and third-party notices, declare their deployme alongside the runtime files. Verify those bytes in a relocated consumer bundle; automatic license discovery may copy LICENSE while omitting additional notices. Use `runtime.deploy` for explicit relative destinations without changing the engine. +Vendored static implementations need this too: shared-library discovery does not +find their notices. Preserve retained license/copyright files under distinct source +paths so unrelated LICENSE basenames cannot overwrite one another. Each repository's PR retains checkpoint commits. Record commands and actual results, inspect the final dependency/ABI/API graph, and wait for every relevant @@ -195,3 +198,5 @@ CI check before reporting merge/release readiness. The pinned client routes each requested namespace independently. Registering the same full checkout twice makes xlings report duplicate project candidates. Local wrapper/SDK tests therefore call tests/prepare_mwrap_indices.py and route each namespace to a test-only view under tests/mwrap/.indices. Every descriptor is copied unchanged and its source SHA256 is recorded; no aliases share a descriptor. These views are ignored build outputs, not a publication format. Normal installed consumers are tested independently by tests/mwrap/check_installed_template.py: it copies the complete pkgs/index.toml publication layout into one isolated built-in index, downloads the real checksummed source archive, runs mcpp new and builds/runs the generated manifest without a path substitution. The Git and published pkgs/mwrap directory layout remains identical. + +Coupled runtime/plugin/build-module consumers may require several candidate namespaces at once. Use `python3 tests/prepare_builtin_index.py --home ` to install the complete unchanged candidate layout as the test home's single built-in index; the member uses an empty `[indices]` table to override legacy workspace redirects. Updater candidate CI uses this mode too. Do not register the same full checkout through multiple project aliases. This is test-home preparation only: no publication staging, path conversion or mcpp engine change is required. diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index 18d9956a..7023e2f8 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -128,6 +128,7 @@ Linux 本地执行,CI 重跑发布门槛;Windows/macOS 原生 CI。区分编 覆盖冷索引消费和动态加载库的运行时分发。SDK 附带 LICENSE 与第三方通知时,通过 `runtime.deploy` 显式声明它们的相对部署位置,并在搬迁后的消费方分发包中校验字节; 自动许可证收集可能只复制 LICENSE 而遗漏其他通知,无需为此修改引擎。 +vendored 静态实现同样需要显式部署,不能依赖共享库扫描发现通知。保留许可/版权文件的来源路径,避免不同组件的同名 LICENSE 相互覆盖。 每仓库一个 PR,保留 checkpoint commits; 逐项核查最终 API/ABI/依赖图,并等待相关 CI 全通过后报告合入与发布就绪。 @@ -140,3 +141,5 @@ Linux 本地执行,CI 重跑发布门槛;Windows/macOS 原生 CI。区分编 正常安装由 tests/mwrap/check_installed_template.py 独立验证:把完整 pkgs/index.toml 发布布局放进独立 MCPP_HOME 的单一内置索引, 下载真实且带校验的源码归档,执行 mcpp new,再原样构建/运行生成清单,不替换为 path 依赖。 Git 与发布的 pkgs/mwrap 目录始终一致。 + +运行库、插件、构建模块耦合的测试可能同时依赖多个候选 namespace。使用 `python3 tests/prepare_builtin_index.py --home <独立测试目录>`,将完整且字节不变的候选布局放入该测试 home 的唯一内置索引;成员用空 `[indices]` 覆盖旧 workspace 路由。自动更新候选 CI 同样使用此模式。不要将同一个完整 checkout 注册为多个项目别名。这只是测试 home 准备,不需要发布 staging、路径转换或修改 mcpp 引擎。 diff --git a/mcpp.toml b/mcpp.toml index 4b51e535..372efaeb 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -10,12 +10,14 @@ mcpp = ">=2026.10.21.1" [workspace] members = [ + "tests/mwrap/opencv-cpu", "tests/mwrap/onnxruntime-cpu", "tests/mwrap/llamacpp-cpu", "tests/mwrap/ffmpeg-cpu", "tests/mwrap/imgui-headless", "tests/mwrap/grpc-cpu", "tests/mwrap/grpc-tools", + "tests/mwrap/godot-math", "tests/mwrap/onnxruntime-sdk", "tests/examples/abseil", "tests/examples/archive", diff --git a/pkgs/g/godotengine.godot-cpp-m.lua b/pkgs/g/godotengine.godot-cpp-m.lua index 3336fe4b..4eb023dd 100644 --- a/pkgs/g/godotengine.godot-cpp-m.lua +++ b/pkgs/g/godotengine.godot-cpp-m.lua @@ -72,8 +72,20 @@ package = { repo = "https://github.com/mcpplibs/godot-cpp-m", type = "package", + extensions = { + autoupdate = { + tag = "v${version}", + url = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/${tag}.tar.gz", + }, + }, + xpm = { linux = { + -- Immutable candidate: runnable template; SDK stays 10.0.0-rc1. + ["10.0.0-rc1.1"] = { + url = "https://github.com/mcpplibs/godot-cpp-m/archive/40f72d5533a9bc29b17e62ae8a6c84b3dc593071.tar.gz", + sha256 = "79f15b4f70b0ef07e8b751c928f9e3fd7a6b32703f09b177ad02cdfd676a7f33", + }, ["10.0.0-rc1"] = { url = { GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz", @@ -90,6 +102,11 @@ package = { }, }, macosx = { + -- Immutable candidate: runnable template; SDK stays 10.0.0-rc1. + ["10.0.0-rc1.1"] = { + url = "https://github.com/mcpplibs/godot-cpp-m/archive/40f72d5533a9bc29b17e62ae8a6c84b3dc593071.tar.gz", + sha256 = "79f15b4f70b0ef07e8b751c928f9e3fd7a6b32703f09b177ad02cdfd676a7f33", + }, ["10.0.0-rc1"] = { url = { GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz", @@ -106,6 +123,11 @@ package = { }, }, windows = { + -- Immutable candidate: runnable template; SDK stays 10.0.0-rc1. + ["10.0.0-rc1.1"] = { + url = "https://github.com/mcpplibs/godot-cpp-m/archive/40f72d5533a9bc29b17e62ae8a6c84b3dc593071.tar.gz", + sha256 = "79f15b4f70b0ef07e8b751c928f9e3fd7a6b32703f09b177ad02cdfd676a7f33", + }, ["10.0.0-rc1"] = { url = { GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz", diff --git a/pkgs/g/grpc-plugin.lua b/pkgs/g/grpc-plugin.lua index 4e40633e..400f20cf 100644 --- a/pkgs/g/grpc-plugin.lua +++ b/pkgs/g/grpc-plugin.lua @@ -38,8 +38,8 @@ package = { linux = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { @@ -52,8 +52,8 @@ package = { macosx = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { @@ -66,8 +66,8 @@ package = { windows = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { diff --git a/pkgs/g/grpc.lua b/pkgs/g/grpc.lua index 165a1404..df26db48 100644 --- a/pkgs/g/grpc.lua +++ b/pkgs/g/grpc.lua @@ -66,8 +66,8 @@ package = { linux = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { @@ -80,8 +80,8 @@ package = { macosx = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { diff --git a/pkgs/g/grpcgen.lua b/pkgs/g/grpcgen.lua index b8be840b..04a77ef9 100644 --- a/pkgs/g/grpcgen.lua +++ b/pkgs/g/grpcgen.lua @@ -38,8 +38,8 @@ package = { linux = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { @@ -52,8 +52,8 @@ package = { macosx = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { @@ -66,8 +66,8 @@ package = { windows = { -- Immutable candidate; published upstream versions remain unchanged. ["1.83.0.1"] = { - url = "https://github.com/mcpplibs/grpc-m/archive/839b61e94fff14892a1dfb2c211a4a8e66342239.tar.gz", - sha256 = "b16dffb45c6f4b09de49a5229f2ec7db98bcfb5609447271d85db8387aeabdd4", + url = "https://github.com/mcpplibs/grpc-m/archive/0d11b22399875f2847f04609fd4fccb50fda5410.tar.gz", + sha256 = "8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a", }, ["1.83.0"] = { url = { diff --git a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua new file mode 100644 index 00000000..69932ae0 --- /dev/null +++ b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua @@ -0,0 +1,32 @@ +-- Immutable candidate source archive; retained OpenCV sources have one owner. +-- No release tag is created by this descriptor. Updates require a verified source +-- revision and archive checksum, not a URL-only automatic version substitution. +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "opencv", + description = "OpenCV 5.0.0 C++ APIs through feature-selected modules", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/opencv-m", + type = "package", + xpm = { + linux = { + ["5.0.0.1"] = { + url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", + sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + }, + }, + macosx = { + ["5.0.0.1"] = { + url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", + sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + }, + }, + windows = { + ["5.0.0.1"] = { + url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", + sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + }, + }, + }, +} diff --git a/tests/autoupdate/ci.py b/tests/autoupdate/ci.py index d6cbb6b5..6209a448 100644 --- a/tests/autoupdate/ci.py +++ b/tests/autoupdate/ci.py @@ -190,7 +190,7 @@ def matrix(plan: Path, output: Path, base: str): print(f"Candidate matrix: {len(jobs)} real build/run jobs.") -def prepare(plan: Path, identity: str, output: Path): +def prepare(plan: Path, identity: str, output: Path, home: Path | None = None): 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"] @@ -233,7 +233,14 @@ def prepare(plan: Path, identity: str, output: Path): 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' + if home is None: + text += f'\n[indices]\n"{namespace}" = {{ path = {json.dumps(str(ROOT).replace(chr(92), "/"))} }}\n' + else: + # Coupled tool/runtime packages cross namespaces. One complete built-in + # index resolves all of them without project-alias cache collisions. + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + from prepare_builtin_index import prepare as prepare_index + prepare_index(ROOT, home) (output / "mcpp.toml").write_text(text) print(f"Prepared {identity}@{version} in {output}") @@ -270,6 +277,7 @@ def main(): parser.add_argument("--package") parser.add_argument("--output", type=Path) parser.add_argument("--consumer", type=Path) + parser.add_argument("--home", type=Path) args = parser.parse_args() if args.command == "audit": audit(args.base, args.output) @@ -278,7 +286,7 @@ def main(): elif args.command == "matrix": matrix(args.plan, args.output, args.base) elif args.command == "prepare": - prepare(args.plan, args.package, args.output) + prepare(args.plan, args.package, args.output, args.home) elif args.command == "diagnostics": diagnostics(args.consumer, args.output) else: diff --git a/tests/autoupdate/test_ci.py b/tests/autoupdate/test_ci.py index e4e3dfb9..c0bd83c8 100644 --- a/tests/autoupdate/test_ci.py +++ b/tests/autoupdate/test_ci.py @@ -108,6 +108,37 @@ def test_dedicated_mwrap_consumer_precedes_shorter_legacy_example(self): with patch.object(ci, "ROOT", root): self.assertEqual(ci.direct_member("ocornut:imgui"), "headless") + def test_coupled_consumer_uses_one_complete_builtin_index(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "tests/mwrap/tools" + source.mkdir(parents=True) + (source / "mcpp.toml").write_text( + '[package]\nname="tools"\nversion="0.1.0"\n' + '[indices]\ngrpc={path="../../.."}\n' + '[dependencies]\n"grpc.grpc-plugin"="1.83.0.1"\n' + '[build-dependencies]\n"mcpplibs.grpcgen"={version="1.83.0.1",host-module=true}\n') + (root / "index.toml").write_text('[index]\nname="mcpplibs"\n') + package = root / "pkgs/mwrap/example.lua" + package.parent.mkdir(parents=True) + package.write_bytes(b'-- unchanged descriptor bytes\n') + home = root / "home" + stale = home / "indices/mcpplibs/pkgs/old.lua" + stale.parent.mkdir(parents=True) + stale.write_text('-- stale') + plan = root / "plan.json" + plan.write_text(json.dumps({"packages": [ + {"id": "mcpplibs:grpcgen", "candidate": "1.83.0.1"}]})) + with patch.object(ci, "ROOT", root): + dest = root / "consumer" + ci.prepare(plan, "mcpplibs:grpcgen", dest, home) + manifest = tomllib.loads((dest / "mcpp.toml").read_text()) + self.assertNotIn("indices", manifest) + self.assertIn("grpc.grpc-plugin", manifest["dependencies"]) + self.assertIn("mcpplibs.grpcgen", manifest["build-dependencies"]) + self.assertEqual((home / "indices/mcpplibs/pkgs/mwrap/example.lua").read_bytes(), package.read_bytes()) + self.assertFalse(stale.exists()) + def test_added_descriptor_and_removed_historical_descriptor(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index cc072a19..34406581 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -17,7 +17,7 @@ def main(): parser.add_argument("--mcpp", default="mcpp") parser.add_argument("--toolchain", default="llvm@23.1.3") parser.add_argument("--package", default="mcpplibs.mwrap.onnxruntime@1.25.1") - parser.add_argument("--deploy", action="store_true", help="pack, relocate and run the consumer; verify SDK notices") + parser.add_argument("--deploy", action="store_true", help="pack, relocate and run the consumer; verify declared notices") parser.add_argument("--run-arg", action="append", default=[]) parser.add_argument("--legacy-exact-key", action="store_true", help="allow only mcpp's nonnumeric exact-version diagnostic; fail other build warnings") @@ -80,20 +80,37 @@ def main(): bundle = project / "target/dist/deployment-audit" relocated = temporary / "relocated/bundle" shutil.copytree(bundle, relocated, symlinks=True) - notices = list(relocated.rglob("licenses/onnxruntime/ThirdPartyNotices.txt")) - assert len(notices) == 1 and notices[0].stat().st_size, notices - installed = [] - for entry in json.loads((project / "compile_commands.json").read_text(encoding="utf-8")): - arguments = entry["arguments"] - for number, argument in enumerate(arguments): - directory = (arguments[number + 1] if argument in ("-I", "/I") else - argument[2:] if argument.startswith(("-I", "/I")) else "") - include = Path(directory) - if directory and (include / "onnxruntime_c_api.h").is_file(): - installed.append(include.parent / "ThirdPartyNotices.txt") - expected = hashlib.sha256(notices[0].read_bytes()).hexdigest() - assert any(p.is_file() and hashlib.sha256(p.read_bytes()).hexdigest() == expected - for p in installed), installed + if identity == "mcpplibs.mwrap.onnxruntime": + notices = list(relocated.rglob("licenses/onnxruntime/ThirdPartyNotices.txt")) + assert len(notices) == 1 and notices[0].stat().st_size, notices + installed = [] + for entry in json.loads((project / "compile_commands.json").read_text(encoding="utf-8")): + arguments = entry["arguments"] + for number, argument in enumerate(arguments): + directory = (arguments[number + 1] if argument in ("-I", "/I") else + argument[2:] if argument.startswith(("-I", "/I")) else "") + include = Path(directory) + if directory and (include / "onnxruntime_c_api.h").is_file(): + installed.append(include.parent / "ThirdPartyNotices.txt") + expected = hashlib.sha256(notices[0].read_bytes()).hexdigest() + assert any(p.is_file() and hashlib.sha256(p.read_bytes()).hexdigest() == expected + for p in installed), installed + elif identity == "mcpplibs.mwrap.opencv": + commands = json.loads((project / "compile_commands.json").read_text(encoding="utf-8")) + sources = [Path(item["file"]).parent.parent for item in commands + if Path(item["file"]).name == "core.cppm"] + source = next(path for path in sources if (path / "upstream/opencv/LICENSE").is_file()) + notices = [source / "LICENSE", *[path for path in (source / "upstream/opencv").rglob("*") + if path.is_file() and any(word in path.name for word in ("LICENSE", "COPYING", "COPYRIGHT", "NOTICE"))]] + roots = list(relocated.rglob("licenses/opencv")) + assert len(roots) == 1, f"Missing OpenCV runtime license tree: {roots}" + for notice in notices: + copied = roots[0] / notice.relative_to(source) + assert copied.is_file(), f"Missing retained OpenCV notice: {copied}" + assert copied.read_bytes() == notice.read_bytes(), copied + print(f"Verified {len(notices)} byte-identical wrapper/upstream license files", flush=True) + else: + raise ValueError(f"No deployment acceptance contract for {identity}") runtime_env = {key: value for key, value in os.environ.items() if key not in {"MCPP_HOME", "XLINGS_HOME", "LD_LIBRARY_PATH", "LD_PRELOAD", "DYLD_LIBRARY_PATH", "DYLD_FALLBACK_LIBRARY_PATH"}} @@ -123,7 +140,12 @@ def main(): resolved = line.split("=>", 1)[1].strip().split(" ", 1)[0] if resolved.startswith("/"): assert Path(resolved).resolve().is_relative_to(relocated.resolve()), line - print("Verified relocated inference bundle and byte-identical SDK notices", flush=True) + if identity == "mcpplibs.mwrap.opencv": + encoded = (empty_home / "out.png").read_bytes() + assert encoded[:8] == b"\x89PNG\r\n\x1a\n", "Relocated template did not create a PNG" + assert int.from_bytes(encoded[16:20], "big") == 64 + assert int.from_bytes(encoded[20:24], "big") == 64 + print("Verified relocated application bundle and byte-identical notices", flush=True) if __name__ == "__main__": diff --git a/tests/mwrap/godot-math/mcpp.toml b/tests/mwrap/godot-math/mcpp.toml new file mode 100644 index 00000000..c18dd1df --- /dev/null +++ b/tests/mwrap/godot-math/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "godot-math" +version = "10.0.0-rc1.1" +description = "Run the immutable godot-cpp module wrapper without an engine" + +[indices] +godotengine = { path = "../../.." } + +[dependencies] +"godotengine.godot-cpp-m" = "10.0.0-rc1.1" diff --git a/tests/mwrap/godot-math/tests/math.cpp b/tests/mwrap/godot-math/tests/math.cpp new file mode 100644 index 00000000..b4956f07 --- /dev/null +++ b/tests/mwrap/godot-math/tests/math.cpp @@ -0,0 +1,11 @@ +import std; +import godot_cpp; + +int main() { + const godot::Vector2 vector(3, 4); + const godot::Vector3 cross = godot::Vector3(1, 0, 0).cross(godot::Vector3(0, 1, 0)); + if (std::abs(vector.length() - 5.0) > 1e-5 || cross != godot::Vector3(0, 0, 1)) return 1; + if (godot::Color(1, 0, 0, 1).to_rgba32() != 0xff0000ffu) return 2; + std::println("godot-cpp: vector length {}, cross product ({}, {}, {})", vector.length(), cross.x, cross.y, cross.z); + return 0; +} diff --git a/tests/mwrap/grpc-cpu/tests/behavior.cpp b/tests/mwrap/grpc-cpu/tests/behavior.cpp index b8b93ef3..875684d9 100644 --- a/tests/mwrap/grpc-cpu/tests/behavior.cpp +++ b/tests/mwrap/grpc-cpu/tests/behavior.cpp @@ -1,4 +1,5 @@ #if defined(MCPP_TEST_GRPC) +#include import grpc; int main() { if (!grpc::Status::OK.ok()) return 1; diff --git a/tests/mwrap/grpc-tools/mcpp.toml b/tests/mwrap/grpc-tools/mcpp.toml index 95f3bb71..3bd1830b 100644 --- a/tests/mwrap/grpc-tools/mcpp.toml +++ b/tests/mwrap/grpc-tools/mcpp.toml @@ -4,8 +4,8 @@ version = "1.83.0.1" description = "Resolve the indexed gRPC plugin and host rule, and verify the built host binary" [indices] -grpc = { path = "../.indices/grpc" } -mcpplibs = { path = "../.indices/mcpplibs" } +# CI and isolated validation install the whole checkout into the test home's +# built-in index. Keep this empty to override the legacy workspace redirect. [dependencies] "grpc.grpc-plugin" = { version = "1.83.0.1", tools = ["grpc_cpp_plugin"] } diff --git a/tests/mwrap/opencv-cpu/mcpp.toml b/tests/mwrap/opencv-cpu/mcpp.toml new file mode 100644 index 00000000..baee39e2 --- /dev/null +++ b/tests/mwrap/opencv-cpu/mcpp.toml @@ -0,0 +1,10 @@ +[package] +name = "opencv-cpu" +version = "5.0.0.1" +description = "Install the OpenCV mwrap archive and run a generated image pipeline" + +[indices] +"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } + +[dependencies] +"mcpplibs.mwrap.opencv" = { version = "5.0.0.1", features = ["imgproc", "imgcodecs-png", "imgcodecs-jpeg"] } diff --git a/tests/mwrap/opencv-cpu/tests/pipeline.cpp b/tests/mwrap/opencv-cpu/tests/pipeline.cpp new file mode 100644 index 00000000..e0d91a5a --- /dev/null +++ b/tests/mwrap/opencv-cpu/tests/pipeline.cpp @@ -0,0 +1,28 @@ +import std; +import mcpplibs.mwrap.opencv; + +int main() { + namespace types = mcpplibs::mwrap::opencv::macros; + cv::Mat color(8, 12, types::CV_8UC3, cv::Scalar(20, 80, 160)), gray; + cv::cvtColor(color, gray, cv::COLOR_BGR2GRAY); + if (gray.rows != 8 || gray.cols != 12 || gray.type() != types::CV_8UC1) return 1; + if (cv::norm(gray, cv::Mat(8, 12, types::CV_8UC1, cv::Scalar(97)), cv::NORM_INF) != 0) return 2; + std::vector bytes; + if (!cv::imencode(".png", gray, bytes) || bytes.empty()) return 3; + const auto decoded = cv::imdecode(bytes, cv::IMREAD_UNCHANGED); + if (decoded.empty() || decoded.size() != gray.size() || + cv::norm(decoded, gray, cv::NORM_INF) != 0) return 4; + if (cv::saturate_cast(-1) != 0 || + cv::saturate_cast(static_cast(256)) != 255 || + cv::saturate_cast(1.8) != 2) return 5; + const int first = 7, second = 7; + if (&cv::min(first, second) != &first || &cv::max(first, second) != &first) return 6; + if (!cv::imencode(".jpg", gray, bytes)) return 8; + const auto jpeg = cv::imdecode(bytes, cv::IMREAD_UNCHANGED); + if (jpeg.empty() || jpeg.size() != gray.size() || + cv::norm(jpeg, gray, cv::NORM_INF) > 2) return 9; + const auto value = cv::makePtr(19); + if (*value != 19) return 7; + std::println("OpenCV {} archive: exact gray pixels, PNG/JPEG roundtrips and scalar contracts passed", + cv::getVersionString()); +} diff --git a/tests/prepare_builtin_index.py b/tests/prepare_builtin_index.py new file mode 100644 index 00000000..f8082e0b --- /dev/null +++ b/tests/prepare_builtin_index.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Put the unchanged checkout layout in a test home's single built-in index.""" +import argparse +from pathlib import Path +import shutil +import tempfile + + +def prepare(source: Path, home: Path): + assert (source / "index.toml").is_file() and (source / "pkgs").is_dir(), source + parent = home / "indices" + parent.mkdir(parents=True, exist_ok=True) + target = parent / "mcpplibs" + with tempfile.TemporaryDirectory(prefix=".candidate-index-", dir=parent) as directory: + staged = Path(directory) / "index" + staged.mkdir() + shutil.copytree(source / "pkgs", staged / "pkgs") + shutil.copyfile(source / "index.toml", staged / "index.toml") + if target.is_symlink(): + target.unlink() + elif target.exists(): + shutil.rmtree(target) + staged.rename(target) + print("Test home uses the complete unchanged candidate index:", target) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--home", required=True, type=Path) + parser.add_argument("--source", default=Path(__file__).resolve().parents[1], type=Path) + args = parser.parse_args() + prepare(args.source.resolve(), args.home.resolve()) From 9f41559c77dc0fa4051980eecc7c5b1f6f4f3dd2 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 13:30:11 +0900 Subject: [PATCH 16/25] ci: apply candidate index after published registry refresh --- .agents/docs/2026-10-24-add-mwrap-and-ort-plan.md | 8 ++++++++ .github/workflows/xpkgindex-update.yml | 3 +++ 2 files changed, 11 insertions(+) diff --git a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md index 6610c7a9..b8732251 100644 --- a/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md +++ b/.agents/docs/2026-10-24-add-mwrap-and-ort-plan.md @@ -68,3 +68,11 @@ Each candidate below was downloaded twice with matching SHA256. Existing publish | grpc-m | `0d11b22399875f2847f04609fd4fccb50fda5410` | `8a4ff91f60ac6c58e3bdb9d18f67617865f948343afff9e94b855cf82612251a` | | imgui-m | `ffebf83be99ba0eec2677cc197dea3316a72b501` | `9aab8af876c6c9eaed887b0d84bf3bb733db1d28613cbd5083845d83f675a92f` | | godot-cpp-m | `40f72d5533a9bc29b17e62ae8a6c84b3dc593071` | `79f15b4f70b0ef07e8b751c928f9e3fd7a6b32703f09b177ad02cdfd676a7f33` | + +## Refresh ordering and final archive evidence + +On 815c647, updater candidate jobs for FFmpeg/llama failed because the workflow prepared the candidate and then ran mcpp index update, replacing it with the published snapshot. This is a fixture ordering bug, not missing package support. An actual pinned-client reproduction installed the candidate first, refreshed, and then correctly failed to resolve 8.1.2.1 against the published 8.1.2 entry. Preparing the complete candidate after refresh restored real archive/template lookup. The workflow now reapplies the fixture after registry setup/index refresh; no engine or low-level registry configuration changes are needed. All seventeen updater regressions still pass. + +The final OpenCV 5da7fc8 archive passed strict GCC 16.1.0 image/scalar consumption in a new home (152.96 seconds). Its actual fresh default template built strictly, packed in release/self-contained mode and ran after relocation with clean HOME/runtime paths. All fourteen wrapper/upstream notices matched their installed source bytes; the relocated application generated a valid 64x64 PNG and the Linux loader resolved every file-backed dependency within the bundle. The previous 52f4c8f package was independently shown to lack that license tree, so the new deployment gate detects the real packaging regression. + +The final Godot 40f72d5 archive/default math template also passed strict fresh-home LLVM build and execution (3m29s build); it printed vector length five and cross product (0,0,1). Source native CI was already green on all three targets. Current OpenCV/gRPC/index native heads remain under validation. No merge, tag or release is included. diff --git a/.github/workflows/xpkgindex-update.yml b/.github/workflows/xpkgindex-update.yml index 2aff6a0c..3208f620 100644 --- a/.github/workflows/xpkgindex-update.yml +++ b/.github/workflows/xpkgindex-update.yml @@ -213,6 +213,9 @@ jobs: PACKAGE: ${{ matrix.id }} run: | "$MCPP" index update + # Refresh replaces the candidate with the published snapshot. + # Apply the candidate last, to both descriptor reader and installer. + python tests/prepare_builtin_index.py --home "$MCPP_HOME" if [[ "$TOOLCHAIN" != default ]]; then "$MCPP" toolchain install "$TOOLCHAIN" "$MCPP" toolchain default "$TOOLCHAIN" From fdac79a73e68c919a2269e42dcc78dab206cb2fa Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 14:34:08 +0900 Subject: [PATCH 17/25] Pin OpenCV persistence and runtime checkpoint; document adapter contracts --- .agents/skills/add-mcpp-index-package/SKILL.md | 4 ++++ docs/mwrap.md | 4 ++++ docs/zh/mwrap.md | 4 ++++ pkgs/mwrap/mcpplibs.mwrap.opencv.lua | 12 ++++++------ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 4a337224..5e0361f2 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -90,6 +90,10 @@ description: Use when adding a new third-party library/package to the mcpp-index (日志中 `selected members: `),`lint`(含 `mcpp xpkg parse`)与 `mirror-cn-reachable` 覆盖新增描述符与 CN url。合并由维护者执行。 +加载 C++ SDK 的 Linux 消费者应验证运行时与 typed catch 契约,即使接口是 C API。 +已验证的 SDK 场景在模板中声明 `cxx_runtime = "toolchain-coupled"`,不能只用成功推理掩盖异常 ABI 冲突。 +模块适配生成器只写入实际变化的文件;复核公共头、manifest 和 facade 的时间戳幂等与增量构建。 + ## 自动更新与 PR 范围 - 对公共 GitHub release/数字 tag、稳定下载模板、无需改变构建配方的包,主动添加 diff --git a/docs/mwrap.md b/docs/mwrap.md index 3d618711..500df583 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -200,3 +200,7 @@ The pinned client routes each requested namespace independently. Registering the Normal installed consumers are tested independently by tests/mwrap/check_installed_template.py: it copies the complete pkgs/index.toml publication layout into one isolated built-in index, downloads the real checksummed source archive, runs mcpp new and builds/runs the generated manifest without a path substitution. The Git and published pkgs/mwrap directory layout remains identical. Coupled runtime/plugin/build-module consumers may require several candidate namespaces at once. Use `python3 tests/prepare_builtin_index.py --home ` to install the complete unchanged candidate layout as the test home's single built-in index; the member uses an empty `[indices]` table to override legacy workspace redirects. Updater candidate CI uses this mode too. Do not register the same full checkout through multiple project aliases. This is test-home preparation only: no publication staging, path conversion or mcpp engine change is required. + +For Linux consumers loading a C++ SDK through a C API, verify the C++ runtime contract as well as successful calls. The OpenCV/ORT integration exposed uncaught typed exceptions when a static runtime was embedded alongside the shared SDK runtime; `cxx_runtime = "toolchain-coupled"` fixed the same test. Templates must encode the verified contract and exercise a typed error path. + +Generated files are build inputs. Preserve their timestamps when content is unchanged, including manifests, module facades and configuration headers; touching an unchanged common header can rebuild the entire vendored implementation. Verify both byte idempotence and incremental behavior. diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index 7023e2f8..fdfc1758 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -143,3 +143,7 @@ vendored 静态实现同样需要显式部署,不能依赖共享库扫描发 Git 与发布的 pkgs/mwrap 目录始终一致。 运行库、插件、构建模块耦合的测试可能同时依赖多个候选 namespace。使用 `python3 tests/prepare_builtin_index.py --home <独立测试目录>`,将完整且字节不变的候选布局放入该测试 home 的唯一内置索引;成员用空 `[indices]` 覆盖旧 workspace 路由。自动更新候选 CI 同样使用此模式。不要将同一个完整 checkout 注册为多个项目别名。这只是测试 home 准备,不需要发布 staging、路径转换或修改 mcpp 引擎。 + +Linux 消费方即使通过 C API 加载 C++ SDK,也要验证 C++ 运行时契约及异常路径。OpenCV/ORT 组合实测中,静态运行时与共享 SDK 运行时混用会使 typed catch 终止;选择 `cxx_runtime = "toolchain-coupled"` 后同一测试通过。模板必须声明已验证的契约,并执行一次类型化错误路径。 + +生成清单、模块入口和配置头是构建输入;内容不变时保留时间戳,避免触碰公共配置头导致整份 vendored 实现重编。除了字节幂等,还应验证增量构建行为。 diff --git a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua index 69932ae0..ec8e0294 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", - sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", + sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", }, }, macosx = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", - sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", + sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", }, }, windows = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/5da7fc8abedc700ce1ed91a1f57067ad552c74fc.tar.gz", - sha256 = "5b26b4150610a25f69947e570bb6eed5d832b276d79d5e04af5fdc5842f5279c", + url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", + sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", }, }, }, From 0887bc2d79a0fe739a6eec88799a896e975f946b Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 14:49:32 +0900 Subject: [PATCH 18/25] Pin verified OpenCV HAL and tracking source archive --- pkgs/mwrap/mcpplibs.mwrap.opencv.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua index ec8e0294..6a8420a1 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", - sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", + url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", + sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", }, }, macosx = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", - sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", + url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", + sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", }, }, windows = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/7e4fe399d3ee4d56133817288fa28ffc38207470.tar.gz", - sha256 = "1248a3fabfc7116941656c476389e86c7654b229570bc1d00ff4943b6eb1afbf", + url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", + sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", }, }, }, From 7e5a54ccee615e2f8b1a32b34dd9cd7d5c2c55cb Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 15:00:16 +0900 Subject: [PATCH 19/25] Add optional WebP mux and demux for OpenCV codecs --- .../docs/2026-10-24-opencv-webp-provider.md | 27 ++++++ pkgs/c/compat.libwebp.lua | 15 +++- tests/examples/libwebp/mcpp.toml | 2 +- tests/examples/libwebp/tests/containers.cpp | 82 +++++++++++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 .agents/docs/2026-10-24-opencv-webp-provider.md create mode 100644 tests/examples/libwebp/tests/containers.cpp diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md new file mode 100644 index 00000000..30310c17 --- /dev/null +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -0,0 +1,27 @@ +# OpenCV WebP provider checkpoint + +OpenCV 5.0.0 grfmt_webp.cpp uses encoding/decoding, animation, EXIF/XMP/ICC +containers and therefore requires both upstream WebP mux and demux implementations. +The existing compat.libwebp 1.5.0 source recipe retains ownership of this C library; +no second vendored implementation or module wrapper is introduced. + +- `demux` adds src/demux/*.c; `mux` adds src/mux/*.c. Each is independent and additive. +- Default encode/decode/sharpyuv source selection, architecture gates and flags are unchanged. +- All three historical platform version/URL/hash blocks remain byte-identical. +- COPYING, PATENTS and AUTHORS are explicitly deployed under licenses/libwebp. +- The existing workspace consumer now requests both features. Its additional + standalone test encodes two lossless animation frames, muxes an EXIF chunk, + demuxes the metadata and checks decoded pixels and timestamps. Checks survive NDEBUG. + +Linux verification uses mcpp 2026.10.24.1 and LLVM23.1.3. Strict fresh projects +passed both the original roundtrip and container test (1.28 seconds), then a +separate unchanged-default dependency passed the original test (1.72 seconds). +Actual compile_commands.json includes mux/demux only for the enabled variant. +Logs: /tmp/mwrap-webp-provider-variants-final.log. All work uses the shared +sixteen-core/32 GiB test slice with three build jobs and disk/memory guards. + +The direct workspace-root strict command rejects its pre-existing deprecated +index inheritance; isolated projects use the byte-identical compat test view, +without editing the client or suppressing diagnostics. Normal workspace/native +CI remains required on this exact index head. OpenCV imgcodecs-webp integration, +its source matrix and actual archive/deployment consumer are separate gates. diff --git a/pkgs/c/compat.libwebp.lua b/pkgs/c/compat.libwebp.lua index 44cfc258..9b0163f3 100644 --- a/pkgs/c/compat.libwebp.lua +++ b/pkgs/c/compat.libwebp.lua @@ -10,9 +10,9 @@ -- CMake builds it as a separate target only to reuse it elsewhere, and there is -- nothing else here to reuse it. -- --- WHAT IS NOT. `src/demux` and `src/mux` (animation and metadata containers: --- WebPDemux* / WebPMux*) are separate upstream libraries with their own public --- headers; they belong in a feature, and nothing asks for them yet. +-- OPTIONAL. `demux` and `mux` add upstream animation/metadata containers to +-- the same implementation owner. OpenCV's WebP codec requires both; ordinary +-- encode/decode consumers retain the original default source set. -- `examples/`, `extras/`, `imageio/` and `swig/` are tools and bindings. -- -- SIMD, and the one thing this package has to decide. Every @@ -143,6 +143,15 @@ package = { "*/sharpyuv/*.c", }, targets = { ["webp"] = { kind = "lib" } }, + features = { + ["demux"] = { sources = { "*/src/demux/*.c" } }, + ["mux"] = { sources = { "*/src/mux/*.c" } }, + }, + runtime = { deploy = { + { from = "libwebp-1.5.0/COPYING", to = "licenses/libwebp" }, + { from = "libwebp-1.5.0/PATENTS", to = "licenses/libwebp" }, + { from = "libwebp-1.5.0/AUTHORS", to = "licenses/libwebp" }, + } }, deps = { }, -- libm for the dsp math, pthread for utils/thread_utils.c's worker pool. diff --git a/tests/examples/libwebp/mcpp.toml b/tests/examples/libwebp/mcpp.toml index d87fa1c4..12811199 100644 --- a/tests/examples/libwebp/mcpp.toml +++ b/tests/examples/libwebp/mcpp.toml @@ -5,4 +5,4 @@ name = "libwebp-tests" version = "0.1.0" [dependencies.compat] -libwebp = "1.5.0" +libwebp = { version = "1.5.0", features = ["demux", "mux"] } diff --git a/tests/examples/libwebp/tests/containers.cpp b/tests/examples/libwebp/tests/containers.cpp new file mode 100644 index 00000000..1388f1be --- /dev/null +++ b/tests/examples/libwebp/tests/containers.cpp @@ -0,0 +1,82 @@ +// Exercise the optional upstream mux/demux implementations with an actual +// two-frame lossless stream and an EXIF container chunk. Checks survive NDEBUG. +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { +void require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} +struct Data { + WebPData value{}; + ~Data() { WebPDataClear(&value); } +}; +} + +int main() { + constexpr int width = 8, height = 8; + const std::array, 2> colors{{{20, 80, 160, 255}, {180, 40, 70, 255}}}; + WebPAnimEncoderOptions options; + require(WebPAnimEncoderOptionsInit(&options), "animation encoder options ABI"); + std::unique_ptr encoder( + WebPAnimEncoderNew(width, height, &options), WebPAnimEncoderDelete); + require(bool(encoder), "animation encoder construction"); + WebPConfig config; + require(WebPConfigInit(&config) && WebPConfigLosslessPreset(&config, 6), "lossless configuration"); + for (int frame = 0; frame < 2; ++frame) { + std::vector rgba(width * height * 4); + for (int pixel = 0; pixel < width * height; ++pixel) + std::memcpy(rgba.data() + pixel * 4, colors[frame].data(), 4); + WebPPicture picture; + require(WebPPictureInit(&picture), "picture ABI"); + picture.use_argb = 1; + picture.width = width; + picture.height = height; + require(WebPPictureImportRGBA(&picture, rgba.data(), width * 4), "picture import"); + const bool added = WebPAnimEncoderAdd(encoder.get(), &picture, frame * 80, &config); + WebPPictureFree(&picture); + require(added, "animation frame encoding"); + } + require(WebPAnimEncoderAdd(encoder.get(), nullptr, 200, nullptr), "animation final timestamp"); + Data animated; + require(WebPAnimEncoderAssemble(encoder.get(), &animated.value), "animation assembly"); + std::unique_ptr mux(WebPMuxCreate(&animated.value, 1), WebPMuxDelete); + require(bool(mux), "mux construction"); + constexpr char metadata[] = "mwrap-webp"; + const WebPData chunk{reinterpret_cast(metadata), sizeof(metadata) - 1}; + require(WebPMuxSetChunk(mux.get(), "EXIF", &chunk, 1) == WEBP_MUX_OK, "metadata mux"); + Data encoded; + require(WebPMuxAssemble(mux.get(), &encoded.value) == WEBP_MUX_OK, "metadata assembly"); + + std::unique_ptr demux(WebPDemux(&encoded.value), WebPDemuxDelete); + require(bool(demux) && WebPDemuxGetI(demux.get(), WEBP_FF_FRAME_COUNT) == 2, "two-frame demux"); + WebPChunkIterator iterator; + require(WebPDemuxGetChunk(demux.get(), "EXIF", 1, &iterator), "metadata demux"); + const bool same_chunk = iterator.chunk.size == chunk.size && + std::memcmp(iterator.chunk.bytes, chunk.bytes, chunk.size) == 0; + WebPDemuxReleaseChunkIterator(&iterator); + require(same_chunk, "metadata payload changed"); + + WebPAnimDecoderOptions decode_options; + require(WebPAnimDecoderOptionsInit(&decode_options), "animation decoder options ABI"); + decode_options.color_mode = MODE_RGBA; + std::unique_ptr decoder( + WebPAnimDecoderNew(&encoded.value, &decode_options), WebPAnimDecoderDelete); + require(bool(decoder), "animation decoder construction"); + for (int frame = 0; frame < 2; ++frame) { + uint8_t* rgba = nullptr; + int timestamp = -1; + require(WebPAnimDecoderGetNext(decoder.get(), &rgba, ×tamp), "animation decode"); + require(timestamp == (frame == 0 ? 80 : 200), "frame timing changed"); + for (int pixel = 0; pixel < width * height; ++pixel) + require(std::memcmp(rgba + pixel * 4, colors[frame].data(), 4) == 0, "lossless pixels changed"); + } + require(!WebPAnimDecoderHasMoreFrames(decoder.get()), "unexpected extra frame"); +} From 39e7972d1b0f06ab959dbe9e58918554a8b64e6d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 15:26:29 +0900 Subject: [PATCH 20/25] Verify OpenCV WebP archive consumers and relocated deployment --- .../docs/2026-10-24-opencv-webp-provider.md | 22 +++++ .github/workflows/validate.yml | 5 + mcpp.toml | 1 + pkgs/mwrap/mcpplibs.mwrap.opencv.lua | 12 +-- tests/mwrap/check_webp_deployment.py | 97 +++++++++++++++++++ tests/mwrap/opencv-webp/mcpp.toml | 11 +++ tests/mwrap/opencv-webp/tests/pipeline.cpp | 42 ++++++++ 7 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 tests/mwrap/check_webp_deployment.py create mode 100644 tests/mwrap/opencv-webp/mcpp.toml create mode 100644 tests/mwrap/opencv-webp/tests/pipeline.cpp diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md index 30310c17..b13e8ade 100644 --- a/.agents/docs/2026-10-24-opencv-webp-provider.md +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -25,3 +25,25 @@ index inheritance; isolated projects use the byte-identical compat test view, without editing the client or suppressing diagnostics. Normal workspace/native CI remains required on this exact index head. OpenCV imgcodecs-webp integration, its source matrix and actual archive/deployment consumer are separate gates. + +## Archive and deployment acceptance + +OpenCV source 16baa03ef4353188c816a6ff5962a5da53998814 was downloaded twice +with SHA256 556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1. +The new tests/mwrap/opencv-webp member consumes that actual source archive. +An isolated home with the complete unchanged publication index strictly built +and ran the consumer, then packed a release application and ran the relocated +bundle with an empty home and cleared library/runtime overrides. Lossless alpha +and transparent RGB, two-frame timing/loop count, XMP bytes and WEBP build +information checks passed. ELF dependencies resolve within the bundle. All +fourteen OpenCV/wrapper notices plus three libwebp notices match their original +source bytes. Log: /tmp/mwrap-webp-archive-deployment.log. + +The deployment helper is also registered for native default-toolchain workspace +CI when opencv-webp is selected; ordinary member tests cover other compiler legs. +All seventeen pinned-updater regressions pass, and the full audit still covers +91 automatic declarations/two reviewed snapshots with historical versions intact. +The initial local unittest invocation lacked the workflow's pinned-updater +PYTHONPATH; the successful rerun uses /tmp/mwrap-updater-6c0bee/src. +Native CI results are required on the final source/index commits; these local +checks do not certify Windows/macOS/musl behavior by inference. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index f34694f7..211b3567 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1555,6 +1555,11 @@ jobs: shell: bash run: python3 tests/prepare_builtin_index.py --home "$MCPP_HOME" + - name: Installed OpenCV WebP archive and relocated consumer + if: contains(format(' {0} ', env.MEMBERS), ' opencv-webp ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_webp_deployment.py --mcpp "$MCPP" + - name: Installed OpenCV archive, image template and relocated deployment if: contains(format(' {0} ', env.MEMBERS), ' opencv-cpu ') && matrix.toolchain == 'default' shell: bash diff --git a/mcpp.toml b/mcpp.toml index 372efaeb..8adef764 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -11,6 +11,7 @@ mcpp = ">=2026.10.21.1" [workspace] members = [ "tests/mwrap/opencv-cpu", + "tests/mwrap/opencv-webp", "tests/mwrap/onnxruntime-cpu", "tests/mwrap/llamacpp-cpu", "tests/mwrap/ffmpeg-cpu", diff --git a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua index 6a8420a1..d297f5ba 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", - sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", + url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", + sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", }, }, macosx = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", - sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", + url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", + sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", }, }, windows = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/8d1ee87583f4a39334476cbaf8e90cfa6a0318fa.tar.gz", - sha256 = "55f43a1f357854486790f5d8d426a68fbc87c37c2de3d7755d3be3eef3ef2373", + url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", + sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", }, }, }, diff --git a/tests/mwrap/check_webp_deployment.py b/tests/mwrap/check_webp_deployment.py new file mode 100644 index 00000000..66026154 --- /dev/null +++ b/tests/mwrap/check_webp_deployment.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Install the actual OpenCV/WebP archives and verify a relocated consumer.""" +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--mcpp", default="mcpp") + parser.add_argument("--toolchain", default="llvm@23.1.3") + args = parser.parse_args() + root = Path(__file__).resolve().parents[2] + with tempfile.TemporaryDirectory(prefix="mwrap-webp-deployment-") as directory: + temporary = Path(directory) + home = temporary / "home" + index = home / "indices/mcpplibs" + index.mkdir(parents=True) + shutil.copytree(root / "pkgs", index / "pkgs") + shutil.copyfile(root / "index.toml", index / "index.toml") + project = temporary / "consumer" + shutil.copytree(root / "tests/mwrap/opencv-webp", project) + manifest = project / "mcpp.toml" + # The fresh home has the complete unchanged publication index. Keep + # dependency identities/features intact; remove test-view routing only. + text = re.sub(r"(?ms)^\[indices\]\n.*?(?=^\[|\Z)", "[indices]\n\n", manifest.read_text()) + manifest.write_text(text) + (project / "src").mkdir() + shutil.copyfile(project / "tests/pipeline.cpp", project / "src/main.cpp") + env = dict(os.environ, MCPP_HOME=str(home)) + commands = [ + [args.mcpp, "self", "config", "--mirror", "GLOBAL"], + [args.mcpp, "build", "--toolchain", args.toolchain, "--strict"], + [args.mcpp, "run", "--toolchain", args.toolchain], + [args.mcpp, "pack", "--toolchain", args.toolchain, "--mode", "self-contained", + "--format", "dir", "--output", "webp-deployment"], + ] + for command in commands: + print("+", " ".join(command), flush=True) + subprocess.run(command, cwd=project, env=env, check=True) + bundle = project / "target/dist/webp-deployment" + relocated = temporary / "relocated" + shutil.copytree(bundle, relocated, symlinks=True) + entries = json.loads((project / "compile_commands.json").read_text()) + sdk = next(parent for entry in entries for parent in Path(entry["file"]).parents + if parent.name == "libwebp-1.5.0") + notices = list(relocated.rglob("licenses/libwebp")) + assert len(notices) == 1, f"Missing/ambiguous WebP notices: {notices}" + for name in ("COPYING", "PATENTS", "AUTHORS"): + assert (notices[0] / name).read_bytes() == (sdk / name).read_bytes(), name + source = next(Path(entry["file"]).parent.parent for entry in entries + if Path(entry["file"]).name == "core.cppm") + originals = [source / "LICENSE", *[path for path in (source / "upstream/opencv").rglob("*") + if path.is_file() and any(word in path.name for word in ("LICENSE", "COPYING", "COPYRIGHT", "NOTICE"))]] + notices = list(relocated.rglob("licenses/opencv")) + assert len(notices) == 1, f"Missing/ambiguous OpenCV notices: {notices}" + for original in originals: + assert (notices[0] / original.relative_to(source)).read_bytes() == original.read_bytes(), original + runtime = {key: value for key, value in os.environ.items() + if key not in {"MCPP_HOME", "XLINGS_HOME", "LD_LIBRARY_PATH", "LD_PRELOAD", + "DYLD_LIBRARY_PATH", "DYLD_FALLBACK_LIBRARY_PATH"}} + empty_home = temporary / "empty-home" + empty_home.mkdir() + runtime.update(HOME=str(empty_home), USERPROFILE=str(empty_home)) + if sys.platform.startswith("linux"): + runtime["PATH"] = "/usr/bin:/bin" + launcher = relocated / "run.sh" + else: + filename = "opencv-webp" + (".exe" if os.name == "nt" else "") + launcher = relocated / "bin" / filename + if not launcher.exists(): + launcher = relocated / filename + if os.name == "nt": + system_root = os.environ.get("SystemRoot", r"C:\Windows") + runtime["PATH"] = os.pathsep.join([system_root, str(Path(system_root) / "System32")]) + subprocess.run([str(launcher)], cwd=empty_home, env=runtime, check=True) + if sys.platform.startswith("linux"): + listing = subprocess.run([str(relocated / "lib/ld-linux-x86-64.so.2"), + "--library-path", str(relocated / "lib"), "--list", + str(relocated / "bin/opencv-webp")], + cwd=empty_home, env=runtime, text=True, capture_output=True, check=True) + for line in listing.stdout.splitlines(): + if "=>" in line: + resolved = line.split("=>", 1)[1].strip().split(" ", 1)[0] + if resolved.startswith("/"): + assert Path(resolved).resolve().is_relative_to(relocated.resolve()), line + print(f"Verified relocated WebP consumer and {len(originals) + 3} byte-identical notices", flush=True) + + +if __name__ == "__main__": + main() diff --git a/tests/mwrap/opencv-webp/mcpp.toml b/tests/mwrap/opencv-webp/mcpp.toml new file mode 100644 index 00000000..299f29e3 --- /dev/null +++ b/tests/mwrap/opencv-webp/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "opencv-webp" +version = "5.0.0.1" +description = "Consume the OpenCV archive with optional WebP image, animation and metadata support" + +[indices] +"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } +"compat" = { path = "../.indices/compat" } + +[dependencies] +"mcpplibs.mwrap.opencv" = { version = "5.0.0.1", features = ["imgcodecs-webp"] } diff --git a/tests/mwrap/opencv-webp/tests/pipeline.cpp b/tests/mwrap/opencv-webp/tests/pipeline.cpp new file mode 100644 index 00000000..2085b978 --- /dev/null +++ b/tests/mwrap/opencv-webp/tests/pipeline.cpp @@ -0,0 +1,42 @@ +import std; +import mcpplibs.mwrap.opencv; + +int main() { + namespace types = mcpplibs::mwrap::opencv::macros; + if (cv::getBuildInformation().find("WEBP: YES") == std::string::npos) return 8; + cv::Mat image(8, 8, types::CV_8UC4, cv::Scalar(20, 80, 160, 180)); + image.at(0, 0) = cv::Vec4b(7, 19, 23, 0); + std::vector bytes; + if (!cv::imencode(".webp", image, bytes, + {cv::IMWRITE_WEBP_LOSSLESS_MODE, cv::IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR})) return 1; + auto decoded = cv::imdecode(bytes, cv::IMREAD_UNCHANGED); + if (decoded.empty() || decoded.type() != image.type() || decoded.size() != image.size() || + cv::norm(decoded, image, cv::NORM_INF) != 0) return 2; + + cv::Animation animation(2); + animation.frames = {cv::Mat(8, 8, types::CV_8UC4, cv::Scalar(20, 80, 160, 180)), + cv::Mat(8, 8, types::CV_8UC4, cv::Scalar(100, 40, 70, 200))}; + animation.durations = {80, 120}; + if (!cv::imencodeanimation(".webp", animation, bytes, {cv::IMWRITE_WEBP_QUALITY, 101})) return 3; + cv::Animation restored; + if (!cv::imdecodeanimation(bytes, restored) || restored.loop_count != 2 || + restored.durations != animation.durations || restored.frames.size() != 2) return 4; + for (int frame = 0; frame < 2; ++frame) + if (restored.frames[frame].type() != animation.frames[frame].type() || + restored.frames[frame].size() != animation.frames[frame].size() || + cv::norm(restored.frames[frame], animation.frames[frame], cv::NORM_INF) != 0) return 5; + + const std::string xmp = "archive"; + cv::Mat chunk(1, static_cast(xmp.size()), types::CV_8U, const_cast(xmp.data())); + const std::vector kinds{cv::IMAGE_METADATA_XMP}; + const std::vector metadata{chunk}; + if (!cv::imencodeWithMetadata(".webp", animation.frames[0], kinds, metadata, bytes, + {cv::IMWRITE_WEBP_LOSSLESS_MODE, cv::IMWRITE_WEBP_LOSSLESS_ON})) return 6; + std::vector decoded_kinds; + std::vector decoded_metadata; + decoded = cv::imdecodeWithMetadata(bytes, decoded_kinds, decoded_metadata, cv::IMREAD_UNCHANGED); + if (decoded.empty() || decoded_kinds != kinds || decoded_metadata.size() != 1 || + decoded_metadata[0].total() != xmp.size() || + std::memcmp(decoded_metadata[0].data, xmp.data(), xmp.size()) != 0) return 7; + std::println("OpenCV WebP archive: lossless alpha, animation and XMP metadata passed"); +} From 234dade33e4aabd7399b3eafb545151dc5e9f9af Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 15:30:20 +0900 Subject: [PATCH 21/25] Clarify WebP baseline policy with current scoped flag support --- pkgs/c/compat.libwebp.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/c/compat.libwebp.lua b/pkgs/c/compat.libwebp.lua index 9b0163f3..4e9b9f1e 100644 --- a/pkgs/c/compat.libwebp.lua +++ b/pkgs/c/compat.libwebp.lua @@ -35,8 +35,9 @@ -- but would be inlined into function 'VP8L32bToPlanar_SSE41' that is -- compiled without support for 'ssse3' -- --- Upstream's CMake answers this with a PER-FILE `-msse4.1`, which mcpp has no --- field for. Adding it package-wide instead would let clang emit SSE4.1 in the +-- Upstream's CMake answers this with a per-file `-msse4.1`. Current mcpp +-- supports scoped flags; this recipe retains its original baseline profile. +-- Adding that flag package-wide would let clang emit SSE4.1 in the -- BASELINE translation units too, past libwebp's own runtime CPU dispatch — an -- artifact that SIGILLs on a pre-2008 CPU rather than falling back. So this -- package takes the other half of upstream's mechanism: `HAVE_CONFIG_H` plus a From 2e547350aeb13e86792972f42dcc4ddd3c81cadb Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 17:07:39 +0900 Subject: [PATCH 22/25] Verify embedded OpenCV notices in installed archives and relocated consumers --- .../docs/2026-10-24-opencv-webp-provider.md | 19 +++++++++++++++++++ pkgs/mwrap/mcpplibs.mwrap.opencv.lua | 12 ++++++------ tests/mwrap/check_installed_template.py | 3 ++- tests/mwrap/check_webp_deployment.py | 3 ++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md index b13e8ade..49cf93b7 100644 --- a/.agents/docs/2026-10-24-opencv-webp-provider.md +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -47,3 +47,22 @@ The initial local unittest invocation lacked the workflow's pinned-updater PYTHONPATH; the successful rerun uses /tmp/mwrap-updater-6c0bee/src. Native CI results are required on the final source/index commits; these local checks do not certify Windows/macOS/musl behavior by inference. + +## Embedded notice supplement and updated archive + +Candidate 019eaadb4c2212246cf20e29447bb1ef960333ba was independently downloaded +twice with SHA256 e265add2c6ed3aa9e6dba677a8731e8a385e5f8419093336a18fbdab54be5661. +It adds the SDK inline-helper/value-operator checkpoint and a deterministic +embedded source notice supplement. The original fourteen named OpenCV notices +remain; the additional supplement preserves source comment bytes and locations. +Both installed-template and WebP deployment helpers require that supplement. + +The fresh-home archive test strictly built and ran the consumer (2m55s), packed +a release bundle (1m34s), and ran it after relocation with an empty HOME and +cleared runtime overrides. Image/animation/XMP checks and the bundled ELF loader +closure passed. All fifteen OpenCV plus three WebP notices match the installed +source bytes. Log: /tmp/mwrap-notices-archive-deployment.log. This local test used +the shared resource slice, three build jobs and the whole-host pressure guard. +Descriptor parsing, naming, version parity and duplicate-version checks pass. +Native CI still needs the new exact source and index heads; the previous OpenCV +16baa03 matrix completed all five jobs successfully. diff --git a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua index d297f5ba..cf6efc2f 100644 --- a/pkgs/mwrap/mcpplibs.mwrap.opencv.lua +++ b/pkgs/mwrap/mcpplibs.mwrap.opencv.lua @@ -12,20 +12,20 @@ package = { xpm = { linux = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", - sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", + url = "https://github.com/mcpplibs/opencv-m/archive/019eaadb4c2212246cf20e29447bb1ef960333ba.tar.gz", + sha256 = "e265add2c6ed3aa9e6dba677a8731e8a385e5f8419093336a18fbdab54be5661", }, }, macosx = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", - sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", + url = "https://github.com/mcpplibs/opencv-m/archive/019eaadb4c2212246cf20e29447bb1ef960333ba.tar.gz", + sha256 = "e265add2c6ed3aa9e6dba677a8731e8a385e5f8419093336a18fbdab54be5661", }, }, windows = { ["5.0.0.1"] = { - url = "https://github.com/mcpplibs/opencv-m/archive/16baa03ef4353188c816a6ff5962a5da53998814.tar.gz", - sha256 = "556df2142b56cac8f635597bcec9bf32a45957cc0203cff0feb5cf9e256b09c1", + url = "https://github.com/mcpplibs/opencv-m/archive/019eaadb4c2212246cf20e29447bb1ef960333ba.tar.gz", + sha256 = "e265add2c6ed3aa9e6dba677a8731e8a385e5f8419093336a18fbdab54be5661", }, }, }, diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 34406581..0d14be0c 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -100,7 +100,8 @@ def main(): sources = [Path(item["file"]).parent.parent for item in commands if Path(item["file"]).name == "core.cppm"] source = next(path for path in sources if (path / "upstream/opencv/LICENSE").is_file()) - notices = [source / "LICENSE", *[path for path in (source / "upstream/opencv").rglob("*") + notices = [source / "LICENSE", source / "port/licenses/UPSTREAM_SOURCE_NOTICES.txt", + *[path for path in (source / "upstream/opencv").rglob("*") if path.is_file() and any(word in path.name for word in ("LICENSE", "COPYING", "COPYRIGHT", "NOTICE"))]] roots = list(relocated.rglob("licenses/opencv")) assert len(roots) == 1, f"Missing OpenCV runtime license tree: {roots}" diff --git a/tests/mwrap/check_webp_deployment.py b/tests/mwrap/check_webp_deployment.py index 66026154..f8132874 100644 --- a/tests/mwrap/check_webp_deployment.py +++ b/tests/mwrap/check_webp_deployment.py @@ -56,7 +56,8 @@ def main(): assert (notices[0] / name).read_bytes() == (sdk / name).read_bytes(), name source = next(Path(entry["file"]).parent.parent for entry in entries if Path(entry["file"]).name == "core.cppm") - originals = [source / "LICENSE", *[path for path in (source / "upstream/opencv").rglob("*") + originals = [source / "LICENSE", source / "port/licenses/UPSTREAM_SOURCE_NOTICES.txt", + *[path for path in (source / "upstream/opencv").rglob("*") if path.is_file() and any(word in path.name for word in ("LICENSE", "COPYING", "COPYRIGHT", "NOTICE"))]] notices = list(relocated.rglob("licenses/opencv")) assert len(notices) == 1, f"Missing/ambiguous OpenCV notices: {notices}" From 6ff350aae9eb3cf4c23e764fac43c6c54bbb59f0 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 19:46:54 +0900 Subject: [PATCH 23/25] Prepare isolated namespace views before mwrap workspace validation --- .../docs/2026-10-24-opencv-webp-provider.md | 19 ++++++++ .github/workflows/validate.yml | 6 +++ tests/prepare_mwrap_indices.py | 8 ++++ tests/test_prepare_mwrap_indices.py | 48 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 tests/test_prepare_mwrap_indices.py diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md index 49cf93b7..affc619c 100644 --- a/.agents/docs/2026-10-24-opencv-webp-provider.md +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -66,3 +66,22 @@ the shared resource slice, three build jobs and the whole-host pressure guard. Descriptor parsing, naming, version parity and duplicate-version checks pass. Native CI still needs the new exact source and index heads; the previous OpenCV 16baa03 matrix completed all five jobs successfully. + +## Namespace-isolated validation views + +Validation run 40076826208 failed on Linux default, Linux LLVM and macOS shards: +both compat and mcpplibs.mwrap project aliases exposed complete indices, so +xlings rejected duplicate ORT/OpenCV candidates. The existing view preparer was +not registered in the workspace workflow. Validation now prepares namespace-only +views before consumers and replaces each owned scratch view in full, including +stale categories left by earlier complete-index copies. Publication paths and +descriptor bytes are unchanged; no client engine changes are required. + +The regression fixture seeds a cross-namespace descriptor in an old category, +rebuilds twice and verifies unique candidates plus exact source bytes/provenance. +It passes and runs before preparation in CI. A separate actual consumer with a +fresh MCPP_HOME installed the immutable ORT wrapper, CPU SDK and LLVM23.1.3, +then passed inference and public_surface under strict mode (2 tests, 103.25s). +Log: /tmp/mwrap-disjoint-view-consumer.log. The test used the shared eight-core, +32 GiB resource slice, two build jobs and whole-host 80% pressure guard. Native +workspace CI on the new index commit remains required. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 211b3567..db5bde6c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1550,6 +1550,12 @@ jobs: run: | "$MCPP" index update + - name: Namespace-isolated mwrap test indices + shell: bash + run: | + python3 tests/test_prepare_mwrap_indices.py + python3 tests/prepare_mwrap_indices.py + - name: Complete candidate index for the cross-namespace host-tool consumer if: contains(format(' {0} ', env.MEMBERS), ' grpc-tools ') shell: bash diff --git a/tests/prepare_mwrap_indices.py b/tests/prepare_mwrap_indices.py index 65572146..f749db72 100644 --- a/tests/prepare_mwrap_indices.py +++ b/tests/prepare_mwrap_indices.py @@ -22,6 +22,14 @@ def main(): namespaces[match[1]].append(source) for namespace, sources in namespaces.items(): view = root / "tests/mwrap/.indices" / namespace + # These are generated scratch views. Clear older complete-index copies + # too; cleaning only pkgs/view leaves stale categories and ambiguous + # namespace candidates in another project alias. + assert view.parent.resolve().is_relative_to(root / "tests/mwrap"), view + if view.is_symlink(): + view.unlink() + elif view.exists(): + shutil.rmtree(view) payload = view / "pkgs/view" payload.mkdir(parents=True, exist_ok=True) expected = {source.name for source in sources} diff --git a/tests/test_prepare_mwrap_indices.py b/tests/test_prepare_mwrap_indices.py new file mode 100644 index 00000000..06dfb9bb --- /dev/null +++ b/tests/test_prepare_mwrap_indices.py @@ -0,0 +1,48 @@ +"""Regression for ambiguous candidates left by complete test-index copies.""" +from pathlib import Path +import json +import shutil +import subprocess +import sys +import tempfile +import unittest + + +class NamespaceViews(unittest.TestCase): + def test_rebuild_removes_cross_namespace_candidates(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "tests").mkdir() + script = root / "tests/prepare_mwrap_indices.py" + shutil.copyfile(Path(__file__).with_name(script.name), script) + (root / "index.toml").write_text('name = "fixture"\n') + originals = {} + namespaces = ("compat", "mcpplibs.mwrap", "grpc", "mcpplibs") + for namespace in namespaces: + source = root / "pkgs/mwrap" / (namespace + ".sample.lua") + source.parent.mkdir(parents=True, exist_ok=True) + data = ('namespace = "' + namespace + '"\nname = "sample"\n').encode() + source.write_bytes(data) + originals[source.relative_to(root).as_posix()] = data + views = root / "tests/mwrap/.indices" + for attempt in range(2): + # Old clients could populate a missing view with a full index. + stale = views / "compat/pkgs/old-category/mcpplibs.mwrap.sample.lua" + stale.parent.mkdir(parents=True, exist_ok=True) + stale.write_bytes(originals["pkgs/mwrap/mcpplibs.mwrap.sample.lua"]) + subprocess.run([sys.executable, str(script)], check=True, capture_output=True) + self.assertFalse(stale.exists()) + candidates = [] + for namespace in namespaces: + view = views / namespace + descriptors = list(view.glob("pkgs/*/*.lua")) + self.assertEqual(len(descriptors), 1) + key = "pkgs/mwrap/" + namespace + ".sample.lua" + self.assertEqual(descriptors[0].read_bytes(), originals[key]) + self.assertEqual(set(json.loads((view / "source-manifest.json").read_text())), {key}) + candidates.append(descriptors[0].read_bytes()) + self.assertEqual(len(set(candidates)), 4) + + +if __name__ == "__main__": + unittest.main() From ee52fdb7da3ade45fc6572565a77cc563c51e3e0 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 20:21:37 +0900 Subject: [PATCH 24/25] Use one complete candidate index across mixed mwrap workspaces --- .../docs/2026-10-24-opencv-webp-provider.md | 26 +++++++++++++++++++ .github/workflows/validate.yml | 6 ++--- tests/mwrap/ffmpeg-cpu/mcpp.toml | 3 ++- tests/mwrap/godot-math/mcpp.toml | 3 ++- tests/mwrap/grpc-cpu/mcpp.toml | 3 ++- tests/mwrap/grpc-tools/mcpp.toml | 4 +-- tests/mwrap/imgui-headless/mcpp.toml | 3 ++- tests/mwrap/llamacpp-cpu/mcpp.toml | 3 ++- tests/mwrap/onnxruntime-cpu/mcpp.toml | 4 +-- tests/mwrap/onnxruntime-sdk/mcpp.toml | 3 ++- tests/mwrap/opencv-cpu/mcpp.toml | 3 ++- tests/mwrap/opencv-webp/mcpp.toml | 4 +-- tests/run_members.sh | 10 ++++++- tests/test_prepare_mwrap_indices.py | 12 +++++++++ 14 files changed, 69 insertions(+), 18 deletions(-) diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md index affc619c..e177eddd 100644 --- a/.agents/docs/2026-10-24-opencv-webp-provider.md +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -85,3 +85,29 @@ then passed inference and public_surface under strict mode (2 tests, 103.25s). Log: /tmp/mwrap-disjoint-view-consumer.log. The test used the shared eight-core, 32 GiB resource slice, two build jobs and whole-host 80% pressure guard. Native workspace CI on the new index commit remains required. + +## Mixed workspace resolution correction + +Run 40087278903 / Linux LLVM shard 0/6 still failed ORT/OpenCV ambiguity after +view preparation. The log confirms both preparations ran successfully before +libwebp and llama.cpp consumers; the later mwrap consumers nevertheless saw +cross-namespace candidates. The standalone fresh-home result above did not +cover this mixed workspace ordering. + +A private-home fixture now reproduces the actual sequence: configure a legacy +compat.zlib 1.3.2 consumer against the complete project index, then resolve ORT +with the two namespace views. That second resolution fails with duplicate +candidates. Keeping the same warmed home/workspace and changing only the ORT +member to an explicit empty [indices] table succeeds: strict inference and +public_surface both run (19.97s). The fixture installs the complete unchanged +candidate layout into its built-in index; it does not edit the client engine +or a user-global registry. Log: /tmp/mwrap-mixed-workspace-index.log. + +All ten mwrap workspace members now use that explicit empty table. CI installs +the complete candidate built-in index after its published refresh, and the local +member runner does the same for mwrap selections. An unset local MCPP_HOME +selects target/mwrap-test-home, with bootstrap before copying the candidate; +normal user's default home is not replaced. Static regression guards require +mwrap members to retain their explicit override. Namespace-view tooling remains +available to isolated single-project consumers, with its scope documented. +New exact-head native workspace validation remains required. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index db5bde6c..cb0bc305 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1550,14 +1550,12 @@ jobs: run: | "$MCPP" index update - - name: Namespace-isolated mwrap test indices + - name: Namespace view preparation regression shell: bash run: | python3 tests/test_prepare_mwrap_indices.py - python3 tests/prepare_mwrap_indices.py - - name: Complete candidate index for the cross-namespace host-tool consumer - if: contains(format(' {0} ', env.MEMBERS), ' grpc-tools ') + - name: Complete candidate built-in index for workspace consumers shell: bash run: python3 tests/prepare_builtin_index.py --home "$MCPP_HOME" diff --git a/tests/mwrap/ffmpeg-cpu/mcpp.toml b/tests/mwrap/ffmpeg-cpu/mcpp.toml index a001508d..21c02cfe 100644 --- a/tests/mwrap/ffmpeg-cpu/mcpp.toml +++ b/tests/mwrap/ffmpeg-cpu/mcpp.toml @@ -4,7 +4,8 @@ version = "8.1.2.1" description = "Consume the immutable FFmpeg wrapper revision and decode a real raw frame" [indices] -ffmpeg = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "ffmpeg.ffmpeg" = "8.1.2.1" diff --git a/tests/mwrap/godot-math/mcpp.toml b/tests/mwrap/godot-math/mcpp.toml index c18dd1df..4da22d5c 100644 --- a/tests/mwrap/godot-math/mcpp.toml +++ b/tests/mwrap/godot-math/mcpp.toml @@ -4,7 +4,8 @@ version = "10.0.0-rc1.1" description = "Run the immutable godot-cpp module wrapper without an engine" [indices] -godotengine = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "godotengine.godot-cpp-m" = "10.0.0-rc1.1" diff --git a/tests/mwrap/grpc-cpu/mcpp.toml b/tests/mwrap/grpc-cpu/mcpp.toml index 885f3ac6..49c19051 100644 --- a/tests/mwrap/grpc-cpu/mcpp.toml +++ b/tests/mwrap/grpc-cpu/mcpp.toml @@ -4,7 +4,8 @@ version = "1.83.0.1" description = "Consume the immutable gRPC wrapper revision and check module status semantics" [indices] -grpc = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [target.'cfg(unix)'.dependencies] "grpc.grpc" = "1.83.0.1" diff --git a/tests/mwrap/grpc-tools/mcpp.toml b/tests/mwrap/grpc-tools/mcpp.toml index 3bd1830b..bab6b806 100644 --- a/tests/mwrap/grpc-tools/mcpp.toml +++ b/tests/mwrap/grpc-tools/mcpp.toml @@ -4,8 +4,8 @@ version = "1.83.0.1" description = "Resolve the indexed gRPC plugin and host rule, and verify the built host binary" [indices] -# CI and isolated validation install the whole checkout into the test home's -# built-in index. Keep this empty to override the legacy workspace redirect. +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "grpc.grpc-plugin" = { version = "1.83.0.1", tools = ["grpc_cpp_plugin"] } diff --git a/tests/mwrap/imgui-headless/mcpp.toml b/tests/mwrap/imgui-headless/mcpp.toml index 6b5311cb..9d594667 100644 --- a/tests/mwrap/imgui-headless/mcpp.toml +++ b/tests/mwrap/imgui-headless/mcpp.toml @@ -4,7 +4,8 @@ version = "1.92.8.1" description = "Consume the immutable ImGui wrapper and render actual headless draw data" [indices] -ocornut = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "ocornut.imgui" = "1.92.8.1" diff --git a/tests/mwrap/llamacpp-cpu/mcpp.toml b/tests/mwrap/llamacpp-cpu/mcpp.toml index 320539b2..cee7c31f 100644 --- a/tests/mwrap/llamacpp-cpu/mcpp.toml +++ b/tests/mwrap/llamacpp-cpu/mcpp.toml @@ -4,7 +4,8 @@ version = "b10069.3" description = "Install the immutable llama.cpp-m archive and exercise the CPU API" [indices] -ggml-org = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies.ggml-org] llamacpp = "b10069.3" diff --git a/tests/mwrap/onnxruntime-cpu/mcpp.toml b/tests/mwrap/onnxruntime-cpu/mcpp.toml index 383d4e14..925081f2 100644 --- a/tests/mwrap/onnxruntime-cpu/mcpp.toml +++ b/tests/mwrap/onnxruntime-cpu/mcpp.toml @@ -4,8 +4,8 @@ version = "1.25.1" description = "Install the immutable mwrap archive and execute CPU inference" [indices] -"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } -compat = { path = "../.indices/compat" } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies."mcpplibs.mwrap"] onnxruntime = "1.25.1" diff --git a/tests/mwrap/onnxruntime-sdk/mcpp.toml b/tests/mwrap/onnxruntime-sdk/mcpp.toml index be25297a..6b0cdc67 100644 --- a/tests/mwrap/onnxruntime-sdk/mcpp.toml +++ b/tests/mwrap/onnxruntime-sdk/mcpp.toml @@ -4,7 +4,8 @@ version = "1.25.1" description = "Exercise the official ONNX Runtime SDK through the local index" [indices] -compat = { path = "../../.." } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies.compat] onnxruntime = "1.25.1" diff --git a/tests/mwrap/opencv-cpu/mcpp.toml b/tests/mwrap/opencv-cpu/mcpp.toml index baee39e2..225cdae2 100644 --- a/tests/mwrap/opencv-cpu/mcpp.toml +++ b/tests/mwrap/opencv-cpu/mcpp.toml @@ -4,7 +4,8 @@ version = "5.0.0.1" description = "Install the OpenCV mwrap archive and run a generated image pipeline" [indices] -"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "mcpplibs.mwrap.opencv" = { version = "5.0.0.1", features = ["imgproc", "imgcodecs-png", "imgcodecs-jpeg"] } diff --git a/tests/mwrap/opencv-webp/mcpp.toml b/tests/mwrap/opencv-webp/mcpp.toml index 299f29e3..9c67369e 100644 --- a/tests/mwrap/opencv-webp/mcpp.toml +++ b/tests/mwrap/opencv-webp/mcpp.toml @@ -4,8 +4,8 @@ version = "5.0.0.1" description = "Consume the OpenCV archive with optional WebP image, animation and metadata support" [indices] -"mcpplibs.mwrap" = { path = "../.indices/mcpplibs.mwrap" } -"compat" = { path = "../.indices/compat" } +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. [dependencies] "mcpplibs.mwrap.opencv" = { version = "5.0.0.1", features = ["imgcodecs-webp"] } diff --git a/tests/run_members.sh b/tests/run_members.sh index 961cd9bb..ba261d37 100755 --- a/tests/run_members.sh +++ b/tests/run_members.sh @@ -20,6 +20,7 @@ # Env: # MCPP path to the mcpp binary (default: `mcpp` on PATH) # MCPP_TIMINGS where to append `\t\t` rows +# MCPP_HOME test home; mwrap defaults to target/mwrap-test-home # # Exit status is non-zero if any member failed. The timing table is printed # regardless — a slow run is worth measuring even when it breaks. @@ -114,7 +115,14 @@ fi [ -n "$cache" ] && export MCPP_BUILD_CACHE="$cache" for m in "${members[@]}"; do if [ -d "tests/mwrap/$m" ]; then - python3 tests/prepare_mwrap_indices.py || exit $? + # Mixing a full compat project alias with later namespace-only aliases + # reuses xlings candidates across workspace members. A single complete + # built-in candidate index avoids that order-dependent ambiguity. + export MCPP_HOME="${MCPP_HOME:-$PWD/target/mwrap-test-home}" + if [ ! -d "$MCPP_HOME/registry" ]; then + "$MCPP" index update || exit $? + fi + python3 tests/prepare_builtin_index.py --home "$MCPP_HOME" || exit $? break fi done diff --git a/tests/test_prepare_mwrap_indices.py b/tests/test_prepare_mwrap_indices.py index 06dfb9bb..8f4f0eb5 100644 --- a/tests/test_prepare_mwrap_indices.py +++ b/tests/test_prepare_mwrap_indices.py @@ -5,10 +5,22 @@ import subprocess import sys import tempfile +import tomllib import unittest class NamespaceViews(unittest.TestCase): + def test_workspace_members_override_project_aliases(self): + root = Path(__file__).resolve().parents[1] + workspace = tomllib.loads((root / "mcpp.toml").read_text())["workspace"]["members"] + members = [root / member / "mcpp.toml" for member in workspace if member.startswith("tests/mwrap/")] + self.assertTrue(members) + for manifest in members: + with self.subTest(member=manifest.parent.name): + parsed = tomllib.loads(manifest.read_text()) + self.assertIn("indices", parsed) + self.assertEqual(parsed["indices"], {}) + def test_rebuild_removes_cross_namespace_candidates(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) From 086194645ba9c2b49c18f77efb38e0af90abefef Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 24 Oct 2026 20:50:20 +0900 Subject: [PATCH 25/25] Document mixed-workspace index contracts in the mwrap spec and skill --- .agents/docs/2026-10-24-opencv-webp-provider.md | 11 +++++++++++ .agents/skills/add-mcpp-index-package/SKILL.md | 13 +++++++------ docs/mwrap.md | 4 +++- docs/zh/mwrap.md | 10 +++++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.agents/docs/2026-10-24-opencv-webp-provider.md b/.agents/docs/2026-10-24-opencv-webp-provider.md index e177eddd..93b232d1 100644 --- a/.agents/docs/2026-10-24-opencv-webp-provider.md +++ b/.agents/docs/2026-10-24-opencv-webp-provider.md @@ -111,3 +111,14 @@ normal user's default home is not replaced. Static regression guards require mwrap members to retain their explicit override. Namespace-view tooling remains available to isolated single-project consumers, with its scope documented. New exact-head native workspace validation remains required. + +The first mixed fixture used [workspace.indices]. A second private-home fixture +now uses the same legacy root [indices] form as repository CI: the two-view +consumer again fails with ambiguous candidates, while its empty-table form +passes inference/public_surface (17.42s). This run keeps CI's non-strict entry +because the existing root form emits a deprecation warning; no diagnostic is +suppressed. Log: /tmp/mwrap-legacy-workspace-index.log. The strict modern-root +result above and this exact legacy-root result verify distinct configurations. +The bilingual specification and authoring skill now direct mwrap workspace +members to the single built-in candidate and reserve namespace views for +isolated projects. Skill validation and both index-view/member guards pass. diff --git a/.agents/skills/add-mcpp-index-package/SKILL.md b/.agents/skills/add-mcpp-index-package/SKILL.md index 5e0361f2..a6b79d49 100644 --- a/.agents/skills/add-mcpp-index-package/SKILL.md +++ b/.agents/skills/add-mcpp-index-package/SKILL.md @@ -68,9 +68,9 @@ description: Use when adding a new third-party library/package to the mcpp-index - 包含 `mcpp.toml` 与 `tests/*.cpp`,后者须包含**可失败的有效断言**(`return ok ? 0 : 1`)。 - 普通成员用一条 `[indices] = { path = "../../.." }` 将所消费命名空间重定向到 checkout。 不把同一个完整索引路径注册成多个 namespace:当前 xlings 会将同一实体报为歧义。 - mwrap wrapper + compat SDK 的组合测试先运行 `python3 tests/prepare_mwrap_indices.py`, - 再将两条 namespace 路由指向 `tests/mwrap/.indices/` 中字节不变的测试视图。 - `tests/run_members.sh` 自动准备这些视图;它们不提交、不发布,也不改变 pkgs/mwrap 的正式布局。 + mwrap workspace 成员保留显式空 `[indices]` 表,由 `tests/run_members.sh` 将完整且字节不变的 + checkout 索引放入测试 home 的唯一内置索引。未设置 MCPP_HOME 时使用 target/mwrap-test-home。 + namespace 视图仅供隔离的单项目消费者;不能以独立项目通过证明混合 workspace 顺序可用。 正常发布布局另由 `tests/mwrap/check_installed_template.py` 在独立 MCPP_HOME 中使用单一完整索引验收。 运行库/插件/host-module 多 namespace 耦合时,优先用 `tests/prepare_builtin_index.py --home <独立测试 home>` 安装唯一完整内置索引,成员以空 `[indices]` 覆盖旧 workspace 路由。自动更新候选 CI 同样采用此方式, @@ -159,15 +159,16 @@ MV=$(grep -oP 'MCPP_VERSION:\s*"\K[0-9.]+' .github/workflows/validate.yml) curl -L -fsS -o mcpp.tgz "https://github.com/mcpp-community/mcpp/releases/download/v$MV/mcpp-$MV-linux-x86_64.tar.gz" tar -xzf mcpp.tgz root="$PWD/mcpp-$MV-linux-x86_64" -mkdir -p ~/.mcpp/registry && cp -a "$root/registry/." ~/.mcpp/registry/ +export MCPP_HOME="$PWD/target/package-validation-home" +mkdir -p "$MCPP_HOME/registry" && cp -a "$root/registry/." "$MCPP_HOME/registry/" export MCPP="$root/bin/mcpp" export MCPP_VENDORED_XLINGS="$root/registry/bin/xlings" export MCPP_INDEX_MIRROR=GLOBAL # CI 使用 GLOBAL;CN 由 mirror-cn-reachable 单独校验 -rm -rf "tests/examples//target" "tests/examples//.mcpp" # 冷验证:自干净状态走完整管线 -"$MCPP" test -p +bash tests/run_members.sh ``` - 输出末尾应为 `test result ok`(测试二进制退出码非 0 即失败)。 +- 冷验证使用独立 worktree 和新测试 home;保留已有日志,不删除归属不明的构建目录。 - 如需查看头文件或源码,解包结果位于 `tests/examples//.mcpp/.xlings/data/xpkgs/-x-///`。 ## 常见错误与规避 diff --git a/docs/mwrap.md b/docs/mwrap.md index 500df583..bff6304b 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -195,7 +195,9 @@ CI check before reporting merge/release readiness. ## Candidate index tests across namespaces -The pinned client routes each requested namespace independently. Registering the same full checkout twice makes xlings report duplicate project candidates. Local wrapper/SDK tests therefore call tests/prepare_mwrap_indices.py and route each namespace to a test-only view under tests/mwrap/.indices. Every descriptor is copied unchanged and its source SHA256 is recorded; no aliases share a descriptor. These views are ignored build outputs, not a publication format. +The pinned client can report duplicate candidates when a workspace mixes full project indices and namespace views. Mwrap workspace members retain an explicit empty `[indices]` table. `tests/run_members.sh` prepares one complete unchanged built-in candidate index before their tests; an unset MCPP_HOME uses the task-owned target/mwrap-test-home. CI prepares that candidate after refreshing the published registry. Keep the legacy workspace redirect semantics; `[workspace.indices]` merges entries and is not a mechanical spelling replacement. + +Namespace views created by tests/prepare_mwrap_indices.py remain available for isolated single-project consumers. Their descriptors preserve source bytes and SHA256 provenance. A standalone success does not certify a mixed workspace sequence. Views are ignored outputs, not a publication format. Normal installed consumers are tested independently by tests/mwrap/check_installed_template.py: it copies the complete pkgs/index.toml publication layout into one isolated built-in index, downloads the real checksummed source archive, runs mcpp new and builds/runs the generated manifest without a path substitution. The Git and published pkgs/mwrap directory layout remains identical. diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index fdfc1758..fbbdb729 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -134,9 +134,13 @@ vendored 静态实现同样需要显式部署,不能依赖共享库扫描发 ## 候选索引的跨 namespace 测试 -固定客户端按请求 namespace 分别路由;同一个完整 checkout 注册两次会让 xlings 报项目候选歧义。 -本地 wrapper/SDK 组合测试先执行 tests/prepare_mwrap_indices.py,再分别指向 tests/mwrap/.indices 中的测试视图。 -各描述符字节不变并记录源 SHA256,视图之间不重复登记实体。它们是不提交、不发布的构建输出,不是新的发布格式。 +固定客户端在同一 workspace 混用完整项目索引与 namespace 视图时可能报告候选歧义。 +mwrap 成员保留显式空 `[indices]` 表,`tests/run_members.sh` 先将完整且字节不变的候选索引放进测试 home 的唯一内置索引。 +未设置 MCPP_HOME 时使用任务自有的 target/mwrap-test-home;CI 在刷新已发布索引后准备候选。 +保留旧 workspace 重定向的语义;`[workspace.indices]` 会合并条目,不能仅视为同义语法替换。 + +tests/prepare_mwrap_indices.py 的 namespace 视图仅供隔离的单项目消费者,描述符保留字节及源 SHA256。 +不能以独立项目通过证明混合 workspace 顺序可用。视图是不提交、不发布的输出,不是新的发布格式。 正常安装由 tests/mwrap/check_installed_template.py 独立验证:把完整 pkgs/index.toml 发布布局放进独立 MCPP_HOME 的单一内置索引, 下载真实且带校验的源码归档,执行 mcpp new,再原样构建/运行生成清单,不替换为 path 依赖。