Skip to content

feat(pkg): 新增 compat.gmp —— GNU GMP 6.3.0 静态库(install() 驱动,linux/macOS) - #4

Closed
wellwei wants to merge 12 commits into
mainfrom
codex/gmp
Closed

feat(pkg): 新增 compat.gmp —— GNU GMP 6.3.0 静态库(install() 驱动,linux/macOS)#4
wellwei wants to merge 12 commits into
mainfrom
codex/gmp

Conversation

@wellwei

@wellwei wellwei commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Closes mcpplibs#171 (adapted in this fork for CI verification first).

变更内容

  • pkgs/c/compat.gmp.lua —— 新增 compat.gmp(GNU GMP 6.3.0):C 源码库走 xpkg install() 钩子模式(同 compat.openssl/compat.openblas),钩子内跑上游 configure && make && make install 产出静态 libgmp.a + include/gmp.h
  • tests/examples/gmp/ —— 消费成员:mpz(100! / 2^1000 mod 12345 / gcd)+ mpq(1/2+1/3)+ mpf(sqrt2)断言,参考值由 Python 独立预计算;cfg(linux)/cfg(macos) 门控,其他平台 no-op main。
  • mcpp.toml 注册成员;README(EN/ZH)参考示例表补充;设计文档 .agents/docs/2026-08-09-add-gmp-plan.md

关键决策

  • configure 参数:--disable-shared --enable-static --disable-assembly(可移植通用 C 内核);--libdir 为绝对路径(GMP configure 拒绝相对值)。
  • 平台:linux + macOS;Windows 推迟(GMP 无 MSVC 构建路径,与 compat.openssl 现状一致)。
  • 镜像:暂无 mcpp-res 写权限 → 纯字符串上游 url(ftp.gnu.org 权威 tarball),lint 合规,维护者后续可补 CN。
  • 许可证:LGPL-3.0-or-later(GMP 双许可取宽松侧)。

本地已验证(mcpp 2026.8.8.2 = CI pin,macOS arm64)

  • RED → GREEN:mcpp test -p gmp 通过,gmp ... ok, test result ok. 1 passed; 0 failed
  • 冷启动全流程(清 target/.mcpp 重跑 45s)通过;链接行确认用本包 -Llib -lgmp 静态库
  • lint 8 项全过(lua 语法 / 必填字段 / 无前导 v / mirror / package-name / mcpp xpkg parse / 全仓 cross-refs / 全仓 platform parity)

本 PR 用于让 CI 选跑 gmp 成员,补 linux(gcc+llvm)与 macOS 腿的实测证据。

wellwei and others added 12 commits August 9, 2026 00:06
* feat: add libmysqlclient and MySQL Connector/C++

* fix(pkg): refresh compat.libmysqlclient sha after config repair

The vendored libmysqlclient staging repo now defines HAVE_SYS_RESOURCE_H
(plus the other POSIX macros upstream configure.cmake pairs with them) and
carries Windows support sources; tag 8.4.6 was force-moved to the repaired
commit, so the linux/macosx archives changed. mcpp-index still declares only
linux+macosx for this package.

* fix(pkg): bump compat.libmysqlclient sha (strlcat/strlcpy on Linux)

Linux toolchain glibc >= 2.38 declares strlcat/strlcpy; vendored config now
defines HAVE_STRLCAT/HAVE_STRLCPY there so crypt_genhash_impl.cc uses libc
instead of its static fallback. Tag 8.4.6 updated again.

* diag(connector): surface install-hook failures to Actions stdout

The connector install hook fails with only an empty E_INTERNAL on macOS CI
with no build log. Add stdout diagnostics (pcall wrapper, step values, run()
error dump) to locate the failing step. Temporary diagnostic commit.

* diag(connector): write hook trace to a findable build log

xlings swallows install-hook stdout/log.error, so the previous stdout
diagnostics never reached the Actions log. Write every step (including a
descriptor-load marker) to $HOME/.mcpp/registry/data/mcpp_mysql_connector_cpp_build.log,
which validate.yml's 'find ... -name mcpp_*_build.log' dump tail-prints.

* diag(connector): guard diag helper for descriptor-executing lint

check_mirror_urls/check_package_name execute the descriptor with only import
stubbed; top-level path.join crashed lint. Make diag lazy and a no-op unless
the xlings runtime's path global is present.

