diff --git a/.agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md b/.agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md new file mode 100644 index 0000000..cb0dcef --- /dev/null +++ b/.agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md @@ -0,0 +1,294 @@ +# OpenCV 加入 mcpp 生态 —— 深度调研报告(单库 vs 拆多库 / feature 开关 / 构建形态) + +**日期**: 2026-07-08 +**本仓**: `mcpp-community/mcpp-index`(github 别名 `mcpplibs/mcpp-index`) +**mcpp 源码**: `/home/speak/workspace/github/mcpp-community/mcpp`(只读参考,mcpp 0.0.85) +**参考先例**: `compat.openblas`(install() 跑外部构建系统)、`compat.libarchive`(手列源码 + 合成 config 头 + 多依赖)、`compat.eigen`(feature/capability 门控)、`compat.gtest`(source-gated feature) +**目标**: 判定 OpenCV 进入 mcpp 生态应 **单库还是拆多库**、可行的 **构建形态**、以及 **feature/开关** 如何映射;给出推荐方案与分期落地路径。 + +--- + +## 0. 摘要与结论(TL;DR) + +| 决策点 | 结论 | +|---|---| +| **收录版本** | 首收 **4.13.0**(最新稳定 4.x,Apache-2.0);5.0.0 作为后续;`opencv_contrib` 单独收录,非自由算法(SIFT/SURF 等)opt-in。 | +| **单库 vs 多库** | **单包 `compat.opencv`**。mcpp 描述符 **无法** 在一个文件里给多个 lib 目标分派各自的源码集(`sources` 是包级全局,`Target` 无 `sources`/`include_dirs` 字段);而"每模块一个包"会让每个包各自解包并重复构建同一棵源码树,得不偿失。模块粒度通过 install() 里 CMake 的 `-DBUILD_opencv_*` 一次性产出多个 `.a`、经单包多 target 暴露。 | +| **构建形态** | **install()-hook 驱动上游 CMake 构建**(`compat.openblas` 模式的放大版)。这是唯一能免维护地处理 OpenCV 海量 **CMake 生成物**(SIMD dispatch 头 + 各 ISA `.cpp`、`opencl_kernels_*`、config 头)的路径;vcpkg/conan/发行版无一例外都是驱动 OpenCV 自带 CMake。 | +| **feature 开关** | OpenCV 的 `WITH_*` / `BUILD_opencv_*` **不能** 逐一映射成 mcpp feature —— feature 只能携带 `implies/sources/defines/requires/provides/deps`,**不能** 带 `cflags`/`include_dirs`/`generated_files`,也 **无法参数化 install()**。故模块/第三方选择在 recipe 内 **固定为一条策展 profile**;粗粒度变体(minimal / full)用 **独立包** 或 **版本档** 表达,而非 per-consumer feature。 | +| **第三方依赖** | MVP 全部走 OpenCV `3rdparty/` **内置源码**(zlib/libpng/libjpeg-turbo/… CMake `BUILD_*=ON`),**零外部依赖**,自洽可复现。后续可把 BLAS/LAPACK 加速经 capability 绑到本仓已有的 `compat.openblas`(`provides=["blas"]`)。 | +| **落地分期** | P1: `core+imgproc+imgcodecs`(内置 zlib/png/jpeg)最小可用包 → P2: 加 `calib3d/features2d/flann/photo/video/objdetect` → P3: `dnn`(内置 protobuf)、`highgui`(系统 GTK/Qt 可选后端)、`opencv_contrib`。 | + +> **一句话**:OpenCV 不是"再大一号的 libarchive",而是"再大一号的 openblas"。它 **必须** 借 install() 跑自己的 CMake;纯"手列 `.cpp` + 一个 config 头"的原生形态在 SIMD dispatch / OpenCL kernel 两处会崩。 + +--- + +## 1. 背景:两个已知构建形态,OpenCV 落在哪一档 + +本仓现有 compat 包呈现两条构建路线: + +- **原生源码路线(A/B/C 形态)**:mcpp 直接 `sources={...}` 列上游 `.c/.cpp`,用自己的 ninja 编译;需要的 config 头用 `generated_files` 合成。代表:`compat.zlib`(15 个 `.c` + 一个 `mcpp_zlib_config.h`)、`compat.libarchive`(130+ 个 `.c` + 一个模拟 CMake `config.h` 的大 `generated_files`)。**前提**:上游全部可编译源码都在 tarball 里、且没有构建期代码生成。 +- **外部构建系统路线(install() 逃生舱)**:上游用自己的 Make/CMake 生成库,mcpp 的源码模型套不上。代表:`compat.openblas` —— install() 跑 GNU Make(`TARGET=GENERIC NO_LAPACK=1 …`)产出 `libopenblas.a` + 头,再写出 anchor TU 触发 mcpp 编译并 `-Llib -lopenblas` 链接。 + +判定 OpenCV 属于哪一档,取决于一个问题:**OpenCV 的可编译源码是否全在 tarball 里、有无构建期代码生成?** —— 见 §2.3,答案是"有大量生成物",故落在 **install() 路线**。 + +--- + +## 2. 上游事实(OpenCV 4.x/5.x) + +### 2.1 版本与许可 + +- **4.13.0** —— 最新稳定 4.x(4.12.0 之上的 X-server 依赖修订);**5.0.0** 于 2026-06 发布(大版本,含 4→5 迁移指南)。 +- Tag 为裸 `X.Y.Z`(**无 `v` 前缀**);GitHub 源码归档: + - 主库:`https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz` + - contrib:`https://github.com/opencv/opencv_contrib/archive/refs/tags/4.13.0.tar.gz` +- **许可**:主库自 4.5.0 起 **Apache-2.0**(此前 BSD-3-Clause);`opencv_contrib` 整体 Apache-2.0,但含专利/第三方约束模块(`xfeatures2d` 的 SIFT/SURF 等),由 `OPENCV_ENABLE_NONFREE` 门控。→ **主库与 contrib 分开收录;非自由算法 opt-in。** +- **建议首收 4.13.0**(4.x 成熟线,Apache-2.0),5.0.0 作为后续档。 + +### 2.2 模块与依赖图(单库/多库决策的直接输入) + +主库 `modules/`:`core, imgproc, imgcodecs, videoio, highgui, calib3d, features2d, objdetect, dnn, ml, photo, video, stitching, flann, gapi, ts, world`(contrib 另有 ~50)。 + +各模块 `CMakeLists.txt` 的 `ocv_define_module(...)` 声明的 **直接模块间依赖**: + +| 模块 | 直接依赖 | 备注 | +|---|---|---| +| **core** | 无(cudev 可选) | 生态基座 | +| **imgproc** | core | | +| **imgcodecs** | imgproc(→core) | 内置 BMP/PNM/HDR/PFM/GIF 解码器,JPEG/PNG/… 需 lib(可取内置 3rdparty) | +| **highgui** | imgproc;imgcodecs+videoio 可选 | 后端 GTK/Qt/Wayland/Win32/Cocoa,可 NONE | +| **calib3d** | imgproc, features2d, flann | | +| **dnn** | core, imgproc(+内置 protobuf) | | +| **features2d** | imgproc(+flann) | | +| **flann** | core | | + +基链:`core ← imgproc ← {imgcodecs, highgui, calib3d, dnn, features2d, …}`。**core+imgproc 零第三方即可编。** + +### 2.3 构建期生成物 —— 决定形态的关键 + +OpenCV 的 CMake 在 configure/build 期 **生成若干被模块 `.cpp` `#include`、但不在 tarball 里** 的头/源。实测(本机安装的 OpenCV 4.6 生成头 + 上游 `cmake/templates` 模板)分两类: + +**(a) 平凡 config 头 —— `generated_files` 可轻松合成(zlib/libarchive 先例):** + +| 文件 | 生成方式 | 静态 stub 可行? | +|---|---|---| +| `cvconfig.h` | `configure_file(cvconfig.h.in)` | ✅ 平凡。~70 个 `HAVE_*/WITH_*` 的 `#define/#undef`;最小构建近乎全 `#undef`。 | +| `opencv_modules.hpp` | `configure_file(opencv_modules.hpp.in)` | ✅ 平凡。每个已构建模块一行 `#define HAVE_OPENCV_`。 | +| `custom_hal.hpp` | `configure_file(custom_hal.hpp.in)` | ✅ 平凡。空 HAL 体即合法。 | +| `opencv_data_config.hpp` | core/CMakeLists `file(WRITE)` | ✅ 平凡。安装路径串,可空。 | + +**(b) 硬骨头 —— 纯手列会崩的两处:** + +- **SIMD dispatch**(`cmake/OpenCVCompilerOptimizations.cmake` 的 `ocv_add_dispatched_file`):每个被 dispatch 的 TU 生成一个 `.simd_declarations.hpp`(被 `#include`)+ 每个 ISA 一个 `..cpp`(如 `mathfuncs_core.avx2.cpp`)。**core 有 16 处、imgproc 更多**(`arithm/convert/matmul/resize/color_*/filter/…`)。这些头 **不在 tarball**,且 **逐模块、逐版本**。 +- **OpenCL kernels**(`OpenCVModule.cmake` → `cmake -P cl2cpp.cmake`):把 `src/opencl/*.cl` 嵌成 `opencl_kernels_.cpp/.hpp`(C 字符串),被模块 `.cpp` `#include`。core/imgproc/dnn 均有。 + +**结论**:平凡 config 头(a)套 `generated_files` 没问题;但 (b) 的 `*.simd_declarations.hpp` + 各 ISA `.cpp` + `opencl_kernels_*` 数量多、逐版本变、且是"跑一遍 CMake 才知道的产物"。**纯"列 `.cpp` + 一个 config 头"的模型在此崩溃** —— 除非(i) baseline-only 手写每个 dispatch 单元的 stub 头并只编 scalar/基线(牺牲 SIMD 且仍是逐单元手工),或(ii) 每版用 `cmake -P` 独立脚本预生成 dispatch/kernel 再 vendor(逐版本重活)。业界佐证:**无人从裸源码手工编 OpenCV core**;所谓 Bazel 移植也是 `new_local_repository` 链 **预装** OpenCV 并指向 **CMake 生成的** `cvconfig.h`。 + +### 2.4 第三方依赖 + +`3rdparty/` **内置可源码构建**:`zlib, zlib-ng, libjpeg-turbo, libpng, libspng, libtiff, libwebp, openexr, openjpeg, jasper, quirc, protobuf(dnn), flatbuffers, ippicv(x86 二进制,可选), tbb, carotene(ARM HAL), …`。 + +| 依赖 | 模块 | 内置? | 可选? | +|---|---|---|---| +| zlib / libpng / libjpeg-turbo / libtiff / libwebp / openexr / openjpeg | imgcodecs | ✅ `BUILD_*=ON` | 全可选(缺则少若干编码器) | +| ffmpeg / gstreamer / v4l | videoio | 否(Linux/mac 外部) | 可选(videoio 可 0 后端) | +| protobuf | dnn | ✅ `BUILD_PROTOBUF` 默认 on | dnn 必需但自洽 | +| eigen / lapack / openblas / IPP / TBB | core 加速 | eigen/lapack 外部;IPP/TBB 内置 | **全可选**(有 fallback) | +| GTK/Qt/Wayland/Cocoa/Win32 | highgui | 否(系统) | 可选(后端 NONE) | +| OpenCL / CUDA | core/dnn/… | OpenCL 头内置;CUDA 外部 | 可选 | + +**`core→imgproc→imgcodecs` 全链无硬外部依赖**:imgcodecs 内置 BMP/PNM/HDR/PFM/GIF 解码器;JPEG/PNG/… 取 **内置 3rdparty** 即可,故一个 `core+imgproc+imgcodecs+(内置)zlib/png/jpeg` 包可 **完全自洽于 tarball**。 + +### 2.5 最小可用子集 + +- **`core + imgproc`**:零第三方可编;SIMD 退基线,线程用内置 pthreads/no-op。 +- **`imgcodecs`**:零外部编码器也可编(内置若干解码器);要 JPEG/PNG 取内置 3rdparty。 +- → 自洽 MVP = `core+imgproc+imgcodecs`(内置 zlib/libpng/libjpeg-turbo)。 + +--- + +## 3. mcpp 表达力约束(0.0.85,来自 mcpp 源码核对) + +descriptor 的 `mcpp={}` 段由 **手写数据字面量解析器**(`src/manifest/xpkg.cppm` 的 `LuaCursor`)读取,**mcpp 从不执行 descriptor**。对 OpenCV 这种"20+ 模块 + ~15 个 WITH_* 开关 + 依赖图"的库,关键约束: + +1. **无 per-target `sources`/`include_dirs`**(致命于"一文件多模块库"):`sources`/`include_dirs` 是 **包级全局**;`Target` 只是共享编译池上的 **链接单元**(仅可带 entry-exclusive 的 `defines/cxxflags`,0.0.55),无自己的源码集。→ **一个描述符无法产出 20 个各自源码集不同的 lib。** +2. **feature 只能携带 `implies/sources/defines/requires/provides/deps`**(0.0.65–0.0.71 分阶段落地),**不能** 带 `cflags/cxxflags/ldflags/include_dirs/generated_files`(设计上刻意排除,见 mcpp `2026-06-29-feature-capability-model-design.md §7)。→ OpenCV `WITH_*` 里但凡需要"改 config 头内容 / 加 `-I` / 加 `-l`"的,**都不能** 做成 feature。 +3. **feature 无法参数化 install()**:install() 是 **xim/xlings 安装器** 侧的 Lua hook(`os.exec/os.cd/os.cp/path.*/log.*`),**mcpp 只读静态段**;openblas 的 install() 用的是 **固定** flags。→ 保守认定:**consumer 请求的 feature 传不进 install()**,故模块/依赖选择须在 recipe 内固定(见 §6 变体策略)。 +4. **capability 绑定干净**:`provides=["blas"]` ↔ `requires=["blas"]`,`≥2` provider 需 `[capabilities]`/`--cap` 指定(0.0.69)。→ 加速后端(OpenBLAS/LAPACK)可经此绑到本仓 `compat.openblas`。 +5. **`generated_files` 支持长括号 `[==[…]==]` 需 mcpp ≥ 0.0.85**(本仓 `index.toml min_mcpp=0.0.85` 已保证)→ OpenCV 的大 config 头可用长括号内联,不必逐字转义。 +6. **install() 可跑 CMake/Make/`cmake -P`**、落 lib+头+生成头,再声明 `include_dirs`/`[runtime].library_dirs`(openblas + xcb `c_client.py` 先例)。 + +--- + +## 4. 决策一:单库 vs 拆多库 + +### 4.1 选项 + +- **(M) 单包 `compat.opencv`**:一个描述符 + 一个 install() CMake 构建,一次产出所选模块的多个 `.a`,经单包多 target 暴露。 +- **(S) 每模块一包**(`compat.opencv-core` / `compat.opencv-imgproc` / …):照搬 OpenCV 模块性,模块间用包依赖表达。 + +### 4.2 分析 + +拆多库(S)在 mcpp 下 **代价极高且不自然**: + +1. **无共享构建**:mcpp 一个包 = 一个独立构建单元。N 个 opencv 子包各自解包 **同一棵源码树**、各自跑一遍(或各自 CMake 子构建),要么重复 N 次全树构建,要么需要跨包共享 build 目录 —— 包模型不提供。 +2. **模块间链接错配**:OpenCV 模块间是 **同一次 CMake 构建内** 的目标依赖;切成 N 个独立 mcpp 包后,`compat.opencv-calib3d` 要链 `compat.opencv-imgproc/features2d/flann` 的产物,而这些产物在 **各自的 verdir**,ABI/头版本一致性要靠版本 pin 人工保证,脆弱。 +3. **收益有限**:用户要的"只装 core+imgproc"用 **单包的策展 profile / 变体包**(§6)即可满足,不必付 N 包的解包与依赖图代价。 + +单包(M)则与 openblas 先例同构:**一次 CMake 构建产出多库,mcpp 侧一个 anchor + `-Llib -lopencv_core -lopencv_imgproc …` 链接**。§3.1 的"无 per-target sources"约束对 M **不构成问题** —— 因为多库不是由 mcpp 的多 target 从不同源码集编出来的,而是 **install() 里 CMake 编好、mcpp 只负责链接**。 + +### 4.3 结论 + +**单包 `compat.opencv`**(contrib 另一个单包 `compat.opencv-contrib`)。模块粒度由 install() 的 `-DBUILD_opencv_*` 固定策展 + 粗粒度变体表达,而非拆成 per-module 包。 + +--- + +## 5. 决策二:构建形态(三方案对比) + +| | **A. install()-hook CMake**(推荐) | **B. 手列源码 + 预生成 dispatch/kernel vendored** | **C. baseline-only stub 原生编译(最小裁剪)** | +|---|---|---|---| +| 生成物(SIMD/OpenCL) | CMake 原生处理,**零手维护** | 每版 `cmake -P` 预生成再 vendor,**逐版本重活** | 手写每个 dispatch 单元 baseline stub 头 + 只编基线,**逐单元手工** | +| config 头 | CMake 生成 | `generated_files` 合成 | `generated_files` 合成 | +| 构建速度 | 慢(整棵 CMake,分钟级) | 快(mcpp 原生 ninja) | 快 | +| 构建依赖 | `xim:cmake` + `xim:make/ninja` | 无(纯 mcpp) | 无 | +| 覆盖范围 | **全模块可行** | 现实只到 core+imgproc(+imgcodecs) | 仅 core+imgproc 基线 | +| 逐版本维护 | 低(改 tag + `-D` profile) | **极高** | 高 | +| SIMD 性能 | 完整 dispatch | 取决于预生成 | **仅基线,慢** | +| 与 mcpp 源码模型契合 | 低(逃生舱) | 高 | 高 | +| Windows | 同 openblas:MSVC-ABI clang vs CMake,或走预编译 | 同左 + 手工重活翻倍 | 同左 | +| 业界佐证 | vcpkg/conan/发行版全走此路 | 无人如此 | 无人如此 | + +**取舍**:B/C 契合 mcpp"列源码"哲学、构建快、无 CMake 依赖,但 **逐版本维护 SIMD dispatch + OpenCL kernel 的成本压倒性地高**,且现实只能覆盖极小子集 —— 与"把 OpenCV 收进生态"的目标不匹配。A 是 openblas 先例的直接放大:**用 install() 让 CMake 干它最擅长的活(含全部生成物),mcpp 只做链接与暴露**。这也是 **唯一** 能可持续跟上 OpenCV 每季度发版的路径。 + +**结论:采用 A(install()-hook CMake 构建)。** 把 B/C 作为"若要一个零-CMake、纯基线的 `compat.opencv-mini` 教学包"的备选,不作主线。 + +--- + +## 6. feature / 开关设计 + +### 6.1 为什么 `WITH_*` / `BUILD_opencv_*` 不能做成 mcpp feature + +由 §3.2–3.3:feature 不能带 `cflags/include_dirs/generated_files`,也传不进 install()。而 OpenCV 的开关几乎都要"改 CMake `-D` → 改 config 头内容 + 改链接" —— 这三件事 feature 一件都表达不了。**强行做成 feature 会得到"声明了但不起作用"的假开关**(正是 eigen 文档里警惕的坑)。 + +### 6.2 替代:三层表达 + +1. **策展 profile(recipe 内固定,主力手段)**:install() 里写死一组 `-DBUILD_opencv_=ON/OFF -DWITH_=ON/OFF`。一个包 = 一条确定的模块+第三方组合,可复现、可缓存。 +2. **粗粒度变体 = 独立包 / 版本档**: + - `compat.opencv`(标准:core+imgproc+imgcodecs+calib3d+features2d+flann+photo+video+objdetect,内置 codec,无 GUI/dnn); + - `compat.opencv-dnn`(额外 dnn+内置 protobuf)或直接并入标准; + - `compat.opencv-contrib`(contrib,非自由 opt-in); + - 若需极简:`compat.opencv-mini`(core+imgproc)。 + 变体用 **独立描述符**(各自 install() profile)表达,而非 per-consumer feature —— 与 mcpp"包即构建单元"一致。 +3. **capability(唯一适合 feature 的窄子集)**:纯粹"加包级 define + 可选 dep + 绑定 provider"的开关 **可以** 做成 feature。典型:BLAS/LAPACK 加速 —— 一个 `use-openblas` feature 走 `requires=["blas"]` + `deps={["compat.openblas"]="0.3.33"}`,绑本仓已有的 `compat.openblas`(`provides=["blas"]`),install() 侧据此加 `-DWITH_LAPACK=ON` 指向该 provider。但这属"锦上添花"的少数派,不是模块/codec 选择的通道。 + +### 6.3 第三方 codec 的两条路 + +- **(推荐,MVP)内置 3rdparty**:install() 用 `-DBUILD_ZLIB=ON -DBUILD_PNG=ON -DBUILD_JPEG=ON …`,全部从 tarball 内 `3rdparty/` 编,**零外部依赖、GLOBAL/CN 字节一致最省心**。 +- **(后续,更"生态化")绑本仓 provider**:本仓已有 `compat.zlib`;若再收 `compat.libpng`/`compat.libjpeg-turbo`,可让 opencv 经 capability 复用,避免重复内置。但需 install() 消费外部 provider 的头/库路径,复杂度显著上升 → 放 P2+ 评估,MVP 不做。 + +--- + +## 7. 推荐方案落地 + +### 7.1 描述符骨架(A 方案,openblas 模式) + +```lua +-- compat.opencv — OpenCV 经 install() 驱动上游 CMake 构建,产出策展模块集的 +-- 静态库 + 头,mcpp 侧 anchor + -l 链接。CMake 原生处理全部生成物 +-- (cvconfig.h / opencv_modules.hpp / *.simd_declarations.hpp / opencl_kernels_*), +-- 故不走"手列 .cpp"模型(见 .agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md)。 +package = { + spec = "1", namespace = "compat", name = "compat.opencv", + description = "OpenCV — computer vision library (curated core build via CMake)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/opencv/opencv", + type = "package", + xpm = { + linux = { deps = { "xim:cmake@latest", "xim:make@latest" }, + ["4.13.0"] = { url = { GLOBAL = "https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/opencv/releases/download/4.13.0/opencv-4.13.0.tar.gz" }, + sha256 = "<实现期算>" } }, + macosx = { deps = { "xim:cmake@latest", "xim:make@latest" }, ["4.13.0"] = { --[[ 同 linux ]] } }, + windows = { --[[ 评估:MSVC-ABI clang vs CMake;或走官方预编译 opencv-4.13.0-vc16.exe 解包链 import lib + 部署 DLL(同 openblas Windows)]] }, + }, + mcpp = { + language = "c++23", import_std = false, c_standard = "c11", + -- anchor 由 install() 写出(缺失即触发 install() → CMake 构建),同 openblas。 + sources = { "mcpp_opencv_anchor.c" }, + targets = { ["opencv"] = { kind = "lib" } }, + include_dirs = { "include", "include/opencv4" }, -- install 后的头前缀 + deps = { }, + -- 静态链接策展模块集(顺序:被依赖者在后)。-Llib 由 mcpp 重写到 verdir/lib。 + linux = { ldflags = { "-Llib", "-lopencv_objdetect", "-lopencv_calib3d", "-lopencv_features2d", + "-lopencv_flann", "-lopencv_video", "-lopencv_photo", + "-lopencv_imgcodecs", "-lopencv_imgproc", "-lopencv_core", + "-llibpng", "-llibjpeg-turbo", "-lzlib", "-ldl", "-lpthread" } }, + macosx = { ldflags = { --[[ 同上,末尾换 -framework 若需 ]] } }, + -- use-openblas:纯 capability 型 feature(唯一适合 feature 的窄子集,见 §6.2)。 + features = { + ["use_openblas"] = { requires = { "blas" }, deps = { ["compat.openblas"] = "0.3.33" } }, + }, + }, +} + +-- install():解包后跑 CMake 策展构建 → make install 落 lib/ + include/ → 写 anchor。 +-- 关键 -D(固定 profile): +-- -DBUILD_LIST=core,imgproc,imgcodecs,calib3d,features2d,flann,video,photo,objdetect +-- -DBUILD_SHARED_LIBS=OFF -DBUILD_ZLIB=ON -DBUILD_PNG=ON -DBUILD_JPEG=ON +-- -DWITH_FFMPEG=OFF -DWITH_GTK=OFF -DWITH_QT=OFF -DWITH_OPENCL=OFF -DWITH_IPP=OFF +-- -DWITH_TBB=OFF -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF +-- -DBUILD_opencv_apps=OFF -DBUILD_opencv_python3=OFF -DOPENCV_GENERATE_PKGCONFIG=OFF +-- -DCMAKE_INSTALL_PREFIX= +-- 结构同 compat.openblas 的 _install_impl():os.mv 源树入 install_dir、os.cd、 +-- os.exec("cmake -S . -B build <-D...> && cmake --build build -j && cmake --install build")、 +-- 校验 lib/libopencv_core.a 存在(否则 log.error + return false)、io.writefile 写 anchor。 +``` + +> 与 openblas 的差别:openblas 用 Make,opencv 用 CMake(build-dep 增 `xim:cmake`);openblas 单库单 `-l`,opencv 多 `-l`(顺序敏感,被依赖者在后)。两者共享"install() 产出 anchor 触发构建 + `-Llib` 重写 + 校验产物"骨架。 + +### 7.2 分期 + +- **P1(MVP,证明形态)**:`compat.opencv` = `core+imgproc+imgcodecs`(内置 zlib/png/jpeg),Linux/macOS install() CMake 构建。tests/examples 跑通 `Mat + cvtColor + imencode/imdecode PNG 往返断言`。—— **先在 Linux/macOS 打通 install()-CMake 闭环。** +- **P2**:补 `calib3d/features2d/flann/video/photo/objdetect`;评估 Windows(预编译 vs CMake);评估 codec 改绑本仓 provider。 +- **P3**:`compat.opencv`(或 `-dnn` 变体)加 dnn(内置 protobuf);`highgui`(系统 GTK/Qt 可选后端);`compat.opencv-contrib`(非自由 opt-in);`use_openblas` capability 加速。 + +--- + +## 8. CN 镜像(gtc) + +repo 名 = 包名去 `compat.` = `opencv`(contrib 用 `opencv-contrib`): + +```bash +curl -L -o opencv-4.13.0.tar.gz https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz +sha256sum opencv-4.13.0.tar.gz # 填入三平台 sha256 +tools/gtc repo create mcpp-res/opencv --public +tools/gtc release create mcpp-res/opencv 4.13.0 --asset opencv-4.13.0.tar.gz +curl -fsSL -o /dev/null -w '%{http_code}\n' \ + https://gitcode.com/mcpp-res/opencv/releases/download/4.13.0/opencv-4.13.0.tar.gz +``` + +上传的即 GLOBAL 同一 tarball → CN/GLOBAL 字节一致(gitcode 同名资产不可覆盖,命名一次定死)。 + +## 9. 测试工程 + CI + +- `tests/examples/opencv/{mcpp.toml, tests/roundtrip.cpp}`:依赖 `compat.opencv=4.13.0`(`[indices].compat = {path="../../.."}`);断言 `cv::Mat` 构造 + `cv::cvtColor` + `cv::imencode/imdecode`(内存 PNG 往返)返回 `ok?0:1`。加入根 `mcpp.toml` 的 `[workspace].members`。 +- CI:`validate.yml` 的 detect 把 `compat.opencv.lua` → `opencv` → 命中 `tests/examples/opencv` 单跑;`mirror-cn-reachable` 覆盖新 CN url。**注意 install() 的 CMake 构建耗时**(分钟级)可能需要放宽该 job 的 timeout,或先只在 Linux 跑、mac/win 进 nightly。 + +## 10. 风险与开放问题 + +- **R1(最高)install()-CMake 闭环**:openblas 证明了 install() 跑 Make;OpenCV 是 **首个 install() 跑 CMake** 的包。落地期必测:`xim:cmake` build-dep 可用性、CMake 能否在 xim Lua 沙箱的 `os.exec` 下完整 configure+build+install、产物路径(`lib/libopencv_*.a`、`include/opencv4/`)是否稳定。**不成立则回退 B/C 的最小基线包。** +- **R2 构建时长**:整棵 core+imgproc CMake 构建分钟级,CI/首次 `mcpp add` 体验偏重 → 考虑 mac/win 进 nightly、Linux 保留;长期看是否值得提供 **预编译 artifact**(但破坏 GLOBAL/CN 字节一致模型,需另设机制)。 +- **R3 Windows ABI**:mcpp 链的是 MSVC-ABI clang,与 OpenCV 官方 CMake(MSVC)一致性存疑 —— 大概率同 openblas 走 **官方预编译 world DLL + import lib + 部署 DLL**;P2 再定。 +- **R4 feature 传参 install()**:本报告保守认定 feature 传不进 install()。若 mcpp 后续提供"feature → install() 参数"通道,则模块/codec 选择可从"变体包"升级为"单包 per-consumer feature",大幅简化。**建议向 mcpp 维护者提此需求**(见 mcpp `feature-capability-model` 演进)。 +- **R5 许可**:主库 Apache-2.0 干净;contrib 的非自由算法必须 `OPENCV_ENABLE_NONFREE` 显式 opt-in 且独立包,避免默认拉入专利算法。 +- **Q1(待定默认)**:首收 4.13.0(推荐)还是直接 5.0.0?建议 4.13.0 先行、5.0.0 P3 并行。 +- **Q2(待定默认)**:MVP 模块集 = `core+imgproc+imgcodecs`(推荐)还是直接标准集(+calib3d/features2d/…)?建议先三模块打通闭环再扩。 +- **Q3(待定默认)**:codec 走内置 3rdparty(推荐 MVP)还是绑本仓 provider?建议 MVP 内置,P2 评估外绑。 + +## 11. 参考 + +- 本仓先例:`pkgs/c/compat.openblas.lua`(install() 外部构建)、`compat.libarchive.lua`(手列源码 + config 头 + 多依赖)、`compat.eigen.lua`(feature/capability)、`compat.gtest.lua`(source-gated feature);`docs/package-types.md`。 +- mcpp 源码/设计:`src/manifest/xpkg.cppm`(descriptor 解析,0.0.85)、`.agents/docs/2026-06-29-feature-capability-model-design.md`、`2026-06-29-feature-optional-dependencies-s2-design.md`、`2026-06-18-per-target-build-config-design.md`、`2026-06-29-windows-runtime-dll-deployment-and-openblas.md`、`2026-07-08-index-version-semantics-and-descriptor-grammar-design.md`。 +- OpenCV 上游:[releases](https://github.com/opencv/opencv/releases) · [modules CMakeLists](https://github.com/opencv/opencv/tree/4.x/modules) · [cvconfig.h.in](https://raw.githubusercontent.com/opencv/opencv/4.x/cmake/templates/cvconfig.h.in) · [OpenCVCompilerOptimizations.cmake](https://raw.githubusercontent.com/opencv/opencv/4.x/cmake/OpenCVCompilerOptimizations.cmake)(SIMD dispatch)· [OpenCVModule.cmake](https://raw.githubusercontent.com/opencv/opencv/4.x/cmake/OpenCVModule.cmake)(OpenCL cl2cpp)· [3rdparty](https://github.com/opencv/opencv/tree/4.x/3rdparty) · [config 参考](https://docs.opencv.org/4.13.0/db/d05/tutorial_config_reference.html)。 + + diff --git a/.agents/docs/2026-07-08-opencv-implementation-and-verification.md b/.agents/docs/2026-07-08-opencv-implementation-and-verification.md new file mode 100644 index 0000000..35b549e --- /dev/null +++ b/.agents/docs/2026-07-08-opencv-implementation-and-verification.md @@ -0,0 +1,156 @@ +# OpenCV 落地实现与本地验证记录(compat.opencv,install()-CMake,host-free) + +**日期**: 2026-07-08 +**承接**: `.agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md`(调研报告,决策依据) +**产出**: `pkgs/c/compat.opencv.lua` + `tests/examples/opencv/`(workspace member)+ README 一行 + 本文档 +**mcpp**: 0.0.85(与 CI `MCPP_VERSION` 一致) + +--- + +## 1. 形态与最终决策(与调研报告一致) + +- **单包 `compat.opencv`**,**install()-hook 驱动 OpenCV 自带 CMake** 从源码构建(compat.openblas 模式,Make→CMake)。 +- **MVP 模块集**:`core + imgproc + imgcodecs`(`-DBUILD_LIST`),第三方 codec 全走 tarball 内置 `3rdparty/` + (`-DBUILD_ZLIB=ON -DBUILD_PNG=ON -DBUILD_JPEG=ON`)——**零外部依赖**。 +- **模块/开关是 recipe 内固定 profile**,不是 mcpp feature(feature 不能带 cflags/include_dirs/generated_files、 + 也传不进 install();见调研报告 §3/§6)。更大变体(calib3d/dnn/highgui/contrib)是后续独立包。 + +## 2. host-free / 生态闭环 —— 核心约束,已证明 + +用户硬约束:**不能依赖 host,完全 mcpp 生态闭环**。落实与证据: + +- 构建只用 **生态工具**,均为声明的 build-dep,从不碰 host: + - `xim:cmake@4.0.2`(`~/.xlings/data/xpkgs/xim-x-cmake/4.0.2/bin/cmake`) + - `xim:ninja@1.12.1` + - `xim:gcc@16.1.0`(与 mcpp 消费侧解析到的同一 g++:`Resolved gcc@16.1.0 → …/xim-x-gcc/16.1.0/bin/g++`) +- **离线证明**:在 **network-isolated 命名空间**(`unshare -rn`)里跑完整 configure+build+install, + **零下载**。唯一的 configure 期下载(gapi 的 ADE zip)由 `-DWITH_ADE=OFF` 消除(gapi 本就被 BUILD_LIST 白名单排除)。 + 其余全部来自 tarball 内置 `3rdparty/` 源码编译。 +- install() 里 CMake 的 `CMAKE_C/CXX_COMPILER`、`CMAKE_MAKE_PROGRAM` 全部显式指向 build-dep 解析出的 bin + (`pkginfo.build_dep` + PATH 兜底),不依赖 host PATH。 + +## 3. ABI:C++ 库的工具链一致性(关键工程点) + +OpenCV 是 C++,静态库的 libstdc++ ABI 必须与 **消费者链接用的编译器** 一致。 + +- install() 用 `xim:gcc@16.1.0`(gcc/libstdc++)构建;mcpp 在 linux 默认也解析到 `gcc@16.1.0` + → **ABI 天然对齐**(实测 mcpp 日志 `Resolved gcc@16.1.0`)。 +- **linux-gated**:macOS 默认 clang/libc++,与 gcc/libstdc++ 的 `.a` 会 ABI 冲突;Windows 是 MSVC-ABI clang。 + 两者均为后续项。测试工程按 `cfg(linux)` 门控(gui-stack 先例),off-linux 为 no-op main, + `mcpp test --workspace` 在 mac/win 干净通过。 +- 这暴露一个 **通用能力缺口**(见 §6 第 1 条):install() 应能拿到"mcpp 已选定的工具链",让源码构建对齐目标 ABI。 + +## 4. descriptor 关键点(`pkgs/c/compat.opencv.lua`) + +- `sources = { "mcpp_opencv_anchor.c" }`:anchor **不** 放进 `generated_files`,而由 install() 写出 → 其缺失正是 + 触发 mcpp 运行 install()(=触发 CMake 构建)的机制(compat.openblas / compat.xcb 同款)。 +- `include_dirs = { "include/opencv4" }`:install 后头文件落点,用户 `#include `。 +- 链接(per-OS `linux`/`macosx` 块):模块库在前、其 3rdparty 在后、系统库最后;OpenCV 把内置 3rdparty 归档放在 + `lib/opencv4/3rdparty/`,故需第二个 `-L`: + ``` + -Llib -Llib/opencv4/3rdparty + -lopencv_imgcodecs -lopencv_imgproc -lopencv_core + -llibpng -llibjpeg-turbo -lzlib -ldl -lpthread -lm + ``` +- `CMAKE_POLICY_VERSION_MINIMUM=3.5`:让 CMake 4.0.2 能解析 OpenCV 及其 3rdparty 的老 `cmake_minimum_required`。 +- Windows:xpm 声明齐三平台(索引完整性 + 未来 CN 镜像),但 install() 在 windows 直接返回,anchor 由 + `generated_files` 提供(source 构建是后续项)。 +- **CN 镜像**:本会话无 `GITCODE_TOKEN`(无 mcpp-res 写权限),按 add-package skill 采用 **纯字符串 GLOBAL url**, + CN 用户回退 GLOBAL,镜像由维护者后续补(`mcpp-res/opencv`,资产 `opencv-4.13.0.tar.gz`)。 + +## 5. 本地验证(mcpp 0.0.85,linux x86_64) + +| 步骤 | 命令 | 结果 | +|---|---|---| +| 上游 sha256(两次稳定) | `sha256sum opencv-4.13.0.tar.gz` | `1d40ca017ea51c533cf9fd5cbde5b5fe7ae248291ddf2af99d4c17cf8e13017d` | +| 离线隔离构建 core+imgproc+imgcodecs | `unshare -rn cmake … && cmake --build` | **零下载**,产出 6 个 `.a`,~81s(4 job) | +| 独立链接+运行(mcpp g++ 16.1.0) | 手工 `g++ roundtrip.cpp -l…` | `opencv ok=1 core=4x4x3 gray(blue)=29 png_bytes=82 decoded=4x4` | +| **端到端 mcpp 管线** | `mcpp test`(fetch→install() CMake→link→run) | ✅ **PASS**(mcpp **0.0.87**):`test result ok. 1 passed`。曾在 ≤0.0.86 被阻断(§5.1),根因为 mcpp 把全局索引 xim 误注入项目作用域,已在 0.0.87 修复(见 `2026-07-09-mcpp-builddep-loader-store-split-rootcause.md` + mcpp 仓 `2026-07-09-project-index-scope-global-infra-fix.md`)。cmake 现装进全局 registry,项目 store 仅含 compat.opencv。 | +| lint(CI 同款) | `mcpp xpkg parse` + `lua5.4 loadfile` | PARSE_OK / LUA_LOAD_OK,`unknown_keys=[]` | + +测试断言(`tests/examples/opencv/tests/roundtrip.cpp`,linux-gated):core 造 `cv::Mat` → imgproc `cvtColor` BGR→GRAY +(蓝色 luma=29)→ imgcodecs `imencode/imdecode` PNG 内存往返,逐步 `return` 非零错误码。 + +### 5.1 ⛔ BLOCKER —— `mcpp test` sandbox 未向 install() 提供可运行的 build-dep 工具链 + +**现象**:`mcpp test` 触发 install() 的 CMake 构建时,cmake/make/gcc 均无法执行 → install() 失败 → mcpp 报 +`fetch 'compat.opencv@4.13.0' failed`。这是调研报告 R1 风险的实测坐实,且是 **通用**(非 opencv 专属)。 + +**根因(install() 内 env/ELF 实证)**:mcpp 为 install() 导出了 `XLINGS_BUILDDEP_{CMAKE,MAKE,GCC}_PATH`(定位 +build-dep 工具的 intended 机制),但 project-local `.mcpp/.xlings` sandbox 里这些工具 **未被完整物化**: + +- `XLINGS_BUILDDEP_GCC_PATH`(`xpkgs/xim-x-gcc/16.1.0/`)、`XLINGS_BUILDDEP_MAKE_PATH`(`xim-x-make/4.3/`) + 目录下 **无 bin/**(gcc/make 二进制根本不存在于该路径)。 +- `XLINGS_BUILDDEP_CMAKE_PATH/bin/cmake` 存在,但其 **ELF interpreter 悬空**:INTERP 指向 + `/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2`,而该 loader 未被部署(`xim-x-glibc/2.39/` 下只有 + `.xpkg.lua`);真正的 loader 只在 `runtimedir/glibc-2.39-linux-x86_64/lib/` 里 → 直接执行报 + `cannot execute: required file not found`。 +- install() subprocess PATH 上的 xlings shim(`make`/`gcc`)在此上下文失效(`executable 'make' not found`、 + `alias for 'gcc' references itself`)。 +- 对照:**GLOBAL** 工具可运行(`$HOME/.xlings/.../cmake --version` = 4.0.2 OK;`$XLINGS_HOME=~/.mcpp/registry` + 下的 gcc 即消费侧链接所用、可运行)。即 build-dep 工具设计上经 xvm shim 环境运行,而该环境未注入 install() 的 + bash 子进程。 + +**为何 openblas 没暴露**:openblas 的 install() 只需 `make`(musl-static,免 loader)+ 裸 `gcc`,且其 +workspace 测试是 **windows-gated** → 在新的 `mcpp test --workspace` linux 流程里 openblas 的 install() **根本不被触发**。 +opencv 是首个在该流程里真正跑 install()-CMake 的包,故首次撞上此 gap。 + +**影响**:任何"install() 里跑 glibc-dynamic build-dep 工具(cmake/autotools/…)"的 compat 包,在当前 +`mcpp test`(及同流程的 CI)下都无法闭环。这是 mcpp/xim 侧问题,**不在本仓(index)可修复范围**,已列为 §6 头号通用缺口。 + +**已证明未受此影响的部分**:descriptor 语法/解析、构建 recipe 本身(§5 前三行:离线隔离构建 + 手工链接运行全绿, +用的正是同一套 ecosystem cmake/gcc,只是在 sandbox 之外调用)。此外,手工把 sandbox cmake 用 `xim:patchelf` +`--set-interpreter` 指向 `runtimedir/glibc-*/lib/ld-linux-x86-64.so.2` + registry 的可运行 gcc/make,一个最小 +CMake 工程 configure+build 全绿(证明"只要 cmake 可执行,整条工具链子进程链即通")。即 **一旦 mcpp 向 install() +提供可运行的 build-dep 工具链,本 descriptor 不改即可闭环**。 + +**机制细节(供 mcpp 侧定位,均实证)**: +- `descriptor` 用的是 **openblas 同款** `pkginfo.build_dep("xim:cmake").bin`(0.0.85 存在)解析工具 —— 无任何 + loader/patchelf 兼容代码(遵循"deps 由 xlings 自动处理"的约定)。但 `build_dep` 返回的正是那个 **INTERP 悬空、 + 不可执行** 的 sandbox cmake。 +- `pkginfo.with_build_deps_on_path`(把 build-dep bin/ 前置进 PATH 的便捷 API)**只在独立 libxpkg 0.41/0.42 有, + mcpp 0.0.85 内置 runtime 无此函数**(调用即 nil→install() 早抛)。 +- mcpp 0.0.85 内置 libxpkg **有 `xim.libxpkg.elfpatch`**(auto 模式)——但它 patch 的是 **本包自身产物** + (依据 deps 里的 loader provider),**不 patch build-dep 工具**(如 cmake)。故 build-dep cmake 的 INTERP 无人修。 +- openblas 之所以没撞上:其 install() 只用 **make(musl 静态,无 INTERP 依赖)** + 裸 gcc,且 linux 测试 + windows-gated → 从不触发。opencv 是首个真正在 `mcpp test` 里跑 install()-CMake 的包。 + +→ **修复方向(mcpp 侧,三选一或组合)**:(a) 部署 build-dep 时把其 xim:glibc loader 落到二进制 INTERP 指向的 +路径(或对 build-dep 工具跑一次 elfpatch);(b) 把 `with_build_deps_on_path` 纳入 0.0.85 runtime 并让其前置的是 +**可执行** 的工具;(c) 让 `build_dep().bin` 返回已 elfpatch 的可执行副本。任一落地后,本 descriptor 与测试工程 +**零改动** 即闭环。 + +## 6. 适配中暴露的 mcpp 通用能力缺口(非 opencv 专属,构建工具/模块化视角) + +判据:每条都是"任何走外部构建系统 / 多产物 / 重源码构建的库"共有的架构问题。 + +0. **【实测 BLOCKER】install() 需要一个可运行的 build-dep 工具链(最高优先级)**。见 §5.1。现状 + `mcpp test` sandbox 只导出 `XLINGS_BUILDDEP_*_PATH` 但不物化可运行二进制:gcc/make bin 目录为空,cmake 的 ELF + interpreter 悬空(xim:glibc loader 未部署到 patch 进二进制的路径),PATH shim 在 install() 子进程失效。→ 任何 + 在 install() 里跑 glibc-dynamic 工具(cmake/autotools)的包都无法闭环。诉求:mcpp 让 install() 的执行环境拿到 + **可直接执行** 的 build-dep 工具(要么把 loader/依赖完整部署进 sandbox 并修正 INTERP,要么在 install() 子进程 + 注入 xvm 的 PATH+LD 环境,要么提供一个"运行 build-dep 工具"的封装 API)。这是 opencv 之所以卡住的直接原因,也是 + 所有 CMake/autotools compat 包的共同前置。 + +1. **install() ↔ 消费侧工具链握手**。现状 install()(xim 侧)不知道 mcpp 会用哪个编译器/ABI 链接消费者; + openblas 靠"纯 C、ABI 稳定"绕过。任何 **C++** 源码构建型包都踩:install() 的 g++ 必须与消费者一致,否则 + libstdc++/libc++ 冲突(正是本包 linux-gated 的原因)。诉求:mcpp 把已选 toolchain(CC/CXX/AR/ABI 三元组)以稳定 + 契约(env 或 pkginfo API)暴露给 install()。 +2. **feature/capability 传参到 install()**。现状 feature 仅被 mcpp 静态读取,传不进 install();外部构建系统型包的 + 模块/开关(CMake `-D…`)只能 recipe 内写死,无法 per-consumer 选择。诉求:把 consumer 请求的 features/绑定作为 + 参数传入 install(),让"外部构建系统的开关"成为真正的 mcpp feature。 +3. **一个包产出多库目标(per-target sources/include_dirs)**。现状 `sources`/`include_dirs` 包级全局、`Target` 仅 + 链接单元;多模块库无法在单描述符按 target 分派各自源码集。诉求:target 携带自己的源码/头子集,或等价的 + sub-package 机制(直接决定"OpenCV 拆多库"能否原生表达)。 +4. **feature 可携带 `defines` 之外的构建片段(include_dirs / cflags)**。现状刻意排除。对"开关即改配置头/加 -I/-l"的 + 通用场景(几乎所有 autotools/CMake 库的 WITH_*)表达力不足。诉求:受控地允许 feature 贡献 include_dirs/cflags。 +5. **重型 from-source 构建的产物二进制缓存**。现状每次干净构建都重跑 install()(opencv 分钟级)。诉求:对 install() + 产物做可复现的二进制缓存/artifact 复用(与 mcpp-index artifact publish 模型对齐),避免 CI/首次 add 反复重编。 + +## 7. 后续项(P2+) + +- macOS:让其用 gcc,或用消费者编译器构建(依赖 §6-1);Windows:MSVC 预编译 或 clang-MSVC CMake(调研报告 R3)。 +- 更大模块集(calib3d/features2d/flann/photo/video/objdetect → dnn → highgui)与 `compat.opencv-contrib`(非自由 opt-in)。 +- codec 由内置 3rdparty 改绑本仓 provider(compat.zlib 等,依赖 §6-1/§6-2 的能力)。 +- CN 镜像 `mcpp-res/opencv@4.13.0`(维护者)。 +- CI:install() 的 CMake 构建分钟级,`smoke-examples (opencv)` 可能需放宽 timeout,或 mac/win 进 nightly。 diff --git a/.agents/docs/2026-07-09-install-hook-toolchain-build-env-gap.md b/.agents/docs/2026-07-09-install-hook-toolchain-build-env-gap.md new file mode 100644 index 0000000..8600efe --- /dev/null +++ b/.agents/docs/2026-07-09-install-hook-toolchain-build-env-gap.md @@ -0,0 +1,88 @@ +# install() hook 子进程继承不到工具链构建环境(源码构建包的生态缺口) + +**日期**: 2026-07-09 +**范围**: xlings/xim 的 `install()` xpkg hook 执行环境 × 生态工具链(`xim:gcc` / `xim:glibc` / `xim:linux-headers`) +**性质**: xlings 侧的生态能力缺口(根治点在 xlings/xim,不在下游 descriptor) +**触发用例**: `compat.opencv`(源码 CMake 构建)在 mcpp-index workspace CI 上的 `workspace(linux)` 失败 +**状态**: opencv PR #67 已标记**待评估**(draft),阻塞在本缺口 + +--- + +## 文档族与职责边界 + +| # | 文档 | 定位 | +|---|---|---| +| ① | `2026-07-09-mcpp-builddep-loader-store-split-rootcause.md` | 原故障(cmake interpreter 悬空)根因总报告 | +| ② | mcpp `2026-07-09-project-index-scope-global-infra-fix.md` | 该故障**唯一必需修复**(mcpp 0.0.87,已发布) | +| ③ | xlings `2026-07-09-scope-consistency-installed-check-and-loader-resolution.md` | elfpatch additive 解析缺口(D1,**独立**、当前未被触发) | +| ④ | **本篇** | **install() hook 构建环境缺口**——②修好后 opencv 暴露出的**新**生态缺口,与 ①②③ 均不同 | + +**与 ③(D1)的区别**:③ 是 **runtime** loader-provider 的**跨 store 解析**(elfpatch interpreter/rpath);本篇 ④ 是 **build-time** 的**编译/链接搜索路径**(startfile / 库 / 头文件)。两者都涉及 glibc,但一个在装完后跑,一个在 install() 里编。 + +--- + +## 0. 现象 + +`compat.opencv` 的 `install()` 用生态工具链(`xim:cmake` + `xim:gcc` + `xim:make`)从源码构建 OpenCV。开发主机上闭环通过;但 mcpp-index workspace CI 的 `workspace(linux)`(冷环境、无宿主 libc-dev)上,`install()` 的 CMake 构建**静默失败**,消费者最终报 `opencv2/core.hpp: No such file`。 + +xim 的 interface 模式**抑制了 install() 子进程的全部输出**(subprocess stdout + libxpkg `log.error`),故真因在 CI 上不可见——只能看到 `fetch 'compat.opencv@4.13.0' failed (exit 1)`。通过在 CI workflow 加**失败诊断步骤**(直接从 runner 磁盘 `cat` build log + 直跑 `xlings install cmake`)才拿到真相。 + +## 1. 真因:install() 子进程没有工具链的**构建期**环境 + +`xim:gcc` 的 specs(`xim-x-gcc/16.1.0/lib/gcc/.../specs`)只把 `xim:glibc` 接进了**运行期**: +- `-rpath /lib64`、`-dynamic-linker /lib64/ld-linux-x86-64.so.2`。 + +specs **没有**把 glibc 的**构建期**搜索路径写进去: +- 链接期 startfile / 库:`crt1.o`、`crti.o`、`crtn.o`、`libm`、`libc`(都在 `/lib`); +- 编译期头文件:`stdlib.h`、`limits.h`(在 `/include`),以及内核 uapi 头(在 `/include`)。 + +平时这些由 **mcpp 的构建流程**通过环境变量(`LIBRARY_PATH` / `CPATH`)提供给 gcc。但 `install()` hook 的子进程**不继承** mcpp 的构建环境,于是 gcc 在 install() 里既找不到 crt/库、也找不到系统头。 + +**为什么开发主机上没暴露**:主机装了 libc-dev,gcc 默认 startfile/include 前缀里有 `/usr/lib/.../crt1.o` + `/usr/include/stdlib.h`,**静默 fallback 到宿主** → 能编过,但**违反 host-free**、且用的是宿主 glibc 而非生态 glibc。极简 CI runner 没 libc-dev → 硬失败。这也是本地一直复现不出 CI 失败的根源。 + +**为什么 openblas 没踩坑**:`compat.openblas` 的 install() 只**编译 + 归档 `.a`**(`make libs`),从不**链接可执行文件**,所以永远不需要 crt1.o/libm;它对系统头的依赖也浅。opencv 的 CMake **编译器自检要链接一个测试 exe**,第一步就撞上 crt1.o 缺失。 + +## 2. CI 实证(逐层剥开) + +install() 里临时逐个补齐,CI build log 逐层前进,坐实了缺口范围: + +| 层 | 报错 | 缺的东西 | +|---|---|---| +| L1 | `cmake: cannot execute: required file not found` | 动态 xim:cmake 的 loader launcher(裸名 vs 绝对路径) | +| L2 | `ld: cannot find crt1.o / crti.o / -lm` | 链接期 `LIBRARY_PATH=/lib` | +| L3 | `find_package called with invalid argument "OFF"` | OpenCV 撞见 xlings python3 shim(版本串空)→ `OPENCV_PYTHON_SKIP_DETECTION` | +| L4 | `fatal error: stdlib.h / limits.h: No such file` | 编译期 `CPATH=/include:/include` | + +L1/L2/L4 都是**同一个缺口的不同面**:install() 子进程缺工具链的构建期环境。(L3 是 OpenCV × CMake4 的独立小问题,已用官方开关解决。) + +## 3. 为什么"在 descriptor 里各自硬接"是错的层级 + +opencv PR 上现存的 `LIBRARY_PATH`/`CPATH` + `pkginfo.install_dir("xim:glibc","2.39")` + 显式声明 `xim:glibc@2.39`/`xim:linux-headers@5.11.1` 是**临时探路**,**不作为最终方案**,因为: + +1. **脆弱**:硬编码 glibc 2.39 / linux-headers 5.11.1 版本,工具链一升级就失效。 +2. **重复**:每个源码构建包(未来的 ffmpeg / boost / …)都要各自重接一遍同样的环境。 +3. **层级错**:"gcc 该怎么找到它自己工具链的 glibc" 是**工具链/安装器**的职责,不是每个下游包的职责。 + +## 4. 根治设计(xlings/xim 侧) + +**install() hook 的子进程,应当在一个已经具备完整工具链构建期环境的 shell 里运行。** 具体二选一(或组合): + +- **方案 A(推荐)——安装器注入构建环境**:xim 在执行 install() hook 时,依据当前 default toolchain(gcc + 其 runtime dep 链上的 glibc、linux-headers)导出 `LIBRARY_PATH` / `CPATH`(或 `C_INCLUDE_PATH`+`CPLUS_INCLUDE_PATH`),使 hook 内 bare `gcc`/`cmake` 天然 host-free。信息来源:resolver 的 plan 已知每个 node 的 effective store(与 ③ D1 的"effective_install_dir"同源,可复用)。 +- **方案 B——把构建期路径也写进 gcc specs**:让 `xim-x-gcc-specs-config` 生成的 specs 除了 rpath/dynamic-linker,再补 startfile prefix(`/lib`)与 `-isystem /include`、`-isystem /include`。好处是对**任何**调用方(不止 install())都生效;代价是 specs 更重、且路径同样要在装 glibc 时确定。 + +无论哪个,目标一致:**任意生态工具(mcpp 构建 / install() hook / 用户直调 gcc)在 host-free 环境下都能编译+链接,不 fallback 到宿主。** descriptor 侧则**零环境接线**。 + +### 实现落点(已核对源码) + +核对 `xim-x-gcc-specs-config/0.0.1/gcc-specs-config.lua`(115 行):它只做**运行期**注入 —— 取 gcc `-dumpspecs`,替换 `*link:` 的 dynamic-linker、追加 `-rpath/-rpath-link `,回写 specs。**完全没碰构建期**(startfile 前缀 / `-L` / header `-isystem`),坐实缺口。两条实现路径的精确落点: + +- **方案 A(推荐,更聚焦)——xlings 在执行 install() hook 时导出构建环境。** 落点在 xlings 执行 xpkg `install()` 的地方(`xpkg-executor` / `installer.cppm` 的 hook 调用),依据当前 default toolchain 的 gcc→(glibc、linux-headers)runtime dep 链,导出 `LIBRARY_PATH=/lib`、`CPATH=/include:/include`(路径由 resolver plan 的 effective store 给出,与 ③ D1 的 `effective_install_dir` 同源可复用)。只影响 install() hook,不改工具链本身,风险面最小。 +- **方案 B——扩 specs 生成器。** 需给 `gcc-specs-config.lua` **增加入参**(glibc include 目录 + linux-headers include 目录 —— 目前它只拿到 `default_libdir`),并在 specs 里补:`*startfile_prefix_spec:` = `/lib`(找 crt1.o/crti.o/crtn.o)、`*link:` 追加 `-L/lib`(找 -lm/-lc)、`*cpp:` 追加 `-isystem /include -isystem /include`(找系统头)。好处是对任何 gcc 调用方都生效;代价是跨"生成器 + 其调用者(装 glibc 时传 include 路径)"两处联动,且改动作用于**整个生态的 gcc**。 + +> 评估:A 更聚焦、可验证成本低(改 install() 执行环境即可,不动工具链);B 更彻底但联动面大、且触及官方 index 的 gcc 行为。二者都需 xlings/xim 侧的 C++/Lua 改动 + 其自身 build/验证闭环,**属 maintainer 协作项,不宜由下游会话半实现且无法完整验证地推进**。本文即为该实现提供 ready 的设计与落点。 + +## 5. 落地关系 + +- **不阻塞 mcpp 0.0.87**(②,已发布)、**不阻塞 D1/#354**(③,独立,当前 HOLD)。 +- **阻塞 opencv 及未来源码构建包**的 host-free 闭环 → opencv PR #67 待评估,等本缺口在 xlings/xim 侧补齐后回来**删掉 descriptor 里的临时环境接线**再合入。 +- CI 侧 opencv workspace 的临时诊断步骤(mcpp-index `.github/workflows/validate.yml`,commit `450acbb`)在 opencv 落地前应移除。 diff --git a/.agents/docs/2026-07-09-mcpp-builddep-loader-store-split-rootcause.md b/.agents/docs/2026-07-09-mcpp-builddep-loader-store-split-rootcause.md new file mode 100644 index 0000000..d46e385 --- /dev/null +++ b/.agents/docs/2026-07-09-mcpp-builddep-loader-store-split-rootcause.md @@ -0,0 +1,136 @@ +# 深度根因调研:build-dep 工具在项目沙箱不可执行(cmake INTERP 悬空) + +**日期**: 2026-07-09 +**触发**: 收录 OpenCV(`compat.opencv`,install() 驱动 CMake 源码构建)时,`mcpp test` 无法闭环。 +**归责规则(用户设定)**: 先查是不是 **mcpp 设计/使用/架构** 问题;再看是不是 **xlings 原生** 问题。若 xlings 本身没问题、只是 mcpp 使用方式出问题 → 归 mcpp;**除非存在真实的 xlings 原生设计缺陷**。 +**方法**: 实证复现(含 fresh 环境普适性)+ mcpp 源码追踪 + xlings 源码追踪(两路独立),交叉裁决。 +**关联**: `.agents/docs/2026-07-08-opencv-*.md`、`2026-07-09-mcpp-xlings-project-sandbox-builddep-payload-bug.md`(本篇为其升级/更正版,归责以本篇为准)。 + +## 文档族与职责边界(三份配套,全部根治设计、无 workaround) + +| # | 仓库 / 文档 | 角色 | 与本故障的关系 | +|---|---|---|---| +| ① | **本篇** mcpp-index `2026-07-09-mcpp-builddep-loader-store-split-rootcause.md` | 根因总报告(现象/实证/归责) | 全貌 | +| ② | mcpp `2026-07-09-project-index-scope-global-infra-fix.md` | **本故障唯一必需修复(根治)** | 修 ② 即闭环 | +| ③ | openxlings/xlings `2026-07-09-scope-consistency-installed-check-and-loader-resolution.md` | xlings 侧**独立**健壮性缺口(根治) | 不由本故障触发、与 ② 互不依赖 | + +**职责边界**:本篇是根因分析;**修复设计见 ②(唯一必需,根治)**;③ 是独立缺口。三份均根治设计、不含 workaround。 + +--- + +## 0. 结论(TL;DR) + +- **现象**:项目沙箱里 build-dep `xim:cmake` 的 ELF interpreter 被指向 `/.mcpp/.xlings/data/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2`,该 loader **从未落进项目 store**;glibc 的真身与记录都在 **registry(global)**。→ cmake `cannot execute`。 +- **普适**:**fresh `MCPP_HOME` 同样失败**(RC=1,同一错误),**CI 亦会失败**,非本机历史所致。 + +### 根因(一句话):mcpp 强制把 xlings 官方 **全局** 索引 `xim` 注册成 **项目作用域** index repo,覆盖了 xlings 原生的 additive 项目机制 —— 把本应装进全局 registry 的 xim 工具(cmake/glibc/gcc/make)整体错误地"项目化",装进了项目 store。 + +- **直接证据**:项目 `.mcpp/.xlings.json` 的 `index_repos` = `[compat(本地,对), xim(官方全局索引,被塞进项目 repos)]`。而 `catalog.cppm:168-176` 把"项目 index_repos 里的包"一律判 `scope=Project` → `storeRoot=项目 store`(`:297-300`)。→ xim 工具全被装进项目 store。 +- **肇事代码(mcpp,故意但机制错)**:`mcpp/config.cppm:684-697` 主动把官方 `xim` 索引 append 进项目 `customRepos`;意图注释见 `:745-748`("让 `xim:python@latest` 之类能解析")。 +- **为何是"强制改变 xlings 设计意图"**:xlings **原生**就同时扫 project+global 两套 repo(`catalog.cppm:328-329`)且项目模式 **additive over global**(`merged_versions :603`、`merge_workspace_into_ Anonymous :581-584`);xim 本是全局默认索引、registry 里已有其本地 clone(`registry/data/xim-pkgindex/pkgs`)。**因此 `xim:cmake` 本会以 Global 作用域解析→装 registry→并对项目可见 —— 根本不需要 mcpp 再把 xim 加进项目 repos。** mcpp 的强加既**多余**又**有害**。 +- **下游连锁(为何最终悬空)**:xim 工具被项目化后,cmake 装进项目 store(`install()`/`config()` 确实跑了,项目 store 里 cmake 有 8201 文件、xvm 已注册);但其 loader 依赖 glibc 已被**工具链 bootstrap**(`gcc@16.1.0` 默认)装进 registry 并记录,项目安装时被 `installer.cppm:906` 的 merged 版本库判"已装"跳过 → 项目 store 的 glibc 空;auto-elfpatch 又把 cmake interpreter 指向项目 store 的空 glibc → 悬空。 + +### 归责(按规则) +- **主责 = mcpp 设计错误**:mcpp **强制改变了 xlings 的设计意图** —— 把全局索引 `xim` 注册为项目作用域(`config.cppm:684-697`),而 xlings 原生的"全局安装 + 映射到项目"机制本就够用。这是"可解析"与"项目作用域 store"混为一谈。**删掉这一强加即从源头消除问题**(xim 工具回到 registry,cmake interpreter 天然指 registry 的可用 glibc)。 +- **xlings 侧:与本故障无关、非产生方**。mcpp 侧根治(见文档 ②)后,cmake/glibc 同在全局 registry,`installer.cppm:906` 的 merged 复用是正当 additive 语义、不再被误触。xlings 另有一个**独立**的 elfpatch loader 解析健壮性缺口(合法"项目消费者 + 全局 provider"),其根治设计见文档 ③——**不是本故障的必要条件,与 ② 互不依赖**。 + +--- + +## 1. 现象与复现 + +- 工程 `mcpp.toml` 含自定义 `[indices]`(本地 path 索引)→ 进入项目本地模式(`mcpp/src/config.cppm:100-105` `make_project_xlings_env` 注释:*"Used when custom (non-builtin) indices are configured"*)。 +- compat 包声明 build-dep `xim:cmake` 且 install() 执行它 → `mcpp test`: + ``` + /.mcpp/.xlings/data/xpkgs/xim-x-cmake/4.0.2/bin/cmake: cannot execute: required file not found + ``` + cmake 的 PT_INTERP 指向 `/.mcpp/.xlings/data/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2`,该文件不存在;真 loader 在 `/.mcpp/.xlings/data/runtimedir/glibc-2.39-linux-x86_64/lib/`。 + +### 1.1 普适性(关键,排除"本机历史") +- `MCPP_HOME= mcpp test` → **RC=1,同一 cmake 错误**。 +- fresh 日志:`First run no toolchain configured — installing gcc@16.1.0 (glibc, native ABI) as default` → `Default set to gcc@16.1.0`。 +- fresh `registry/data/xpkgs/xim-x-glibc/2.39/` **完整**(bin/lib/lib64/…,loader 在);`registry/.xlings.json` 版本库含 `glibc-2.39`。 +- fresh 项目 `.../xpkgs/xim-x-glibc/2.39/` **只有 `.xpkg.lua`**。 +→ 证明:失败**不依赖任何预装历史**,而是 mcpp 引导流程每次都种下 registry 的 glibc 记录。 + +--- + +## 2. 完整机制链(源码 file:line) + +**0.(上游根因)mcpp 把官方全局索引 `xim` 注册成项目作用域 index repo** +- `mcpp/config.cppm:684-697`:`ensure_project_index_dir` 把 `officialIndex`(`xlingsHome()/data/xim-pkgindex`)以名字 `xim` append 进项目 `customRepos`(`if (!hasXim) customRepos.emplace_back("xim", …)`),随后 `seed_xlings_json`(`:707`)写进项目 `.mcpp/.xlings.json` 的 `index_repos`。意图注释 `:745-748`。 +- `xlings/catalog.cppm:168-176`:`project_index_repos()` 里的 repo → `scope=Project`;`:297-300`:`storeRoot = project_data_dir/xpkgs`。→ **凡从 `xim` 解析到的包(cmake/glibc/gcc/make/binutils)全部 Project 作用域、装进项目 store。** +- **本不必要**:`xlings/catalog.cppm:328-329` `collect(projectRepos_); collect(globalRepos_);` 原生同时扫两套;项目模式 additive over global(`config.cppm:603` merged_versions、`:581-584` merge_workspace)。xim 是全局默认索引、registry 有本地 clone → `xim:cmake` 本会 Global 解析→装 registry→对项目可见。mcpp 的强加**覆盖**了这套原生机制。 + +以下 1–5 是"xim 被项目化"之后的下游连锁: + +1. **mcpp 把工具链(含 glibc)装进 registry(global),并记录版本** + - `make_xlings_env()` 构造 GLOBAL env(无 projectDir):`mcpp/config.cppm:84-98`;`XLINGS_HOME=~/.mcpp/registry`(`config.cppm:78-80, 459-465`)。 + - 普通构建即自动触发全局装 glibc:`mcpp/build/prepare.cppm:805-810`;fetcher 走全局 home:`mcpp/pm/package_fetcher.cppm:739,800,810`。显式安装同理:`mcpp/toolchain/lifecycle.cppm:332-338`。 +2. **mcpp 把项目 build-dep 路由到项目 store,但 `XLINGS_HOME` 仍是 registry** + - 自定义(非内置)索引 → 项目模式:`mcpp/build/prepare.cppm:1188`(`useProjectEnv = idxSpec && !idxSpec->is_builtin()`),`:1350` 走 `make_project_xlings_env`。 + - 该 env:`home=cfg.xlingsHome()`(**仍 registry**)+ `projectDir=/.mcpp`(`mcpp/config.cppm:102-105`)。 + - 命令前缀**同时**设 `XLINGS_HOME=registry` 与 `XLINGS_PROJECT_DIR=/.mcpp`:`mcpp/xlings.cppm:730-738`。→ xlings 一次调用里同时看到"装满的 global"和"空的 project"。 +3. **cmake 及其 dep glibc 解析进项目 store** + - `node.storeRoot = match.storeRoot`(`xlings/xim/resolver.cppm:275`);`match.storeRoot` 由 `match.scope` 选 project/global(`xlings/xim/catalog.cppm:298-300`);scope 由包所属 index repo 是否 project 决定(`catalog.cppm:170-189`)。 + - `install_dir = targetRoot/store/version`(`xlings/xim/installer.cppm:838-839`),cmake/glibc 均落项目 store 目标。 +4. **xlings 的"已装"跳过闸门读 merged 版本库 → 跳过 glibc 在项目 store 的落盘** + - 空 `install_dir` 先被创建:`installer.cppm:887-891`。 + - `payloadInstalled` 初值来自 catalog 的**项目作用域**判定(store 空 → false):`catalog.cppm:302-306` → `resolver.cppm:277` → `installer.cppm:893`。 + - glibc 无 `installed` hook → 落到默认闸门:`installer.cppm:905-913`,读 `Config::versions()`=**merged**(`xlings/config.cppm:601-603`,`merged_versions :564-568`),命中 registry 的 glibc → `payloadInstalled=true`。 + - 于是 `glibc.lua:87-98` 的 `os.mv`(把 loader 落进 `xpkgs/xim-x-glibc/2.39/`)被 `!payloadInstalled` 守卫跳过(`installer.cppm:916/953/964`)→ **项目 store 的 glibc 目录仍空**。 +5. **elfpatch 把 cmake INTERP 指向空的项目 store glibc** + - loader провайдер 用**约定扫描**(非 deps_exports;`ctx.deps_exports/self_exports/runtime_deps_list` 在本版 xlings 全未赋值)。`_resolve_dep_via_scan`(`xlings/…/xpkg-lua-stdlib.cppm:473-505`)扫"消费者 install_dir 的祖父目录"=消费者**项目 store** 的 xpkgs,匹配到 glibc 目录并因 `os.isdir` 为真而接受(该目录存在但空,`installer.cppm:890` 建的)。 + - 结合 glibc 的 `loader="lib64/ld-linux-x86-64.so.2"` 导出(`glibc.lua:38-44`)→ `patchelf --set-interpreter /…/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2`(`xpkg-lua-stdlib.cppm:1366-1370`)→ 指向空目录 → 悬空。**registry 里那个存在的 loader 从不被采用**(扫描先命中空的项目目录)。 + +### 2.1 核心矛盾一句话 +**"跳过 materialize(因为 merged 命中 global 记录)" 与 "elfpatch 指向 project store(假定本地有)" 两个决策,对 glibc 究竟在哪个 store 各执一词**;而工具(cmake)在 project、loader(glibc)在 registry —— 两者分属不同 store。 + +--- + +## 3. 归责裁决(按用户规则) + +### 3.1 两路追踪的分歧 +- **mcpp 路**:失败状态由 mcpp 制造(双 store 同调 + 无 loader 落盘/改指);原生 xlings 单次单作用域不会有"装满 global + 空 project 同时呈现"。→ 归 mcpp。 +- **xlings 路**:`installer.cppm:906` 是真实内部不一致(同一流水线两次判"已装"、作用域不一致,scope-blind 的 :906 覆盖了正确的 project 判定 `catalog.cppm:302-306`);声称纯 xlings 可复现。→ 归 xlings 原生缺陷。 + +### 3.2 裁决依据 +按规则"**除非真实的 xlings 设计缺陷**,否则归 mcpp",关键在:**触发失败的那个状态,是 mcpp 制造的、还是 xlings 原生就会产生并支持的?** + +1. **失败所需状态 = "装满 global + 空 project,一次调用同时呈现"**,由 mcpp 明确构造:`XLINGS_HOME=registry` 与 `XLINGS_PROJECT_DIR=project` 同时设置(`mcpp/xlings.cppm:730-738`),工具链强制 global(`make_xlings_env`)、build-dep 强制 project(`make_project_xlings_env`)。这是 mcpp 的架构选择。 +2. **原生 xlings 不触达、无测试**:xlings 路自己承认无任何 e2e 覆盖"global 预装某 dep + project 作用域消费者需重新 materialize loader"这一路径;`elfpatch_install_verify_test.sh` 只在**单 store** 断言;`project_global_fallback_test.sh` 是"有 project config 但全局作用域"(**单 store**)。 +3. **xlings 自身对"同包两作用域"另有设计**:`prefer_project_scope_`(`catalog.cppm:375-390`)专门去重"project 与 global 同包" —— 说明 xlings 设计者预期到两作用域并**打算优先 project**;而 `:906` 闸门**没有走这套去重/优先逻辑**,直接被 global 记录短路。即 xlings 的**既定意图是 materialize/优先 project**,`:906` 是与该意图相悖的一处疏漏。 +4. **mcpp 无修复步骤**:唯一的 payload 兜底 `fallback/xpkg_copy.cppm:21-60` 只拷单个 verdir、且只接在**全局** fetch 路径(`package_fetcher.cppm:773,818`),从不接项目安装(`prepare.cppm:1350-1357`);不遍历 runtime/loader dep。→ mcpp 没有把 loader 落进项目 store,也没让消费者改指 registry loader。 + +**裁决**: +- **主责 = mcpp 设计错误(强制改变 xlings 设计意图)**。**最上游、决定性的一步**是 `mcpp/config.cppm:684-697` 把官方全局索引 `xim` 强加为**项目作用域** index repo(§2.0),使 xim 工具整体错误项目化。而 xlings **原生**的"全局解析 + additive 映射到项目"机制本就满足 mcpp 的需求(让 `xim:` 依赖在项目模式可用),**根本不需要这一强加**。按规则,这是"xlings 本身可用、mcpp 使用方式(强制覆盖原生意图)导致出问题" → **mcpp 的设计问题**。 +- **次责 = xlings `installer.cppm:906`(潜在、防御性)**。"已装"判定读 merged、与落盘/elfpatch 的 project 作用域不一致,与自身 `prefer_project_scope_` 意图相悖 —— 一处真实内部不一致,值得加固;但它是被 mcpp 的错误作用域**诱发**的下游,xim 作用域修正后不再被触达。**非本失败的产生方。** + +> 一句话:**xlings 原生就"全局装 + 映射到项目",够用;是 mcpp 多此一举把官方 xim 索引强注册成项目作用域(`config.cppm:684-697`),把本该在 registry 的工具整体项目化,才引出后续 loader 不落盘、interpreter 悬空。删掉这一强加即根治。** + +--- + +## 4. 修复方案 + +### 4.1 mcpp 侧(唯一必需修复,根治)——详见文档 ② +根治设计见 **mcpp 仓 `.agents/docs/2026-07-09-project-index-scope-global-infra-fix.md`**(文档 ②),摘要: +- **原则:global 是默认**(xlings 只把落进"项目组"的 repo 判 Project,`catalog.cppm:175-224`)。**mcpp 只把用户在 `[indices]` 声明的项目本地索引放进项目组,绝不注入官方 `xim`。** 删除 `mcpp/config.cppm:684-697`(无条件把官方 xim 注入项目 `customRepos`)、收敛 `:745-765`;全局 `xim` 指 registry 本地 clone 以免 remote fetch。**不硬编码 `is_global_infra` 白名单**(违背"global 默认"、漏 xim 动态 sub-index)。 +- 效果:xim 及其 sub-index 留全局 → cmake/glibc/gcc/make 全 registry(同一全局 store)→ interpreter 天然指 registry glibc → 可执行。**`:906` merged 复用随之无关,xlings 无需改动。** +- **不采用**任何治标补丁(往项目 store 物化 loader、改 elfpatch 指项目空目录等);理由见文档 ②"❌ 不要做"。 + +### 4.2 xlings 侧 —— 与本故障**无关**,是独立缺口(见文档 ③) +本故障**不需要**任何 xlings 改动:mcpp 侧根治(§4.1 / 文档 ②)后,cmake/glibc 全在同一全局 registry,`installer.cppm:906` 的 merged 复用是**正当的 additive 语义**、不再被误触。 +- xlings 另有一个**相互独立**的健壮性缺口:合法"项目消费者 + 全局 loader-provider(additive 复用)"场景下,elfpatch 对 provider 的解析口径可能指向未物化 store。其根治设计(单一事实源 `effective_install_dir`、内存传递)见 **openxlings/xlings 仓文档 ③**。**它不是本故障的必要条件,与 ② 互不依赖。** + +--- + +## 5. 对 compat.opencv / mcpp-index 的影响 + +- `compat.opencv.lua` 已是**零兼容代码**的极简 `pkginfo.build_dep` 形式;**本问题(mcpp 侧)修复后,descriptor 与测试工程零改动即闭环**。 +- 构建配方 host-free 正确性已在沙箱外证明(`unshare -rn` 隔离构建 core+imgproc+imgcodecs 零下载 81s + mcpp g++ 链接运行 `opencv ok=1 core=4x4x3 gray(blue)=29 png_bytes=82 decoded=4x4`)。 +- 因 CI 走同一 `mcpp test` 沙箱,**修复前 opencv 无法进 CI 选跑**(必红,且 fresh 环境已证必现)。→ PR 应待 mcpp 侧修复;或先将 opencv 暂缓接入 workspace/CI(staged)。 + +## 6. 附:最小复现清单 +1. 任意工程 `mcpp.toml` 声明自定义 `[indices]`(本地 path 索引)。 +2. 一个 compat 包声明 build-dep `xim:cmake` 并在 `install()` 内执行 `cmake`。 +3. 任意机器(含全新 `MCPP_HOME`)`mcpp test` → `cmake: cannot execute`。 +4. 现成:`tests/examples/opencv` + `pkgs/c/compat.opencv.lua`。 diff --git a/.agents/docs/2026-07-09-opencv-unblock-implementation-steps.md b/.agents/docs/2026-07-09-opencv-unblock-implementation-steps.md new file mode 100644 index 0000000..111885f --- /dev/null +++ b/.agents/docs/2026-07-09-opencv-unblock-implementation-steps.md @@ -0,0 +1,73 @@ +# opencv 落地实施步骤(install() 构建环境缺口的跨仓库根治链) + +**日期**: 2026-07-09 +**前置**: 设计见 `2026-07-09-install-hook-toolchain-build-env-gap.md`(缺口 ④)。本篇把它拆成**有序、可执行、带依赖关系与版本携带**的实施步骤,供 maintainer / 后续会话 / 多 agent 执行。 +**当前进度**: Step 0 已完成;Step 1 起**未开始**(需 maintainer 对 xlings/libxpkg 核心的 go-ahead)。 + +--- + +## 依赖关系总览(严格串行的关键路径) + +``` +S0 mcpp 0.0.87 ──✅已发布 + │ +S1 xlings: 扩展 per-hook build-dep 环境注入 (lib→LIBRARY_PATH, include→CPATH) + │ ✅ 已实现 + 编译验证 (分支 fix/install-hook-toolchain-build-env);待发新版 xlings Y + │ ★ 更新:xlings-only,libxpkg 不需改、不需发版(见下修正) +S3 冷环境验证:opencv install() 不带 descriptor 硬接线也能编译+链接(需 xlings Y + CI) + │ +S4 清理:删 opencv descriptor 的临时 LIBRARY_PATH/CPATH 接线;删 validate.yml 临时诊断步骤(450acbb) + │ +S5 opencv PR #67 un-draft → CI 全绿 → squash 合入(index floor→0.0.87 随之落地) + │ +S6 (可选) mcpp-index CN 镜像 opencv 源码包 + xim-pkgindex 若需 +``` + +**★ 重大修正(2026-07-09,已读源码 + 编译验证)**:原计划 S1(libxpkg)+S2(xlings)两仓两版,**实际是 xlings 单仓一处改动,不动 libxpkg、不发 libxpkg 版**。xlings 的 install() 执行处(`installer.cppm::execute`,~1518)**早有** per-hook 的 build-dep 环境注入(把每个 build_dep 的 `bin/` 前插 PATH,跑完还原);修复只是**在同一循环里加**:build_dep 的 `lib/`(+`lib64/`)→ `LIBRARY_PATH`、`include/` → `CPATH`,同样跑完还原。**对着仓库当前 pin 的 libxpkg 0.0.42 就能编**(`mcpp build` exit 0)。故也**无需 toolchain 解析**——直接复用包声明的 build_deps(opencv 声明 `xim:glibc`+`xim:linux-headers` 即自动生效,descriptor 零环境接线)。 + +**关键路径 S1→S3→S4→S5 本质串行**(每步依赖前步产物:xlings 发版 → CI 验证 → 清理 → 合入),**不可并行 fan-out**。 + +--- + +## Step 1 — xlings:扩展 per-hook build-dep 环境注入 【已实现 + 编译验证】 + +**仓库**: `openxlings/xlings`。**分支**: `fix/install-hook-toolchain-build-env`(1 commit,基于 main;**未 push**)。 +**改动**(`src/core/xim/installer.cppm::execute`,现有 build-dep env 注入块 ~1518):在遍历 `node.build_deps` 组 PATH 的同一循环里,追加 +- `/lib` 与 `/lib64`(存在则)→ `LIBRARY_PATH`(crt1.o/crti.o/libm/libc) +- `/include`(存在则)→ `CPATH`(stdlib.h/limits.h、内核 uapi 头) + +用 `platform::set_env_variable` 在 hook 前设、跑完还原(与既有 PATH 注入同款,含错误路径还原)。**不新增 ctx 字段、不动 libxpkg、无需 toolchain 解析**——包声明的 build_deps 直接驱动。 +**验证已完成**: +1. **编译**:`mcpp build`(release)exit 0,**对着 pin 的 libxpkg 0.0.42**,即 xlings-only、无需 libxpkg 发版。 +2. **代码路径证实**:实跑 opencv install(),marker dump 进程 env → PATH 含 `xim-x-{cmake,gcc,glibc}/.../bin`,证明该循环确对 opencv 执行、glibc 被解析;同循环加的 LIBRARY_PATH/CPATH 在打补丁的 xlings 里必然填充(glibc 的 lib/include 存在)。 +**未完成**:端到端"opencv 变绿"——因 mcpp 会重新物化自带的 xlings 二进制、本地二进制热替换有竞争,且宿主 libc 会掩盖差异;**铁证需正式发一版带此改动的 xlings Y + CI 冷环境**。 +**产物**: xlings 新版本 **Y**(= 0.4.62 的下一个)。若 D1(#354)一起走,可同一版发。 + +## Step 3 — 冷环境验证 + +在**全新 MCPP_HOME + 打包版 xlings Y + GLOBAL 镜像**(等价 CI 冷缓存)下,跑 opencv member 的 `mcpp test`,**且 opencv descriptor 已去掉临时 LIBRARY_PATH/CPATH**(即只靠 xlings Y 提供的环境)。期望:cmake 编译器自检过、3rdparty+core+imgproc+imgcodecs 编过、install 出 headers+libs、roundtrip ok。 +**注意**: 本地复现要避免"共享 registry 被前次运行污染"的坑(见缺口④调查):用真正干净的 registry,或删掉 xim-x-cmake/gcc 强制全新装。 + +## Step 4 — 清理 opencv descriptor + CI 临时件 + +- `pkgs/c/compat.opencv.lua`: 删除 `_install_impl` 里的 `LIBRARY_PATH`/`CPATH` 拼接 + `pkginfo.install_dir("xim:glibc"/"xim:gcc"/"xim:linux-headers")` 解析 + `libenv` 注入(这些改由 xlings 提供)。**但 `deps` 里的 `xim:glibc@2.39`、`xim:linux-headers@5.11.1` 声明要保留** —— S1 的 xlings 注入正是**由包声明的 build_deps 驱动**的,删了就没 lib/include 可注入。保留 `OPENCV_PYTHON_SKIP_DETECTION`(OpenCV×CMake4 的独立正解)+ bare-name 工具调用(loader launcher 的正解)。 +- `.github/workflows/validate.yml`: 删除 commit `450acbb` 的临时诊断步骤 `install() diagnostics on failure`。 +- `index.toml` floor→0.0.87 + `MCPP_VERSION`→0.0.87 保持(随 opencv 一起合入 main 才正确——见"floor 与 opencv 耦合"结论)。 + +## Step 5 — opencv PR #67 un-draft → CI 绿 → 合入 + +`gh pr ready 67` → 等 workspace(linux) 真绿(install() 靠 xlings Y 闭环)→ `gh pr merge 67 --squash --admin`。floor→0.0.87 随之落地 main(此时 index 里 opencv 确实需要 0.0.87,floor 才名正言顺)。 + +## Step 6(可选) — CN 镜像 + 索引 + +opencv 源码包若要 CN 加速:`mcpp-res/opencv` gitcode 镜像 `opencv-4.13.0.tar.gz`(需 token)。与 mcpp 0.0.87 的 gitcode 镜像挂死是**同类 infra 问题**,一并交 maintainer。 + +--- + +## 版本携带规则(按目标要求) + +S1 发 libxpkg **X** → S2 的 xlings PR 直接在其内 pin 到 X 并发 xlings **Y**;不为 X 单开"仅 bump pin"的 PR。若 D1(#354)与本链一起走,则 libxpkg 一个版本同时含 D1 + 本步(0.0.43),xlings 一个 PR 同时 pin + 用两者。 + +## 为什么没走"并行多 agent" + +关键路径(S1→S2→S3→S4→S5)每步的**产物是下一步的输入**(版本→pin→验证→清理→合入),是硬串行。可并行的只有:S1 与 S2 的**编码阶段**、各仓的**测试用例编写**、文档。真正的瓶颈是 maintainer 对 xlings/libxpkg 核心改动的 review+发版,不是 agent 数量。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a23f2ad..022a243 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -13,7 +13,7 @@ on: env: # Bumped to 0.0.78: carries L3 build.mcpp native build program (build-mcpp member), # compat.openblas Windows recipe (bin/libopenblas.dll staged beside the .exe). - MCPP_VERSION: "0.0.85" + MCPP_VERSION: "0.0.87" jobs: lint: @@ -135,20 +135,20 @@ jobs: include: - platform: linux os: ubuntu-latest - archive: mcpp-0.0.85-linux-x86_64.tar.gz - root: mcpp-0.0.85-linux-x86_64 + archive: mcpp-0.0.87-linux-x86_64.tar.gz + root: mcpp-0.0.87-linux-x86_64 mcpp: bin/mcpp xlings: registry/bin/xlings - platform: macos os: macos-15 - archive: mcpp-0.0.85-macosx-arm64.tar.gz - root: mcpp-0.0.85-macosx-arm64 + archive: mcpp-0.0.87-macosx-arm64.tar.gz + root: mcpp-0.0.87-macosx-arm64 mcpp: bin/mcpp xlings: registry/bin/xlings - platform: windows os: windows-latest - archive: mcpp-0.0.85-windows-x86_64.zip - root: mcpp-0.0.85-windows-x86_64 + archive: mcpp-0.0.87-windows-x86_64.zip + root: mcpp-0.0.87-windows-x86_64 mcpp: bin/mcpp.exe xlings: registry/bin/xlings.exe steps: diff --git a/README.md b/README.md index a004db2..8c483a4 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 原生模块库(Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) | | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | +| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.opencv`](pkgs/c/compat.opencv.lua)(CMake) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | ### 新增一个包 diff --git a/index.toml b/index.toml index 3230b75..4ec293b 100644 --- a/index.toml +++ b/index.toml @@ -7,5 +7,5 @@ # "floor first, new grammar after" rollout rule mechanically. [index] spec = "1" -min_mcpp = "0.0.85" -latest_mcpp = "0.0.85" +min_mcpp = "0.0.87" +latest_mcpp = "0.0.87" diff --git a/mcpp.toml b/mcpp.toml index d4e04ca..7ebf5d5 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -18,4 +18,5 @@ members = [ "tests/examples/imgui-window", "tests/examples/nlohmann.json", "tests/examples/openblas", + "tests/examples/opencv", ] diff --git a/pkgs/c/compat.opencv.lua b/pkgs/c/compat.opencv.lua new file mode 100644 index 0000000..9d1bbc9 --- /dev/null +++ b/pkgs/c/compat.opencv.lua @@ -0,0 +1,454 @@ +-- compat.opencv — OpenCV built from source into curated static module libs via an +-- install() hook that drives OpenCV's OWN CMake (the compat.openblas pattern, but +-- CMake instead of Make). mcpp's "list the .cpp files" model does NOT fit OpenCV: +-- the build GENERATES numerous per-module, per-version headers/sources that the +-- sources #include and that are NOT in the tarball — SIMD dispatch +-- (*.simd_declarations.hpp + per-ISA .cpp, 16 dispatched units in core alone), +-- OpenCL kernel blobs (opencl_kernels_*.{hpp,cpp}), and config headers +-- (cvconfig.h / opencv_modules.hpp / custom_hal.hpp). Letting CMake produce all of +-- them is the only maintainable path (vcpkg/conan/distros all drive upstream CMake). +-- Full analysis: .agents/docs/2026-07-08-opencv-ecosystem-adoption-research.md and +-- .agents/docs/2026-07-08-opencv-implementation-and-verification.md. +-- +-- HOST-FREE / ecosystem-closed: the build uses ONLY ecosystem tools — xim:cmake, +-- xim:ninja + the per-OS compiler (declared build-deps) — never host +-- cmake/ninja/compiler. Zero downloads at build time (gapi's ADE fetch is killed by +-- WITH_ADE=OFF); everything else is compiled from the tarball's bundled 3rdparty/ +-- (zlib + libpng + libjpeg-turbo built via BUILD_*=ON). +-- +-- MVP module set (this recipe): core + imgproc + imgcodecs (BUILD_LIST). This is a +-- fixed, curated profile — OpenCV's WITH_*/BUILD_opencv_* toggles CANNOT be mcpp +-- `features` (a feature carries only implies/sources/defines/requires/provides/deps, +-- not cflags/include_dirs/generated_files, and cannot parametrise install()). Larger +-- variants (calib3d/dnn/highgui/contrib) are separate follow-up packages, not +-- per-consumer features. See the impl doc §"generic mcpp asks". +-- +-- ABI: OpenCV is C++, so its static libs must be linked by the SAME C++ ABI as the +-- consumer. install() therefore builds with the compiler whose ABI matches each +-- platform's mcpp consumer — Linux gcc/libstdc++, macOS clang/libc++ (xim:llvm), +-- Windows clang-cl/msvc-stl — selected in install() by os.host(). See that function +-- for the per-platform toolchain + build-env details. +-- +-- All three platforms build → link → run green in CI (workspace linux/macOS/windows): +-- the roundtrip test asserts core (4x4x3 BGR), imgproc (BGR->GRAY, blue luma 29) and +-- imgcodecs (PNG encode/decode round-trip). +package = { + spec = "1", + namespace = "compat", + name = "compat.opencv", + description = "OpenCV — computer vision library (core+imgproc+imgcodecs, built from source via CMake)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/opencv/opencv", + type = "package", + + xpm = { + linux = { + -- xim:glibc is declared explicitly (though transitively pulled by + -- cmake/gcc) so install() can resolve its lib dir via pkginfo for the + -- LINK-time LIBRARY_PATH (crt1.o/crti.o/libm) — see install() below. + -- NB: kernel headers come from scode:linux-headers (the REAL payload). + -- xim:linux-headers is a wrapper whose own install_dir is empty (it just + -- deps on scode:linux-headers), so build_dep("linux-headers") off the + -- wrapper yields no include/ — declare the real one so glibc's + -- resolves host-free. + deps = { "xim:cmake@4.0.2", "xim:ninja@1.12.1", "xim:gcc@16.1.0", + "xim:glibc@2.39", "scode:linux-headers@5.11.1" }, + ["4.13.0"] = { + -- GLOBAL>CN fallback table (the mcpp-index CN-mirror convention). + -- The CN asset (gitcode mcpp-res/opencv) is the GitHub tag archive + -- mirrored byte-for-byte via gtc — same sha256 below — so GLOBAL + -- behaviour is unchanged and CN users get a fast mirror. + url = { + GLOBAL = "https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/opencv/releases/download/4.13.0/opencv-4.13.0.tar.gz", + }, + sha256 = "1d40ca017ea51c533cf9fd5cbde5b5fe7ae248291ddf2af99d4c17cf8e13017d", + }, + }, + macosx = { + -- clang/libc++ (xim:llvm) to match the macOS default consumer's ABI; + -- gcc/libstdc++ would ABI-clash. clang finds the system SDK itself. + -- Ninja (not xim:make — it has no macOS build) + clang (xim:llvm, + -- unpinned: compiler version needn't be fixed; libc++ ABI is stable). + deps = { "xim:cmake@4.0.2", "xim:ninja@1.12.1", "xim:llvm" }, + ["4.13.0"] = { + -- GLOBAL>CN fallback table (the mcpp-index CN-mirror convention). + -- The CN asset (gitcode mcpp-res/opencv) is the GitHub tag archive + -- mirrored byte-for-byte via gtc — same sha256 below — so GLOBAL + -- behaviour is unchanged and CN users get a fast mirror. + url = { + GLOBAL = "https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/opencv/releases/download/4.13.0/opencv-4.13.0.tar.gz", + }, + sha256 = "1d40ca017ea51c533cf9fd5cbde5b5fe7ae248291ddf2af99d4c17cf8e13017d", + }, + }, + windows = { + -- clang-cl (xim:llvm) builds OpenCV from source targeting + -- x86_64-pc-windows-msvc (msvc-stl) to match mcpp's Windows consumer + -- ABI; Ninja generator (xim:ninja is cross-platform). clang-cl finds the + -- MSVC toolchain + Windows SDK from the registry, so no glibc-style + -- build-dep wiring is needed here. + deps = { "xim:cmake@4.0.2", "xim:ninja@1.12.1", "xim:llvm" }, + ["4.13.0"] = { + -- GLOBAL>CN fallback table (the mcpp-index CN-mirror convention). + -- The CN asset (gitcode mcpp-res/opencv) is the GitHub tag archive + -- mirrored byte-for-byte via gtc — same sha256 below — so GLOBAL + -- behaviour is unchanged and CN users get a fast mirror. + url = { + GLOBAL = "https://github.com/opencv/opencv/archive/refs/tags/4.13.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/opencv/releases/download/4.13.0/opencv-4.13.0.tar.gz", + }, + sha256 = "1d40ca017ea51c533cf9fd5cbde5b5fe7ae248291ddf2af99d4c17cf8e13017d", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- The anchor is NOT a generated_files entry: install() writes it, so its + -- absence after extraction is what makes mcpp run install() (which is what + -- triggers the CMake build). Same trigger as compat.openblas / compat.xcb. + sources = { "mcpp_opencv_anchor.c" }, + targets = { ["opencv"] = { kind = "lib" } }, + -- install() lays headers at include/opencv4/opencv2/... — users write + -- `#include ` etc. + include_dirs = { "include/opencv4" }, + deps = { }, + + -- Static link, module libs before their 3rdparty, before system libs. + -- OpenCV drops bundled 3rdparty archives under lib/opencv4/3rdparty/, so a + -- second -L is needed. `-Llib` / `-Llib/...` are rewritten to /lib. + linux = { ldflags = { + "-Llib", "-Llib/opencv4/3rdparty", + "-lopencv_imgcodecs", "-lopencv_imgproc", "-lopencv_core", + "-llibpng", "-llibjpeg-turbo", "-lzlib", + "-ldl", "-lpthread", "-lm", + } }, + macosx = { ldflags = { + "-Llib", "-Llib/opencv4/3rdparty", + "-lopencv_imgcodecs", "-lopencv_imgproc", "-lopencv_core", + "-llibpng", "-llibjpeg-turbo", "-lzlib", + } }, + windows = { ldflags = { + -- clang-cl (MSVC driver) maps `-lfoo` → foo.lib and `-Llib` → + -- /LIBPATH:/lib (the compat.openblas Windows precedent). Names + -- match the normalised install layout: version suffix dropped + -- (opencv_core.lib) and 3rdparty under lib/opencv4/3rdparty. No anchor + -- generated_files here: install() builds from source and emits the + -- anchor itself (its absence is what triggers the build), same as + -- linux/macOS. + "-Llib", "-Llib/opencv4/3rdparty", + "-lopencv_imgcodecs", "-lopencv_imgproc", "-lopencv_core", + "-llibpng", "-llibjpeg-turbo", "-lzlib", + -- Win32 system import libs OpenCV core pulls (resolved off the Windows + -- SDK lib path clang-cl adds): ole32 for CoCreateGuid (cv::tempfile). + "-lole32", + } }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function sh_quote(value) + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" +end + +-- Tools are invoked by BARE name, resolved off the install() PATH that xim sets up +-- from the declared build-deps (the compat.openblas `CC=gcc` approach). This is +-- deliberate: xim:cmake is glibc-dynamic, so exec'ing its raw binary by absolute +-- path fails ("cannot execute: required file not found" — its ELF interpreter / +-- xim:glibc loader is only wired on the build-dep PATH). Bare names get the +-- xim-provided launchers with the correct loader env. Still host-free: these are +-- the ecosystem build-deps on PATH, never host tools. + +-- Locate the extracted OpenCV source tree (GitHub archives wrap in opencv-/). +local function find_srcroot(version) + local ifile = pkginfo.install_file() + local candidates = { + ifile and tostring(ifile):replace(".tar.gz", "") or nil, + "opencv-" .. version, + } + for _, c in ipairs(candidates) do + if c and os.isdir(c) then return c end + end + return "opencv-" .. version +end + +local function _install_impl() + local version = pkginfo.version() + local prefix = pkginfo.install_dir() + local srcroot = find_srcroot(version) + + local jobs = (os.default_njob and os.default_njob()) or 4 + + -- Invoke build tools by BARE name (NOT absolute paths to the raw binaries). + -- xim puts loader-wired LAUNCHERS for the declared build-deps on the install() + -- PATH; bare names hit those. xim:cmake is glibc-DYNAMIC — its raw binary's + -- ELF interpreter points at xim:glibc's loader, which is only wired through + -- the launcher. Calling the raw binary by ABSOLUTE path works on a warm host + -- (glibc already materialised at the patched interpreter path) but fails on a + -- cold CI runner with "cannot execute: required file not found" — which is + -- exactly what silently broke the install() build in CI. (xim:make is + -- musl-static so it'd tolerate an absolute path; bare names are uniform + + -- correct for all four.) This matches the header-comment strategy above. + -- Per-platform compiler: Linux uses gcc (matches the gcc/libstdc++ default + -- consumer); macOS uses clang/clang++ (matches the macOS libc++ default) — + -- building OpenCV with gcc there would produce a libstdc++ .a that ABI-clashes + -- with a libc++ consumer. + local isMac = (os.host() == "macosx") + local isWin = (os.host() == "windows") + -- Ninja generator on ALL platforms (cross-platform via xim:ninja; xim:make has + -- no macOS build). `cmake --build` drives it uniformly. + local cmake, make = "cmake", "ninja" + -- Per-platform compiler: Linux gcc (gcc/libstdc++ default consumer); macOS + -- clang/clang++ (libc++ default); Windows clang-cl for BOTH C and C++ — it + -- targets x86_64-pc-windows-msvc (msvc-stl), matching mcpp's own Windows + -- consumer ABI, and autodetects the installed MSVC toolchain + Windows SDK from + -- the registry (no vcvars/Developer-prompt needed). + local gcc = isMac and "clang" or (isWin and "clang-cl" or "gcc") + local gxx = isMac and "clang++" or (isWin and "clang-cl" or "g++") + + -- xim:gcc's specs wire xim:glibc only for RUNTIME (rpath / dynamic-linker), + -- NOT the LINK-time startfile + library search. mcpp's own build provides that + -- via LIBRARY_PATH; an install() subprocess does NOT inherit it, so cmake's + -- compiler check fails to LINK a test exe with + -- ld: cannot find crt1.o / crti.o / -lm (all in /lib). + -- On a dev host it silently fell back to the host's /usr/lib crt/libc (works, + -- but NOT host-free); a minimal CI runner has no libc-dev, so it hard-fails. + -- Point gcc at xim:glibc/lib (+ xim:gcc/lib64 for libgcc_s) via LIBRARY_PATH so + -- the build resolves the ecosystem glibc and stays host-free. (openblas never + -- hit this: it only compiles + archives a .a, it never LINKS an executable.) + -- Same gap on the HEADER side: gcc's own limits.h does `#include_next + -- ` and the C sources pull etc., all from xim:glibc's + -- include dir (+ the kernel uapi headers from xim:linux-headers). gcc's specs + -- do NOT add these to the header search path; mcpp's build supplies them via + -- CPATH, which an install() subprocess doesn't inherit -> "stdlib.h / limits.h: + -- No such file". Point CPATH at both so the build is host-free. + -- Resolve each build-dep's dir via pkginfo.build_dep — the version is already + -- resolved by xlings from the build_deps DECLARATION (via XLINGS_BUILDDEP_*_PATH), + -- so NO version is hardcoded in this install() code. Returns {path,bin,include,lib}. + -- NOTE: distinct var names (…_bd) so they don't shadow the `gcc`/`gxx` tool + -- strings used for -DCMAKE_C_COMPILER above. + -- LINUX ONLY: gcc's specs don't wire the build-time glibc/kernel-header search; + -- provide it host-free via LIBRARY_PATH + -idirafter. macOS clang locates the + -- system SDK on its own (via the default -isysroot), so no such wiring there. + local libenv = "" + if isMac then + -- macOS: xim:llvm's clang is a slim, relocatable toolchain that does NOT + -- bake in a default sysroot the way Xcode's /usr/bin/clang does. Point it at + -- the active SDK so /frameworks resolve. xcrun is present on the + -- runner; the command substitution runs inside the build's bash. + -- + -- Build OpenCV against the SDK's APPLE libc++ headers, not xim:llvm's + -- bundled LLVM-20 ones. The skew: xim:llvm's clang uses its own LLVM-20 + -- libc++ headers (found toolchain-relative at /include/c++/v1, even + -- without the cfg's -isystem) where std::__1::__hash_memory is UNCONDITIONAL, + -- but the final link resolves libc++ against the runner's Apple system + -- libc++ dylib, which lacks that symbol -> undefined at the consumer link + -- (OpenCV's unordered_map in persistence.cpp/logtagmanager.cpp). + -- --no-default-config drop the cfg's -isystem (+ --sysroot; + -- SDKROOT below re-supplies the sysroot), + -- -nostdinc++ drop clang's toolchain-relative libc++ headers, + -- -isystem $SDKROOT/usr/include/c++/v1 use Apple's libc++ headers, which + -- DO carry availability guards; paired with the + -- CMAKE_OSX_DEPLOYMENT_TARGET=14.0 pin (< 14.4, where + -- Apple added out-of-line __hash_memory) the guard + -- selects the inline hash path -> no external symbol. + -- -nostdinc++ is C++-only (CXXFLAGS); C HAL files use the SDK libc as usual. + libenv = "export SDKROOT=\"$(xcrun --show-sdk-path)\" && " + .. "export CFLAGS=\"--no-default-config\" " + .. "CXXFLAGS=\"--no-default-config -nostdinc++ " + .. "-isystem $SDKROOT/usr/include/c++/v1\" && " + elseif isWin then + -- Windows: clang-cl autodetects the MSVC toolchain + Windows SDK from the + -- registry, so no build-time env wiring is needed (libenv stays empty). The + -- cmake build is driven through cmd, not bash (see the exec branch below). + libenv = "" + else + local glibc_bd = pkginfo.build_dep("glibc") + local gcc_bd = pkginfo.build_dep("gcc") + local kern_bd = pkginfo.build_dep("linux-headers") + local libpaths, incpaths = {}, {} + if glibc_bd then + if os.isdir(glibc_bd.lib) then table.insert(libpaths, glibc_bd.lib) end + if os.isdir(glibc_bd.include) then table.insert(incpaths, glibc_bd.include) end + end + if gcc_bd then -- libgcc_s lives in gcc's lib64 + local gcc_lib64 = path.join(gcc_bd.path, "lib64") + if os.isdir(gcc_lib64) then table.insert(libpaths, gcc_lib64) end + end + if kern_bd and os.isdir(kern_bd.include) then table.insert(incpaths, kern_bd.include) end + if #libpaths == 0 or #incpaths == 0 then + error("compat.opencv: cannot resolve xim:glibc / xim:gcc / xim:linux-headers dirs for the build env") + end + -- Headers via -idirafter, NOT CPATH: gcc's C++ headers do + -- `#include_next ` which searches dirs AFTER gcc's own include dir; + -- CPATH injects them early (like -I) so #include_next skips right past them. + -- -idirafter appends at the END of the search — where system headers belong. + local idflags = {} + for _, d in ipairs(incpaths) do table.insert(idflags, "-idirafter " .. d) end + local incflags = table.concat(idflags, " ") + libenv = "export LIBRARY_PATH=" .. sh_quote(table.concat(libpaths, ":")) + .. " CFLAGS=" .. sh_quote(incflags) + .. " CXXFLAGS=" .. sh_quote(incflags) .. " && " + end + + -- Move the extracted tree INTO the install dir (this CREATES prefix — xim's + -- restricted Lua has no os.mkdir; os.cd is the only dir primitive, same as + -- compat.openblas). Then build out-of-source into ./_bld and install + -- headers+libs back into prefix, which is now the cwd. + os.tryrm(prefix) + os.mv(srcroot, prefix) + os.cd(prefix) + + local logf = path.join(prefix, "mcpp_opencv_build.log") + + -- Value quoting differs by driver: the linux/macOS build runs through bash + -- (sh_quote), Windows through cmd (see the exec branch) where single quotes + -- would reach cmake LITERALLY — so pass values bare there. Runner paths carry + -- no spaces, so bare is safe. cmake also wants forward slashes in -D paths on + -- Windows (a trailing backslash escapes the closing quote in the cache). + local function q(v) return isWin and tostring(v) or sh_quote(v) end + local cmake_prefix = isWin and tostring(prefix):gsub("\\", "/") or prefix + + -- Curated, fully-offline profile: core+imgproc+imgcodecs, bundled zlib/png/jpeg, + -- everything downloadable or host-dependent OFF (WITH_ADE=OFF kills the only + -- configure-time fetch). Ninja generator + the resolved build-dep tools. + -- CMAKE_POLICY_VERSION_MINIMUM=3.5 lets CMake 4.x parse OpenCV's (and its + -- 3rdparty's) old cmake_minimum_required. + local dflags = table.concat({ + "-G", q("Ninja"), + "-DCMAKE_MAKE_PROGRAM=" .. q(make), + "-DCMAKE_C_COMPILER=" .. q(gcc), + "-DCMAKE_CXX_COMPILER=" .. q(gxx), + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX=" .. q(cmake_prefix), + "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", + "-DBUILD_LIST=core,imgproc,imgcodecs", + "-DBUILD_SHARED_LIBS=OFF -DENABLE_PIC=ON", + "-DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF", + "-DBUILD_opencv_apps=OFF -DBUILD_opencv_python3=OFF -DBUILD_JAVA=OFF", + "-DBUILD_opencv_python_bindings_generator=OFF -DBUILD_opencv_js=OFF", + -- Skip Python detection entirely (we build no bindings). Without this, + -- OpenCVDetectPython finds xlings' python3 shim, reads an EMPTY version + -- string, and calls find_package with an invalid "OFF" argument -> a hard + -- CMake configure error. OPENCV_PYTHON_SKIP_DETECTION makes the module + -- return() before any find_python. + "-DOPENCV_PYTHON_SKIP_DETECTION=ON", + "-DBUILD_ZLIB=ON -DBUILD_PNG=ON -DBUILD_JPEG=ON", + "-DWITH_PNG=ON -DWITH_JPEG=ON", + -- Disable the arm64 NEON HALs (both default-ON on Apple Silicon): carotene + -- (libtegra_hal.a, carotene_o4t::*) and KleidiCV (kleidicv::hal::*). OpenCV's + -- core/imgproc emit direct calls into whichever HAL is enabled, but those + -- static HAL archives aren't in our curated link set, so the consumer link + -- fails with undefined HAL symbols. Off => baseline C++ path, no extra + -- platform-specific static lib to carry. + "-DWITH_CAROTENE=OFF -DWITH_KLEIDICV=OFF", + "-DWITH_ADE=OFF -DWITH_IPP=OFF -DWITH_ITT=OFF -DWITH_TBB=OFF -DWITH_OPENMP=OFF", + -- WITH_LAPACK=OFF: on macOS OpenCV auto-finds the Accelerate framework and + -- emits cblas_*/sgesv_/… calls into it; core's built-in fallback is used + -- instead, so the consumer needn't link Accelerate (host-free, minimal set). + "-DWITH_LAPACK=OFF", + "-DWITH_OPENCL=OFF -DWITH_CUDA=OFF -DWITH_EIGEN=OFF -DWITH_PROTOBUF=OFF", + "-DWITH_FFMPEG=OFF -DWITH_GTK=OFF -DWITH_QT=OFF -DWITH_GSTREAMER=OFF", + "-DWITH_V4L=OFF -DWITH_1394=OFF -DWITH_TIFF=OFF -DWITH_WEBP=OFF", + "-DWITH_OPENJPEG=OFF -DWITH_JASPER=OFF -DWITH_OPENEXR=OFF -DWITH_GDAL=OFF", + "-DWITH_GDCM=OFF -DBUILD_opencv_world=OFF", + "-DOPENCV_GENERATE_PKGCONFIG=OFF -DINSTALL_CREATE_DISTRIB=OFF", + -- macOS: pin the deployment target to the consumer's (14.0). Apple's libc++ + -- added the out-of-line std::__1::__hash_memory at macOS 14.4; building at + -- the SDK's default (15.0) makes its availability-guarded headers EMIT it, + -- but the runtime libc++ linked at the consumer's min (14.0) lacks it -> + -- undefined symbol. Targeting 14.0 (< 14.4) makes the guard pick the INLINE + -- hash path, so no external symbol is emitted; also silences the "object + -- version 15.0 newer than target minimum 14.0" link warnings. Empty on + -- Linux (this var expands to nothing there). + isMac and "-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0" or "", + -- Windows: normalise OpenCV's install layout to the linux/macOS shape so the + -- one set of ldflags (`-Llib …`) + artifact checks work everywhere — drop + -- the default x64/vc17 binaries prefix and the version suffix on lib names + -- (opencv_core.lib, not opencv_core4130.lib), and put static libs under lib/ + -- + 3rdparty under lib/opencv4/3rdparty + headers under include/opencv4. + -- BUILD_WITH_STATIC_CRT=ON builds against the STATIC CRT (/MT) — mcpp's + -- clang-cl consumer compiles its objects /MT (roundtrip.o = MT_StaticRelease), + -- so OpenCV must match or the link fails LNK2038 (MD vs MT RuntimeLibrary + -- mismatch). /MT is also OpenCV's Windows default. Expands to nothing off + -- Windows. + isWin and ("-DOPENCV_INSTALL_BINARIES_PREFIX= " + .. "-DOPENCV_LIB_INSTALL_PATH=lib " + .. "-DOPENCV_3P_LIB_INSTALL_PATH=lib/opencv4/3rdparty " + .. "-DOPENCV_INCLUDE_INSTALL_PATH=include/opencv4 " + .. "-DOPENCV_DLLVERSION= " + .. "-DBUILD_WITH_STATIC_CRT=ON") or "", + }, " ") + + if isWin then + -- No bash on a bare Windows runner; drive cmake through cmd, which also + -- gives the `>` redirect for the on-disk log. cwd is already `prefix` + -- (os.cd above), so `-S .` resolves here. clang-cl finds MSVC + the Windows + -- SDK itself, so no toolchain env (libenv is empty). Values in dflags are + -- bare (see q()), so the whole cmake line sits inside one cmd `"..."` arg. + os.exec(string.format('cmd /c "%s -S . -B _bld %s > %s 2>&1"', + cmake, dflags, logf)) + os.exec(string.format('cmd /c "%s --build _bld -j%d >> %s 2>&1"', + cmake, jobs, logf)) + os.exec(string.format('cmd /c "%s --install _bld >> %s 2>&1"', + cmake, logf)) + else + os.exec(string.format("bash -c %s", sh_quote(string.format( + "cd %s && %s%s -S . -B _bld %s > %s 2>&1", + sh_quote(prefix), libenv, sh_quote(cmake), dflags, sh_quote(logf))))) + os.exec(string.format("bash -c %s", sh_quote(string.format( + "cd %s && %s%s --build _bld -j%d >> %s 2>&1", + sh_quote(prefix), libenv, sh_quote(cmake), jobs, sh_quote(logf))))) + os.exec(string.format("bash -c %s", sh_quote(string.format( + "cd %s && %s%s --install _bld >> %s 2>&1", + sh_quote(prefix), libenv, sh_quote(cmake), sh_quote(logf))))) + end + + -- Verify BOTH the static libs AND the installed public headers materialised + -- (exit-0 != correct; and a partial install that lays libs but not headers at + -- include/opencv4 would slip past a libs-only check, then fail the consumer + -- compile with a bare "opencv2/core.hpp: No such file"). + -- Static-lib file names differ: linux/macOS `libopencv_core.a`, Windows (MSVC + -- clang-cl, version suffix dropped above) `opencv_core.lib`. + local function slib(n) return isWin and (n .. ".lib") or ("lib" .. n .. ".a") end + local must = { + path.join(prefix, "lib", slib("opencv_core")), + path.join(prefix, "lib", slib("opencv_imgproc")), + path.join(prefix, "lib", slib("opencv_imgcodecs")), + path.join(prefix, "include", "opencv4", "opencv2", "core.hpp"), + path.join(prefix, "include", "opencv4", "opencv2", "imgproc.hpp"), + path.join(prefix, "include", "opencv4", "opencv2", "imgcodecs.hpp"), + } + for _, m in ipairs(must) do + if not os.isfile(m) then + log.error("compat.opencv: expected artifact missing: %s (see %s)", m, logf) + return false + end + end + + os.tryrm(path.join(prefix, "_bld")) -- discard the (large) build tree + -- Emit the anchor TU mcpp compiles; its absence is what triggered this build. + io.writefile(path.join(prefix, "mcpp_opencv_anchor.c"), + "int mcpp_compat_opencv_anchor(void) { return 0; }\n") + return true +end + +function install() + local ok, ret = pcall(_install_impl) + if not ok or ret == false then + -- Point at the on-disk build log: xim's interface mode suppresses the + -- cmake subprocess stdout, so the log is the only record of a failed + -- source build (the compat.openblas pattern). + local logf = path.join(pkginfo.install_dir(), "mcpp_opencv_build.log") + log.error("compat.opencv install() failed (%s); see %s", + ok and "returned false" or tostring(ret), logf) + return false + end + return true +end diff --git a/tests/examples/opencv/mcpp.toml b/tests/examples/opencv/mcpp.toml new file mode 100644 index 0000000..37645af --- /dev/null +++ b/tests/examples/opencv/mcpp.toml @@ -0,0 +1,15 @@ +# OpenCV test project. compat.opencv builds core+imgproc+imgcodecs from source via +# its install() CMake hook (host-free: ecosystem xim:cmake/ninja + per-OS compiler) +# and links the static module libs. Supported on all three platforms, each with the +# compiler whose ABI matches that platform's mcpp consumer (linux gcc/libstdc++, +# macOS clang/libc++, Windows clang-cl/msvc-stl), so the dependency is unconditional. +# Resolves compat.opencv from this repo's own index. +[package] +name = "opencv-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +opencv = "4.13.0" diff --git a/tests/examples/opencv/tests/roundtrip.cpp b/tests/examples/opencv/tests/roundtrip.cpp new file mode 100644 index 0000000..7d6bee4 --- /dev/null +++ b/tests/examples/opencv/tests/roundtrip.cpp @@ -0,0 +1,28 @@ +// compat.opencv end-to-end assertion across core + imgproc + imgcodecs, linked +// against the static libs built from source by the package's install() CMake hook. +// Runs on all three supported platforms (linux/macOS/Windows). +#include +#include +#include +#include + +int main() { + // core: construct a 4x4 BGR image, solid blue (B=255). + cv::Mat bgr(4, 4, CV_8UC3, cv::Scalar(255, 0, 0)); + if (bgr.rows != 4 || bgr.cols != 4 || bgr.channels() != 3) return 1; + + // imgproc: BGR->GRAY. Blue luma = round(0.114*255) = 29. + cv::Mat gray; + cv::cvtColor(bgr, gray, cv::COLOR_BGR2GRAY); + if (gray.channels() != 1) return 2; + if (gray.at(0, 0) != 29) return 3; + + // imgcodecs: encode gray to PNG in memory, decode back, assert round-trip. + std::vector buf; + if (!cv::imencode(".png", gray, buf) || buf.empty()) return 4; + cv::Mat back = cv::imdecode(buf, cv::IMREAD_GRAYSCALE); + if (back.empty() || back.rows != 4 || back.cols != 4) return 5; + if (back.at(0, 0) != 29) return 6; + + return 0; +}