Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .agents/docs/2026-06-28-add-eigen-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 新增 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 <Eigen/Dense>` 开箱即用。文件名 `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 <Eigen/...>` 与
`#include <unsupported/Eigen/...>` 均可解析。
- 用一个 trivial anchor TU(`mcpp_generated/eigen_anchor.c`)给 mcpp 一个可构建的 `lib` 目标(同 opengl/khrplatform)。
- `language=c++23`、`c_standard=c11`(anchor 是 C)、`deps={}`。

## 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(`*`)已一并暴露它
(实测 `<unsupported/Eigen/MatrixFunctions>` / `<AutoDiff>` 直接可编);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`)是干净的接入点。

## 4. CN 镜像(gtc)

repo 名 = 包名去 `compat.` 前缀 = `eigen`:
```
gtc repo create mcpp-res/eigen
gtc repo push mcpp-res/eigen <init> # 新仓需先有 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}` —— 既测 header-only 核(`#include <Eigen/Dense>`,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。

## 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`(开 `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<root>` 编 `<Eigen/Dense> + <unsupported/Eigen/MatrixFunctions> +
<AutoDiff>` → rc=0。
- 全量 lint 本地模拟通过(语法 / 必填字段 / 无前导 v / mirror url 检查)。
- **坑**:compat 包目录按 **完整包名首字母** 归类 —— `compat.eigen` → `pkgs/c/`(不是短名 `eigen` 的 `pkgs/e/`),
否则本地 path index 扫不到(`dependency 'compat.eigen': not found in local index`)。
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cJSON.h>`,`compat` 源码构建) |
| `eigen` | 5.0.1 | C++ 模板线性代数库(header-only,`#include <Eigen/Dense>`;`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 |
Expand Down
110 changes: 110 additions & 0 deletions pkgs/c/compat.eigen.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
-- Form B inline descriptor for Eigen — a C++ template library for linear
-- 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 <Eigen/Dense>` 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-<ver>/`), 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 out of the box.
--
-- 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-<tag>/` 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 <Eigen/...>`.
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" } },
-- 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 = { },
},
}
21 changes: 21 additions & 0 deletions tests/examples/eigen/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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 = "../../.." }

# `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 <Eigen/...>` path still works on its own.
[dependencies.compat]
eigen = { version = "5.0.1", features = ["blas"] }

[targets.eigen-example]
kind = "bin"
main = "src/main.cpp"
47 changes: 47 additions & 0 deletions tests/examples/eigen/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Eigen is header-only; the compat package puts the source tree root on the
// include path so `#include <Eigen/...>` 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 <Eigen/Dense>
#include <cstdio>
#include <cmath>

// 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]

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;

// --- 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;

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;
}
Loading