From ba508d912f656322d67bfaf04fb557352514813d Mon Sep 17 00:00:00 2001 From: tz12323 <2760686250@qq.com> Date: Wed, 14 Oct 2026 21:17:49 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20GNU=20Scientif?= =?UTF-8?q?ic=20Library=202.8=20=E6=94=AF=E6=8C=81=E5=8F=8A=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/docs/2026-10-14-add-gsl-plan.md | 40 +++++++ README.md | 2 +- README.zh-CN.md | 2 +- mcpp.toml | 1 + pkgs/c/compat.gsl.lua | 132 ++++++++++++++++++++++++ tests/examples/gsl/mcpp.toml | 12 +++ tests/examples/gsl/tests/gsl.cpp | 49 +++++++++ 7 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 .agents/docs/2026-10-14-add-gsl-plan.md create mode 100644 pkgs/c/compat.gsl.lua create mode 100644 tests/examples/gsl/mcpp.toml create mode 100644 tests/examples/gsl/tests/gsl.cpp diff --git a/.agents/docs/2026-10-14-add-gsl-plan.md b/.agents/docs/2026-10-14-add-gsl-plan.md new file mode 100644 index 00000000..f622bb64 --- /dev/null +++ b/.agents/docs/2026-10-14-add-gsl-plan.md @@ -0,0 +1,40 @@ +# 新增 GSL 收录(compat,install() 构建) + +## 1. 来源与形态 + +- 收录 GNU Scientific Library 2.8,官方站点列出的当前稳定版本。 +- 采用 GNU 源码归档 `gsl-2.8.tar.gz`,SHA-256 为 + `6a99eeed15632c6354895b1dd542ed5a855c0f15d9ad1326c6fe2b2c9e423190`。 +- GSL 2.8 是 autotools 工程:`configure` 生成 `config.h`,顶层构建再通过 libtool + 组合 `libgsl.a` 和 `libgslcblas.a`。其源码树包含大量相互依赖的子库和测试源,不能用 + 一个宽泛的 `sources` glob 安全表达生产库边界。 +- 描述符因此采用与 `compat.openblas`/`compat.openssl` 相同的 `install()` 触发方式: + 构建期运行上游 `configure`、`make`、`make install`,然后由 hook 写入 anchor TU。 + +## 2. 描述符设计 + +- 身份为 `compat:gsl`,文件为 `pkgs/c/compat.gsl.lua`。 +- Linux/macOS 使用官方源码包和 `xim:make@latest`,关闭 shared、启用 static, + 产物安装到包的 `include/` 与 `lib/`。 +- 消费侧公开 `include/gsl/*.h`,链接顺序为 `libgsl`、`libgslcblas`、`libm`。 +- GSL 自带 CBLAS 随包默认提供,不再声明或隐式绑定 `compat.openblas`,避免 BLAS ABI + 和符号来源不明确。 +- Windows 暂不声明 xpm 版本:官方 2.8 归档没有原生 MSVC 构建路径,当前 index 也不 + 应凭空引入未经验证的预编译库。测试工程在非 Unix 平台编译 no-op。 + +## 3. Feature 评估 + +GSL 的各数值领域在上游构建中共同链接到 `libgsl`,不是可以由 mcpp `features` +(只能可靠门控源码列表)独立切分的消费者组件,因此本版本不增加 feature。CBLAS +同样作为完整 GSL 的默认组成部分。 + +## 4. 测试与验证目标 + +`tests/examples/gsl` 覆盖: + +- Bessel 特殊函数; +- 自适应积分; +- CBLAS `ddot` 的链接和结果。 + +验证包括描述符 lint、`mcpp xpkg parse`、`mcpp test -p gsl`,以及一次清理缓存后的 +冷构建,确认头文件和两个静态归档来自该包而不是宿主系统。 diff --git a/README.md b/README.md index 0459ca25..134638da 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ One descriptor per common shape: | Generated config header | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | | C++23 module shipped by upstream | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) | -| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) | +| External build system (`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.gsl`](pkgs/c/compat.gsl.lua) | Every other shape, and why each descriptor is written the way it is, is in **[Descriptor examples by shape](docs/descriptor-examples.md)**. diff --git a/README.zh-CN.md b/README.zh-CN.md index a996bda4..b771820b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -41,7 +41,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 生成 config 头 | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | | 上游自带 C++23 module | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) | -| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) | +| 外部构建系统(`install()`) | [`compat.openssl`](pkgs/c/compat.openssl.lua) · [`compat.gsl`](pkgs/c/compat.gsl.lua) | 其余形态,以及每个描述符为何这样写,见 **[描述符示例总览(按形态)](docs/zh/descriptor-examples.md)**。 diff --git a/mcpp.toml b/mcpp.toml index a96c9edc..7c2b1629 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -62,6 +62,7 @@ members = [ "tests/examples/gzip-hpp", "tests/examples/huxerui-module", "tests/examples/gmp-gmpxx", + "tests/examples/gsl", "tests/examples/godot-cpp", "tests/examples/godot-cpp-module", "tests/examples/godot-cpp-module-v10", diff --git a/pkgs/c/compat.gsl.lua b/pkgs/c/compat.gsl.lua new file mode 100644 index 00000000..e2963871 --- /dev/null +++ b/pkgs/c/compat.gsl.lua @@ -0,0 +1,132 @@ +-- compat.gsl -- GNU Scientific Library 2.8, built from the official +-- autotools release. GSL's configure step generates config.h and libtool +-- assembles two archives, so the install hook is used instead of trying to +-- approximate the upstream source graph with broad globs. +-- +-- The package exposes both libgsl and the GSL CBLAS implementation. Windows +-- is intentionally not declared: the official release has no native MSVC +-- build system, and silently using a host or third-party binary would break +-- the index's source-build and ABI guarantees. +package = { + spec = "1", + namespace = "compat", + name = "gsl", + description = "GNU Scientific Library with its CBLAS implementation (static, source-built)", + licenses = {"GPL-3.0-or-later"}, + repo = "https://www.gnu.org/software/gsl/", + type = "package", + + xpm = { + linux = { + deps = { "xim:make@latest" }, + ["2.8"] = { + url = "https://ftp.gnu.org/gnu/gsl/gsl-2.8.tar.gz", + sha256 = "6a99eeed15632c6354895b1dd542ed5a855c0f15d9ad1326c6fe2b2c9e423190", + }, + }, + macosx = { + deps = { "xim:make@latest" }, + ["2.8"] = { + url = "https://ftp.gnu.org/gnu/gsl/gsl-2.8.tar.gz", + sha256 = "6a99eeed15632c6354895b1dd542ed5a855c0f15d9ad1326c6fe2b2c9e423190", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + sources = { "mcpp_gsl_anchor.c" }, + include_dirs = { "include" }, + targets = { ["gsl"] = { kind = "lib" } }, + linux = { + ldflags = { "-Llib", "-lgsl", "-lgslcblas", "-lm" }, + }, + macosx = { + ldflags = { "-Llib", "-lgsl", "-lgslcblas", "-lm" }, + }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function sh_quote(value) + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" +end + +local function resolve_make() + local dep = pkginfo.build_dep("xim:make") or pkginfo.build_dep("make") + if dep and dep.bin then + local candidate = path.join(dep.bin, "make") + if os.isfile(candidate) then return candidate end + end + return "make" +end + +local function run(command, logf) + local ok, result = pcall(os.exec, string.format( + "bash -c %s", sh_quote(command .. " >> " .. sh_quote(logf) .. " 2>&1"))) + if not ok or not result then + log.error("compat.gsl: build command failed; see %s", logf) + return false + end + return true +end + +local function install_gsl() + local archive = pkginfo.install_file() + local source = archive and tostring(archive):replace(".tar.gz", "") + or ("gsl-" .. pkginfo.version()) + if not os.isdir(source) then + source = "gsl-" .. pkginfo.version() + end + + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mv(source, prefix) + os.cd(prefix) + + local logf = path.join(prefix, "mcpp_gsl_build.log") + local make = resolve_make() + local jobs = (os.default_njob and os.default_njob()) or 4 + local configure = string.format( + "CC=cc %s --disable-shared --enable-static --disable-dependency-tracking " + .. "--prefix=%s --libdir=%s", + "./configure", sh_quote(prefix), sh_quote(path.join(prefix, "lib"))) + if not run(configure, logf) then return false end + if not run(string.format("%s -j%d", sh_quote(make), jobs), logf) then + return false + end + if not run(string.format("%s install", sh_quote(make)), logf) then + return false + end + + local required = { + path.join(prefix, "include", "gsl", "gsl_math.h"), + path.join(prefix, "include", "gsl", "gsl_cblas.h"), + path.join(prefix, "lib", "libgsl.a"), + path.join(prefix, "lib", "libgslcblas.a"), + } + for _, file in ipairs(required) do + if not os.isfile(file) then + log.error("compat.gsl: build did not produce %s; see %s", file, logf) + return false + end + end + + io.writefile(path.join(prefix, "mcpp_gsl_anchor.c"), + "int mcpp_compat_gsl_anchor(void) { return 0; }\n") + return true +end + +function install() + if os.host() == "windows" then return true end + local ok, result = pcall(install_gsl) + if not ok then + log.error("compat.gsl install() failed: %s", tostring(result)) + return false + end + return result +end diff --git a/tests/examples/gsl/mcpp.toml b/tests/examples/gsl/mcpp.toml new file mode 100644 index 00000000..073e5a4a --- /dev/null +++ b/tests/examples/gsl/mcpp.toml @@ -0,0 +1,12 @@ +[package] +name = "gsl-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[target.'cfg(unix)'.dependencies.compat] +gsl = "2.8" + +[target.'cfg(unix)'.build] +cxxflags = ["-DHAVE_GSL=1"] diff --git a/tests/examples/gsl/tests/gsl.cpp b/tests/examples/gsl/tests/gsl.cpp new file mode 100644 index 00000000..fe72bd8f --- /dev/null +++ b/tests/examples/gsl/tests/gsl.cpp @@ -0,0 +1,49 @@ +#ifdef HAVE_GSL + +#include +#include + +#include +#include +#include +#include + +namespace { + +double square(double x, void*) { + return x * x; +} + +} // namespace + +int main() { + if (std::abs(gsl_sf_bessel_J0(0.0) - 1.0) > 1e-12) return 1; + + double result = 0.0; + double error = 0.0; + gsl_function function; + function.function = □ + function.params = nullptr; + gsl_integration_workspace* workspace = gsl_integration_workspace_alloc(100); + if (!workspace) return 2; + gsl_integration_qags(&function, 0.0, 1.0, 0.0, 1e-10, 100, + workspace, &result, &error); + gsl_integration_workspace_free(workspace); + if (std::abs(result - 1.0 / 3.0) > 1e-8 || error > 1e-8) return 2; + + const double left[2] = {1.0, 2.0}; + const double right[2] = {3.0, 4.0}; + const double product = cblas_ddot(2, left, 1, right, 1); + if (std::abs(product - 11.0) > 1e-12) return 3; + + std::printf("compat.gsl: special functions, integration, and CBLAS ok\n"); + return 0; +} + +#else + +int main() { + return 0; +} + +#endif From 56697898fccf0d721926c77d3c0b702c6b38302f Mon Sep 17 00:00:00 2001 From: tz12323 <2760686250@qq.com> Date: Wed, 14 Oct 2026 21:27:16 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(pkg):=20=E6=9B=B4=E6=96=B0=20GSL=20?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E7=AC=A6=EF=BC=8C=E6=B7=BB=E5=8A=A0=20CFLAGS?= =?UTF-8?q?=20=E8=AE=BE=E7=BD=AE=E4=BB=A5=E6=94=AF=E6=8C=81=20C11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/docs/2026-10-14-add-gsl-plan.md | 7 +++++-- pkgs/c/compat.gsl.lua | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.agents/docs/2026-10-14-add-gsl-plan.md b/.agents/docs/2026-10-14-add-gsl-plan.md index f622bb64..0b6afa21 100644 --- a/.agents/docs/2026-10-14-add-gsl-plan.md +++ b/.agents/docs/2026-10-14-add-gsl-plan.md @@ -14,8 +14,11 @@ ## 2. 描述符设计 - 身份为 `compat:gsl`,文件为 `pkgs/c/compat.gsl.lua`。 -- Linux/macOS 使用官方源码包和 `xim:make@latest`,关闭 shared、启用 static, - 产物安装到包的 `include/` 与 `lib/`。 +- Linux 使用官方源码包和 `xim:make@latest`,macOS 使用系统自带的 GNU Make; + 关闭 shared、启用 static,产物安装到包的 `include/` 与 `lib/`。 +- hook 显式使用 `CFLAGS=-std=c11`。GSL 2.8 的 configure 探测包含旧式 C + 兼容性代码,在 GCC 16 的默认 C23 模式下会被拒绝;固定到 C11 同时符合 + GSL 的 ANSI C 构建要求,并保持 Linux CI 与本地工具链一致。 - 消费侧公开 `include/gsl/*.h`,链接顺序为 `libgsl`、`libgslcblas`、`libm`。 - GSL 自带 CBLAS 随包默认提供,不再声明或隐式绑定 `compat.openblas`,避免 BLAS ABI 和符号来源不明确。 diff --git a/pkgs/c/compat.gsl.lua b/pkgs/c/compat.gsl.lua index e2963871..28bf01ba 100644 --- a/pkgs/c/compat.gsl.lua +++ b/pkgs/c/compat.gsl.lua @@ -25,7 +25,6 @@ package = { }, }, macosx = { - deps = { "xim:make@latest" }, ["2.8"] = { url = "https://ftp.gnu.org/gnu/gsl/gsl-2.8.tar.gz", sha256 = "6a99eeed15632c6354895b1dd542ed5a855c0f15d9ad1326c6fe2b2c9e423190", @@ -92,7 +91,8 @@ local function install_gsl() local make = resolve_make() local jobs = (os.default_njob and os.default_njob()) or 4 local configure = string.format( - "CC=cc %s --disable-shared --enable-static --disable-dependency-tracking " + "CC=cc CFLAGS='-std=c11' %s --disable-shared --enable-static " + .. "--disable-dependency-tracking " .. "--prefix=%s --libdir=%s", "./configure", sh_quote(prefix), sh_quote(path.join(prefix, "lib"))) if not run(configure, logf) then return false end From 1a4a3aa8473e56c1125579ac845364f2a85b362a Mon Sep 17 00:00:00 2001 From: tz12323 <2760686250@qq.com> Date: Wed, 14 Oct 2026 21:39:25 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20GSL=20?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E8=84=9A=E6=9C=AC=EF=BC=8C=E6=98=8E=E7=A1=AE?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E7=BC=96=E8=AF=91=E5=99=A8=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=97=A5=E5=BF=97=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/docs/2026-10-14-add-gsl-plan.md | 3 +++ pkgs/c/compat.gsl.lua | 30 ++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.agents/docs/2026-10-14-add-gsl-plan.md b/.agents/docs/2026-10-14-add-gsl-plan.md index 0b6afa21..458c7053 100644 --- a/.agents/docs/2026-10-14-add-gsl-plan.md +++ b/.agents/docs/2026-10-14-add-gsl-plan.md @@ -19,6 +19,9 @@ - hook 显式使用 `CFLAGS=-std=c11`。GSL 2.8 的 configure 探测包含旧式 C 兼容性代码,在 GCC 16 的默认 C23 模式下会被拒绝;固定到 C11 同时符合 GSL 的 ANSI C 构建要求,并保持 Linux CI 与本地工具链一致。 +- Linux hook 显式选择 `/usr/bin/gcc`,而不是 PATH 中可能由 xlings 注入的 + payload compiler。GSL configure 会编译并运行 ABI 探测程序,宿主编译器 + 保证这些探测程序能在 CI runner 上执行。 - 消费侧公开 `include/gsl/*.h`,链接顺序为 `libgsl`、`libgslcblas`、`libm`。 - GSL 自带 CBLAS 随包默认提供,不再声明或隐式绑定 `compat.openblas`,避免 BLAS ABI 和符号来源不明确。 diff --git a/pkgs/c/compat.gsl.lua b/pkgs/c/compat.gsl.lua index 28bf01ba..60939aa2 100644 --- a/pkgs/c/compat.gsl.lua +++ b/pkgs/c/compat.gsl.lua @@ -64,11 +64,33 @@ local function resolve_make() return "make" end +local function resolve_cc() + if os.host() == "linux" and os.isfile("/usr/bin/gcc") then + return "/usr/bin/gcc" + end + if os.host() == "macosx" and os.isfile("/usr/bin/cc") then + return "/usr/bin/cc" + end + return "cc" +end + +local function tail_lines(file, count) + local ok, content = pcall(io.readfile, file) + if not ok or not content then return "" end + local lines = {} + for line in (tostring(content) .. "\n"):gmatch("(.-)\n") do + lines[#lines + 1] = line + end + local first = math.max(1, #lines - count + 1) + return table.concat(lines, "\n", first, #lines) +end + local function run(command, logf) local ok, result = pcall(os.exec, string.format( "bash -c %s", sh_quote(command .. " >> " .. sh_quote(logf) .. " 2>&1"))) if not ok or not result then - log.error("compat.gsl: build command failed; see %s", logf) + log.error("compat.gsl: build command failed; see %s\n%s", + logf, tail_lines(logf, 80)) return false end return true @@ -89,12 +111,14 @@ local function install_gsl() local logf = path.join(prefix, "mcpp_gsl_build.log") local make = resolve_make() + local cc = resolve_cc() local jobs = (os.default_njob and os.default_njob()) or 4 local configure = string.format( - "CC=cc CFLAGS='-std=c11' %s --disable-shared --enable-static " + "CC=%s CFLAGS='-std=c11' %s --disable-shared --enable-static " .. "--disable-dependency-tracking " .. "--prefix=%s --libdir=%s", - "./configure", sh_quote(prefix), sh_quote(path.join(prefix, "lib"))) + sh_quote(cc), "./configure", sh_quote(prefix), + sh_quote(path.join(prefix, "lib"))) if not run(configure, logf) then return false end if not run(string.format("%s -j%d", sh_quote(make), jobs), logf) then return false