* fix(connector): find deps in shared cache + surface hook errors

Root cause (from CI diagnostics): pkginfo.install_dir('compat:openssl')
returns nil when openssl was installed into the shared registry cache by the
sibling libmysqlclient member instead of this member's local xpkgs, so the
install hook bailed with only an empty E_INTERNAL.

- declare compat:libmysqlclient/compat:openssl as xpm deps so the resolver
  records them for this package
- add find_dep_install_dir() fallback scanning the known xpkgs roots
  (member-local + $MCPP_HOME/registry/data/xpkgs)
- write the hook trace + failure reason to
  $HOME/.mcpp/registry/data/mcpp_mysql_connector_cpp_build.log, which the
  validate.yml find-dump tail-prints (log.error/stdout are swallowed)
- macOS: sanitize PATH to /usr/bin:/bin:/usr/sbin:/sbin so a GNU libtool on
  PATH (conda/homebrew) cannot shadow Apple's libtool in the CMake merge

* fix(connector): resolve xim cmake .app bundle, prepend system PATH

CI reached CMake configure for the first time and failed with exit 127:
resolve_tool fell back to bare 'cmake' because the xim cmake macOS payload is
CMake.app/Contents/bin/cmake, not bin/cmake, and the previous fix replaced
PATH entirely so the fallback had nothing to resolve. Handle the .app layout
for an absolute path and prepend /usr/bin:/bin:/usr/sbin:/sbin to the existing
PATH (system libtool wins, tools stay reachable).

* fix(connector): quote PATH value in env invocation

A dev-machine PATH containing spaces (e.g. 'Library/Application Support/...')
split the env command line and failed CMake configure with exit 127. Wrap the
prepended PATH value in single quotes (sh_quote).
`Plan the shards` 用平台的矩阵条目数当分片数:

    linux:$(jq -r '[.include[]|select(.platform=="linux")]|length' ...)

在 mcpplibs#184 之前这两个数恰好相等,所以一直是对的。mcpplibs#184 给 linux 加了第二条工具链
腿之后不再相等:平台发 2 x 3 = 6 个条目,而切分仍然是 3 路,每个条目带的
shard 是 0..2。于是 plan 按 6 路切,job 只消费 0/1/2 —— 分到 3/4/5 的成员
一个都没跑。

65 个成员实际只跑了 29 个,而且是**静默**的:一个从未被分配的成员,和一个跑
过并通过的成员,在 CI 界面上长得一模一样。丢掉的里面有 ffmpeg、opencv-module
及其两个 feature 成员、catch2-v2、catch2-main、openssl —— 正是那条新腿被加进
来要测的东西。mcpplibs#184 全绿,但它想验证的路径一次都没执行。

改成读 `.shards`,也就是 emit() 已经写进每个条目、job 自己也在用
(matrix.shards)的那个值。plan 和消费方从此读同一个数,而不是两个碰巧相等
的数。

macos / windows 不受影响也不需要改:它们仍是单腿,条目数正好等于分片数 ——
这正是这个 bug 只咬 linux 的原因,也是它能在 review 里活下来的原因。

验证:拿 mcpplibs#184 那次运行的真实 matrix.json 跑 jq,linux 6 → 3,macos / windows
维持 2;plan_shards 三片合计从 29 回到 65/65,上面点名的成员全部归位。
`catch2_main.cpp` 用 `__has_include(<catch2/catch_all.hpp>)` 区分两个大版本。
这个探测同样会翻系统 include 目录,而 catch_all.hpp 正是每个发行版的 catch2
包都会装的头。于是在一台装了系统 Catch2 v3 的机器上,一个 v2 消费者被回答
"你是 v3",编出 Catch::Session 那条入口,再死在链接:

    ld.lld: error: undefined symbol: Catch::Session::Session()
    >>> referenced by /usr/include/catch2/catch_session.hpp:39

和 mcpplibs#183 修的 compat.ffmpeg 是同一类问题:宿主机装了同名开发包,vendored 的
东西就被挤掉。gcc 不中招是因为它通过 --sysroot 进 xlings subos,那里没有
/usr/include/catch2 —— 所以这条在 CI 和默认工具链下一直是绿的。

