From 90e8b01cabdd39af799f1154272fff9b1b93ddf4 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 28 Jun 2026 20:30:43 +0800 Subject: [PATCH 1/2] feat(pkgs): add Eigen 5.0.1 (compat, header-only) + CN mirror MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - compat.eigen @5.0.1 — latest Eigen (libeigen/eigen on GitLab; 5.x line). Header-only: include_dirs={"*"} exposes the source root so `#include ` works out of the box; a trivial anchor TU gives mcpp a buildable lib target (same shape as compat.opengl/khrplatform). Both the stable `Eigen/` modules and the experimental `unsupported/Eigen/` modules resolve from the single root (verified: MatrixFunctions + AutoDiff compile). - CN mirror mcpp-res/eigen (GitCode), byte-identical to the GitLab archive (sha256 e9c326dc…, http 200). - feature mechanism: deliberately NOT used — analysis recorded in the descriptor + design doc. mcpp 0.0.68 features gate sources only; Eigen is header-only with no gateable source, and `unsupported/` shares the include root with core so it can't be hidden behind a sources-only feature. Eigen's other knobs (EIGEN_MPL2_ONLY, …) are compile defines the feature table can't carry. Documented for a future define-capable feature gate. - tests/examples/eigen/ minimal project (2x2 linear-algebra round-trip), wired into the per-package CI selector (detect → smoke-examples (eigen)). Verified locally on mcpp 0.0.68 (CI version, MCPP_INDEX_MIRROR=GLOBAL): `tests/run_example.sh eigen` → `eigen ok=1 y=[3 7] det=-2 dot=5`. Design: .agents/docs/2026-06-28-add-eigen-plan.md --- .agents/docs/2026-06-28-add-eigen-plan.md | 79 ++++++++++++++++++++ README.md | 1 + pkgs/c/compat.eigen.lua | 88 +++++++++++++++++++++++ tests/examples/eigen/mcpp.toml | 18 +++++ tests/examples/eigen/src/main.cpp | 29 ++++++++ 5 files changed, 215 insertions(+) create mode 100644 .agents/docs/2026-06-28-add-eigen-plan.md create mode 100644 pkgs/c/compat.eigen.lua create mode 100644 tests/examples/eigen/mcpp.toml create mode 100644 tests/examples/eigen/src/main.cpp diff --git a/.agents/docs/2026-06-28-add-eigen-plan.md b/.agents/docs/2026-06-28-add-eigen-plan.md new file mode 100644 index 0000000..9addaeb --- /dev/null +++ b/.agents/docs/2026-06-28-add-eigen-plan.md @@ -0,0 +1,79 @@ +# 新增 Eigen 收录(compat 源码/header-only)+ GitCode CN 镜像方案 + +**日期**: 2026-06-28 +**本仓**: `mcpp-community/mcpp-index`(github 别名 `mcpplibs/mcpp-index`) +**参考**: PR #48(cJSON compat + nlohmann.json module + per-pkg CI) +**目标**: +1. 收录 [`libeigen/eigen`](https://gitlab.com/libeigen/eigen) 最新版 **5.0.1** —— header-only C++ 线性代数库, + **全兼容(compat)** 形态,用户 `#include ` 开箱即用。文件名 `pkgs/c/compat.eigen.lua`。 +2. 补 **GitCode CN 镜像** `mcpp-res/eigen`(`gitcode.com/mcpp-res/eigen/...`),用本仓 `tools/gtc` 推送。 +3. 评估 Eigen 是否有可走 mcpp **feature 机制** 的"类 feature"可选项(见 §3 结论:当前不适用,已说明原因)。 +4. 在 `tests/examples/eigen/` 放最小工程,CI 选跑(detect → `eigen` matrix job)。 + +--- + +## 1. 最新版本与上游布局(关键前置) + +- 最新 tag:`git ls-remote --tags` 显示 **5.0.1**(>3.4.x,Eigen 已跨大版本到 5.x;另有 5.0.0 / 3.4.1)。收录 `5.0.1`。 +- 上游托管在 **GitLab**(非 GitHub): + GLOBAL = `https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz`(归档 sha256 已实测两次稳定:`e9c326dc…`)。 +- tarball 单层 wrap `eigen-5.0.1/`,glob `*` 吸收。根目录含: + - `Eigen/` —— **稳定模块**(Dense / Core / LU / QR / SVD / Sparse / Geometry …) + - `unsupported/Eigen/` —— **实验模块**(CXX11/Tensor、AutoDiff、Splines、MatrixFunctions、FFT …) + - `blas/` `lapack/` —— Eigen 自带的 BLAS/LAPACK 实现(含 12 个 **Fortran `.f`**,需 Fortran 编译器) + - 许可:主体 **MPL-2.0**(另有少量 Apache/BSD/MINPACK 文件) +- **无 `.cppm`**:Eigen 5.0.1 仍是纯 header,未提供 C++ module 单元 → 走 compat header-only 形态(非 nlohmann 那种 module wrapper)。 + +## 2. descriptor 形态(header-only,参照 compat.opengl/khrplatform) + +Eigen 无可编译源(纯模板头),故: +- `include_dirs = {"*"}` 暴露 tarball 根 —— 即上游推荐放入 include path 的目录;`#include ` 与 + `#include ` 均可解析。 +- 用一个 trivial anchor TU(`mcpp_generated/eigen_anchor.c`)给 mcpp 一个可构建的 `lib` 目标(同 opengl/khrplatform)。 +- `language=c++23`、`c_standard=c11`(anchor 是 C)、`deps={}`。 + +## 3. feature 机制评估 —— 当前不适用(已记录原因) + +用户要求"如果 Eigen 有类似 feature 的东西,可走 mcpp feature 机制"。结论:**当前(mcpp 0.0.68)不适用**,原因经源码核实: + +- mcpp 包描述符的 `features` 表 **只能门控 `sources`**(feature 贡献额外源码 glob,默认排除、请求时编入 —— + 见 `compat.cjson` 的 `utils`、`compat.gtest` 的 `main`;源码 `manifest.cppm` 中 feature 子字段仅识别 `sources`, + 其余被 skip)。 +- Eigen 是 header-only,**没有可门控的可选源码**。其天然的"可选轴"是 `unsupported/` 实验模块,但它与 `Eigen/` + **同处 tarball 根** —— 任何暴露稳定核(`*`)的 include path 都不可避免地一并暴露 `unsupported/`(已实测: + `` / `` 直接可编)。**既无源可门控,也无法用 sources-only 的 + feature 把头藏起来**。 +- 其它 Eigen 开关(`EIGEN_MPL2_ONLY`、`EIGEN_USE_BLAS/LAPACKE`…)是编译 **define**,feature 表同样不能携带。 +- `blas/` 是唯一可门控的真实源,但需 Fortran(`.f`)+ 独立 lib 目标,feature 仅能往既有目标 *追加* 源 → 不匹配,过重。 + +→ 故本期 **暴露完整头集(core + unsupported)、不加 feature**,并在 descriptor 注释中完整记录该分析。 +若 mcpp 未来允许 feature 携带 define/cflags,`mpl2only`(→ `-DEIGEN_MPL2_ONLY`)是干净的接入点。 + +## 4. CN 镜像(gtc) + +repo 名 = 包名去 `compat.` 前缀 = `eigen`: +``` +gtc repo create mcpp-res/eigen +gtc repo push mcpp-res/eigen # 新仓需先有 main 分支才能发 release +gtc release publish mcpp-res/eigen --tag 5.0.1 --target main --asset eigen-5.0.1.tar.gz +``` +上传的就是 GLOBAL 同一个 tarball → CN/GLOBAL **byte-identical**(实测 sha 一致 + CN http 200)。 +CN url:`https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz`。 + +## 5. 最小工程 + CI + +- `tests/examples/eigen/{mcpp.toml, src/main.cpp}` —— `#include `,2x2 线代往返断言 + (`A*x`、determinant、dot、QR solve)。纯文本 include,不混 `import std;`。 +- `validate.yml` 的 detect 把 `compat.eigen.lua` → `eigen` → 命中 `tests/examples/eigen` → `smoke-examples (eigen)` 单跑。 +- `mirror-cn-reachable` 覆盖新 CN url。 + +## 6. 落地记录(2026-06-28) + +- CN 镜像:`mcpp-res/eigen@5.0.1` 已建 + 发布,CN 与 GLOBAL byte-identical(sha `e9c326dc…`),http 200。 +- 本地实测(mcpp **0.0.68**,与 CI 同版本,`MCPP_INDEX_MIRROR=GLOBAL`): + `tests/run_example.sh eigen` → `eigen ok=1 y=[3 7] det=-2 dot=5`,`OK: eigen`。 +- `unsupported/` 可达性单独实测:`g++ -std=c++23 -I` 编 ` + + + ` → rc=0。 +- 全量 lint 本地模拟通过(语法 / 必填字段 / 无前导 v / mirror url 检查)。 +- **坑**:compat 包目录按 **完整包名首字母** 归类 —— `compat.eigen` → `pkgs/c/`(不是短名 `eigen` 的 `pkgs/e/`), + 否则本地 path index 扫不到(`dependency 'compat.eigen': not found in local index`)。 diff --git a/README.md b/README.md index 1985f0e..0f7695c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ mcpp build # 自动拉取源码 + 构建 | `glfw` | 3.4 | GLFW 窗口与输入库(X11/null 后端源码构建) | | `gtest` | 1.15.2 | Google Test 测试框架 | | `cjson` | 1.7.19 | 超轻量 ANSI C JSON 解析库(`#include `,`compat` 源码构建) | +| `eigen` | 5.0.1 | C++ 模板线性代数库(header-only,`#include `;`unsupported/` 实验模块亦可用) | | `imgui` | 1.92.8 | Dear ImGui immediate-mode GUI 核心源码 | | `opengl` | 2026.05.31 | Khronos OpenGL API 头文件 | | `glx-runtime` | 2026.06.03 | Linux host GLVND/GLX/OpenGL runtime adapter | diff --git a/pkgs/c/compat.eigen.lua b/pkgs/c/compat.eigen.lua new file mode 100644 index 0000000..fe34fb7 --- /dev/null +++ b/pkgs/c/compat.eigen.lua @@ -0,0 +1,88 @@ +-- Form B inline descriptor for Eigen — a C++ template library for linear +-- algebra (matrices, vectors, numerical solvers, related algorithms). Eigen +-- is HEADER-ONLY: there is nothing to compile, so the package just exposes +-- the source tree's root on the include path (`#include ` etc.) +-- and carries a tiny anchor translation unit so mcpp still has a buildable +-- `lib` target (same shape as compat.opengl / compat.khrplatform). +-- +-- `include_dirs = {"*"}` points at the tarball root (`eigen-/`), which is +-- exactly the directory upstream tells you to put on the include path. That +-- makes BOTH the stable modules under `Eigen/` and the experimental modules +-- under `unsupported/Eigen/` (Tensor, AutoDiff, Splines, MatrixFunctions, …) +-- resolvable — see the note on features below for why `unsupported` is not a +-- separate opt-in here. +-- +-- On the `feature` mechanism (asked for, deliberately omitted — analysis): +-- mcpp's package-descriptor `features` table gates **sources only** (a +-- feature contributes extra source globs that are excluded by default and +-- compiled in when the feature is requested — see compat.cjson's `utils` +-- and compat.gtest's `main`). Eigen has no such optional *source*: it is +-- header-only. Its natural opt-in axis would be the `unsupported/` modules, +-- but those are headers that live BESIDE `Eigen/` under the same tarball +-- root, so any include path that exposes the stable core (`*`) inevitably +-- exposes `unsupported/` too — there is no source to gate and no way to +-- hide the headers behind a feature with the current (sources-only) gate. +-- Other Eigen knobs (EIGEN_MPL2_ONLY, EIGEN_USE_BLAS/LAPACKE, …) are +-- compile *defines*, which the feature table also cannot carry on mcpp +-- 0.0.68. So a feature here would be cosmetic/misleading; we expose the +-- full header set instead and document it. If mcpp later lets a feature +-- contribute defines/cflags, an `mpl2only` (-> -DEIGEN_MPL2_ONLY) feature +-- would be the clean fit. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs +-- the GitLab archive's `eigen-/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "compat.eigen", + description = "C++ template library for linear algebra (header-only)", + licenses = {"MPL-2.0"}, + repo = "https://gitlab.com/libeigen/eigen", + type = "package", + + xpm = { + linux = { + ["5.0.1"] = { + url = { + GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz", + }, + sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec", + }, + }, + macosx = { + ["5.0.1"] = { + url = { + GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz", + }, + sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec", + }, + }, + windows = { + ["5.0.1"] = { + url = { + GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz", + CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz", + }, + sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Tarball root: exposes `Eigen/` (stable) and `unsupported/Eigen/` + -- (experimental) to consumers writing `#include `. + include_dirs = { "*" }, + -- Header-only: a trivial anchor TU gives mcpp a buildable lib target. + generated_files = { + ["mcpp_generated/eigen_anchor.c"] = "int mcpp_compat_eigen_headers_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/eigen_anchor.c" }, + targets = { ["eigen"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/tests/examples/eigen/mcpp.toml b/tests/examples/eigen/mcpp.toml new file mode 100644 index 0000000..79799cf --- /dev/null +++ b/tests/examples/eigen/mcpp.toml @@ -0,0 +1,18 @@ +# Minimal example: Eigen consumed header-only via the compat source build. +# Run from this directory: mcpp run (index path is relative to repo root) +[package] +name = "eigen-example" +version = "0.1.0" + +[toolchain] +default = "gcc@16.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +eigen = "5.0.1" + +[targets.eigen-example] +kind = "bin" +main = "src/main.cpp" diff --git a/tests/examples/eigen/src/main.cpp b/tests/examples/eigen/src/main.cpp new file mode 100644 index 0000000..eab9316 --- /dev/null +++ b/tests/examples/eigen/src/main.cpp @@ -0,0 +1,29 @@ +// Eigen is header-only; the compat package puts the source tree root on the +// include path so `#include ` just works. Plain textual includes +// here (Eigen is not a module), so we do NOT mix in `import std;`. +#include +#include +#include + +int main() { + // A * x for a 2x2 system: [[1,2],[3,4]] * [1,1]^T = [3,7]^T + Eigen::Matrix2d A; + A << 1, 2, + 3, 4; + Eigen::Vector2d x(1.0, 1.0); + Eigen::Vector2d y = A * x; + + double det = A.determinant(); // 1*4 - 2*3 = -2 + double dot = x.dot(Eigen::Vector2d(2.0, 3.0)); // 1*2 + 1*3 = 5 + + // Solve A z = y, expect z back to [1,1]. + Eigen::Vector2d z = A.colPivHouseholderQr().solve(y); + + bool ok = y(0) == 3.0 && y(1) == 7.0 + && det == -2.0 && dot == 5.0 + && std::abs(z(0) - 1.0) < 1e-9 && std::abs(z(1) - 1.0) < 1e-9; + + std::printf("eigen ok=%d y=[%g %g] det=%g dot=%g\n", + ok, y(0), y(1), det, dot); + return ok ? 0 : 1; +} From 5e43683a62748808a99cbfeb42e5f309d881e1c5 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 28 Jun 2026 21:12:53 +0800 Subject: [PATCH 2/2] feat(pkgs): add Eigen `blas` feature (reference BLAS, no Fortran) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correcting the earlier "BLAS needs Fortran, too heavy → no feature" call: it was wrong. Eigen's `eigen_blas` library (blas/CMakeLists.txt EigenBlas_SRCS) builds from blas/*.cpp (5 files) + blas/f2c/*.c (18 f2c-translated C files); the only .f files live under blas/testing/ (the test suite) and are NOT part of the library. So it compiles with a plain C/C++ toolchain and fits mcpp's sources-only feature gate exactly like compat.cjson's `utils`. - compat.eigen: add `features = { blas = { sources = {"*/blas/*.cpp", "*/blas/f2c/*.c"} } }`. Off by default; `features = ["blas"]` compiles Eigen's reference BLAS into the eigen lib, exposing the standard Fortran-ABI symbols (sgemm_/dgemm_/ddot_/…). - example now opts into `blas` and calls dgemm_ to exercise the feature in CI alongside the header-only core path. Verified on mcpp 0.0.68: - with feature: `eigen ok=1 core=1 blas(dgemm)=1 C=[1 3 2 4]`. - without feature (negative): `undefined reference to 'dgemm_'` — gate works. Descriptor comment + design doc updated with the corrected analysis. --- .agents/docs/2026-06-28-add-eigen-plan.md | 47 ++++++++++------ README.md | 2 +- pkgs/c/compat.eigen.lua | 68 +++++++++++++++-------- tests/examples/eigen/mcpp.toml | 5 +- tests/examples/eigen/src/main.cpp | 38 +++++++++---- 5 files changed, 109 insertions(+), 51 deletions(-) diff --git a/.agents/docs/2026-06-28-add-eigen-plan.md b/.agents/docs/2026-06-28-add-eigen-plan.md index 9addaeb..a3ec08d 100644 --- a/.agents/docs/2026-06-28-add-eigen-plan.md +++ b/.agents/docs/2026-06-28-add-eigen-plan.md @@ -32,19 +32,32 @@ Eigen 无可编译源(纯模板头),故: - 用一个 trivial anchor TU(`mcpp_generated/eigen_anchor.c`)给 mcpp 一个可构建的 `lib` 目标(同 opengl/khrplatform)。 - `language=c++23`、`c_standard=c11`(anchor 是 C)、`deps={}`。 -## 3. feature 机制评估 —— 当前不适用(已记录原因) - -用户要求"如果 Eigen 有类似 feature 的东西,可走 mcpp feature 机制"。结论:**当前(mcpp 0.0.68)不适用**,原因经源码核实: - -- mcpp 包描述符的 `features` 表 **只能门控 `sources`**(feature 贡献额外源码 glob,默认排除、请求时编入 —— - 见 `compat.cjson` 的 `utils`、`compat.gtest` 的 `main`;源码 `manifest.cppm` 中 feature 子字段仅识别 `sources`, - 其余被 skip)。 -- Eigen 是 header-only,**没有可门控的可选源码**。其天然的"可选轴"是 `unsupported/` 实验模块,但它与 `Eigen/` - **同处 tarball 根** —— 任何暴露稳定核(`*`)的 include path 都不可避免地一并暴露 `unsupported/`(已实测: - `` / `` 直接可编)。**既无源可门控,也无法用 sources-only 的 - feature 把头藏起来**。 -- 其它 Eigen 开关(`EIGEN_MPL2_ONLY`、`EIGEN_USE_BLAS/LAPACKE`…)是编译 **define**,feature 表同样不能携带。 -- `blas/` 是唯一可门控的真实源,但需 Fortran(`.f`)+ 独立 lib 目标,feature 仅能往既有目标 *追加* 源 → 不匹配,过重。 +## 3. feature 机制 —— `blas`(已落地) + +用户要求"如果 Eigen 有类似 feature 的东西,可走 mcpp feature 机制"。结论:**有,且已实现 `blas` feature**。 + +> ⚠️ **修正**:初稿曾错判"`blas/` 需 Fortran、过重、不做"。复核 `blas/CMakeLists.txt` 的 `EigenBlas_SRCS`: +> 库源是 `blas/*.cpp`(5 个:single/double/complex_single/complex_double/xerbla)+ `blas/f2c/*.c`(18 个, +> **f2c = Fortran 已转 C**)。**唯一的 `.f` 全在 `blas/testing/`(测试套件),不参与库构建** → 纯 C++/C 即可编。 + +- **机制**:mcpp 包描述符的 `features` 表门控 `sources`(默认排除、请求时编入既有 lib 目标 —— 同 `compat.cjson` + 的 `utils`、`compat.gtest` 的 `main`)。Eigen 的 `eigen_blas` 正好匹配: + ```lua + features = { ["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } } } + ``` + 默认不编;`eigen = { version = "5.0.1", features = ["blas"] }` 时编入 `eigen` lib,暴露标准 BLAS 符号 + (`sgemm_`/`dgemm_`/`ddot_`…,Fortran ABI)。`blas/common.h` 用相对 `../Eigen/Core` 取头,源码须就地编译, + `*/blas/*.cpp` glob 保证它们留在解包树内 → OK。 +- **实测(mcpp 0.0.68)**: + - 开 feature:example `eigen ok=1 core=1 blas(dgemm)=1 C=[1 3 2 4]`。 + - 关 feature(负向):同样调用 `dgemm_` → `undefined reference to 'dgemm_'`(证明默认确实排除、门控生效)。 + +### 3.1 其余"非 feature"项(原因仍成立) +- `unsupported/` 实验模块是 header-only,与 `Eigen/` 同处 tarball 根 —— 核 include path(`*`)已一并暴露它 + (实测 `` / `` 直接可编);sources-only 门控无法"藏头",故无可门控、 + 直接可用。 +- Eigen 的编译 **define** 开关(`EIGEN_MPL2_ONLY`、`EIGEN_USE_BLAS/LAPACKE`…)feature 表(0.0.68)不能携带; + 若将来 feature 支持 define/cflags,`mpl2only`(→ `-DEIGEN_MPL2_ONLY`)是干净接入点。 → 故本期 **暴露完整头集(core + unsupported)、不加 feature**,并在 descriptor 注释中完整记录该分析。 若 mcpp 未来允许 feature 携带 define/cflags,`mpl2only`(→ `-DEIGEN_MPL2_ONLY`)是干净的接入点。 @@ -62,8 +75,9 @@ CN url:`https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.t ## 5. 最小工程 + CI -- `tests/examples/eigen/{mcpp.toml, src/main.cpp}` —— `#include `,2x2 线代往返断言 - (`A*x`、determinant、dot、QR solve)。纯文本 include,不混 `import std;`。 +- `tests/examples/eigen/{mcpp.toml, src/main.cpp}` —— 既测 header-only 核(`#include `,2x2 线代往返: + `A*x` / determinant / QR solve),又取 `features = ["blas"]` 调 `dgemm_` 验证 BLAS feature 端到端。纯文本 include, + 不混 `import std;`。 - `validate.yml` 的 detect 把 `compat.eigen.lua` → `eigen` → 命中 `tests/examples/eigen` → `smoke-examples (eigen)` 单跑。 - `mirror-cn-reachable` 覆盖新 CN url。 @@ -71,7 +85,8 @@ CN url:`https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.t - CN 镜像:`mcpp-res/eigen@5.0.1` 已建 + 发布,CN 与 GLOBAL byte-identical(sha `e9c326dc…`),http 200。 - 本地实测(mcpp **0.0.68**,与 CI 同版本,`MCPP_INDEX_MIRROR=GLOBAL`): - `tests/run_example.sh eigen` → `eigen ok=1 y=[3 7] det=-2 dot=5`,`OK: eigen`。 + `tests/run_example.sh eigen`(开 `blas` feature)→ `eigen ok=1 core=1 blas(dgemm)=1 C=[1 3 2 4]`,`OK: eigen`。 +- `blas` feature 门控负向实测:不带 feature 调 `dgemm_` → 链接 `undefined reference to 'dgemm_'`(默认排除生效)。 - `unsupported/` 可达性单独实测:`g++ -std=c++23 -I` 编 ` + + ` → rc=0。 - 全量 lint 本地模拟通过(语法 / 必填字段 / 无前导 v / mirror url 检查)。 diff --git a/README.md b/README.md index 0f7695c..3a317b7 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ mcpp build # 自动拉取源码 + 构建 | `glfw` | 3.4 | GLFW 窗口与输入库(X11/null 后端源码构建) | | `gtest` | 1.15.2 | Google Test 测试框架 | | `cjson` | 1.7.19 | 超轻量 ANSI C JSON 解析库(`#include `,`compat` 源码构建) | -| `eigen` | 5.0.1 | C++ 模板线性代数库(header-only,`#include `;`unsupported/` 实验模块亦可用) | +| `eigen` | 5.0.1 | C++ 模板线性代数库(header-only,`#include `;`unsupported/` 实验模块亦可用;`features = ["blas"]` 编入 Eigen 参考 BLAS) | | `imgui` | 1.92.8 | Dear ImGui immediate-mode GUI 核心源码 | | `opengl` | 2026.05.31 | Khronos OpenGL API 头文件 | | `glx-runtime` | 2026.06.03 | Linux host GLVND/GLX/OpenGL runtime adapter | diff --git a/pkgs/c/compat.eigen.lua b/pkgs/c/compat.eigen.lua index fe34fb7..5329b8a 100644 --- a/pkgs/c/compat.eigen.lua +++ b/pkgs/c/compat.eigen.lua @@ -1,33 +1,42 @@ -- Form B inline descriptor for Eigen — a C++ template library for linear --- algebra (matrices, vectors, numerical solvers, related algorithms). Eigen --- is HEADER-ONLY: there is nothing to compile, so the package just exposes --- the source tree's root on the include path (`#include ` etc.) --- and carries a tiny anchor translation unit so mcpp still has a buildable --- `lib` target (same shape as compat.opengl / compat.khrplatform). +-- algebra (matrices, vectors, numerical solvers, related algorithms). The +-- Eigen *core* is HEADER-ONLY: there is nothing to compile for normal use, so +-- the package exposes the source tree's root on the include path +-- (`#include ` etc.) and carries a tiny anchor translation unit +-- so mcpp always has a buildable `lib` target (same shape as compat.opengl / +-- compat.khrplatform). The optional `blas` feature additionally compiles +-- Eigen's reference BLAS into that lib (see below). -- -- `include_dirs = {"*"}` points at the tarball root (`eigen-/`), which is -- exactly the directory upstream tells you to put on the include path. That -- makes BOTH the stable modules under `Eigen/` and the experimental modules -- under `unsupported/Eigen/` (Tensor, AutoDiff, Splines, MatrixFunctions, …) --- resolvable — see the note on features below for why `unsupported` is not a --- separate opt-in here. +-- resolvable out of the box. -- --- On the `feature` mechanism (asked for, deliberately omitted — analysis): --- mcpp's package-descriptor `features` table gates **sources only** (a --- feature contributes extra source globs that are excluded by default and --- compiled in when the feature is requested — see compat.cjson's `utils` --- and compat.gtest's `main`). Eigen has no such optional *source*: it is --- header-only. Its natural opt-in axis would be the `unsupported/` modules, --- but those are headers that live BESIDE `Eigen/` under the same tarball --- root, so any include path that exposes the stable core (`*`) inevitably --- exposes `unsupported/` too — there is no source to gate and no way to --- hide the headers behind a feature with the current (sources-only) gate. --- Other Eigen knobs (EIGEN_MPL2_ONLY, EIGEN_USE_BLAS/LAPACKE, …) are --- compile *defines*, which the feature table also cannot carry on mcpp --- 0.0.68. So a feature here would be cosmetic/misleading; we expose the --- full header set instead and document it. If mcpp later lets a feature --- contribute defines/cflags, an `mpl2only` (-> -DEIGEN_MPL2_ONLY) feature --- would be the clean fit. +-- Feature: `blas` — Eigen's reference BLAS implementation. +-- Eigen ships a full BLAS library under `blas/` (the `eigen_blas` target in +-- upstream CMake). Despite the historical name, it builds from pure C++ +-- (`blas/*.cpp`) + f2c-translated C (`blas/f2c/*.c`) — NO Fortran compiler +-- is needed (the only `.f` files live under `blas/testing/`, the test +-- suite, and are not part of the library). So it fits mcpp's sources-only +-- feature gate cleanly, exactly like compat.cjson's `utils`: excluded by +-- default, and compiled into the `eigen` lib when the dependency requests +-- `features = ["blas"]`. The result exposes the standard BLAS symbols +-- (sgemm_/dgemm_/ddot_/… , Fortran ABI) for code that wants to link a BLAS. +-- Common.h pulls Eigen via a path RELATIVE to blas/ (`../Eigen/Core`), so +-- the sources must compile in place — they do, since `*/blas/*.cpp` keeps +-- them under the unpacked tree. Verified locally on mcpp 0.0.68. +-- +-- What is NOT a feature here (and why): +-- * `unsupported/` modules are header-only and live BESIDE `Eigen/` under +-- the same tarball root, so the core include path (`*`) already exposes +-- them; a sources-only gate cannot hide headers, so there is nothing to +-- gate — they are simply available. +-- * Eigen's compile-define knobs (EIGEN_MPL2_ONLY, EIGEN_USE_BLAS/LAPACKE, +-- …) are preprocessor defines; the feature table carries only `sources` +-- on mcpp 0.0.68, so they cannot be feature-gated yet. If mcpp later lets +-- a feature contribute defines/cflags, `mpl2only` (-> -DEIGEN_MPL2_ONLY) +-- would be the clean fit. -- -- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs -- the GitLab archive's `eigen-/` wrap layer. @@ -83,6 +92,19 @@ package = { }, sources = { "mcpp_generated/eigen_anchor.c" }, targets = { ["eigen"] = { kind = "lib" } }, + -- Optional: compile Eigen's reference BLAS (`eigen_blas`) into the lib. + -- C++ + f2c-C only, no Fortran. Off by default; pulled in with + -- `features = ["blas"]`. blas/ has exactly the 5 library .cpp and + -- blas/f2c/ exactly the 18 library .c (the upstream eigen_blas source + -- set); the `*.cpp`/`*.c` globs match them and nothing else. + features = { + ["blas"] = { + sources = { + "*/blas/*.cpp", + "*/blas/f2c/*.c", + }, + }, + }, deps = { }, }, } diff --git a/tests/examples/eigen/mcpp.toml b/tests/examples/eigen/mcpp.toml index 79799cf..c227201 100644 --- a/tests/examples/eigen/mcpp.toml +++ b/tests/examples/eigen/mcpp.toml @@ -10,8 +10,11 @@ default = "gcc@16.1.0" [indices] compat = { path = "../../.." } +# `features = ["blas"]` opts into Eigen's reference BLAS (eigen_blas) so this +# example also exercises the feature-gated source build. Without it, the core +# header-only `#include ` path still works on its own. [dependencies.compat] -eigen = "5.0.1" +eigen = { version = "5.0.1", features = ["blas"] } [targets.eigen-example] kind = "bin" diff --git a/tests/examples/eigen/src/main.cpp b/tests/examples/eigen/src/main.cpp index eab9316..025e8d9 100644 --- a/tests/examples/eigen/src/main.cpp +++ b/tests/examples/eigen/src/main.cpp @@ -1,29 +1,47 @@ // Eigen is header-only; the compat package puts the source tree root on the // include path so `#include ` just works. Plain textual includes // here (Eigen is not a module), so we do NOT mix in `import std;`. +// +// This example also opts into the `blas` feature (see mcpp.toml), which +// compiles Eigen's reference BLAS into the lib — so we additionally link and +// call dgemm_ (Fortran BLAS ABI) to prove the feature-gated build works. #include #include #include +// Standard Fortran BLAS ABI, provided by Eigen's eigen_blas (feature "blas"). +extern "C" void dgemm_(const char* transa, const char* transb, + const int* m, const int* n, const int* k, + const double* alpha, const double* a, const int* lda, + const double* b, const int* ldb, + const double* beta, double* c, const int* ldc); + int main() { + // --- core Eigen (header-only) --- // A * x for a 2x2 system: [[1,2],[3,4]] * [1,1]^T = [3,7]^T Eigen::Matrix2d A; A << 1, 2, 3, 4; Eigen::Vector2d x(1.0, 1.0); Eigen::Vector2d y = A * x; + double det = A.determinant(); // -2 + Eigen::Vector2d z = A.colPivHouseholderQr().solve(y); // back to [1,1] - double det = A.determinant(); // 1*4 - 2*3 = -2 - double dot = x.dot(Eigen::Vector2d(2.0, 3.0)); // 1*2 + 1*3 = 5 - - // Solve A z = y, expect z back to [1,1]. - Eigen::Vector2d z = A.colPivHouseholderQr().solve(y); + bool core_ok = y(0) == 3.0 && y(1) == 7.0 && det == -2.0 + && std::abs(z(0) - 1.0) < 1e-9 && std::abs(z(1) - 1.0) < 1e-9; - bool ok = y(0) == 3.0 && y(1) == 7.0 - && det == -2.0 && dot == 5.0 - && std::abs(z(0) - 1.0) < 1e-9 && std::abs(z(1) - 1.0) < 1e-9; + // --- BLAS feature: C = A * B via dgemm_ (column-major) --- + // A = [[1,2],[3,4]] col-major = {1,3,2,4}; B = identity. Expect C == A. + const double a[4] = {1, 3, 2, 4}; + const double b[4] = {1, 0, 0, 1}; + double c[4] = {0, 0, 0, 0}; + const int n = 2; + const double one = 1.0, zero = 0.0; + dgemm_("N", "N", &n, &n, &n, &one, a, &n, b, &n, &zero, c, &n); + bool blas_ok = c[0] == 1 && c[1] == 3 && c[2] == 2 && c[3] == 4; - std::printf("eigen ok=%d y=[%g %g] det=%g dot=%g\n", - ok, y(0), y(1), det, dot); + bool ok = core_ok && blas_ok; + std::printf("eigen ok=%d core=%d blas(dgemm)=%d y=[%g %g] C=[%g %g %g %g]\n", + ok, core_ok, blas_ok, y(0), y(1), c[0], c[1], c[2], c[3]); return ok ? 0 : 1; }