Skip to content

Commit a240c7f

Browse files
authored
feat(pkgs): add Eigen 5.0.1 (compat, header-only) + CN mirror (#50)
* feat(pkgs): add Eigen 5.0.1 (compat, header-only) + CN mirror - compat.eigen @5.0.1 — latest Eigen (libeigen/eigen on GitLab; 5.x line). Header-only: include_dirs={"*"} exposes the source root so `#include <Eigen/Dense>` 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 * feat(pkgs): add Eigen `blas` feature (reference BLAS, no Fortran) 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.
1 parent 5456b4d commit a240c7f

5 files changed

Lines changed: 273 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# 新增 Eigen 收录(compat 源码/header-only)+ GitCode CN 镜像方案
2+
3+
**日期**: 2026-06-28
4+
**本仓**: `mcpp-community/mcpp-index`(github 别名 `mcpplibs/mcpp-index`)
5+
**参考**: PR #48(cJSON compat + nlohmann.json module + per-pkg CI)
6+
**目标**:
7+
1. 收录 [`libeigen/eigen`](https://gitlab.com/libeigen/eigen) 最新版 **5.0.1** —— header-only C++ 线性代数库,
8+
**全兼容(compat)** 形态,用户 `#include <Eigen/Dense>` 开箱即用。文件名 `pkgs/c/compat.eigen.lua`
9+
2.**GitCode CN 镜像** `mcpp-res/eigen`(`gitcode.com/mcpp-res/eigen/...`),用本仓 `tools/gtc` 推送。
10+
3. 评估 Eigen 是否有可走 mcpp **feature 机制** 的"类 feature"可选项(见 §3 结论:当前不适用,已说明原因)。
11+
4.`tests/examples/eigen/` 放最小工程,CI 选跑(detect → `eigen` matrix job)。
12+
13+
---
14+
15+
## 1. 最新版本与上游布局(关键前置)
16+
17+
- 最新 tag:`git ls-remote --tags` 显示 **5.0.1**(>3.4.x,Eigen 已跨大版本到 5.x;另有 5.0.0 / 3.4.1)。收录 `5.0.1`
18+
- 上游托管在 **GitLab**(非 GitHub):
19+
GLOBAL = `https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz`(归档 sha256 已实测两次稳定:`e9c326dc…`)。
20+
- tarball 单层 wrap `eigen-5.0.1/`,glob `*` 吸收。根目录含:
21+
- `Eigen/` —— **稳定模块**(Dense / Core / LU / QR / SVD / Sparse / Geometry …)
22+
- `unsupported/Eigen/` —— **实验模块**(CXX11/Tensor、AutoDiff、Splines、MatrixFunctions、FFT …)
23+
- `blas/` `lapack/` —— Eigen 自带的 BLAS/LAPACK 实现(含 12 个 **Fortran `.f`**,需 Fortran 编译器)
24+
- 许可:主体 **MPL-2.0**(另有少量 Apache/BSD/MINPACK 文件)
25+
- **`.cppm`**:Eigen 5.0.1 仍是纯 header,未提供 C++ module 单元 → 走 compat header-only 形态(非 nlohmann 那种 module wrapper)。
26+
27+
## 2. descriptor 形态(header-only,参照 compat.opengl/khrplatform)
28+
29+
Eigen 无可编译源(纯模板头),故:
30+
- `include_dirs = {"*"}` 暴露 tarball 根 —— 即上游推荐放入 include path 的目录;`#include <Eigen/...>`
31+
`#include <unsupported/Eigen/...>` 均可解析。
32+
- 用一个 trivial anchor TU(`mcpp_generated/eigen_anchor.c`)给 mcpp 一个可构建的 `lib` 目标(同 opengl/khrplatform)。
33+
- `language=c++23``c_standard=c11`(anchor 是 C)、`deps={}`
34+
35+
## 3. feature 机制 —— `blas`(已落地)
36+
37+
用户要求"如果 Eigen 有类似 feature 的东西,可走 mcpp feature 机制"。结论:**有,且已实现 `blas` feature**
38+
39+
> ⚠️ **修正**:初稿曾错判"`blas/` 需 Fortran、过重、不做"。复核 `blas/CMakeLists.txt``EigenBlas_SRCS`:
40+
> 库源是 `blas/*.cpp`(5 个:single/double/complex_single/complex_double/xerbla)+ `blas/f2c/*.c`(18 个,
41+
> **f2c = Fortran 已转 C**)。**唯一的 `.f` 全在 `blas/testing/`(测试套件),不参与库构建** → 纯 C++/C 即可编。
42+
43+
- **机制**:mcpp 包描述符的 `features` 表门控 `sources`(默认排除、请求时编入既有 lib 目标 —— 同 `compat.cjson`
44+
`utils``compat.gtest``main`)。Eigen 的 `eigen_blas` 正好匹配:
45+
```lua
46+
features = { ["blas"] = { sources = { "*/blas/*.cpp", "*/blas/f2c/*.c" } } }
47+
```
48+
默认不编;`eigen = { version = "5.0.1", features = ["blas"] }` 时编入 `eigen` lib,暴露标准 BLAS 符号
49+
(`sgemm_`/`dgemm_`/`ddot_`…,Fortran ABI)。`blas/common.h` 用相对 `../Eigen/Core` 取头,源码须就地编译,
50+
`*/blas/*.cpp` glob 保证它们留在解包树内 → OK。
51+
- **实测(mcpp 0.0.68)**:
52+
- 开 feature:example `eigen ok=1 core=1 blas(dgemm)=1 C=[1 3 2 4]`
53+
- 关 feature(负向):同样调用 `dgemm_``undefined reference to 'dgemm_'`(证明默认确实排除、门控生效)。
54+
55+
### 3.1 其余"非 feature"项(原因仍成立)
56+
- `unsupported/` 实验模块是 header-only,与 `Eigen/` 同处 tarball 根 —— 核 include path(`*`)已一并暴露它
57+
(实测 `<unsupported/Eigen/MatrixFunctions>` / `<AutoDiff>` 直接可编);sources-only 门控无法"藏头",故无可门控、
58+
直接可用。
59+
- Eigen 的编译 **define** 开关(`EIGEN_MPL2_ONLY``EIGEN_USE_BLAS/LAPACKE`…)feature 表(0.0.68)不能携带;
60+
若将来 feature 支持 define/cflags,`mpl2only`(→ `-DEIGEN_MPL2_ONLY`)是干净接入点。
61+
62+
→ 故本期 **暴露完整头集(core + unsupported)、不加 feature**,并在 descriptor 注释中完整记录该分析。
63+
若 mcpp 未来允许 feature 携带 define/cflags,`mpl2only`(→ `-DEIGEN_MPL2_ONLY`)是干净的接入点。
64+
65+
## 4. CN 镜像(gtc)
66+
67+
repo 名 = 包名去 `compat.` 前缀 = `eigen`:
68+
```
69+
gtc repo create mcpp-res/eigen
70+
gtc repo push mcpp-res/eigen <init> # 新仓需先有 main 分支才能发 release
71+
gtc release publish mcpp-res/eigen --tag 5.0.1 --target main --asset eigen-5.0.1.tar.gz
72+
```
73+
上传的就是 GLOBAL 同一个 tarball → CN/GLOBAL **byte-identical**(实测 sha 一致 + CN http 200)。
74+
CN url:`https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz`
75+
76+
## 5. 最小工程 + CI
77+
78+
- `tests/examples/eigen/{mcpp.toml, src/main.cpp}` —— 既测 header-only 核(`#include <Eigen/Dense>`,2x2 线代往返:
79+
`A*x` / determinant / QR solve),又取 `features = ["blas"]``dgemm_` 验证 BLAS feature 端到端。纯文本 include,
80+
不混 `import std;`
81+
- `validate.yml` 的 detect 把 `compat.eigen.lua``eigen` → 命中 `tests/examples/eigen``smoke-examples (eigen)` 单跑。
82+
- `mirror-cn-reachable` 覆盖新 CN url。
83+
84+
## 6. 落地记录(2026-06-28)
85+
86+
- CN 镜像:`mcpp-res/eigen@5.0.1` 已建 + 发布,CN 与 GLOBAL byte-identical(sha `e9c326dc…`),http 200。
87+
- 本地实测(mcpp **0.0.68**,与 CI 同版本,`MCPP_INDEX_MIRROR=GLOBAL`):
88+
`tests/run_example.sh eigen`(开 `blas` feature)→ `eigen ok=1 core=1 blas(dgemm)=1 C=[1 3 2 4]`,`OK: eigen`
89+
- `blas` feature 门控负向实测:不带 feature 调 `dgemm_` → 链接 `undefined reference to 'dgemm_'`(默认排除生效)。
90+
- `unsupported/` 可达性单独实测:`g++ -std=c++23 -I<root>` 编 `<Eigen/Dense> + <unsupported/Eigen/MatrixFunctions> +
91+
<AutoDiff>` → rc=0。
92+
- 全量 lint 本地模拟通过(语法 / 必填字段 / 无前导 v / mirror url 检查)。
93+
- ****:compat 包目录按 **完整包名首字母** 归类 —— `compat.eigen``pkgs/c/`(不是短名 `eigen``pkgs/e/`),
94+
否则本地 path index 扫不到(`dependency 'compat.eigen': not found in local index`)。

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mcpp build # 自动拉取源码 + 构建
3939
| `glfw` | 3.4 | GLFW 窗口与输入库(X11/null 后端源码构建) |
4040
| `gtest` | 1.15.2 | Google Test 测试框架 |
4141
| `cjson` | 1.7.19 | 超轻量 ANSI C JSON 解析库(`#include <cJSON.h>`,`compat` 源码构建) |
42+
| `eigen` | 5.0.1 | C++ 模板线性代数库(header-only,`#include <Eigen/Dense>`;`unsupported/` 实验模块亦可用;`features = ["blas"]` 编入 Eigen 参考 BLAS) |
4243
| `imgui` | 1.92.8 | Dear ImGui immediate-mode GUI 核心源码 |
4344
| `opengl` | 2026.05.31 | Khronos OpenGL API 头文件 |
4445
| `glx-runtime` | 2026.06.03 | Linux host GLVND/GLX/OpenGL runtime adapter |

pkgs/c/compat.eigen.lua

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
-- Form B inline descriptor for Eigen — a C++ template library for linear
2+
-- algebra (matrices, vectors, numerical solvers, related algorithms). The
3+
-- Eigen *core* is HEADER-ONLY: there is nothing to compile for normal use, so
4+
-- the package exposes the source tree's root on the include path
5+
-- (`#include <Eigen/Dense>` etc.) and carries a tiny anchor translation unit
6+
-- so mcpp always has a buildable `lib` target (same shape as compat.opengl /
7+
-- compat.khrplatform). The optional `blas` feature additionally compiles
8+
-- Eigen's reference BLAS into that lib (see below).
9+
--
10+
-- `include_dirs = {"*"}` points at the tarball root (`eigen-<ver>/`), which is
11+
-- exactly the directory upstream tells you to put on the include path. That
12+
-- makes BOTH the stable modules under `Eigen/` and the experimental modules
13+
-- under `unsupported/Eigen/` (Tensor, AutoDiff, Splines, MatrixFunctions, …)
14+
-- resolvable out of the box.
15+
--
16+
-- Feature: `blas` — Eigen's reference BLAS implementation.
17+
-- Eigen ships a full BLAS library under `blas/` (the `eigen_blas` target in
18+
-- upstream CMake). Despite the historical name, it builds from pure C++
19+
-- (`blas/*.cpp`) + f2c-translated C (`blas/f2c/*.c`) — NO Fortran compiler
20+
-- is needed (the only `.f` files live under `blas/testing/`, the test
21+
-- suite, and are not part of the library). So it fits mcpp's sources-only
22+
-- feature gate cleanly, exactly like compat.cjson's `utils`: excluded by
23+
-- default, and compiled into the `eigen` lib when the dependency requests
24+
-- `features = ["blas"]`. The result exposes the standard BLAS symbols
25+
-- (sgemm_/dgemm_/ddot_/… , Fortran ABI) for code that wants to link a BLAS.
26+
-- Common.h pulls Eigen via a path RELATIVE to blas/ (`../Eigen/Core`), so
27+
-- the sources must compile in place — they do, since `*/blas/*.cpp` keeps
28+
-- them under the unpacked tree. Verified locally on mcpp 0.0.68.
29+
--
30+
-- What is NOT a feature here (and why):
31+
-- * `unsupported/` modules are header-only and live BESIDE `Eigen/` under
32+
-- the same tarball root, so the core include path (`*`) already exposes
33+
-- them; a sources-only gate cannot hide headers, so there is nothing to
34+
-- gate — they are simply available.
35+
-- * Eigen's compile-define knobs (EIGEN_MPL2_ONLY, EIGEN_USE_BLAS/LAPACKE,
36+
-- …) are preprocessor defines; the feature table carries only `sources`
37+
-- on mcpp 0.0.68, so they cannot be feature-gated yet. If mcpp later lets
38+
-- a feature contribute defines/cflags, `mpl2only` (-> -DEIGEN_MPL2_ONLY)
39+
-- would be the clean fit.
40+
--
41+
-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs
42+
-- the GitLab archive's `eigen-<tag>/` wrap layer.
43+
package = {
44+
spec = "1",
45+
namespace = "compat",
46+
name = "compat.eigen",
47+
description = "C++ template library for linear algebra (header-only)",
48+
licenses = {"MPL-2.0"},
49+
repo = "https://gitlab.com/libeigen/eigen",
50+
type = "package",
51+
52+
xpm = {
53+
linux = {
54+
["5.0.1"] = {
55+
url = {
56+
GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz",
57+
CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz",
58+
},
59+
sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec",
60+
},
61+
},
62+
macosx = {
63+
["5.0.1"] = {
64+
url = {
65+
GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz",
66+
CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz",
67+
},
68+
sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec",
69+
},
70+
},
71+
windows = {
72+
["5.0.1"] = {
73+
url = {
74+
GLOBAL = "https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz",
75+
CN = "https://gitcode.com/mcpp-res/eigen/releases/download/5.0.1/eigen-5.0.1.tar.gz",
76+
},
77+
sha256 = "e9c326dc8c05cd1e044c71f30f1b2e34a6161a3b6ecf445d56b53ff1669e3dec",
78+
},
79+
},
80+
},
81+
82+
mcpp = {
83+
language = "c++23",
84+
import_std = false,
85+
c_standard = "c11",
86+
-- Tarball root: exposes `Eigen/` (stable) and `unsupported/Eigen/`
87+
-- (experimental) to consumers writing `#include <Eigen/...>`.
88+
include_dirs = { "*" },
89+
-- Header-only: a trivial anchor TU gives mcpp a buildable lib target.
90+
generated_files = {
91+
["mcpp_generated/eigen_anchor.c"] = "int mcpp_compat_eigen_headers_anchor(void) { return 0; }\n",
92+
},
93+
sources = { "mcpp_generated/eigen_anchor.c" },
94+
targets = { ["eigen"] = { kind = "lib" } },
95+
-- Optional: compile Eigen's reference BLAS (`eigen_blas`) into the lib.
96+
-- C++ + f2c-C only, no Fortran. Off by default; pulled in with
97+
-- `features = ["blas"]`. blas/ has exactly the 5 library .cpp and
98+
-- blas/f2c/ exactly the 18 library .c (the upstream eigen_blas source
99+
-- set); the `*.cpp`/`*.c` globs match them and nothing else.
100+
features = {
101+
["blas"] = {
102+
sources = {
103+
"*/blas/*.cpp",
104+
"*/blas/f2c/*.c",
105+
},
106+
},
107+
},
108+
deps = { },
109+
},
110+
}

tests/examples/eigen/mcpp.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Minimal example: Eigen consumed header-only via the compat source build.
2+
# Run from this directory: mcpp run (index path is relative to repo root)
3+
[package]
4+
name = "eigen-example"
5+
version = "0.1.0"
6+
7+
[toolchain]
8+
default = "gcc@16.1.0"
9+
10+
[indices]
11+
compat = { path = "../../.." }
12+
13+
# `features = ["blas"]` opts into Eigen's reference BLAS (eigen_blas) so this
14+
# example also exercises the feature-gated source build. Without it, the core
15+
# header-only `#include <Eigen/...>` path still works on its own.
16+
[dependencies.compat]
17+
eigen = { version = "5.0.1", features = ["blas"] }
18+
19+
[targets.eigen-example]
20+
kind = "bin"
21+
main = "src/main.cpp"

tests/examples/eigen/src/main.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Eigen is header-only; the compat package puts the source tree root on the
2+
// include path so `#include <Eigen/...>` just works. Plain textual includes
3+
// here (Eigen is not a module), so we do NOT mix in `import std;`.
4+
//
5+
// This example also opts into the `blas` feature (see mcpp.toml), which
6+
// compiles Eigen's reference BLAS into the lib — so we additionally link and
7+
// call dgemm_ (Fortran BLAS ABI) to prove the feature-gated build works.
8+
#include <Eigen/Dense>
9+
#include <cstdio>
10+
#include <cmath>
11+
12+
// Standard Fortran BLAS ABI, provided by Eigen's eigen_blas (feature "blas").
13+
extern "C" void dgemm_(const char* transa, const char* transb,
14+
const int* m, const int* n, const int* k,
15+
const double* alpha, const double* a, const int* lda,
16+
const double* b, const int* ldb,
17+
const double* beta, double* c, const int* ldc);
18+
19+
int main() {
20+
// --- core Eigen (header-only) ---
21+
// A * x for a 2x2 system: [[1,2],[3,4]] * [1,1]^T = [3,7]^T
22+
Eigen::Matrix2d A;
23+
A << 1, 2,
24+
3, 4;
25+
Eigen::Vector2d x(1.0, 1.0);
26+
Eigen::Vector2d y = A * x;
27+
double det = A.determinant(); // -2
28+
Eigen::Vector2d z = A.colPivHouseholderQr().solve(y); // back to [1,1]
29+
30+
bool core_ok = y(0) == 3.0 && y(1) == 7.0 && det == -2.0
31+
&& std::abs(z(0) - 1.0) < 1e-9 && std::abs(z(1) - 1.0) < 1e-9;
32+
33+
// --- BLAS feature: C = A * B via dgemm_ (column-major) ---
34+
// A = [[1,2],[3,4]] col-major = {1,3,2,4}; B = identity. Expect C == A.
35+
const double a[4] = {1, 3, 2, 4};
36+
const double b[4] = {1, 0, 0, 1};
37+
double c[4] = {0, 0, 0, 0};
38+
const int n = 2;
39+
const double one = 1.0, zero = 0.0;
40+
dgemm_("N", "N", &n, &n, &n, &one, a, &n, b, &n, &zero, c, &n);
41+
bool blas_ok = c[0] == 1 && c[1] == 3 && c[2] == 2 && c[3] == 4;
42+
43+
bool ok = core_ok && blas_ok;
44+
std::printf("eigen ok=%d core=%d blas(dgemm)=%d y=[%g %g] C=[%g %g %g %g]\n",
45+
ok, core_ok, blas_ok, y(0), y(1), c[0], c[1], c[2], c[3]);
46+
return ok ? 0 : 1;
47+
}

0 commit comments

Comments
 (0)