改成探测 `catch2/catch_user_config.hpp.in`:上游把它作为 CMake 模板放在 v3
的源码树里,装的是生成后的 .hpp,模板本身从不安装。三种情形在 clang 22.1.8
和 gcc 16.1.0 上都验过:

    vendored v3 在 -I 上   -> v3     (两种探测一致)
    vendored v2 在 -I 上   -> v2     (catch_all.hpp 答 v3,即本 bug)
    只有系统 v3            -> v2     (catch_all.hpp 答 v3)

四个 catch2 成员 × 两个工具链,在一台装了系统 Catch2 v3 的机器上全过 ——
catch2-v2-main 此前在 llvm 下是 FAIL。

mcpplibs#183 的描述里把这条列为"要等 per-version build blocks(mcpp#290)才能修",
那个判断下早了:要的不是"知道自己是哪个版本",只是一个系统装不出来的探针。
mcpp#290 仍然是更干净的答案,它让这个问题整个消失,而不是换一个探针。
… 1.3.13) (mcpplibs#188)

* feat(pkg): add compat.hiredis@1.2.0 and compat.redis-plus-plus@1.3.13

* feat(pkg): redis-plus-plus@1.3.3 — pre-watershed structure via source-list union

One descriptor now serves both sides of the 1.3.6 source-structure watershed:
1.3.13 (17 sync TUs) and 1.3.3 (15 TUs, no redis_uri.cpp/redlock.cpp, no
hiredis_features.h). The union works because 1.3.3's TUs are a strict subset
of 1.3.13's, so exactly two source globs match nothing there (a warning, not
an error — same premise as compat.catch2's disjoint union). New member
tests/examples/redis-plus-plus-v133 pins 1.3.3; both members pass the same
offline RESP round-trip test under mcpp 2026.8.8.2.

* fix(pkg): force-include <cstdint> for redis-plus-plus@1.3.3 — 'uint16_t' under vendored gcc/libstdc++

1.3.3's utils.h declares uint16_t crc16(...) without including <cstdint>
(upstream added it in 1.3.6+). clang's libc++ pulls it in transitively, but
the linux-default (gcc, vendored libstdc++) CI leg does not:
'uint16_t' does not name a type. Package cxxflags -include cstdint fixes the
package build on every leg (harmless for 1.3.13, which includes it itself),
and the v133 member's [build] cxxflags covers its test TU (which includes
<sw/redis++/redis++.h> first). Caught by CI: workspace linux default 0/1.

* fix(pkg): move compat.redis-plus-plus to pkgs/c/ — <x> is the full-name initial, not the short name

The repository rule is pkgs/<x>/<name>.lua with <x> = initial of the FULL
package name (compat.* → c, docs/repository-and-schema.md). compat.redis-plus-plus
was wrongly placed in pkgs/r/ (short-name initial); the compat namespace
belongs in pkgs/c/ next to compat.hiredis. Resolution is path-independent, so
this is purely the directory convention — README links, the member comment and
the design doc updated in lock-step.
* ci: 刷新计时表并把 linux 分片上限抬到 4 —— 3 片已经装不下了

`linux default 0/3` 在 run 31266814148 被 90 分钟上限砍掉,成员全绿,死在
cache/artifact 的 post 步骤。查下来既不是冷缓存也不是 runner 争抢,是工作量
真的超了,而**陈旧的计时表把这件事盖住了**。

## 计时表漏了两个重家伙

`tests/member-timings.tsv` 停在 62 行 linux,而 workspace 已经有 67 个成员。
`plan_shards` 对表里没有的成员按**中位数**计价,于是:

    mysql-connector-cpp   实测 881s   被当成 ~60s
    libmysqlclient        实测 329s   被当成 ~60s

两个加起来 20 分钟的工作量,被当轻的塞进了已经扛着 grpc-codegen 的 shard 0。
这两个数是从 run 31266814148 的 `linux default 0/3` 步骤耗时里量的 —— 那一片
正好被砍,timings artifact 从没上传,而它们又正是导致溢出的成员。这个先有鸡还
是先有蛋只能手工破一次。表的其余部分来自 run 31260545520 的 member-timings
artifact(顺带补上 cli11 / cmdline / llmapi,并修正 curl 26→264s、
eui-neo-sdl2 119→591s)。

