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
46 changes: 46 additions & 0 deletions .agents/docs/2026-10-14-add-gsl-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 新增 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 使用官方源码包和 `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 与本地工具链一致。
- 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
和符号来源不明确。
- 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`,以及一次清理缓存后的
冷构建,确认头文件和两个静态归档来自该包而不是宿主系统。
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
156 changes: 156 additions & 0 deletions pkgs/c/compat.gsl.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
-- 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 = {
["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 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 "<log unavailable>" 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\n%s",
logf, tail_lines(logf, 80))
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 cc = resolve_cc()
local jobs = (os.default_njob and os.default_njob()) or 4
local configure = string.format(
"CC=%s CFLAGS='-std=c11' %s --disable-shared --enable-static "
.. "--disable-dependency-tracking "
.. "--prefix=%s --libdir=%s",
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
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
12 changes: 12 additions & 0 deletions tests/examples/gsl/mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
49 changes: 49 additions & 0 deletions tests/examples/gsl/tests/gsl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifdef HAVE_GSL

#include <cmath>
#include <cstdio>

#include <gsl/gsl_blas.h>
#include <gsl/gsl_cblas.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_sf_bessel.h>

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 = &square;
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
Loading