## 刷完表才看清:3 片本来就不够

刷新后 linux 总量 15891s = 265 分钟。按当前表模拟最慢分片:

    3 片   98 分   ← 超 90 分钟 job 上限
    4 片   74 分
    5 片   60 分

`shards_for` 的公式 `secs / 4200 + 1` 对这个总量算出来就是 4,一直被
`cap 3` 压回 3。所以这不是新问题被引入,是旧上限被工作量长过去了 —— 旧表让
总量看起来只有 13570s,刚好还压得住。

## 上限 3 -> 4

旧注释给这个上限的理由是"linux 实测并发 3,第 4 片会排队"。这个前提已经两头
失效:工作量长大了,而且自 mcpplibs#184 起 linux 每轮发 2 x N 个 job,任何 N 都会排队。
排队是对的取舍 —— 背靠背跑完的分片仍然完成,超过上限的分片不会。

代价:linux 每轮 8 个 job(原 6),模拟确认 `.shards` 读作 4、四片覆盖 67/67。

同时把那段容量注释改成现在为真的样子,并写明"表要保持新鲜"不是打扫卫生:
一个没计时的成员按中位数打包,一个重的新成员就会像轻的一样被塞进任何地方。

* ci: 改计时表不该触发全量运行

tests/member-timings.tsv 在 select 的 case 里一条都不匹配 —— 不是 tests/*.sh,
不是 tests/examples/*,不是 pkgs/*.lua,也不在忽略清单里 —— 于是落进
`*) full "unclassified change"`。

这张表决定活儿怎么在分片间分配,从不决定构建什么:没有任何成员的结果会因为
一个实测数字变了而改变。让它触发全量,是这份 workflow 里"花最大代价测试零
东西"的做法,而下一次真正的全量运行本来就会读到新数字。

代价写在注释里了:这样一来这个文件对 CI 不可见,坏行是静默的,而
plan_shards 对解析不了的东西按中位数计价 —— 正是刚刚撑爆一个分片的那个失效
模式。真要咬到就在 lint 里加校验。

* ci: 计时表只留每平台最重的 10 个,加测试不再需要动它

这张表之前列出每个成员,于是每加一个测试它就过期一次 —— 而这次撑爆分片的
正是"表里没有的成员按中位数计价":mysql-connector-cpp 实测 881s,被当成 60s。

但打包决策其实只由重的那几个做出。linux 上 top10 占总量的 69%,其余 57 个平
均 86s、中位 24s。把它们从表里拿掉,最慢分片一分钟都不动:

    完整表 67 行,4 片    74 / 64 / 63 / 63    最慢 74 分
    top10 表,4 片        74 / 73 / 60 / 58    最慢 74 分

## 兜底价必须是固定常数,不能再取中位数

直接缩表会当场炸:表里只剩 10 个重的,中位数就变成"第 5 重的那个",在 linux
上是 875s,于是 57 个小成员每个都按 875s 计价 ——

    top10 表 + 中位数兜底   90 / 48 / 81 / 46    最慢 90 分  ← 正好撞 job 上限

改成固定 90s(未计时成员的实测均值 86s 取整)。这个值不吃调参:兜底价从 30s
到 300s,最慢分片始终在 72–84 分之间,全都在 90 以下。这种不敏感正是"表可以
长期不动"的依据。

## shards_for 也得跟着改,否则片数会掉回去

它原本直接把表里的行加起来当工作量。表一缩,linux 总量从 15891s 读成 10965s
→ 3 片,而 3 片在这张表下最慢 107 分,直接超时。

给 plan_shards 加一个 `<platform> 0 0` 模式返回总量,shards_for 改调它。这样
"有哪些成员"和"未计时的算多少钱"只有一份定义,而不是 yaml 和 lua 各一份等着
漂移。估算精度:linux 16095s vs 实测 15891s,+1.3%。macOS / windows 的成员比
默认价便宜,总量偏高 —— 两者本来就顶着 cap 2,而且偏多分片是安全方向。

## timings job 同步产出 top10

否则下次刷新又胖回 197 行。

端到端复核:总量 16095s → 4 片,四片覆盖 67/67,最慢 74 分。
每次合并都在重跑全部 67 个成员 × 三平台 —— 约 11 小时 runner 时间,用来复核
几分钟前那个 PR 已经绿过的同一批东西。

原因是 select 的第一行 `event != pull_request -> full`。它的前提是"push 没有
可 diff 的 base",而这个前提不成立:本仓的合并都是 squash,`github.event.before`
就是上一个 main,`before..HEAD` 复现出来的文件列表和 PR 当时看到的一模一样。
拿 698b95e 实测,两边都选出 `redis-plus-plus redis-plus-plus-v133`,十个改动
文件逐个对得上。

## 事件分派

    pull_request  三点 diff(自己的提交对 merge base)
    push          两点 diff(这条分支上真正落下的东西)
    其他          全量

schedule 和 workflow_dispatch 保持全量:它们表达的不是"有改动落地",而是
"把所有东西查一遍",那正是下面那张网的意义。

push 的两个兜底都倒向全量:before 是全零(建分支)或那个对象不在历史里
(force-push 把它丢了)时,没有可 diff 的东西,重跑好过猜。

## 定时从每天改成每周

选择性测试结构上看不到的只有一件事:两个改动各自绿、落在一起互相踩 —— 两个
PR 的 diff 都没提到那个坏掉的成员,所以两个都选不中它。全量是这件事的网。

这张网每天撒一次和每七天撒一次,抓到的是同一批交互;而全量已经不便宜了(加
了第二条 linux 工具链腿之后约 11 小时)。改成周日 06:00 UTC。手动触发本来就
在,想立刻验一遍不用等。

五条路径都验过:schedule / dispatch / before 全零 / before 不在历史 → 全量;
before 正常 → 选择性。
…dely deployed 3.45.x line) (mcpplibs#190)

- Shape A (C-source compat): compile the single sqlite3.c amalgamation into a
  lib, expose sqlite3.h/sqlite3ext.h via include_dirs; shell.c (interactive
  CLI, needs editline/readline) stays out.
- Version 3.45.3 = final maintenance release of the most widely deployed
  SQLite series (Ubuntu 24.04 LTS ships 3.45.1; CPython 3.11/3.12/3.13
  installers ship 3.45.1/3.45.3/3.45.3), chosen over newer series per user
  request (widely-used, not latest).
- GLOBAL = sqlite.org amalgamation zip (sha256 verified twice); no CN mirror
  (plain-string url fallback — the GitHub sqlite/sqlite mirror does not carry
  the generated sqlite3.c).
- Consumer member tests/examples/sqlite3 asserts sqlite3_libversion()==3.45.3
  and exercises open/create/insert/prepared-query end to end; verified cold
  with the CI-pinned mcpp 2026.8.8.2 (test result ok, 1 passed).
- Design doc covers shape decision, version rationale, multi-version support
  plan (one mcpp block, xpm rows only).
Publish the byte-identical GLOBAL/CN libxpkg 0.0.55 archive on Linux, macOS, and Windows. Required checks passed on 3648230.
- pkgs/c/compat.gmp.lua:xpkg install() 钩子跑上游 configure && make && make install,
  产出 libgmp.a + include/gmp.h(--disable-assembly 通用 C 内核,--libdir 绝对路径)。
- tests/examples/gmp/:mpz/mpq/mpf 三层算术断言,cfg(linux)/cfg(macos) 门控,其他平台 no-op。
- windows 推迟:GMP 无 MSVC 构建路径;镜像待 mcpp-res 权限后补,当前纯字符串上游 url。
- 本地验证:mcpp 2026.8.8.2 macOS arm64 冷启动全流程通过;lint 8 项全过。
- 关联 issue mcpplibs#171
linux default 腿 CI 实测失败:configure 报 could not find a working compiler。
GMP 的 configure 会编译并运行 ABI 探针、make 期运行 gen-* 工具,链接到 xim glibc
payload(--sysroot/-B/-L)的这类可执行文件在本钩子环境无法运行;openssl 的
Configure 只写 Makefile 所以无此问题。改 /usr/bin/gcc 后探针/工具跑宿主 glibc,
静态库符号由消费端工具链更新的 payload glibc 在最终链接时满足。
@wellwei

wellwei commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

验证完成(四条 workspace 腿全绿)。已转到上游 PR: mcpplibs#194 ,本 PR 关闭。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

增加libgmp科学库

2 participants