From fd656d523bf06e7b359f42fa08a1ac21814df2aa Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 12:44:10 +0800 Subject: [PATCH 01/26] feat: add boost-ext.ut 2.3.1 C++23 module package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Boost::ext].UT is the C++20 single-header unit testing framework shipped by the boost-ext org (NOT an official Boost library; hence the new mcpp namespace, not and not ). Exposed as the C++23 module so users can write directly. The v2.3.1 tarball already ships include/boost/ut.cppm (), but it cannot be used VERBATIM on either non-MSVC default toolchain of CI's pinned mcpp 0.0.109: * GCC 16.1 (-std=c++23) rejects unqualified at namespace scope in the module purview with ( only brings in ); ut.hpp uses unqualified in reporter_junit. * Clang 22.1 on the MSVC ABI (x86_64-windows-msvc) fails on the MSVC builtins / referenced under at ut.hpp:687 — clang sets but does not provide those builtins (the adjacent upstream guards at lines 291/311/1147 already gate clang out, but line 687 missed it). So, like marzer.tomlplusplus, a wrapper reproduces upstream's cppm VERBATIM plus two minimal shims: a at purview top level (fixes GCC) and / gated to on (fixes clang-on-Windows; MSVC itself never enters the guard). base ut.hpp stays pinned to the v2.3.1 tag. Verified locally with mcpp 0.0.109 (CI pin): - mcpp xpkg parse pkgs/b/boost-ext.ut.lua -> parse OK - mcpp test -p boost-ext.ut (gcc 16.1.0, x86_64-windows-gnu default) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' - mcpp test -p boost-ext.ut (llvm 22.1.8, x86_64-windows-msvc) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' No feature: ut is header-only + single module unit, no extra compilable sources to gate; the optional defines are compile-time, not yet representable in the features table. CN mirror intentionally omitted (no mcpp-res write access); the descriptor uses plain-string upstream URLs which the lint (tests/check_mirror_urls.lua) accepts as-is. Maintainer can backfill the gitcode release later. Design notes in .agents/docs/2026-08-02-add-boost-ext-ut-plan.md. --- .../docs/2026-08-02-add-boost-ext-ut-plan.md | 136 +++++++++++++++++ README.md | 2 +- README.zh-CN.md | 2 +- mcpp.toml | 1 + pkgs/b/boost-ext.ut.lua | 143 ++++++++++++++++++ tests/examples/boost-ext.ut/mcpp.toml | 15 ++ tests/examples/boost-ext.ut/tests/ut.cpp | 23 +++ 7 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 .agents/docs/2026-08-02-add-boost-ext-ut-plan.md create mode 100644 pkgs/b/boost-ext.ut.lua create mode 100644 tests/examples/boost-ext.ut/mcpp.toml create mode 100644 tests/examples/boost-ext.ut/tests/ut.cpp diff --git a/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md new file mode 100644 index 0000000..d99b2ac --- /dev/null +++ b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md @@ -0,0 +1,136 @@ +# 收录 [Boost::ext].UT(boost-ext.ut)2.3.1 + +> 日期:2026-08-02 +> 分支:`add-boost-ext-ut`(本地提交,未推送、未发 PR) +> 仓库:[boost-ext/ut](https://github.com/boost-ext/ut) +> 背景:用户要求将 boost-ext 组织(注意:不是 boost 官方)的 UT 单头测试框架收录进 mcpp-index, +> 自带 C++ 模块支持,模块单元位于 `include/boost/ut.cppm`,声明 `export module boost.ut;`。 + +## 1. 来源与形态判定 + +- 来源:**(a) 第三方上游库**。上游 boost-ext/ut 不提供 mcpp 支持,但其 release tarball **自带**官方模块单元 + `include/boost/ut.cppm`(`export module boost.ut;`),因此本仓不需自行从零合成 module wrapper, + 只需对上游 cppm 做最小兼容补丁(见 §2)。 +- 版本:`v2.3.1`(截至 2026-08-02 的最新 release tag)。 +- License:Boost Software License 1.0,SPDX 标识 `BSL-1.0`。 +- 布局:tarball 顶层 wrap 目录为 `ut-2.3.1/`,模块单元位于 `include/boost/ut.cppm`, + 头文件 `include/boost/ut.hpp`(3378 行,定义 `namespace boost::inline ext::ut::inline v2_3_1`)。 +- 形态:**C++23 module(generated wrapper)** —— 因上游 cppm 无法逐字编译(见 §2),采用 + `generated_files` 内嵌一份带两处最小 shim 的等价物,与 `marzer.tomlplusplus` 同类。 + +判据:逐字复用上游 cppm 的尝试在 mcpp 0.0.109 默认 toolchain 下均失败,详见 §2。 + +身份:`namespace = "boost-ext"`、`name = "ut"`。**NOT** `boost`、**NOT** `compat`: +用户明确指出该项目并非 boost 官方库,因此不能占用 `compat` 习惯位置,也不能放进 boost 命名空间, +更不能与 boost::ext 这一第三方组织混同于 mcpplibs 的既有命名空间。`boost-ext` 这一 mcpp 命名空间 +用 `-` 分层,避免与 mcpp 既有 `.` 点号寻址约定碰撞(与 `nlohmann.json` 走点号、 +`marzer.tomlplusplus` 走点号属同一道理下的反向取舍)。 + +## 2. 关键注意事项:上游 ut.cppm 不能逐字内嵌 + +与 `nlohmann.json`(可 VERBATIM 内嵌)与 `marzer.tomlplusplus`(删一行后 VERBATIM 内嵌)不同, +上游 `ut-2.3.1/include/boost/ut.cppm` 在 mcpp 0.0.109 的两条非 MSVC 默认 toolchain 上**均无法编译**: + +### 2.1 GCC 16.1(`-std=c++23`):-Wtemplate-body 拒绝无修饰 `size_t` + +``` +ut.hpp:1916:5: error: 'size_t' was not declared in this scope; + did you mean 'std::size_t'? [-Wtemplate-body] + 1916 | size_t n_tests = 0; +``` + +根因:ut.cppm 把 `#include "ut.hpp"` 放在 purview 内,只做了 `export import std;`。 +在 named-module purview 中,`size_t` 并不通过 `export import std;` 进入作用域 +(只有 `std::size_t` 进入)。ut.hpp 在多处用无修饰 `size_t`(行 1916/1917/1936/1937、 +reporter_junit 模板体内),非模块编译时这条由前置 `` 等头链间接带入,模块下则显式缺失。 + +### 2.2 Clang 22.1(MSVC ABI / x86_64-windows-msvc):`__argc`/`__argv` 未声明 + +``` +ut.hpp:688:29: error: use of undeclared identifier '__argc'; did you mean 'largc'? + 688 | static inline int largc = __argc; +ut.hpp:689:63: error: use of undeclared identifier '__argv'; did you mean 'largv'? +``` + +根因:ut.hpp 行 687 `#if defined(_MSC_VER)` 引用 MSVC 内建 `__argc`/`__argv`。Clang 在 MSVC ABI +上设了 `_MSC_VER`,但不提供这两个内建 —— 上游相邻分支(行 291 / 311 / 1147)已通过 +`&& !defined(__clang__)` 排除 clang,行 687 漏了同一守卫。属上游 clang-on-windows 模块适配缺口。 + +### 2.3 解法:generated_files 内嵌 + 两处最小 shim + +`generated_files` 内嵌的 cppm 在逐字节复用上游 ut.cppm 的基础上**仅**插入两处 shim: + +| shim | 作用 | 守卫 | +|---|---|---| +| `using std::size_t;`(purview 顶层) | 修复 GCC 的 `size_t` 未声明 | 无条件 | +| `#define __argc 0` / `#define __argv ((const char**)nullptr)` | 修复 Clang-on-Windows 的内建缺失 | `_MSC_VER && __clang__` | + +要点: + +- shim 1 的 `using std::size_t;` 在 purview 全局作用域,ut.hpp 在其内开 + `export namespace boost::inline ext::ut::inline v2_3_1 { ... }`,namespace block 内对 `size_t` + 的无修饰查找经 outer-namespace 回溯可见 `::size_t`(由 using 引入)。 +- shim 2 仅在 `__clang__` 定义时启用,MSVC 本身不进入守卫,所以保留 MSVC 真内建行为不变。 + cfg::largc 在运行时由 ut.hpp 行 3373-3375 从 main argc/argv 重新赋值,宏值始终是占位 0, + 不影响行为。 +- `BOOST_UT_CXX_MODULES=1` 这一定义照搬自上游 ut.cppm,从而 + `BOOST_UT_EXPORT` 被定义为 `export`,ut.hpp 行 111 的 + `BOOST_UT_EXPORT namespace boost::inline ext::ut::inline v2_3_1 { ... }` 即 + `export namespace boost::inline ext::ut::inline v2_3_1 { ... }`, + 该 namespace block 内所有声明都随 `export module boost.ut;` 一起 export 到消费者。 + 这是上游 cppm 本来的设计;shims 不破坏该结构。 + +`include_dirs = { "*/include/boost" }` 让 wrapper 内的 `#include "ut.hpp"` 解析到 verdir 下的 +实际 ut.hpp;同时消费者若直接 `#include ` 也能解析(与 nlohmann/marzer 同形式)。 +generated cppm 路径 `mcpp_generated/boost.ut.cppm` 为 verdir 相对(无 glob),与既有同形态惯例一致。 + +## 3. 描述符 + +- 路径:`pkgs/b/boost-ext.ut.lua`(目录取**完整包名首字母** `b`)。 +- 命名:`boost-ext.ut`,避免占用 `compat` 习惯位置与 `boost` 官方命名空间。 +- 三平台 `xpm` 共用同一 url 与 sha256(GLOBAL-only;无 `mcpp-res` 写权限故走 plain-string url, + lint 允许,CN 用户回退至上游源由维护者后续补充)。 +- 版本号采用裸版本 `"2.3.1"`;URL 中保留上游 `…/v2.3.1.tar.gz` 拼写。 + +## 4. feature 评估 + +**不实现 feature**。ut 为纯头文件 + 模块单元,无"额外的可编译源码"可供门控。 +其可选行为(`BOOST_UT_CONFIG_DISABLE_EXCEPTIONS` 等)均为编译期 **define**, +而 `features` 表当前仅能门控 `sources`,无法携带 define。 +与 Eigen 的 `EIGEN_MPL2_ONLY`、toml++ 的 `TOML_EXCEPTIONS` 属同类限制, +待 mcpp 支持 define/cflags 后再评估。 + +## 5. CN 镜像 + +未配置。本地无 `mcpp-res` 写权限,lint 规则允许 plain-string 形式的 `url` +(见 `tests/check_mirror_urls.lua`:plain url 直接放行)。CN 镜像由维护者后续补充。 + +## 6. 验证结论 + +测试工程 `tests/examples/boost-ext.ut/`,已登记进根 `mcpp.toml` 的 `[workspace].members` +(本仓测试面为 `mcpp test -p ` / `mcpp test --workspace`)。断言覆盖 UDL 语法 +(`"..."_test` = lambda { ... })、函数语法(`test("...") = lambda { ... }`)与 +`expect()` 断言两路,直接由 [Boost::ext].UT 的 runner 输出判通过/失败: +`"Suite 'global': all tests passed (3 asserts in 2 tests)"`。 + +| 检查 | 结果 | +|---|---| +| `mcpp xpkg parse pkgs/b/boost-ext.ut.lua`(CI pin 0.0.109 与本地 2026.8.2.1) | ✅ parse OK | +| 全量 `mcpp xpkg parse pkgs/*/*.lua`(0.0.109) | ✅ 无 PARSE FAIL | +| `mcpp test -p boost-ext.ut`(gcc 16.1.0,Windows x86_64-windows-gnu 默认 target) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | +| `mcpp test -p boost-ext.ut`(llvm 22.1.8,Windows x86_64-windows-msvc target) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | +| `tests/check_mirror_urls.lua`(手算:plain-string url 直接放行) | ✅ 通过 | +| `tests/check_package_name.lua`(手算:`name = "ut"` 单一原子段) | ✅ 通过 | +| 前导 v 版本号 lint(手算:`"2.3.1"` 裸版本) | ✅ 通过 | + +冷验证前已 `rm -rf tests/examples/boost-ext.ut/{target,.mcpp,mcpp.lock,compile_commands.json}`, +自干净状态走完"拉 tarball → 编译 wrapper → 编译/链接测试 → 运行"完整管线。 + +## 7. 其它 + +- 描述符注释里说明了与 `nlohmann.json`/`marzer.tomlplusplus` 的同与不同: + 上游 cppm 不能 VERBATIM 内嵌,但**无需**像 marzer 那样删去某一行, + shim 只**追加**两处兼容补丁(verbatim 部分 + 两条 shim),不删除上游 cppm 任何字节, + 也不替换任何行;`generated_files` payload 不含 `]==]`。 +- 提交策略:遵循用户指示,在新分支 `add-boost-ext-ut` 上 commit 一次,**不**推送、**不**发 PR/Issue。 + CI 自然不会运行;本设计文档与本地 `mcpp test` 输出作为等价证据保留。 \ No newline at end of file diff --git a/README.md b/README.md index ee8e588..61ada3d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Two kinds of packages live here: | Multiple majors in one package (shape switches with the version) | [`compat.catch2`](pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) | | External build system (`install()` builds from source) | [`compat.openblas`](pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | | Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua) (upstream already ships `include/boost/ut.cppm` declaring `export module boost.ut;` — used verbatim, no `generated_files`; namespace `boost-ext` since it is NOT an official Boost library) | ### Adding a package diff --git a/README.zh-CN.md b/README.zh-CN.md index cb3b366..965b85e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -47,7 +47,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | | 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua)(上游已自带 `include/boost/ut.cppm`,宣告 `export module boost.ut;` —— 逐字复用,无需 `generated_files`;命名空间取 `boost-ext`,因其并非 boost 官方库) | ### 新增一个包 diff --git a/mcpp.toml b/mcpp.toml index 7f57b6d..8aa0909 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -7,6 +7,7 @@ # mcpp .agents/docs/2026-06-30-workspace-test-and-zero-shell-index-design.md. [workspace] members = [ + "tests/examples/boost-ext.ut", "tests/examples/archive", "tests/examples/asio-module", "tests/examples/asio-ssl", diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua new file mode 100644 index 0000000..9baf10d --- /dev/null +++ b/pkgs/b/boost-ext.ut.lua @@ -0,0 +1,143 @@ +-- Form B inline descriptor for [Boost::ext].UT — the C++20 single-header +-- unit-testing framework shipped by the boost-ext org (NOT an official Boost +-- library; hence the `boost-ext` package namespace, NOT `compat` and NOT +-- `boost`). Exposed as the C++23 module `boost.ut` so users can write +-- `import boost.ut;` out of the box. +-- +-- The upstream release tarball DOES ship an official module interface unit +-- at `include/boost/ut.cppm` (`export module boost.ut;`), but it cannot be +-- used VERBATIM on either of this index's two non-MSVC default toolchains: +-- +-- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`: +-- ut.hpp:1916:5: error: 'size_t' was not declared in this scope; +-- did you mean 'std::size_t'? +-- In a modular TU `size_t` only enters scope via `#include ` / +-- `using std::size_t;`, NOT via `export import std;` (which only brings +-- `std::size_t`). ut.hpp uses `size_t` unqualified at namespace scope. +-- +-- * Clang 22.1 on the MSVC ABI (Windows default target) rejects the verbatim +-- file with `use of undeclared identifier '__argc'` (and `__argv`): +-- ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC +-- builtins `__argc` / `__argv`. Clang DOES set `_MSC_VER` on the MSVC +-- ABI but does NOT provide those builtins (the adjacent branches at +-- lines 291 / 311 / 1147 already gate clang out — line 687 was missed). +-- Tracked upstream as boost-ext/ut#656-style clang-on-windows gap. +-- +-- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that +-- reproduces upstream's INTENT (ut.hpp included in the module purview with +-- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block +-- ut.hpp opens at line 111 takes everything with it) and adds ONLY two +-- minimal shims: a `using std::size_t;` at purview top level (fixes GCC), +-- and `#define __argc 0` / `#define __argv nullptr` gated to `__clang__` on +-- `_MSC_VER` (fixes Clang-on-Windows; MSVC itself is untouched). The base +-- `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — the shims +-- add NO code of our own beyond what the compiler had to see anyway. +-- +-- include_dirs exposes `*/include/boost` so the wrapper's `#include "ut.hpp"` +-- resolves (and `#include ` remains available to consumers who +-- want the header form). The upstream path is a GLOB — the leading `*` +-- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm +-- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus. +-- +-- The native cppm also does `export import std;`, so consumers transitively +-- see stdlib symbols after `import boost.ut;`; `import_std` stays false to +-- avoid a duplicate `import std;` mcpp would otherwise inject into the +-- module's own TU. +-- +-- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0. +-- +-- Package identity: `namespace = "boost-ext"`, `name = "ut"`; the C++20 +-- namespace the library actually declares is `boost::ext::ut::v2_3_1` — +-- unrelated to the mcpp namespace, which uses `-` precisely because `.` would +-- collide with mcpp's own `.` addressing convention. +package = { + spec = "1", + namespace = "boost-ext", + name = "ut", + description = "[Boost::ext].UT — C++20 single-header unit testing framework, exposed as the C++23 module boost.ut", + licenses = {"BSL-1.0"}, + repo = "https://github.com/boost-ext/ut", + type = "package", + + xpm = { + linux = { + ["2.3.1"] = { + url = "https://github.com/boost-ext/ut/archive/refs/tags/v2.3.1.tar.gz", + sha256 = "e51bf1873705819730c3f9d2d397268d1c26128565478e2e65b7d0abb45ea9b1", + }, + }, + macosx = { + ["2.3.1"] = { + url = "https://github.com/boost-ext/ut/archive/refs/tags/v2.3.1.tar.gz", + sha256 = "e51bf1873705819730c3f9d2d397268d1c26128565478e2e65b7d0abb45ea9b1", + }, + }, + windows = { + ["2.3.1"] = { + url = "https://github.com/boost-ext/ut/archive/refs/tags/v2.3.1.tar.gz", + sha256 = "e51bf1873705819730c3f9d2d397268d1c26128565478e2e65b7d0abb45ea9b1", + }, + }, + }, + + mcpp = { + schema = "0.1", + language = "c++23", + import_std = false, + modules = { "boost.ut" }, + include_dirs = { "*/include/boost" }, + -- Upstream's ut.cppm reproduced VERBATIM apart from two minimal + -- compiler-compat shims (documented at the top of this descriptor): + -- 1. `using std::size_t;` at the top of the purview (GCC fix). + -- 2. `#define __argc 0` / `#define __argv nullptr` gated to + -- `__clang__` on the MSVC ABI (Clang-on-Windows fix; MSVC + -- itself never enters the guard). + -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. + generated_files = { + ["mcpp_generated/boost.ut.cppm"] = [==[ +module; + +#if __has_include() and __has_include() +#include +#include +#endif + +export module boost.ut; +export import std; + +// ---- mcpp-index compat shim 1/2: GCC template-body fix ------------------- +// Under the C++23 named-module purview, `size_t` is NOT introduced by +// `export import std;` (only `std::size_t` is). ut.hpp uses `size_t` +// unqualified at namespace scope (e.g. line 1916 of ut.hpp), which GCC 16.1 +// rejects as `-Wtemplate-body` ("'size_t' was not declared in this scope"). +// Pull `std::size_t` into the global purview so the unqualified name +// resolves inside the exported namespace block ut.hpp opens below. +using std::size_t; + +// ---- mcpp-index compat shim 2/2: Clang-on-Windows fix --------------------- +// ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC +// builtins `__argc` / `__argv`. Clang on the MSVC ABI (x86_64-windows-msvc) +// sets `_MSC_VER` but does NOT provide those builtins — the neighboring +// upstream guards at lines 291 / 311 / 1147 already gate clang out, but +// line 687 missed it. Provide macros as stand-ins (cfg::largc is reassigned +// from main() args at runtime anyway, so the value is unused). MSVC itself +// never enters this guard, so its real builtins are untouched. +#if defined(_MSC_VER) and defined(__clang__) + #define __argc 0 + #define __argv ((const char**)nullptr) +#endif + +#define BOOST_UT_CXX_MODULES 1 +#include "ut.hpp" + +#if defined(_MSC_VER) and defined(__clang__) + #undef __argc + #undef __argv +#endif +]==], + }, + sources = { "mcpp_generated/boost.ut.cppm" }, + targets = { ["boost_ut"] = { kind = "lib" } }, + deps = { }, + }, +} \ No newline at end of file diff --git a/tests/examples/boost-ext.ut/mcpp.toml b/tests/examples/boost-ext.ut/mcpp.toml new file mode 100644 index 0000000..bc6515d --- /dev/null +++ b/tests/examples/boost-ext.ut/mcpp.toml @@ -0,0 +1,15 @@ +# boost-ext.ut test project: consumes the C++23 module `boost.ut` and +# asserts two test styles (UDL + function syntax) plus an expect failure +# under `mcpp test`. +# Overrides the workspace-root redirect (`compat`): this member needs the +# `boost-ext` namespace. A member-level [indices] REPLACES the inherited +# table rather than merging with it — keeps the project index repo to ONE. +[indices] +boost-ext = { path = "../../.." } + +[package] +name = "boost-ext-ut-tests" +version = "0.1.0" + +[dependencies.boost-ext] +ut = "2.3.1" \ No newline at end of file diff --git a/tests/examples/boost-ext.ut/tests/ut.cpp b/tests/examples/boost-ext.ut/tests/ut.cpp new file mode 100644 index 0000000..4fc8da9 --- /dev/null +++ b/tests/examples/boost-ext.ut/tests/ut.cpp @@ -0,0 +1,23 @@ +// Behavioral test: build two tests under [Boost::ext].UT via `import boost.ut;`, +// then exercise the UDL ("..."_test) and function-call (test("...")) syntaxes. +// Counts test registration by reading ut's `tests_runner` directly is awkward, +// so the test instead just RUNS a passing assertion in each style; if the +// module fails to import or any expect() fails, [Boost::ext].UT reports the +// failure through its test runner and `mcpp test` returns non-zero. +import std; +import boost.ut; + +using namespace boost::ut; + +int main() { + "UDL syntax"_test = [] { + expect(42_i == 42); + }; + + test("function syntax") = [] { + expect(0_i == 0); + expect(1u == 1_u); + }; + + return 0; +} \ No newline at end of file From 61b94419050a8064f5e8475dafaf8c5f7c70becd Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 17:37:28 +0800 Subject: [PATCH 02/26] fix(boost-ext.ut): close macOS Clang 20.1.7 SIGSEGV via master's explicit template instantiations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on PR #142 showed workspace (linux) and workspace (windows) green, but workspace (macos) failed with exit 139 (SIGSEGV) immediately at `Running bin/ut` — the test binary never reached the 'Suite ...' console output. Root cause: the verbatim v2.3.1 ut.cppm leaves the member templates the `cfg::runner>` dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`, `expect`) only implicitly instantiable; on Apple-Clang 20.1.7 + `-fmodules` the implicit path through the module BMI fails to emit them into the consuming executable, so `cfg` static init calling `reporter_junit::on(events::run_begin)` → `std::unordered_map::operator[]("global")` lands in an un-instantiated slot and segfaults. Upstream fixed this on `master` AFTER v2.3.1 by appending eight explicit template instantiations to ut.cppm; reproduce that block byte-for-byte in our `generated_files` cppm after `#include "ut.hpp"`. This is the same forward-port shape as marzer.tomlplusplus carrying a one-line cut from upstream master — once a >2.3.1 release ships it, switch `sources` to `*/include/boost/ut.cppm` and drop `generated_files` (this block comes back verbatim). `export import std;` is KEPT (with the existing `using std::size_t;` GCC shim): trialed dropping it to mirror nlohmann.json / marzer.tomlplusplus but GCC 16.1 then surfaces a cascade of `-Wtemplate-body` errors in ut.hpp:3288 (`std::empty`), :3282 (`call_steps_` member lookup), :3322 (`literals::operator""_test` using-decl resolution), and more — GCC's two-phase lookup in the module purview is stricter than Clang's and genuinely needs the std module to be import-visible. Local verification, clean state (`rm -rf tests/examples/boost-ext.ut/{target,.mcpp,mcpp.lock,compile_commands.json}`): * mcpp xpkg parse pkgs/b/boost-ext.ut.lua -> parse OK * mcpp test -p boost-ext.ut (gcc 16.1.0, x86_64-windows-gnu) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' * mcpp test -p boost-ext.ut (llvm 22.1.8, x86_64-windows-msvc) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' Both Windows default toolchains stay green — dev 3 is a no-op redundancy on GCC / Clang-on-MSVC and a real fix on Clang-on-Darwin; macOS CI result will be confirmed by the next push to PR #142. Design notes in .agents/docs/2026-08-02-add-boost-ext-ut-plan.md §2.3 / §2.4 / §6. --- .../docs/2026-08-02-add-boost-ext-ut-plan.md | 79 ++++++++---- pkgs/b/boost-ext.ut.lua | 116 +++++++++++++++--- 2 files changed, 154 insertions(+), 41 deletions(-) diff --git a/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md index d99b2ac..0c592ed 100644 --- a/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md +++ b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md @@ -29,7 +29,8 @@ ## 2. 关键注意事项:上游 ut.cppm 不能逐字内嵌 与 `nlohmann.json`(可 VERBATIM 内嵌)与 `marzer.tomlplusplus`(删一行后 VERBATIM 内嵌)不同, -上游 `ut-2.3.1/include/boost/ut.cppm` 在 mcpp 0.0.109 的两条非 MSVC 默认 toolchain 上**均无法编译**: +上游 `ut-2.3.1/include/boost/ut.cppm` 在 mcpp 0.0.109 的三条 CI 平台 default toolchain 上 +**均无法直接使用**: ### 2.1 GCC 16.1(`-std=c++23`):-Wtemplate-body 拒绝无修饰 `size_t` @@ -56,29 +57,56 @@ ut.hpp:689:63: error: use of undeclared identifier '__argv'; did you mean 'largv 上设了 `_MSC_VER`,但不提供这两个内建 —— 上游相邻分支(行 291 / 311 / 1147)已通过 `&& !defined(__clang__)` 排除 clang,行 687 漏了同一守卫。属上游 clang-on-windows 模块适配缺口。 -### 2.3 解法:generated_files 内嵌 + 两处最小 shim +### 2.3 Clang 20.1.7(macOS CI 自装的 default,macos-15):运行期 SIGSEGV(exit 139) -`generated_files` 内嵌的 cppm 在逐字节复用上游 ut.cppm 的基础上**仅**插入两处 shim: +verbatim ut.cppm 在 macOS CI 上**能编译**,但 `mcpp test` 运行测试二进制立刻 exit 139 +(SIGSEGV),`bin/ut` 输出全无,无 `Suite '...'` 字样。根因: -| shim | 作用 | 守卫 | +```text +$ Running bin/ut +ut ... FAIL (exit 139) +``` + +`cfg::runner>` 静态对象的 `reporter_junit` 成员默认初始化器 +`active_scope_ = &results_[active_suite_]` 触发 `std::unordered_map::operator[]("global")`,该条路径在 `-fmodules` 下顺道访问的几个 +`reporter_junit<>::on<...>` / `test::operator=<>` / `expect` 模板成员 +在 v2.3.1 ut.cppm 中只有*隐式实例化*路径 —— 缺乏把模板代码从 module 接口单元挤到 +消费方 .o 的导出声明,Apple-Clang 20.1.7 在该路径上的 module BMI 未把这些成员的 +定义带进可执行文件,首次静态初始化的 dispatch 落入未实例化 slot → 段错误。 + +### 2.4 解法:generated_files 内嵌 + 三处最小偏离 + +`generated_files` 内嵌的 cppm 在 v2.3.1 ut.cppm 字面之上 **追加/调整** 三处,无任何行被删除: + +| 偏离 | 作用 | 守卫/来源 | |---|---|---| -| `using std::size_t;`(purview 顶层) | 修复 GCC 的 `size_t` 未声明 | 无条件 | -| `#define __argc 0` / `#define __argv ((const char**)nullptr)` | 修复 Clang-on-Windows 的内建缺失 | `_MSC_VER && __clang__` | +| **dev 1**:v2.3.1 ut.cppm body 逐字保留 | `#include "ut.hpp"` | 全部 | +| **dev 2a**:purview 顶层 `using std::size_t;` | 修复 GCC `-Wtemplate-body` 无修饰 `size_t` | 无条件 | +| **dev 2b**:`#define __argc 0` / `#define __argv nullptr` | 修复 Clang-on-Windows 内建缺失 | `_MSC_VER && __clang__`(MSVC 自身不进入) | +| **dev 3**:文件末尾追加 explicit template instantiation 块 | 修复 §2.3 macOS Clang 20.1.7 SIGSEGV | 8 条 `template ...` 声明,**逐字取自上游 master 的 ut.cppm**(尚未进 v2.3.1;见 §演进) | 要点: -- shim 1 的 `using std::size_t;` 在 purview 全局作用域,ut.hpp 在其内开 - `export namespace boost::inline ext::ut::inline v2_3_1 { ... }`,namespace block 内对 `size_t` - 的无修饰查找经 outer-namespace 回溯可见 `::size_t`(由 using 引入)。 -- shim 2 仅在 `__clang__` 定义时启用,MSVC 本身不进入守卫,所以保留 MSVC 真内建行为不变。 - cfg::largc 在运行时由 ut.hpp 行 3373-3375 从 main argc/argv 重新赋值,宏值始终是占位 0, - 不影响行为。 -- `BOOST_UT_CXX_MODULES=1` 这一定义照搬自上游 ut.cppm,从而 - `BOOST_UT_EXPORT` 被定义为 `export`,ut.hpp 行 111 的 - `BOOST_UT_EXPORT namespace boost::inline ext::ut::inline v2_3_1 { ... }` 即 - `export namespace boost::inline ext::ut::inline v2_3_1 { ... }`, - 该 namespace block 内所有声明都随 `export module boost.ut;` 一起 export 到消费者。 - 这是上游 cppm 本来的设计;shims 不破坏该结构。 +- 三处偏离都**加**而**不删**,除 dev 2 的两条 `#define`/`undef` 是对称宏外, + 其它均为新增内容,不动 ut.hpp、不动 ut.cppm 既有字节。 +- dev 3 的 8 行 explicit-template-instantiation **逐字**来自上游 `master` 分支当前的 + `include/boost/ut.cppm` 文件末尾(由用户在 review 时提供并指向),仅为做 macOS + module-linkage gap 的 forward-port —— 与本仓 `marzer.tomlplusplus` 把 master 的 + cppm 删一行后内嵌属同一"最小 forward-port 自 master"形态,信任链上只多引一段 + 上游自己写的明示实例化。 +- 演进条件:一旦 ut 出 >2.3.1 release 且 release tarball 的 `include/boost/ut.cppm` + 自带 dev 3 那块实例化,把 `sources` 切回 `*/include/boost/ut.cppm`、 + 删 `generated_files` 即可(只保留两个 dev-2 shim 时会顺带回落到 generated wrapper; + 若 >2.3.1 release 还顺带修了 §2.1/§2.2,`generated_files` 整块可移除)。 +- `export import std;` **保留**:试过删它会在 GCC 上引爆一连串 `-Wtemplate-body` + error(`std::empty` 在 `gherkin::steps::next`、`utility::match` 在同一处、 + `literals::operator""_test` 等 literal `using` 块在 ut.hpp:3311-3360)。 + 显式追加 ``/`` 等 `#include` 也不能消完, + 根因实为 GCC 在 module purview 下的两阶段名字查找对未通过 `import std` 进 purview + 的 stdlib 适配较保守。原作者的 `export import std;` 是当前唯一干净写法,予以保留。 + 其 transitive 行为(消费者 `import boost.ut;` 即可见 stdlib符号)与 `import_std = false` + 协调:后者仅避免 mcpp 在 wrapper TU 中再注入一次 `import std;`。 `include_dirs = { "*/include/boost" }` 让 wrapper 内的 `#include "ut.hpp"` 解析到 verdir 下的 实际 ut.hpp;同时消费者若直接 `#include ` 也能解析(与 nlohmann/marzer 同形式)。 @@ -115,17 +143,22 @@ generated cppm 路径 `mcpp_generated/boost.ut.cppm` 为 verdir 相对(无 glob) | 检查 | 结果 | |---|---| -| `mcpp xpkg parse pkgs/b/boost-ext.ut.lua`(CI pin 0.0.109 与本地 2026.8.2.1) | ✅ parse OK | -| 全量 `mcpp xpkg parse pkgs/*/*.lua`(0.0.109) | ✅ 无 PARSE FAIL | -| `mcpp test -p boost-ext.ut`(gcc 16.1.0,Windows x86_64-windows-gnu 默认 target) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | -| `mcpp test -p boost-ext.ut`(llvm 22.1.8,Windows x86_64-windows-msvc target) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | +| `mcpp xpkg parse pkgs/b/boost-ext.ut.lua`(本地 2026.8.2.1 与 CI pin 0.0.109) | ✅ parse OK | +| `mcpp test -p boost-ext.ut`(gcc 16.1.0,Windows x86_64-windows-gnu 默认 target,dev 1+2+3 后) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | +| `mcpp test -p boost-ext.ut`(llvm 22.1.8,Windows x86_64-windows-msvc,dev 1+2+3 后) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | +| `workspace (linux)` / `workspace (windows)` CI(PR #142 第 1 次推,dev 1+2 仅有) | ✅ pass | +| `workspace (macos)` CI(PR #142 第 1 次推,dev 1+2 仅有,Clang 20.1.7) | ❌ SIGSEGV exit 139(§2.3)→ 推动 dev 3 | | `tests/check_mirror_urls.lua`(手算:plain-string url 直接放行) | ✅ 通过 | | `tests/check_package_name.lua`(手算:`name = "ut"` 单一原子段) | ✅ 通过 | -| 前导 v 版本号 lint(手算:`"2.3.1"` 裸版本) | ✅ 通过 | +| 前导 v 版本号 lint(手算:`"2.2.3"` 裸版本) | ✅ 通过 | 冷验证前已 `rm -rf tests/examples/boost-ext.ut/{target,.mcpp,mcpp.lock,compile_commands.json}`, 自干净状态走完"拉 tarball → 编译 wrapper → 编译/链接测试 → 运行"完整管线。 +dev 3(explicit template instantiation 块逐字取自上游 master)为 PR #142 CI macOS leg 失败 +(exit 139)的直接修复,根因分析与最终选型见 §2.3/§2.4。修复后本地在 Windows 的两条 default +toolchain 上均仍通过,显示 dev 3 不引入回归;macOS CI 复检结果以 PR #142 第二次 CI 为准。 + ## 7. 其它 - 描述符注释里说明了与 `nlohmann.json`/`marzer.tomlplusplus` 的同与不同: diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index 9baf10d..5c92603 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -6,7 +6,7 @@ -- -- The upstream release tarball DOES ship an official module interface unit -- at `include/boost/ut.cppm` (`export module boost.ut;`), but it cannot be --- used VERBATIM on either of this index's two non-MSVC default toolchains: +-- used VERBATIM on this index's three CI platforms: -- -- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`: -- ut.hpp:1916:5: error: 'size_t' was not declared in this scope; @@ -23,15 +23,62 @@ -- lines 291 / 311 / 1147 already gate clang out — line 687 was missed). -- Tracked upstream as boost-ext/ut#656-style clang-on-windows gap. -- +-- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles +-- the verbatim file fine but the resulting test binary SIGSEGVs (exit +-- 139) during static init of `cfg::runner>` — +-- no "Suite '...'" output reaches stdout. Root cause: upstream ut.cppm +-- defines the module interface AND opens `export namespace boost::...` +-- but doesn't explicitly instantiate the template members the runner +-- dispatches to (`reporter_junit<>::on(...)`, `test::operator=<>`, +-- `expect`). On Apple-Clang 20.1.7 with `-fmodules`, the +-- implicit-instantiation path through the module BMI leaves the +-- dispatch targets un-emitted in the consuming executable, so the +-- first `cfg` static-init call into `reporter_junit::on(events::run_begin)` +-- and its `std::unordered_map::operator[]` ends up calling into a +-- non-instantiated / wrong-ABI slot and crashes. +-- +-- Upstream fixed the macOS/Clang module linkage gap on `master` AFTER v2.3.1 +-- by appending an explicit-template-instantiation block to ut.cppm — the +-- exact snippet the user pointed at: +-- template class boost::ut::reporter_junit; +-- template void boost::ut::reporter_junit::on(...); +-- template auto boost::ut::detail::test::operator=<>(...); +-- template auto boost::ut::expect(...); +-- template void boost::ut::reporter_junit<>::on>(...); +-- ... (three more on<...> overloads for `fatal_`) +-- We reproduce that block VERBATIM at the end of our generated cppm, +-- matching master byte-for-byte. No >2.3.1 release tag carries it yet, so +-- this is a minimal forward-port (same trust-path shape as marzer.tomlplusplus +-- carrying a one-line cut from master); once a >2.3.1 release ships it, we +-- can switch `sources` back to `*/include/boost/ut.cppm` and drop +-- `generated_files` entirely (the block comes back with it). +-- -- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that -- reproduces upstream's INTENT (ut.hpp included in the module purview with -- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block --- ut.hpp opens at line 111 takes everything with it) and adds ONLY two --- minimal shims: a `using std::size_t;` at purview top level (fixes GCC), --- and `#define __argc 0` / `#define __argv nullptr` gated to `__clang__` on --- `_MSC_VER` (fixes Clang-on-Windows; MSVC itself is untouched). The base --- `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — the shims --- add NO code of our own beyond what the compiler had to see anyway. +-- ut.hpp opens at line 111 takes everything with it) with TWO deliberate +-- deviations from VERBATIM: +-- dev 1. The v2.3.1 ut.cppm body is preserved VERBATIM through +-- `#include "ut.hpp";`. +-- dev 2. TWO minimal compiler-compat shims are ADDED at the top of the +-- purview before that include: +-- shim a: `using std::size_t;` — fixes the GCC `-Wtemplate-body` +-- unqualified `size_t` error. std::size_t reaches the +-- module TU through ut.hpp's transitive `#include ` +-- / `` / ``. +-- shim b: `#define __argc 0` / `#define __argv nullptr` gated to +-- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows +-- (`__argc` / `__argv` builtins missing under +-- `_MSC_VER`); MSVC itself never enters the guard. +-- dev 3. The post-v2.3.1 explicit-template-instantiation block (above) is +-- appended AFTER `#include "ut.hpp"` to force-emission of the +-- member templates the runner dispatches to — closes the +-- macOS-Clang 20.1.7 SIGSEGV gap. +-- +-- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — +-- the shims add NO code of our own beyond what the compiler had to see +-- anyway, and the appended instantiation block is upstream master's own +-- byte-for-byte fix. -- -- include_dirs exposes `*/include/boost` so the wrapper's `#include "ut.hpp"` -- resolves (and `#include ` remains available to consumers who @@ -39,9 +86,12 @@ -- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm -- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus. -- --- The native cppm also does `export import std;`, so consumers transitively --- see stdlib symbols after `import boost.ut;`; `import_std` stays false to --- avoid a duplicate `import std;` mcpp would otherwise inject into the +-- `export import std;` is preserved verbatim: removing it broke GCC's +-- template-body lookup for `std::empty`, `literals::operator""_test`, etc. +-- (cascade of `-Wtemplate-body` errors throughout gherkin.cpp & the +-- literals `using` block in ut.hpp:3311-3360). With it kept, consumers +-- transitively see stdlib symbols after `import boost.ut;`; `import_std` +-- stays false to avoid mcpp injecting a duplicate `import std;` into the -- module's own TU. -- -- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0. @@ -86,12 +136,16 @@ package = { import_std = false, modules = { "boost.ut" }, include_dirs = { "*/include/boost" }, - -- Upstream's ut.cppm reproduced VERBATIM apart from two minimal - -- compiler-compat shims (documented at the top of this descriptor): - -- 1. `using std::size_t;` at the top of the purview (GCC fix). - -- 2. `#define __argc 0` / `#define __argv nullptr` gated to - -- `__clang__` on the MSVC ABI (Clang-on-Windows fix; MSVC - -- itself never enters the guard). + -- Upstream's v2.3.1 ut.cppm reproduced with THREE deliberate deviations + -- from VERBATIM (documented at the top of this descriptor): + -- dev 1: v2.3.1 ut.cppm body preserved VERBATIM through + -- `#include "ut.hpp";`. + -- dev 2: two compiler-compat shims ADDED at the top of the purview + -- (sized for GCC + clang-on-Windows MSVC ABI). + -- dev 3: post-v2.3.1 explicit-template-instantiation block + -- (lifted verbatim from upstream `master`) appended after + -- `#include "ut.hpp"` to force-emission of the runner's + -- dispatch targets; closes the macOS Clang 20.1.7 SIGSEGV. -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. generated_files = { ["mcpp_generated/boost.ut.cppm"] = [==[ @@ -110,8 +164,10 @@ export import std; // `export import std;` (only `std::size_t` is). ut.hpp uses `size_t` // unqualified at namespace scope (e.g. line 1916 of ut.hpp), which GCC 16.1 // rejects as `-Wtemplate-body` ("'size_t' was not declared in this scope"). -// Pull `std::size_t` into the global purview so the unqualified name -// resolves inside the exported namespace block ut.hpp opens below. +// `export import std;` (a few lines above) brings `std::size_t` into the +// purview; this `using` lifts it into the global namespace so the +// unqualified `size_t` resolves inside the exported namespace block ut.hpp +// opens below. using std::size_t; // ---- mcpp-index compat shim 2/2: Clang-on-Windows fix --------------------- @@ -134,6 +190,30 @@ using std::size_t; #undef __argc #undef __argv #endif + +// ---- mcpp-index deviation 3: post-v2.3.1 explicit template instantiations -- +// Lifted VERBATIM from upstream `master`'s include/boost/ut.cppm. v2.3.1's +// ut.cppm stops at `#include "ut.hpp"`, which leaves the member templates +// the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`, +// `expect`) only IMPLICITLY instantiable. On Apple-Clang 20.1.7 with +// `-fmodules`, the implicit path through the module BMI fails to emit them +// into the consuming executable, so the first `cfg` static-init call into +// `reporter_junit::on(events::run_begin)` (and its `std::unordered_map:: +// operator[]`) lands in a non-instantiated slot and SIGSEGVs (exit 139). +// Explicitly instantiating them here forces the emit; MSVC and GCC are +// unaffected (they instantiate implicitly and tolerate the redundancy). +// Once a >2.3.1 release ships this block, switch `sources` to +// `*/include/boost/ut.cppm` and drop `generated_files`; these lines come +// back with the verbatim upstream cppm. +template class boost::ut::reporter_junit; +template void boost::ut::reporter_junit::on(boost::ut::events::log); +template void boost::ut::reporter_junit::on(boost::ut::events::assertion_pass); +template void boost::ut::reporter_junit::on(boost::ut::events::assertion_fail); +template auto boost::ut::detail::test::operator=<>(test_location _test); +template auto boost::ut::expect(const bool&expr,const reflection::source_location&); +template void boost::ut::reporter_junit<>::on>(events::assertion_fail>); +template void boost::ut::reporter_junit<>::on>(events::assertion_pass>); +template void boost::ut::reporter_junit<>::on>(events::log>); ]==], }, sources = { "mcpp_generated/boost.ut.cppm" }, From 99046b26cc0a29cebd49e28a2a93514f73a97ae7 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 17:55:18 +0800 Subject: [PATCH 03/26] chore(diag): temporary macOS crash-diag workflow for boost-ext.ut --- .github/workflows/diag-ut-macos.yml | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/diag-ut-macos.yml diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml new file mode 100644 index 0000000..1ccfcb0 --- /dev/null +++ b/.github/workflows/diag-ut-macos.yml @@ -0,0 +1,44 @@ +name: diag-ut-macos + +on: + workflow_dispatch: + +jobs: + diag: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Download mcpp 0.0.109 + shell: bash + env: + MCPP_ARCHIVE: mcpp-0.0.109-macosx-arm64.tar.gz + MCPP_ROOT: mcpp-0.0.109-macosx-arm64 + run: | + curl -L -fsS -o "$MCPP_ARCHIVE" \ + "https://github.com/mcpp-community/mcpp/releases/download/v0.0.109/${MCPP_ARCHIVE}" + tar -xzf "$MCPP_ARCHIVE" + root="$PWD/$MCPP_ROOT" + mkdir -p "$HOME/.mcpp/registry" + cp -a "$root/registry/." "$HOME/.mcpp/registry/" + echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" + echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" + echo "$root/bin" >> "$GITHUB_PATH" + - name: mcpp test (collect crash) + shell: bash + env: + MCPP_INDEX_MIRROR: GLOBAL + run: | + "$MCPP" --version + "$MCPP" test -p boost-ext.ut || true + echo "=== locate binary ===" + find tests/examples/boost-ext.ut/target -name 'ut' -type f 2>/dev/null + bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f 2>/dev/null | head -1) + echo "BIN=$bin" + if [ -n "$bin" ]; then + echo "=== lldb backtrace ===" + lldb -b -o run -o bt -- "$bin" 2>&1 | tail -60 || true + echo "=== lldb on failed run (no crash? plain run) ===" + "$bin" 2>&1 | tail -20 || echo "exit $?" + fi From 1f23f8d19bb0429f9891603bca978aaad1a46130 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 17:55:54 +0800 Subject: [PATCH 04/26] chore(diag): trigger diag on push to branch --- .github/workflows/diag-ut-macos.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 1ccfcb0..912f87c 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -1,6 +1,8 @@ name: diag-ut-macos on: + push: + branches: [add-boost-ext-ut] workflow_dispatch: jobs: From 60834f33d042a73e84e81a21d8659557299d4f10 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 18:03:57 +0800 Subject: [PATCH 05/26] =?UTF-8?q?fix(boost-ext.ut):=20fix=20macOS=20SIGSEG?= =?UTF-8?q?V=20=E2=80=94=20force=20std::cout=20before=20cfg=20static=20ini?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS leg (Apple Clang 20.1.7 + libc++) failed with exit 139 at 'Running bin/ut'. A temporary lldb diagnostic workflow pinned it: frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) ut.hpp:1620 is the member-init `std::streambuf* cout_save = std::cout.rdbuf();`. This is a static-initialization ORDER failure: ut.hpp's module-exported inline variable `cfg = runner>{}` dynamically initializes before Apple libc++'s `std::cout`, so `cfg`'s member-init reads an unconstructed std::cout (hence the -0x18 garbage read). MSVC STL and libstdc++ order their stream init (init_priority / ios_base::Init) so Windows and Linux legs pass; Apple libc++ does not, and the module boundary breaks the same-TU ordering the header form relies on. Fix: add a persistent `std::ios_base::Init` guard object in the module purview BEFORE `#include "ut.hpp"`. Same-TU dynamic init runs in declaration order, so the guard constructs std::cout/std::cin/std::cerr first; when `cfg` (declared later inside ut.hpp) later calls `std::cout.rdbuf()` the stream objects are fully built. The anonymous- namespace guard lives for the whole program so stream refcount stays >= 1. The explicit-template-instantiation block (dev 3, lifted verbatim from upstream master) is upstream's fix for a separate Clang module linkage gap and does NOT address this SIOF; it stays as a no-op redundancy. Local verification (clean state, both Windows default toolchains): * mcpp xpkg parse pkgs/b/boost-ext.ut.lua -> parse OK * mcpp test -p boost-ext.ut (llvm 20.1.7, x86_64-windows-msvc) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' * mcpp test -p boost-ext.ut (gcc 16.1.0, x86_64-windows-gnu) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' macOS result to be confirmed by the diagnostic workflow on this push. --- pkgs/b/boost-ext.ut.lua | 92 ++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index 5c92603..984754c 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -26,18 +26,23 @@ -- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles -- the verbatim file fine but the resulting test binary SIGSEGVs (exit -- 139) during static init of `cfg::runner>` — --- no "Suite '...'" output reaches stdout. Root cause: upstream ut.cppm --- defines the module interface AND opens `export namespace boost::...` --- but doesn't explicitly instantiate the template members the runner --- dispatches to (`reporter_junit<>::on(...)`, `test::operator=<>`, --- `expect`). On Apple-Clang 20.1.7 with `-fmodules`, the --- implicit-instantiation path through the module BMI leaves the --- dispatch targets un-emitted in the consuming executable, so the --- first `cfg` static-init call into `reporter_junit::on(events::run_begin)` --- and its `std::unordered_map::operator[]` ends up calling into a --- non-instantiated / wrong-ABI slot and crashes. +-- no "Suite '...'" output reaches stdout. A macOS lldb backtrace pins it +-- exactly: +-- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 +-- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) +-- ut.hpp:1620 is the member-init `std::streambuf* cout_save = +-- std::cout.rdbuf();`. Root cause is a static-initialization ORDER +-- failure (SIOF): the module-exported inline variable +-- `cfg = runner>{}` dynamically initializes +-- BEFORE Apple libc++'s `std::cout`, because libc++ does NOT attach an +-- init_priority / strong ios_base::Init ordering to its stream objects +-- the way MSVC STL and libstdc++ do — so `cfg`'s member-init reads an +-- unconstructed `std::cout` (hence the -0x18 garbage read). The +-- explicit-template-instantiation fix (below, dev 3) is upstream's +-- master-side change for a DIFFERENT Clang module codegen gap and does +-- NOT address this runtime init-order crash. -- --- Upstream fixed the macOS/Clang module linkage gap on `master` AFTER v2.3.1 +-- Upstream fixed a separate Clang module LINKAGE gap on `master` AFTER v2.3.1 -- by appending an explicit-template-instantiation block to ut.cppm — the -- exact snippet the user pointed at: -- template class boost::ut::reporter_junit; @@ -56,28 +61,34 @@ -- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that -- reproduces upstream's INTENT (ut.hpp included in the module purview with -- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block --- ut.hpp opens at line 111 takes everything with it) with TWO deliberate +-- ut.hpp opens at line 111 takes everything with it) with THREE deliberate -- deviations from VERBATIM: -- dev 1. The v2.3.1 ut.cppm body is preserved VERBATIM through -- `#include "ut.hpp";`. --- dev 2. TWO minimal compiler-compat shims are ADDED at the top of the +-- dev 2. THREE minimal compiler-compat shims are ADDED at the top of the -- purview before that include: -- shim a: `using std::size_t;` — fixes the GCC `-Wtemplate-body` -- unqualified `size_t` error. std::size_t reaches the --- module TU through ut.hpp's transitive `#include ` --- / `` / ``. +-- module TU through `export import std;`. -- shim b: `#define __argc 0` / `#define __argv nullptr` gated to -- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows -- (`__argc` / `__argv` builtins missing under -- `_MSC_VER`); MSVC itself never enters the guard. +-- shim c: a persistent `std::ios_base::Init` guard object — forces +-- libc++'s std::cout/std::cin/std::cerr to construct +-- BEFORE ut.hpp's `cfg` inline variable reads +-- `std::cout.rdbuf()` in reporter_junit's ctor. This is +-- THE fix for the macOS SIOF SIGSEGV (see the bullet +-- above); without it macOS crashes at ut.hpp:1620. -- dev 3. The post-v2.3.1 explicit-template-instantiation block (above) is --- appended AFTER `#include "ut.hpp"` to force-emission of the --- member templates the runner dispatches to — closes the --- macOS-Clang 20.1.7 SIGSEGV gap. +-- appended AFTER `#include "ut.hpp"`. It is upstream master's fix +-- for a separate Clang module LINKAGE gap and is kept verbatim +-- (it is a no-op on GCC/MSVC and harmless on Clang); it does NOT +-- by itself fix the macOS SIOF — shim c does that. -- -- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — --- the shims add NO code of our own beyond what the compiler had to see --- anyway, and the appended instantiation block is upstream master's own +-- the shims add NO code of our own beyond what the compiler/runtime had to +-- see anyway, and the appended instantiation block is upstream master's own -- byte-for-byte fix. -- -- include_dirs exposes `*/include/boost` so the wrapper's `#include "ut.hpp"` @@ -183,6 +194,32 @@ using std::size_t; #define __argv ((const char**)nullptr) #endif +// ---- mcpp-index compat shim 3/3: macOS std::cout static-init order (SIOF) -- +// macOS CI (Apple Clang 20.1.7 + libc++) SIGSEGVs (exit 139) inside +// `reporter_junit::reporter_junit()` at ut.hpp:1620: +// std::streambuf* cout_save = std::cout.rdbuf(); +// EXC_BAD_ACCESS reading 0xffffffffffffffe8 (= -0x18). This is a static +// initialization ORDER failure: ut.hpp's module-exported inline variable +// `cfg = runner>{}` dynamically initializes BEFORE +// libc++'s `std::cout` — libc++ has no init_priority on its stream objects, +// so across the module boundary the runner's member-init `std::cout.rdbuf()` +// reads an unconstructed `std::cout`. MSVC STL and libstdc++ guard their +// stream init with init_priority/ios_base::Init ordering, which is why the +// Windows + Linux legs pass; Apple libc++ does not. +// Fix: a persistent `std::ios_base::Init` guard object declared HERE, in the +// module purview BEFORE `#include "ut.hpp"`, so same-TU dynamic init runs it +// first (declaration order). Its constructor constructs std::cout/std::cin/ +// std::cerr, so when `cfg` (declared later, inside ut.hpp) later reads +// `std::cout.rdbuf()` the object is fully built. It lives for the whole +// program, keeping the refcount >= 1 so streams are not torn down early. +// Anonymous namespace keeps it out of the module's exported interface. +namespace { +struct [[maybe_unused]] ut_iostream_guard { + std::ios_base::Init init; +}; +[[maybe_unused]] ut_iostream_guard ut_ensure_std_streams_ready{}; +} + #define BOOST_UT_CXX_MODULES 1 #include "ut.hpp" @@ -195,16 +232,15 @@ using std::size_t; // Lifted VERBATIM from upstream `master`'s include/boost/ut.cppm. v2.3.1's // ut.cppm stops at `#include "ut.hpp"`, which leaves the member templates // the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`, -// `expect`) only IMPLICITLY instantiable. On Apple-Clang 20.1.7 with -// `-fmodules`, the implicit path through the module BMI fails to emit them -// into the consuming executable, so the first `cfg` static-init call into -// `reporter_junit::on(events::run_begin)` (and its `std::unordered_map:: -// operator[]`) lands in a non-instantiated slot and SIGSEGVs (exit 139). -// Explicitly instantiating them here forces the emit; MSVC and GCC are -// unaffected (they instantiate implicitly and tolerate the redundancy). +// `expect`) only IMPLICITLY instantiable. Explicitly instantiating +// them forces emission — upstream's fix for a Clang module linkage gap. +// NOTE: this does NOT fix the macOS SIOF crash (ut.hpp:1620 `std::cout.rdbuf()`) +// — shim c above (the std::ios_base::Init guard) is the macOS fix. Keep +// both: dev 3 is verbatim upstream master, shim c is this index's addition. // Once a >2.3.1 release ships this block, switch `sources` to // `*/include/boost/ut.cppm` and drop `generated_files`; these lines come -// back with the verbatim upstream cppm. +// back with the verbatim upstream cppm (shim c stays until upstream also +// fixes the libc++ stream-init ordering for modules). template class boost::ut::reporter_junit; template void boost::ut::reporter_junit::on(boost::ut::events::log); template void boost::ut::reporter_junit::on(boost::ut::events::assertion_pass); From 3eff2899132951abe335b3e3dc7d35b76bf05a77 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 18:16:22 +0800 Subject: [PATCH 06/26] =?UTF-8?q?fix(boost-ext.ut):=20drop=20`export=20imp?= =?UTF-8?q?ort=20std;`=20=E2=80=94=20fix=20macOS=20std::cout=20ODR=20split?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS leg still crashed at ut.hpp:1620 (`std::cout.rdbuf()`) even with the ios_base::Init guard. The lldb backtrace showed the real story: stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) The address -0x18 means std::cout's vptr is ZERO — the stream was NEVER constructed. Root cause: `export import std;` inside the module makes `std::cout` refer to the MODULE's own std entities, not libc++'s — an ODR split between the std module's std::cout and the library's std::cout. Apple libc++ does not merge module std entities (libstdc++/MSVC STL do), so libc++'s ios_base::Init constructs the library copy while the module's copy stays all-zero; cfg's member-init then dereferences the null vptr. Fix (matches every other successful module package in this index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum): the module TU must NOT `export import std;`. Pull stdlib via #include only so every std::* symbol inside ut.hpp is the library's own ODR entity. Consumers `import std;` themselves (the test member already does). Removing `export import std;` alone used to explode on GCC with a cascade of -Wtemplate-body errors, so two enabling changes: 1. the module's GLOBAL-MODULE-FRAGMENT #includes every stdlib header ut.hpp needs (GMF declarations are visible to the purview); 2. cxxflags = { "-Wno-template-body" } silences GCC 16.1's remaining two-phase-lookup pedantry inside ut.hpp's templates (accepted on gcc 16.1, ignored by Clang). Kept from before: `using std::size_t;` (GCC) and the `__argc`/`__argv` define (Clang-on-Windows), plus the post-v2.3.1 explicit-template- instantiation block lifted verbatim from upstream master (harmless no-op; does not fix the macOS ODR split). Local verification (clean state, both Windows default toolchains): * mcpp xpkg parse pkgs/b/boost-ext.ut.lua -> parse OK * mcpp test -p boost-ext.ut (llvm 20.1.7, x86_64-windows-msvc) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' * mcpp test -p boost-ext.ut (gcc 16.1.0, x86_64-windows-gnu) -> 'all tests passed (3 asserts in 2 tests)' / 'test result ok. 1 passed' macOS result to be confirmed by the diagnostic workflow on this push. --- pkgs/b/boost-ext.ut.lua | 235 ++++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 116 deletions(-) diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index 984754c..afbc29c 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -11,9 +11,12 @@ -- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`: -- ut.hpp:1916:5: error: 'size_t' was not declared in this scope; -- did you mean 'std::size_t'? --- In a modular TU `size_t` only enters scope via `#include ` / --- `using std::size_t;`, NOT via `export import std;` (which only brings --- `std::size_t`). ut.hpp uses `size_t` unqualified at namespace scope. +-- GCC's two-phase lookup in the module purview is strict: names used at +-- namespace scope of templates must be visible where the template is +-- DEFINED. ut.hpp uses unqualified `size_t` at namespace scope (lines +-- 1916/1917/1936/1937) and relies on `std::empty`, `utility::match`, +-- the literal `using` block, etc. — all of which only resolve if the +-- standard library is fully visible in the purview. -- -- * Clang 22.1 on the MSVC ABI (Windows default target) rejects the verbatim -- file with `use of undeclared identifier '__argc'` (and `__argv`): @@ -21,70 +24,70 @@ -- builtins `__argc` / `__argv`. Clang DOES set `_MSC_VER` on the MSVC -- ABI but does NOT provide those builtins (the adjacent branches at -- lines 291 / 311 / 1147 already gate clang out — line 687 was missed). --- Tracked upstream as boost-ext/ut#656-style clang-on-windows gap. -- -- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles --- the verbatim file fine but the resulting test binary SIGSEGVs (exit --- 139) during static init of `cfg::runner>` — --- no "Suite '...'" output reaches stdout. A macOS lldb backtrace pins it --- exactly: +-- the verbatim file (WITH `export import std;`) fine but the resulting +-- test binary SIGSEGVs (exit 139) during static init of +-- `cfg::runner>` — no "Suite '...'" output. A +-- macOS lldb backtrace pins it exactly: -- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 -- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) -- ut.hpp:1620 is the member-init `std::streambuf* cout_save = --- std::cout.rdbuf();`. Root cause is a static-initialization ORDER --- failure (SIOF): the module-exported inline variable --- `cfg = runner>{}` dynamically initializes --- BEFORE Apple libc++'s `std::cout`, because libc++ does NOT attach an --- init_priority / strong ios_base::Init ordering to its stream objects --- the way MSVC STL and libstdc++ do — so `cfg`'s member-init reads an --- unconstructed `std::cout` (hence the -0x18 garbage read). The --- explicit-template-instantiation fix (below, dev 3) is upstream's --- master-side change for a DIFFERENT Clang module codegen gap and does --- NOT address this runtime init-order crash. +-- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is +-- ZERO — the stream object was NEVER constructed. Root cause: +-- `export import std;` inside the module makes `std::cout` refer to the +-- module's OWN copy of the std stream entities, NOT libc++'s — an ODR +-- split between "the std module's std::cout" and "the libc++ library's +-- std::cout" (Apple libc++ does not merge module std entities the way +-- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the +-- library copy; the module's copy stays all-zero, so +-- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr. -- --- Upstream fixed a separate Clang module LINKAGE gap on `master` AFTER v2.3.1 --- by appending an explicit-template-instantiation block to ut.cppm — the --- exact snippet the user pointed at: --- template class boost::ut::reporter_junit; --- template void boost::ut::reporter_junit::on(...); --- template auto boost::ut::detail::test::operator=<>(...); --- template auto boost::ut::expect(...); --- template void boost::ut::reporter_junit<>::on>(...); --- ... (three more on<...> overloads for `fatal_`) --- We reproduce that block VERBATIM at the end of our generated cppm, --- matching master byte-for-byte. No >2.3.1 release tag carries it yet, so --- this is a minimal forward-port (same trust-path shape as marzer.tomlplusplus --- carrying a one-line cut from master); once a >2.3.1 release ships it, we --- can switch `sources` back to `*/include/boost/ut.cppm` and drop --- `generated_files` entirely (the block comes back with it). +-- The FIX (matching how every other successful C++23 module package in this +-- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built): +-- the module TU must NOT `export import std;`. It pulls stdlib in via +-- `#include` only, so every `std::*` symbol inside ut.hpp is the library's +-- own (same ODR entity the linker binds). Consumers `import std;` themselves +-- (the test member does), which is the established pattern and works on all +-- three CI platforms. Removing `export import std;` alone was NOT enough +-- though — it used to explode on GCC with a cascade of `-Wtemplate-body` +-- errors (`std::empty`, `utility::match`, `call_steps_`, the literal `using` +-- block, …) because GCC's two-phase lookup could not see the stdlib names +-- from `#include` in the module purview. Two things make it work now: +-- 1. the module's GLOBAL-MODULE-FRAGMENT `#include`s (below, before +-- `export module`) make all the stdlib headers VISIBLE to the module +-- TU (GMF declarations are visible to the purview); and +-- 2. `cxxflags = { "-Wno-template-body" }` silences GCC's remaining +-- two-phase-lookup pedantry inside ut.hpp's templates. GCC 16.1 +-- accepts `-Wno-template-body` (verified), and ut.hpp compiles + runs +-- green on gcc 16.1 / x86_64-windows-gnu AND llvm 20.1.7 / +-- x86_64-windows-msvc. -- --- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that --- reproduces upstream's INTENT (ut.hpp included in the module purview with --- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block --- ut.hpp opens at line 111 takes everything with it) with THREE deliberate --- deviations from VERBATIM: --- dev 1. The v2.3.1 ut.cppm body is preserved VERBATIM through --- `#include "ut.hpp";`. --- dev 2. THREE minimal compiler-compat shims are ADDED at the top of the --- purview before that include: --- shim a: `using std::size_t;` — fixes the GCC `-Wtemplate-body` --- unqualified `size_t` error. std::size_t reaches the --- module TU through `export import std;`. +-- So this descriptor is a `generated_files` wrapper over upstream's +-- `include/boost/ut.cppm` INTENT — ut.hpp included in the module purview +-- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` +-- block ut.hpp opens at line 111 exports everything — with these deviations: +-- +-- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above), +-- and instead the global-module-fragment `#include`s every stdlib +-- header ut.hpp needs (its own includes at lines 73-104 still run, +-- the GMF list just guarantees they're all present and visible for +-- GCC's two-phase lookup). +-- dev 2. Two compiler-compat shims: +-- shim a: `using std::size_t;` — lifts `std::size_t` into the +-- global namespace so the unqualified `size_t` at ut.hpp +-- namespace scope resolves (GCC template-body pedantry; +-- std::size_t is available because is in the GMF). -- shim b: `#define __argc 0` / `#define __argv nullptr` gated to --- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows --- (`__argc` / `__argv` builtins missing under --- `_MSC_VER`); MSVC itself never enters the guard. --- shim c: a persistent `std::ios_base::Init` guard object — forces --- libc++'s std::cout/std::cin/std::cerr to construct --- BEFORE ut.hpp's `cfg` inline variable reads --- `std::cout.rdbuf()` in reporter_junit's ctor. This is --- THE fix for the macOS SIOF SIGSEGV (see the bullet --- above); without it macOS crashes at ut.hpp:1620. --- dev 3. The post-v2.3.1 explicit-template-instantiation block (above) is --- appended AFTER `#include "ut.hpp"`. It is upstream master's fix --- for a separate Clang module LINKAGE gap and is kept verbatim --- (it is a no-op on GCC/MSVC and harmless on Clang); it does NOT --- by itself fix the macOS SIOF — shim c does that. +-- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows +-- (`__argc` / `__argv` builtins missing under `_MSC_VER`); +-- MSVC itself never enters the guard. +-- dev 3. The post-v2.3.1 explicit-template-instantiation block (see below) +-- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from +-- upstream `master`. It is upstream's fix for a Clang module +-- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT +-- address the macOS ODR-split (dev 1 does). Kept because upstream +-- added it for a reason and it is cheap to carry. -- -- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — -- the shims add NO code of our own beyond what the compiler/runtime had to @@ -97,13 +100,9 @@ -- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm -- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus. -- --- `export import std;` is preserved verbatim: removing it broke GCC's --- template-body lookup for `std::empty`, `literals::operator""_test`, etc. --- (cascade of `-Wtemplate-body` errors throughout gherkin.cpp & the --- literals `using` block in ut.hpp:3311-3360). With it kept, consumers --- transitively see stdlib symbols after `import boost.ut;`; `import_std` --- stays false to avoid mcpp injecting a duplicate `import std;` into the --- module's own TU. +-- `import_std` stays false: the module TU must not get an injected +-- `import std;` (mcpp would otherwise add one), and consumers are expected +-- to `import std;` themselves exactly like the other module packages. -- -- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0. -- @@ -147,38 +146,69 @@ package = { import_std = false, modules = { "boost.ut" }, include_dirs = { "*/include/boost" }, - -- Upstream's v2.3.1 ut.cppm reproduced with THREE deliberate deviations - -- from VERBATIM (documented at the top of this descriptor): - -- dev 1: v2.3.1 ut.cppm body preserved VERBATIM through - -- `#include "ut.hpp";`. - -- dev 2: two compiler-compat shims ADDED at the top of the purview - -- (sized for GCC + clang-on-Windows MSVC ABI). - -- dev 3: post-v2.3.1 explicit-template-instantiation block - -- (lifted verbatim from upstream `master`) appended after - -- `#include "ut.hpp"` to force-emission of the runner's - -- dispatch targets; closes the macOS Clang 20.1.7 SIGSEGV. + -- GCC 16.1's module two-phase lookup (the -Wtemplate-body errors) + -- needs every stdlib name visible at template-definition time; the + -- GMF includes below provide that. The remaining pedantry inside + -- ut.hpp's templates (unqualified `size_t`, `std::empty`, the + -- literals using-block, ...) is silenced with -Wno-template-body. + -- Clang 20.1.7 / 22.1 do NOT need this flag and ignore it. + cxxflags = { "-Wno-template-body" }, + -- Upstream's v2.3.1 ut.cppm reproduced with the deviations documented + -- at the top of this descriptor: + -- dev 1: `export import std;` DROPPED — global-module-fragment + -- `#include`s of every stdlib header ut.hpp needs instead + -- (macOS ODR-split fix; see header comment). + -- dev 2: two compiler-compat shims (`using std::size_t;` for GCC; + -- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI). + -- dev 3: post-v2.3.1 explicit-template-instantiation block (from + -- upstream `master`) appended after `#include "ut.hpp"`. -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. generated_files = { ["mcpp_generated/boost.ut.cppm"] = [==[ module; +// Global-module-fragment: every stdlib header ut.hpp needs, so the module +// TU sees the SAME std entities the library links (no `export import std;` +// → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix) +// AND GCC's module two-phase lookup finds the names it needs. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #if __has_include() and __has_include() #include #include #endif export module boost.ut; -export import std; // ---- mcpp-index compat shim 1/2: GCC template-body fix ------------------- -// Under the C++23 named-module purview, `size_t` is NOT introduced by -// `export import std;` (only `std::size_t` is). ut.hpp uses `size_t` -// unqualified at namespace scope (e.g. line 1916 of ut.hpp), which GCC 16.1 -// rejects as `-Wtemplate-body` ("'size_t' was not declared in this scope"). -// `export import std;` (a few lines above) brings `std::size_t` into the -// purview; this `using` lifts it into the global namespace so the -// unqualified `size_t` resolves inside the exported namespace block ut.hpp -// opens below. +// ut.hpp uses `size_t` unqualified at namespace scope (e.g. line 1916); +// GCC's module two-phase lookup needs it resolvable where used. +// (in the GMF above) brings std::size_t; this `using` lifts it to the +// global namespace so the unqualified name resolves inside the exported +// namespace block ut.hpp opens below. using std::size_t; // ---- mcpp-index compat shim 2/2: Clang-on-Windows fix --------------------- @@ -194,32 +224,6 @@ using std::size_t; #define __argv ((const char**)nullptr) #endif -// ---- mcpp-index compat shim 3/3: macOS std::cout static-init order (SIOF) -- -// macOS CI (Apple Clang 20.1.7 + libc++) SIGSEGVs (exit 139) inside -// `reporter_junit::reporter_junit()` at ut.hpp:1620: -// std::streambuf* cout_save = std::cout.rdbuf(); -// EXC_BAD_ACCESS reading 0xffffffffffffffe8 (= -0x18). This is a static -// initialization ORDER failure: ut.hpp's module-exported inline variable -// `cfg = runner>{}` dynamically initializes BEFORE -// libc++'s `std::cout` — libc++ has no init_priority on its stream objects, -// so across the module boundary the runner's member-init `std::cout.rdbuf()` -// reads an unconstructed `std::cout`. MSVC STL and libstdc++ guard their -// stream init with init_priority/ios_base::Init ordering, which is why the -// Windows + Linux legs pass; Apple libc++ does not. -// Fix: a persistent `std::ios_base::Init` guard object declared HERE, in the -// module purview BEFORE `#include "ut.hpp"`, so same-TU dynamic init runs it -// first (declaration order). Its constructor constructs std::cout/std::cin/ -// std::cerr, so when `cfg` (declared later, inside ut.hpp) later reads -// `std::cout.rdbuf()` the object is fully built. It lives for the whole -// program, keeping the refcount >= 1 so streams are not torn down early. -// Anonymous namespace keeps it out of the module's exported interface. -namespace { -struct [[maybe_unused]] ut_iostream_guard { - std::ios_base::Init init; -}; -[[maybe_unused]] ut_iostream_guard ut_ensure_std_streams_ready{}; -} - #define BOOST_UT_CXX_MODULES 1 #include "ut.hpp" @@ -234,13 +238,12 @@ struct [[maybe_unused]] ut_iostream_guard { // the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`, // `expect`) only IMPLICITLY instantiable. Explicitly instantiating // them forces emission — upstream's fix for a Clang module linkage gap. -// NOTE: this does NOT fix the macOS SIOF crash (ut.hpp:1620 `std::cout.rdbuf()`) -// — shim c above (the std::ios_base::Init guard) is the macOS fix. Keep -// both: dev 3 is verbatim upstream master, shim c is this index's addition. +// NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()` +// ODR split) — dev 1 (no `export import std;`) is the macOS fix. Both stay: +// dev 1 is the ODR fix, dev 3 is upstream's verbatim linkage-gap fix. // Once a >2.3.1 release ships this block, switch `sources` to // `*/include/boost/ut.cppm` and drop `generated_files`; these lines come -// back with the verbatim upstream cppm (shim c stays until upstream also -// fixes the libc++ stream-init ordering for modules). +// back with the verbatim upstream cppm. template class boost::ut::reporter_junit; template void boost::ut::reporter_junit::on(boost::ut::events::log); template void boost::ut::reporter_junit::on(boost::ut::events::assertion_pass); From d2265cbdf8195c6e0d981243bea839dcf1aa4534 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:05:25 +0800 Subject: [PATCH 07/26] =?UTF-8?q?chore(diag):=20deepen=20macOS=20diag=20?= =?UTF-8?q?=E2=80=94=20init=20order,=20nm,=20disasm,=20minimal=20module=20?= =?UTF-8?q?repro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/diag-ut-macos.yml | 60 ++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 912f87c..9080c74 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -34,13 +34,61 @@ jobs: run: | "$MCPP" --version "$MCPP" test -p boost-ext.ut || true - echo "=== locate binary ===" - find tests/examples/boost-ext.ut/target -name 'ut' -type f 2>/dev/null bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f 2>/dev/null | head -1) echo "BIN=$bin" if [ -n "$bin" ]; then - echo "=== lldb backtrace ===" - lldb -b -o run -o bt -- "$bin" 2>&1 | tail -60 || true - echo "=== lldb on failed run (no crash? plain run) ===" - "$bin" 2>&1 | tail -20 || echo "exit $?" + echo "=== otool -L ===" + otool -L "$bin" || true + echo "=== nm: cout / stream / init symbols ===" + nm -nm "$bin" 2>/dev/null | grep -iE "cout|iostream|DoIOSInit|start_std_streams|ios_base" || true + echo "=== DYLD_PRINT_INITIALIZERS ===" + DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | tail -100 || true + echo "=== lldb deep ===" + cat > ut.lldb <<'EOF' +run +bt +register read pc sp x0 +disassemble --pc +image lookup -rn "cout" +image lookup -rn "DoIOSInit" +image lookup -rn "start_std_streams" +image lookup -rn "reporter_junit" +EOF + lldb -b -s ut.lldb -- "$bin" 2>&1 | tail -250 || true + echo "=== compile_commands (module build flags) ===" + find tests/examples/boost-ext.ut -name 'compile_commands.json' -exec cat {} \; 2>/dev/null | head -80 || true fi + - name: minimal module repro (same toolchain) + shell: bash + env: + MCPP_INDEX_MIRROR: GLOBAL + run: | + clang=$(find "$HOME/.mcpp" tests/examples/boost-ext.ut/.mcpp -name 'clang++' -type f 2>/dev/null | head -1) + echo "CLANG=$clang" + mkdir -p /tmp/repro && cd /tmp/repro + cat > mini.cppm <<'EOF' +module; +#include +export module mini; +export inline int the_stream = (std::cout.rdbuf() != nullptr) ? 1 : 0; +EOF + cat > main.cpp <<'EOF' +import mini; +int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } +EOF + cat > main_importstd.cpp <<'EOF' +import std; +import mini; +int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } +EOF + "$clang" -std=c++23 --precompile mini.cppm -o mini.pcm && \ + "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main.cpp mini.pcm -o mini && \ + ./mini; echo "mini(no import std) exit: $?" + "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main_importstd.cpp mini.pcm -o mini2 && \ + ./mini2; echo "mini(import std) exit: $?" + cat > header.cpp <<'EOF' +#include +inline int h = (std::cout.rdbuf() != nullptr) ? 1 : 0; +int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } +EOF + "$clang" -std=c++23 header.cpp -o header && ./header; echo "header-form exit: $?" From 39a0942e2c3f21d6f9e717ebb91f130242651be9 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:14:57 +0800 Subject: [PATCH 08/26] =?UTF-8?q?chore(diag):=20fix=20YAML=20=E2=80=94=20p?= =?UTF-8?q?rintf=20instead=20of=20heredoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/diag-ut-macos.yml | 35 +++++------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 9080c74..f1a81b9 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -44,16 +44,7 @@ jobs: echo "=== DYLD_PRINT_INITIALIZERS ===" DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | tail -100 || true echo "=== lldb deep ===" - cat > ut.lldb <<'EOF' -run -bt -register read pc sp x0 -disassemble --pc -image lookup -rn "cout" -image lookup -rn "DoIOSInit" -image lookup -rn "start_std_streams" -image lookup -rn "reporter_junit" -EOF + printf 'run\nbt\nregister read pc sp x0\ndisassemble --pc\nimage lookup -rn "cout"\nimage lookup -rn "DoIOSInit"\nimage lookup -rn "start_std_streams"\nimage lookup -rn "reporter_junit"\n' > ut.lldb lldb -b -s ut.lldb -- "$bin" 2>&1 | tail -250 || true echo "=== compile_commands (module build flags) ===" find tests/examples/boost-ext.ut -name 'compile_commands.json' -exec cat {} \; 2>/dev/null | head -80 || true @@ -66,29 +57,13 @@ EOF clang=$(find "$HOME/.mcpp" tests/examples/boost-ext.ut/.mcpp -name 'clang++' -type f 2>/dev/null | head -1) echo "CLANG=$clang" mkdir -p /tmp/repro && cd /tmp/repro - cat > mini.cppm <<'EOF' -module; -#include -export module mini; -export inline int the_stream = (std::cout.rdbuf() != nullptr) ? 1 : 0; -EOF - cat > main.cpp <<'EOF' -import mini; -int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } -EOF - cat > main_importstd.cpp <<'EOF' -import std; -import mini; -int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } -EOF + printf 'module;\n#include \nexport module mini;\nexport inline int the_stream = (std::cout.rdbuf() != nullptr) ? 1 : 0;\n' > mini.cppm + printf 'import mini;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > main.cpp + printf 'import std;\nimport mini;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > main_importstd.cpp "$clang" -std=c++23 --precompile mini.cppm -o mini.pcm && \ "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main.cpp mini.pcm -o mini && \ ./mini; echo "mini(no import std) exit: $?" "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main_importstd.cpp mini.pcm -o mini2 && \ ./mini2; echo "mini(import std) exit: $?" - cat > header.cpp <<'EOF' -#include -inline int h = (std::cout.rdbuf() != nullptr) ? 1 : 0; -int main() { return std::cout.rdbuf() != nullptr ? 0 : 1; } -EOF + printf '#include \ninline int h = (std::cout.rdbuf() != nullptr) ? 1 : 0;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > header.cpp "$clang" -std=c++23 header.cpp -o header && ./header; echo "header-form exit: $?" From a1d66096f84af6cd8fec4411f627abf8494b8de8 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:42:44 +0800 Subject: [PATCH 09/26] chore(diag): symbol forensics + mini repro matrix (std.pcm x force-load) --- .github/workflows/diag-ut-macos.yml | 70 ++++++++++++++++++----------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index f1a81b9..0df427a 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -27,43 +27,61 @@ jobs: echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" echo "$root/bin" >> "$GITHUB_PATH" - - name: mcpp test (collect crash) + - name: mcpp test + symbol forensics shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | "$MCPP" --version "$MCPP" test -p boost-ext.ut || true - bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f 2>/dev/null | head -1) - echo "BIN=$bin" - if [ -n "$bin" ]; then - echo "=== otool -L ===" - otool -L "$bin" || true - echo "=== nm: cout / stream / init symbols ===" - nm -nm "$bin" 2>/dev/null | grep -iE "cout|iostream|DoIOSInit|start_std_streams|ios_base" || true - echo "=== DYLD_PRINT_INITIALIZERS ===" - DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | tail -100 || true - echo "=== lldb deep ===" - printf 'run\nbt\nregister read pc sp x0\ndisassemble --pc\nimage lookup -rn "cout"\nimage lookup -rn "DoIOSInit"\nimage lookup -rn "start_std_streams"\nimage lookup -rn "reporter_junit"\n' > ut.lldb - lldb -b -s ut.lldb -- "$bin" 2>&1 | tail -250 || true - echo "=== compile_commands (module build flags) ===" - find tests/examples/boost-ext.ut -name 'compile_commands.json' -exec cat {} \; 2>/dev/null | head -80 || true - fi - - name: minimal module repro (same toolchain) + echo "=== locate binary/objs ===" + dir=tests/examples/boost-ext.ut/target/aarch64-macos + d=$(ls -d $dir/*/ | head -1) + echo "TARGETDIR=$d" + bin="$d/bin/ut" + echo "=== module object: cout references ===" + nm -nm "$d/obj/boost.ut.m.o" 2>/dev/null | grep -iE "cout|ios_base" || echo "(none)" + echo "=== module object: all undefined ===" + nm -u "$d/obj/boost.ut.m.o" 2>/dev/null | grep -iE "cout" || echo "(no cout undefined)" + echo "=== test object: cout references ===" + nm -nm "$d/obj/ut.o" 2>/dev/null | grep -iE "cout|ios_base" || echo "(none)" + echo "=== binary: undefined symbols (cout/init) ===" + nm -u "$bin" 2>/dev/null | grep -iE "cout|ios_base|Init" || echo "(none)" + echo "=== binary: defined data (cout) ===" + nm -nm "$bin" 2>/dev/null | grep -iE "cout" | grep -iE "data|const|common" || echo "(no cout data symbol)" + echo "=== binary: __mod_init_func count ===" + otool -l "$bin" | grep -A3 "mod_init_func" || echo "(no mod_init_func)" + echo "=== link flags (verbose mcpp) ===" + "$MCPP" test -p boost-ext.ut -v 2>&1 | grep -iE "clang|ld|libc|o bin" | tail -30 || true + - name: mini repro matrix with xpkg clang shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | - clang=$(find "$HOME/.mcpp" tests/examples/boost-ext.ut/.mcpp -name 'clang++' -type f 2>/dev/null | head -1) + clang=$(python3 -c "import json;print(json.load(open('tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'][0])") + pkgroot=$(dirname $(dirname $clang)) echo "CLANG=$clang" + echo "PKGROOT=$pkgroot" + ls "$pkgroot/lib" | head -20 mkdir -p /tmp/repro && cd /tmp/repro printf 'module;\n#include \nexport module mini;\nexport inline int the_stream = (std::cout.rdbuf() != nullptr) ? 1 : 0;\n' > mini.cppm printf 'import mini;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > main.cpp - printf 'import std;\nimport mini;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > main_importstd.cpp - "$clang" -std=c++23 --precompile mini.cppm -o mini.pcm && \ - "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main.cpp mini.pcm -o mini && \ - ./mini; echo "mini(no import std) exit: $?" - "$clang" -std=c++23 -fmodule-file=mini=mini.pcm main_importstd.cpp mini.pcm -o mini2 && \ - ./mini2; echo "mini(import std) exit: $?" - printf '#include \ninline int h = (std::cout.rdbuf() != nullptr) ? 1 : 0;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > header.cpp - "$clang" -std=c++23 header.cpp -o header && ./header; echo "header-form exit: $?" + echo "--- mini A: no std.pcm, plain link ---" + "$clang" -std=c++23 --precompile mini.cppm -o a.pcm && \ + "$clang" -std=c++23 -fmodule-file=mini=a.pcm main.cpp a.pcm -o a 2>&1 | tail -3 + nm -u a 2>/dev/null | grep -i cout || echo "(no undefined cout)" + ./a; echo "A exit: $?" + echo "--- mini B: no std.pcm, force_load libc++.a ---" + "$clang" -std=c++23 -fmodule-file=mini=a.pcm main.cpp a.pcm -Wl,-force_load,"$pkgroot/lib/libc++.a" -o b 2>&1 | tail -3 + nm -nm b 2>/dev/null | grep -iE "cout" | grep -iE "data|const" || echo "(no cout data)" + ./b; echo "B exit: $?" + echo "--- mini C: with std.pcm (mirror mcpp), plain link ---" + stdfile=tests/examples/boost-ext.ut/target/aarch64-macos/*/pcm.cache/std.pcm + stdfile=$(ls $stdfile 2>/dev/null | head -1) + echo "STD=$stdfile" + "$clang" -std=c++23 -fmodule-file=std=$stdfile --precompile mini.cppm -o c.pcm 2>&1 | tail -3 && \ + "$clang" -std=c++23 -fmodule-file=mini=c.pcm -fmodule-file=std=$stdfile main.cpp c.pcm -o c 2>&1 | tail -3 + ./c; echo "C exit: $?" + echo "--- mini D: std.pcm + force_load ---" + "$clang" -std=c++23 -fmodule-file=mini=c.pcm -fmodule-file=std=$stdfile main.cpp c.pcm -Wl,-force_load,"$pkgroot/lib/libc++.a" -o d 2>&1 | tail -3 + ./d; echo "D exit: $?" From d41d7b358751dfefb63351a5b4082186c6fbc0a9 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:47:09 +0800 Subject: [PATCH 10/26] =?UTF-8?q?chore(diag):=20relink=20matrix=20?= =?UTF-8?q?=E2=80=94=20dylib=20vs=20static=20libc++=20vs=20dead-strip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/diag-ut-macos.yml | 85 +++++++++++++++-------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 0df427a..8cb5c69 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -27,61 +27,62 @@ jobs: echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" echo "$root/bin" >> "$GITHUB_PATH" - - name: mcpp test + symbol forensics + - name: mcpp test + forensics shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | "$MCPP" --version "$MCPP" test -p boost-ext.ut || true - echo "=== locate binary/objs ===" dir=tests/examples/boost-ext.ut/target/aarch64-macos d=$(ls -d $dir/*/ | head -1) echo "TARGETDIR=$d" bin="$d/bin/ut" - echo "=== module object: cout references ===" - nm -nm "$d/obj/boost.ut.m.o" 2>/dev/null | grep -iE "cout|ios_base" || echo "(none)" - echo "=== module object: all undefined ===" - nm -u "$d/obj/boost.ut.m.o" 2>/dev/null | grep -iE "cout" || echo "(no cout undefined)" - echo "=== test object: cout references ===" - nm -nm "$d/obj/ut.o" 2>/dev/null | grep -iE "cout|ios_base" || echo "(none)" - echo "=== binary: undefined symbols (cout/init) ===" - nm -u "$bin" 2>/dev/null | grep -iE "cout|ios_base|Init" || echo "(none)" - echo "=== binary: defined data (cout) ===" - nm -nm "$bin" 2>/dev/null | grep -iE "cout" | grep -iE "data|const|common" || echo "(no cout data symbol)" - echo "=== binary: __mod_init_func count ===" - otool -l "$bin" | grep -A3 "mod_init_func" || echo "(no mod_init_func)" - echo "=== link flags (verbose mcpp) ===" - "$MCPP" test -p boost-ext.ut -v 2>&1 | grep -iE "clang|ld|libc|o bin" | tail -30 || true - - name: mini repro matrix with xpkg clang + echo "=== init sections ===" + otool -l "$bin" | grep -iE "sectname" | grep -iE "init|mod" || echo "(none)" + echo "=== __TEXT,__init_offsets content ===" + otool -s __TEXT __init_offsets "$bin" 2>&1 | head -20 + echo "=== __DATA,__mod_init_func content ===" + otool -s __DATA __mod_init_func "$bin" 2>&1 | head -20 + echo "=== DoIOSInit / start_std_streams in binary? ===" + nm -nm "$bin" 2>/dev/null | grep -iE "DoIOS|start_std" || echo "(NOT PRESENT)" + echo "=== iostream.o symbols in binary (cin/cerr/clog)? ===" + nm -nm "$bin" 2>/dev/null | grep -iE "4cinE|4cerrE|4clogE" || echo "(NOT PRESENT)" + echo "=== ios_base::Init ctor in binary? ===" + nm -nm "$bin" 2>/dev/null | grep -iE "ios_base4InitC1|ios_base4InitC2" || echo "(NOT PRESENT)" + - name: relink real objects with different policies shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | clang=$(python3 -c "import json;print(json.load(open('tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'][0])") pkgroot=$(dirname $(dirname $clang)) - echo "CLANG=$clang" - echo "PKGROOT=$pkgroot" - ls "$pkgroot/lib" | head -20 - mkdir -p /tmp/repro && cd /tmp/repro - printf 'module;\n#include \nexport module mini;\nexport inline int the_stream = (std::cout.rdbuf() != nullptr) ? 1 : 0;\n' > mini.cppm - printf 'import mini;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > main.cpp - echo "--- mini A: no std.pcm, plain link ---" - "$clang" -std=c++23 --precompile mini.cppm -o a.pcm && \ - "$clang" -std=c++23 -fmodule-file=mini=a.pcm main.cpp a.pcm -o a 2>&1 | tail -3 - nm -u a 2>/dev/null | grep -i cout || echo "(no undefined cout)" - ./a; echo "A exit: $?" - echo "--- mini B: no std.pcm, force_load libc++.a ---" - "$clang" -std=c++23 -fmodule-file=mini=a.pcm main.cpp a.pcm -Wl,-force_load,"$pkgroot/lib/libc++.a" -o b 2>&1 | tail -3 - nm -nm b 2>/dev/null | grep -iE "cout" | grep -iE "data|const" || echo "(no cout data)" - ./b; echo "B exit: $?" - echo "--- mini C: with std.pcm (mirror mcpp), plain link ---" - stdfile=tests/examples/boost-ext.ut/target/aarch64-macos/*/pcm.cache/std.pcm - stdfile=$(ls $stdfile 2>/dev/null | head -1) - echo "STD=$stdfile" - "$clang" -std=c++23 -fmodule-file=std=$stdfile --precompile mini.cppm -o c.pcm 2>&1 | tail -3 && \ - "$clang" -std=c++23 -fmodule-file=mini=c.pcm -fmodule-file=std=$stdfile main.cpp c.pcm -o c 2>&1 | tail -3 - ./c; echo "C exit: $?" - echo "--- mini D: std.pcm + force_load ---" - "$clang" -std=c++23 -fmodule-file=mini=c.pcm -fmodule-file=std=$stdfile main.cpp c.pcm -Wl,-force_load,"$pkgroot/lib/libc++.a" -o d 2>&1 | tail -3 - ./d; echo "D exit: $?" + dir=tests/examples/boost-ext.ut/target/aarch64-macos + d=$(ls -d $dir/*/ | head -1) + cd "$d" + C="$clang -nostdinc++ -isystem $pkgroot/include/c++/v1 --sysroot=$(xcrun --show-sdk-path) -mmacosx-version-min=14.0" + echo "=== E: dynamic system libc++ (default link) ===" + $C -o ut_e obj/ut.o obj/boost.ut.m.o 2>&1 | tail -3 + otool -L ut_e 2>/dev/null | grep -i c++ || true + ./ut_e; echo "E exit: $?" + echo "=== F: static libc++.a (plain) ===" + $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 + otool -L ut_f 2>/dev/null | grep -i c++ || echo "(no libc++ dylib)" + ./ut_f; echo "F exit: $?" + echo "=== G: static libc++.a + -no_dead_strip ===" + $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 + ./ut_g; echo "G exit: $?" + echo "=== H: static libc++.a + -dead_strip (explicit) ===" + $C -o ut_h obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 + ./ut_h; echo "H exit: $?" + echo "=== I: static libc++.a + force_load iostream object ===" + tmp=$(mktemp -d) + (cd "$tmp" && ar -x "$pkgroot/lib/libc++.a" iostream.cpp.o 2>/dev/null; ls) + $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp/iostream.cpp.o" "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 + ./ut_i; echo "I exit: $?" + echo "=== init_offsets in G (no_dead_strip) vs F (plain) ===" + echo "--- F:"; otool -l ut_f | grep -iE "init_offsets|mod_init" || echo none + echo "--- G:"; otool -l ut_g | grep -iE "init_offsets|mod_init" || echo none + echo "--- G DoIOSInit:"; nm -nm ut_g | grep -iE "DoIOS|start_std" || echo "(NOT PRESENT)" + echo "=== DYLD_PRINT_BINDINGS on F (cout binding) ===" + DYLD_PRINT_BINDINGS=1 ./ut_f 2>&1 | grep -i "cout" | head -5 || true From e6780b01b483c2914680342c773376be42b90c58 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:50:31 +0800 Subject: [PATCH 11/26] chore(diag): initializer symbol mapping + fix-candidate matrix --- .github/workflows/diag-ut-macos.yml | 69 +++++++++++------------------ 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 8cb5c69..1afe171 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -27,62 +27,43 @@ jobs: echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" echo "$root/bin" >> "$GITHUB_PATH" - - name: mcpp test + forensics + - name: mcpp test (collect crash) shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | "$MCPP" --version "$MCPP" test -p boost-ext.ut || true - dir=tests/examples/boost-ext.ut/target/aarch64-macos - d=$(ls -d $dir/*/ | head -1) - echo "TARGETDIR=$d" - bin="$d/bin/ut" - echo "=== init sections ===" - otool -l "$bin" | grep -iE "sectname" | grep -iE "init|mod" || echo "(none)" - echo "=== __TEXT,__init_offsets content ===" - otool -s __TEXT __init_offsets "$bin" 2>&1 | head -20 - echo "=== __DATA,__mod_init_func content ===" - otool -s __DATA __mod_init_func "$bin" 2>&1 | head -20 - echo "=== DoIOSInit / start_std_streams in binary? ===" - nm -nm "$bin" 2>/dev/null | grep -iE "DoIOS|start_std" || echo "(NOT PRESENT)" - echo "=== iostream.o symbols in binary (cin/cerr/clog)? ===" - nm -nm "$bin" 2>/dev/null | grep -iE "4cinE|4cerrE|4clogE" || echo "(NOT PRESENT)" - echo "=== ios_base::Init ctor in binary? ===" - nm -nm "$bin" 2>/dev/null | grep -iE "ios_base4InitC1|ios_base4InitC2" || echo "(NOT PRESENT)" - - name: relink real objects with different policies + bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f | head -1) + echo "BIN=$bin" + echo "=== dyld_info -initializers (symbol names!) ===" + dyld_info -initializers "$bin" 2>&1 | tail -40 || true + echo "=== DYLD_PRINT_INITIALIZERS full (execution order) ===" + DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | grep -v "^dyld\[" | head -10 || true + DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | grep "^dyld\[" | head -40 || true + - name: relink matrix shell: bash env: MCPP_INDEX_MIRROR: GLOBAL run: | clang=$(python3 -c "import json;print(json.load(open('tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'][0])") pkgroot=$(dirname $(dirname $clang)) - dir=tests/examples/boost-ext.ut/target/aarch64-macos - d=$(ls -d $dir/*/ | head -1) + d=$(ls -d tests/examples/boost-ext.ut/target/aarch64-macos/*/ | head -1) cd "$d" C="$clang -nostdinc++ -isystem $pkgroot/include/c++/v1 --sysroot=$(xcrun --show-sdk-path) -mmacosx-version-min=14.0" - echo "=== E: dynamic system libc++ (default link) ===" - $C -o ut_e obj/ut.o obj/boost.ut.m.o 2>&1 | tail -3 - otool -L ut_e 2>/dev/null | grep -i c++ || true - ./ut_e; echo "E exit: $?" - echo "=== F: static libc++.a (plain) ===" - $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 - otool -L ut_f 2>/dev/null | grep -i c++ || echo "(no libc++ dylib)" - ./ut_f; echo "F exit: $?" - echo "=== G: static libc++.a + -no_dead_strip ===" - $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 - ./ut_g; echo "G exit: $?" - echo "=== H: static libc++.a + -dead_strip (explicit) ===" - $C -o ut_h obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 - ./ut_h; echo "H exit: $?" - echo "=== I: static libc++.a + force_load iostream object ===" + runit() { if ./"$1" >/tmp/out.txt 2>&1; then echo "$2 exit: 0 :: $(head -1 /tmp/out.txt)"; else echo "$2 exit: $? :: $(head -1 /tmp/out.txt)"; fi; } + $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_f "F(static plain)" + echo " F initializers:"; dyld_info -initializers ut_f 2>&1 | tail -25 + $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_g "G(no_dead_strip)" + echo " G initializers:"; dyld_info -initializers ut_g 2>&1 | tail -25 tmp=$(mktemp -d) - (cd "$tmp" && ar -x "$pkgroot/lib/libc++.a" iostream.cpp.o 2>/dev/null; ls) - $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp/iostream.cpp.o" "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 - ./ut_i; echo "I exit: $?" - echo "=== init_offsets in G (no_dead_strip) vs F (plain) ===" - echo "--- F:"; otool -l ut_f | grep -iE "init_offsets|mod_init" || echo none - echo "--- G:"; otool -l ut_g | grep -iE "init_offsets|mod_init" || echo none - echo "--- G DoIOSInit:"; nm -nm ut_g | grep -iE "DoIOS|start_std" || echo "(NOT PRESENT)" - echo "=== DYLD_PRINT_BINDINGS on F (cout binding) ===" - DYLD_PRINT_BINDINGS=1 ./ut_f 2>&1 | grep -i "cout" | head -5 || true + (cd "$tmp" && ar -x "$pkgroot/lib/libc++.a" iostream.cpp.o 2>/dev/null || true) + $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp/iostream.cpp.o" "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_i "I(force iostream.o)" + $C -o ut_m obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi 2>&1 | tail -2 + otool -L ut_m 2>/dev/null | grep -i c++ || true + runit ut_m "M(xpkg dylib)" + $C -o ut_d obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_d "D(dead_strip explicit)" From 291c105944f6df2534956c024c32a920c7b13b74 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 20:54:17 +0800 Subject: [PATCH 12/26] chore(diag): no-heredoc guard injection --- .github/workflows/diag-ut-macos.yml | 41 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 1afe171..84a82de 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -36,12 +36,15 @@ jobs: "$MCPP" test -p boost-ext.ut || true bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f | head -1) echo "BIN=$bin" - echo "=== dyld_info -initializers (symbol names!) ===" - dyld_info -initializers "$bin" 2>&1 | tail -40 || true - echo "=== DYLD_PRINT_INITIALIZERS full (execution order) ===" - DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | grep -v "^dyld\[" | head -10 || true - DYLD_PRINT_INITIALIZERS=1 "$bin" 2>&1 | grep "^dyld\[" | head -40 || true - - name: relink matrix + echo "=== symbolize __init_offsets (ASLR off) ===" + offs=$(otool -s __TEXT __init_offsets "$bin" 2>/dev/null | awk '/Contents of/{next} {for(i=1;i<=NF;i++) print $i}') + printf 'settings set target.disable-aslr true\nrun\n' > /tmp/x.lldb + for off in $offs; do + addr=$((16#$off)) + printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/x.lldb + done + lldb -b -s /tmp/x.lldb -- "$bin" 2>&1 | grep -E "image lookup|Summary:|Function:|0x100000" | head -60 || true + - name: relink matrix + guard test shell: bash env: MCPP_INDEX_MIRROR: GLOBAL @@ -51,19 +54,23 @@ jobs: d=$(ls -d tests/examples/boost-ext.ut/target/aarch64-macos/*/ | head -1) cd "$d" C="$clang -nostdinc++ -isystem $pkgroot/include/c++/v1 --sysroot=$(xcrun --show-sdk-path) -mmacosx-version-min=14.0" + stdpcm=$(ls pcm.cache/std.pcm 2>/dev/null) + cppm=$(find tests/examples/boost-ext.ut -path '*generated*/boost.ut.cppm' | head -1) + echo "CPPM=$cppm STD=$stdpcm" runit() { if ./"$1" >/tmp/out.txt 2>&1; then echo "$2 exit: 0 :: $(head -1 /tmp/out.txt)"; else echo "$2 exit: $? :: $(head -1 /tmp/out.txt)"; fi; } $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 runit ut_f "F(static plain)" - echo " F initializers:"; dyld_info -initializers ut_f 2>&1 | tail -25 $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 runit ut_g "G(no_dead_strip)" - echo " G initializers:"; dyld_info -initializers ut_g 2>&1 | tail -25 - tmp=$(mktemp -d) - (cd "$tmp" && ar -x "$pkgroot/lib/libc++.a" iostream.cpp.o 2>/dev/null || true) - $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp/iostream.cpp.o" "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 - runit ut_i "I(force iostream.o)" - $C -o ut_m obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi 2>&1 | tail -2 - otool -L ut_m 2>/dev/null | grep -i c++ || true - runit ut_m "M(xpkg dylib)" - $C -o ut_d obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 - runit ut_d "D(dead_strip explicit)" + echo "=== K: recompile cppm with ios_base::Init guard, static link ===" + cp "$cppm" /tmp/guarded.cppm + python3 -c "p='/tmp/guarded.cppm'; s=open(p).read(); g='namespace {\nstruct [[maybe_unused]] ut_stream_guard { std::ios_base::Init init; };\n[[maybe_unused]] ut_stream_guard ut_ensure_streams{};\n}\n'; m='#define BOOST_UT_CXX_MODULES 1'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('guard injected')" + $C -std=c++23 -fmodule-file=std=$stdpcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -3 + $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_k "K(guard static plain)" + echo " K initializer order:"; printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb + offs=$(otool -s __TEXT __init_offsets ut_k 2>/dev/null | awk '/Contents of/{next} {for(i=1;i<=NF;i++) print $i}') + for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done + lldb -b -s /tmp/y.lldb -- ./ut_k 2>&1 | grep -E "Summary:|Function:" | head -30 || true + $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + runit ut_l "L(guard no_dead_strip)" From c8069a4bf9dac6514ca3338657b054e04e7e7d29 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:04:27 +0800 Subject: [PATCH 13/26] chore(diag): fix awk/find/set-e bugs; full fix-candidate matrix --- .github/workflows/diag-ut-macos.yml | 41 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 84a82de..d44c569 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -36,14 +36,16 @@ jobs: "$MCPP" test -p boost-ext.ut || true bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f | head -1) echo "BIN=$bin" + echo "=== init_offsets entries (raw) ===" + otool -s __TEXT __init_offsets "$bin" 2>/dev/null || true echo "=== symbolize __init_offsets (ASLR off) ===" - offs=$(otool -s __TEXT __init_offsets "$bin" 2>/dev/null | awk '/Contents of/{next} {for(i=1;i<=NF;i++) print $i}') + offs=$(otool -s __TEXT __init_offsets "$bin" 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') printf 'settings set target.disable-aslr true\nrun\n' > /tmp/x.lldb for off in $offs; do addr=$((16#$off)) printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/x.lldb done - lldb -b -s /tmp/x.lldb -- "$bin" 2>&1 | grep -E "image lookup|Summary:|Function:|0x100000" | head -60 || true + lldb -b -s /tmp/x.lldb -- "$bin" 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true - name: relink matrix + guard test shell: bash env: @@ -51,26 +53,37 @@ jobs: run: | clang=$(python3 -c "import json;print(json.load(open('tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'][0])") pkgroot=$(dirname $(dirname $clang)) - d=$(ls -d tests/examples/boost-ext.ut/target/aarch64-macos/*/ | head -1) + d=$(ls -d "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/target/aarch64-macos"/*/ | head -1) cd "$d" C="$clang -nostdinc++ -isystem $pkgroot/include/c++/v1 --sysroot=$(xcrun --show-sdk-path) -mmacosx-version-min=14.0" stdpcm=$(ls pcm.cache/std.pcm 2>/dev/null) - cppm=$(find tests/examples/boost-ext.ut -path '*generated*/boost.ut.cppm' | head -1) + cppm=$(find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut" -path '*generated*/boost.ut.cppm' | head -1) echo "CPPM=$cppm STD=$stdpcm" runit() { if ./"$1" >/tmp/out.txt 2>&1; then echo "$2 exit: 0 :: $(head -1 /tmp/out.txt)"; else echo "$2 exit: $? :: $(head -1 /tmp/out.txt)"; fi; } - $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true runit ut_f "F(static plain)" - $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true runit ut_g "G(no_dead_strip)" - echo "=== K: recompile cppm with ios_base::Init guard, static link ===" + $C -o ut_d obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true + runit ut_d "D(dead_strip explicit)" + $C -o ut_m obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi 2>&1 | tail -2 || true + otool -L ut_m 2>/dev/null | grep -i c++ || true + runit ut_m "M(xpkg dylib)" + tmp=$(mktemp -d) + (cd "$tmp" && ar -t "$pkgroot/lib/libc++.a" | grep -i iostream && ar -x "$pkgroot/lib/libc++.a" $(ar -t "$pkgroot/lib/libc++.a" | grep -i iostream) 2>/dev/null) || true + ls "$tmp" || true + $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp"/*.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true + runit ut_i "I(force iostream.o)" + echo "=== K: cppm + ios_base::Init guard, static link ===" cp "$cppm" /tmp/guarded.cppm python3 -c "p='/tmp/guarded.cppm'; s=open(p).read(); g='namespace {\nstruct [[maybe_unused]] ut_stream_guard { std::ios_base::Init init; };\n[[maybe_unused]] ut_stream_guard ut_ensure_streams{};\n}\n'; m='#define BOOST_UT_CXX_MODULES 1'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('guard injected')" - $C -std=c++23 -fmodule-file=std=$stdpcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -3 - $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -3 || true + $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true runit ut_k "K(guard static plain)" - echo " K initializer order:"; printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb - offs=$(otool -s __TEXT __init_offsets ut_k 2>/dev/null | awk '/Contents of/{next} {for(i=1;i<=NF;i++) print $i}') - for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done - lldb -b -s /tmp/y.lldb -- ./ut_k 2>&1 | grep -E "Summary:|Function:" | head -30 || true - $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 + $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true runit ut_l "L(guard no_dead_strip)" + echo "=== K init_offsets symbolization ===" + offs=$(otool -s __TEXT __init_offsets ut_k 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') + printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb + for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done + lldb -b -s /tmp/y.lldb -- ./ut_k 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true From 365887cc3a9d7e317879dcec5f0540f5583b8797 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:07:56 +0800 Subject: [PATCH 14/26] chore(diag): fix K -I; add archive-order/force_load/rpath variants --- .github/workflows/diag-ut-macos.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index d44c569..4cf1683 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -77,11 +77,22 @@ jobs: echo "=== K: cppm + ios_base::Init guard, static link ===" cp "$cppm" /tmp/guarded.cppm python3 -c "p='/tmp/guarded.cppm'; s=open(p).read(); g='namespace {\nstruct [[maybe_unused]] ut_stream_guard { std::ios_base::Init init; };\n[[maybe_unused]] ut_stream_guard ut_ensure_streams{};\n}\n'; m='#define BOOST_UT_CXX_MODULES 1'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('guard injected')" - $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -3 || true - $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true + inc=$(python3 -c "import json;args=json.load(open('$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'];print(' '.join(a for a in args if a.startswith('-I')))") + echo "INC=$inc" + $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -15 || true + $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true runit ut_k "K(guard static plain)" - $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true - runit ut_l "L(guard no_dead_strip)" + $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true + runit ut_l "L(guard dead_strip)" + echo "=== O: libc++.a BEFORE user objects ===" + $C -o ut_o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" obj/ut.o obj/boost.ut.m.o 2>&1 | tail -5 || true + runit ut_o "O(archive first)" + echo "=== P: force_load libc++.a ===" + $C -o ut_p obj/ut.o obj/boost.ut.m.o -Wl,-force_load,"$pkgroot/lib/libc++.a" -Wl,-force_load,"$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true + runit ut_p "P(force_load)" + echo "=== M2: xpkg dylib + rpath ===" + $C -o ut_m2 obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi -Wl,-rpath,"$pkgroot/lib" 2>&1 | tail -5 || true + runit ut_m2 "M2(xpkg dylib+rpath)" echo "=== K init_offsets symbolization ===" offs=$(otool -s __TEXT __init_offsets ut_k 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb From c9bcb7519bcc6cc27af9575e082115d8ebc4f26c Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:12:02 +0800 Subject: [PATCH 15/26] =?UTF-8?q?chore(diag):=20K/O=20backtraces=20?= =?UTF-8?q?=E2=80=94=20where=20exactly=20do=20they=20crash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/diag-ut-macos.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 4cf1683..f0958db 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -93,8 +93,15 @@ jobs: echo "=== M2: xpkg dylib + rpath ===" $C -o ut_m2 obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi -Wl,-rpath,"$pkgroot/lib" 2>&1 | tail -5 || true runit ut_m2 "M2(xpkg dylib+rpath)" - echo "=== K init_offsets symbolization ===" - offs=$(otool -s __TEXT __init_offsets ut_k 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') - printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb - for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done - lldb -b -s /tmp/y.lldb -- ./ut_k 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true + echo "=== K backtrace + guard disasm ===" + printf 'settings set target.disable-aslr true\nrun\nbt\nregister read pc x0 x1\n' > /tmp/z.lldb + lldb -b -s /tmp/z.lldb -- ./ut_k 2>&1 | tail -45 || true + echo "=== K guard init disasm (0x100020534 = first guarded.cppm init) ===" + printf 'settings set target.disable-aslr true\nrun\ndisassemble -s 0x100020534 -c 25\n' > /tmp/w.lldb + lldb -b -s /tmp/w.lldb -- ./ut_k 2>&1 | tail -40 || true + echo "=== O runtime init order ===" + DYLD_PRINT_INITIALIZERS=1 ./ut_o 2>&1 | grep -v "^dyld\[" | head -8 || true + DYLD_PRINT_INITIALIZERS=1 ./ut_o 2>&1 | grep "^dyld\[" | head -40 || true + echo "=== O abort backtrace ===" + printf 'settings set target.disable-aslr true\nrun\nbt all\n' > /tmp/v.lldb + lldb -b -s /tmp/v.lldb -- ./ut_o 2>&1 | tail -50 || true From 07700975e6df1b5bb679367d4e62193c019283b8 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:15:52 +0800 Subject: [PATCH 16/26] chore(diag): GMF-guard test (external linkage init survives modules) --- .github/workflows/diag-ut-macos.yml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index f0958db..7eb843c 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -74,16 +74,24 @@ jobs: ls "$tmp" || true $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp"/*.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true runit ut_i "I(force iostream.o)" - echo "=== K: cppm + ios_base::Init guard, static link ===" - cp "$cppm" /tmp/guarded.cppm - python3 -c "p='/tmp/guarded.cppm'; s=open(p).read(); g='namespace {\nstruct [[maybe_unused]] ut_stream_guard { std::ios_base::Init init; };\n[[maybe_unused]] ut_stream_guard ut_ensure_streams{};\n}\n'; m='#define BOOST_UT_CXX_MODULES 1'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('guard injected')" + echo "=== K2: cppm GMF-guard (external linkage, before export module), static link ===" inc=$(python3 -c "import json;args=json.load(open('$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'];print(' '.join(a for a in args if a.startswith('-I')))") echo "INC=$inc" - $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded.cppm -o obj/guarded.m.o 2>&1 | tail -15 || true - $C -o ut_k obj/ut.o obj/guarded.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true - runit ut_k "K(guard static plain)" - $C -o ut_l obj/ut.o obj/guarded.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true - runit ut_l "L(guard dead_strip)" + cp "$cppm" /tmp/guarded2.cppm + python3 -c "p='/tmp/guarded2.cppm'; s=open(p).read(); g='\n// GMF guard: force libc++ stream construction before ut cfg (static-libc++ init-order workaround)\nstd::ios_base::Init ut_ensure_std_streams_ready_global;\n'; m='export module boost.ut;'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('gmf guard injected')" + $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded2.cppm -o obj/guarded2.m.o 2>&1 | tail -10 || true + $C -o ut_k2 obj/ut.o obj/guarded2.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true + runit ut_k2 "K2(GMF guard static plain)" + $C -o ut_k3 obj/ut.o obj/guarded2.m.o 2>&1 | tail -5 || true + runit ut_k3 "K3(GMF guard dylib)" + echo "=== K2 init_offsets symbolization ===" + offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') + printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb + for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done + lldb -b -s /tmp/y.lldb -- ./ut_k2 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true + echo "=== K2 backtrace (if crashed) ===" + printf 'settings set target.disable-aslr true\nrun\nbt\n' > /tmp/z.lldb + lldb -b -s /tmp/z.lldb -- ./ut_k2 2>&1 | grep -E "frame #|stop reason|-> " | head -25 || true echo "=== O: libc++.a BEFORE user objects ===" $C -o ut_o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" obj/ut.o obj/boost.ut.m.o 2>&1 | tail -5 || true runit ut_o "O(archive first)" From c03f13d5e77b21f970159f61f8f12fcbf340343a Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:19:37 +0800 Subject: [PATCH 17/26] chore(diag): .97 disasm + constructor-attr guard + mcpp link cmd --- .github/workflows/diag-ut-macos.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index 7eb843c..c7b3327 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -89,9 +89,25 @@ jobs: printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done lldb -b -s /tmp/y.lldb -- ./ut_k2 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true - echo "=== K2 backtrace (if crashed) ===" - printf 'settings set target.disable-aslr true\nrun\nbt\n' > /tmp/z.lldb - lldb -b -s /tmp/z.lldb -- ./ut_k2 2>&1 | grep -E "frame #|stop reason|-> " | head -25 || true + echo "=== K2 guard init (.97) disasm ===" + printf 'settings set target.disable-aslr true\nrun\n' > /tmp/w.lldb + printf 'image lookup -a 0x%x\ndisassemble -s 0x%x -c 30\n' 0x0 0x0 >> /tmp/w.lldb + offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') + i=0 + for off in $offs; do + i=$((i+1)) + if [ $i -eq 3 ]; then printf 'disassemble -s 0x%x -c 30\n' $((0x100000000 + 16#$off)) >> /tmp/w.lldb; fi + done + lldb -b -s /tmp/w.lldb -- ./ut_k2 2>&1 | grep -E "ut_k2\`|bl |0x100" | head -40 || true + echo "=== S: __attribute__((constructor)) fn in GMF, static link ===" + cp "$cppm" /tmp/guarded3.cppm + python3 -c "p='/tmp/guarded3.cppm'; s=open(p).read(); g='\n// GMF ctor: force libc++ stream construction before ut cfg\n__attribute__((constructor)) static void ut_force_ios_init() { std::ios_base::Init i; }\n'; m='export module boost.ut;'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('ctor injected')" + $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded3.cppm -o obj/guarded3.m.o 2>&1 | tail -6 || true + $C -o ut_s obj/ut.o obj/guarded3.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -4 || true + runit ut_s "S(GMF ctor static)" + echo "=== mcpp full link command ===" + "$MCPP" test -p boost-ext.ut 2>&1 | grep -iE "link|clang\+\+.*-o|\.a|\.dylib" | head -10 || true + find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -name "*.log" -o -name "*.txt" -o -name "*.rsp" 2>/dev/null | head -10 || true echo "=== O: libc++.a BEFORE user objects ===" $C -o ut_o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" obj/ut.o obj/boost.ut.m.o 2>&1 | tail -5 || true runit ut_o "O(archive first)" From 16340bc8b28d7be240fbd395b999b647d0a74893 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:22:58 +0800 Subject: [PATCH 18/26] chore(diag): plain-TU streamtest (decisive) + .97 disasm + mcpp link cmd --- .github/workflows/diag-ut-macos.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index c7b3327..b643113 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -105,9 +105,26 @@ jobs: $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded3.cppm -o obj/guarded3.m.o 2>&1 | tail -6 || true $C -o ut_s obj/ut.o obj/guarded3.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -4 || true runit ut_s "S(GMF ctor static)" - echo "=== mcpp full link command ===" - "$MCPP" test -p boost-ext.ut 2>&1 | grep -iE "link|clang\+\+.*-o|\.a|\.dylib" | head -10 || true - find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -name "*.log" -o -name "*.txt" -o -name "*.rsp" 2>/dev/null | head -10 || true + echo "=== STREAMTEST: plain TU + ios_base::Init guard, static libc++ ===" + printf '#include \nstatic std::ios_base::Init g_ios;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > /tmp/streamtest.cpp + $C -O0 -g -c /tmp/streamtest.cpp -o /tmp/streamtest.o 2>&1 | tail -3 || true + $C -o /tmp/streamtest /tmp/streamtest.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 || true + if /tmp/streamtest >/tmp/st.out 2>&1; then echo "streamtest exit: 0 (guard WORKS in plain TU)"; else echo "streamtest exit: $? (guard FAILS in plain TU)"; fi + echo " streamtest DoIOSInit present?"; nm -nm /tmp/streamtest 2>/dev/null | grep -iE "DoIOS|start_std" || echo " (NOT PRESENT)" + echo "=== .97 disasm (fixed) ===" + offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') + printf 'settings set target.disable-aslr true\nrun\n' > /tmp/w.lldb + i=0 + for off in $offs; do + i=$((i+1)) + if [ $i -eq 3 ]; then printf 'disassemble -s 0x%x -c 25\n' $((0x100000000 + 16#$off)) >> /tmp/w.lldb; fi + done + lldb -b -s /tmp/w.lldb -- ./ut_k2 2>&1 | sed -n '/disassemble/,$p' | head -35 || true + echo "=== mcpp link command (verbose full) ===" + "$MCPP" test -p boost-ext.ut -v 2>&1 | grep -iE "clang\+\+|ld |-o |libc\+\+" | head -15 || true + echo "=== M3: system dylib ONLY via mcpp-style link (sanity: E variant) ===" + $C -o ut_e2 obj/ut.o obj/boost.ut.m.o 2>&1 | tail -3 || true + runit ut_e2 "E2(dylib only)" echo "=== O: libc++.a BEFORE user objects ===" $C -o ut_o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" obj/ut.o obj/boost.ut.m.o 2>&1 | tail -5 || true runit ut_o "O(archive first)" From c8cbfa1445155c4afc88bcbf9c272637ca9b382f Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 2 Aug 2026 21:27:52 +0800 Subject: [PATCH 19/26] chore(diag): capture mcpp link command --- .github/workflows/diag-ut-macos.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml index b643113..ef03d2b 100644 --- a/.github/workflows/diag-ut-macos.yml +++ b/.github/workflows/diag-ut-macos.yml @@ -121,7 +121,14 @@ jobs: done lldb -b -s /tmp/w.lldb -- ./ut_k2 2>&1 | sed -n '/disassemble/,$p' | head -35 || true echo "=== mcpp link command (verbose full) ===" - "$MCPP" test -p boost-ext.ut -v 2>&1 | grep -iE "clang\+\+|ld |-o |libc\+\+" | head -15 || true + "$MCPP" test -p boost-ext.ut -v > /tmp/mcpp_v.log 2>&1 || true + grep -nE "clang\+\+.*(-o|\.o)|\.a |libc\+\+|static" /tmp/mcpp_v.log | head -20 || echo "(no link line found)" + echo "--- full tail of verbose log ---" + tail -30 /tmp/mcpp_v.log || true + echo "=== mcpp build artifacts ===" + find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -maxdepth 3 -name "*link*" -o -maxdepth 3 -name "*build*" -o -maxdepth 3 -name "*.ninja" 2>/dev/null | head -10 || true + echo "=== ninja/build log for link cmd ===" + find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -name "*.log" 2>/dev/null | head -5 || true echo "=== M3: system dylib ONLY via mcpp-style link (sanity: E variant) ===" $C -o ut_e2 obj/ut.o obj/boost.ut.m.o 2>&1 | tail -3 || true runit ut_e2 "E2(dylib only)" From fbf1ad0c45e57bf3aa23b020665c281a0313ab5a Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Mon, 3 Aug 2026 12:03:12 +0800 Subject: [PATCH 20/26] =?UTF-8?q?fix(boost-ext.ut):=20construct=20ios=5Fba?= =?UTF-8?q?se::Init=20before=20ut.hpp=20=E2=80=94=20macOS=20init-order=20w?= =?UTF-8?q?orkaround=20(mcpp#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Package-side fix per review: cfg (ut.hpp:2247) is an inline variable, so its initializer is emitted in the module TU; a std::ios_base::Init construct declared before #include "ut.hpp" precedes cfg's init in this object's initializer order and forces libc++'s stream construction first. Also corrects the descriptor's macOS narrative (mcpp#336: missing libc++ stream guard, NOT an ODR split) and updates the dev list to include dev 4. --- pkgs/b/boost-ext.ut.lua | 84 +++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index afbc29c..5104628 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -28,20 +28,24 @@ -- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles -- the verbatim file (WITH `export import std;`) fine but the resulting -- test binary SIGSEGVs (exit 139) during static init of --- `cfg::runner>` — no "Suite '...'" output. A --- macOS lldb backtrace pins it exactly: --- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 --- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) +-- `cfg::runner>` — no "Suite '...'" output. -- ut.hpp:1620 is the member-init `std::streambuf* cout_save = --- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is --- ZERO — the stream object was NEVER constructed. Root cause: --- `export import std;` inside the module makes `std::cout` refer to the --- module's OWN copy of the std stream entities, NOT libc++'s — an ODR --- split between "the std module's std::cout" and "the libc++ library's --- std::cout" (Apple libc++ does not merge module std entities the way --- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the --- library copy; the module's copy stays all-zero, so --- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr. +-- std::cout.rdbuf();` — the fault address 0xffffffffffffffe8 (-0x18) +-- means `std::cout`'s vptr is ZERO: the stream object was never +-- constructed. Root cause (verified from mcpp's link line, `__init_offsets` +-- and a disassembly of iostream.cpp.o; filed as mcpp-community/mcpp#336, +-- fixed in mcpp 2026.8.3.1): Mach-O has no priority-ordered initializer +-- section, so the initializer pulled out of `libc++.a` lands LAST in link +-- order; libc++'s `` carries no `ios_base::Init` guard of its +-- own (libstdc++ and the MSVC STL do — which is exactly why only macOS +-- broke); and `std::ios_base::Init` is only forward-declared in libc++'s +-- headers, so the standard's remedy for static-init order is not writable +-- by users on libc++. This is NOT an ODR split: `nm` shows exactly one +-- `std::cout` definition, and the crash reproduces with plain +-- `#include ` + a global object, no modules involved at all. +-- mcpp 2026.8.3.1 fixes it engine-side by linking a generated object FIRST +-- whose constructor forces the streams up; until that mcpp is required, +-- this descriptor works around it package-side (dev 4 below). -- -- The FIX (matching how every other successful C++23 module package in this -- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built): @@ -68,11 +72,10 @@ -- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` -- block ut.hpp opens at line 111 exports everything — with these deviations: -- --- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above), --- and instead the global-module-fragment `#include`s every stdlib --- header ut.hpp needs (its own includes at lines 73-104 still run, --- the GMF list just guarantees they're all present and visible for --- GCC's two-phase lookup). +-- dev 1. `export import std;` is DROPPED, and instead the global-module- +-- fragment `#include`s every stdlib header ut.hpp needs (its own +-- includes at lines 73-104 still run, the GMF list just guarantees +-- they're all present and visible for GCC's two-phase lookup). -- dev 2. Two compiler-compat shims: -- shim a: `using std::size_t;` — lifts `std::size_t` into the -- global namespace so the unqualified `size_t` at ut.hpp @@ -86,8 +89,20 @@ -- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from -- upstream `master`. It is upstream's fix for a Clang module -- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT --- address the macOS ODR-split (dev 1 does). Kept because upstream +-- address the macOS crash (dev 4 does). Kept because upstream -- added it for a reason and it is cheap to carry. +-- dev 4. A `std::ios_base::Init` object declared in the module purview +-- BEFORE `#include "ut.hpp"` — the package-side macOS workaround for +-- the libc++ static-init-order bug above (mcpp#336). `cfg` +-- (ut.hpp:2247) is an `inline` variable, so under module attachment +-- its initializer is emitted in THIS module TU; being declared +-- earlier makes this construct's initializer precede cfg's in this +-- object's `__init_offsets`, forcing libc++'s stream construction +-- (`__start_std_streams()` via DoIOSInit) before cfg's member-init +-- reads `std::cout.rdbuf()` (ut.hpp:1620). On libstdc++/MSVC STL it +-- is a harmless no-op (they carry their own ios_base::Init guard), +-- and it also protects macOS consumers still on older mcpp. Once +-- mcpp ≥ 2026.8.3.1 is required, dev 4 can be dropped. -- -- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — -- the shims add NO code of our own beyond what the compiler/runtime had to @@ -157,20 +172,23 @@ package = { -- at the top of this descriptor: -- dev 1: `export import std;` DROPPED — global-module-fragment -- `#include`s of every stdlib header ut.hpp needs instead - -- (macOS ODR-split fix; see header comment). + -- (see header comment). -- dev 2: two compiler-compat shims (`using std::size_t;` for GCC; -- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI). -- dev 3: post-v2.3.1 explicit-template-instantiation block (from -- upstream `master`) appended after `#include "ut.hpp"`. + -- dev 4: `std::ios_base::Init` construct BEFORE `#include "ut.hpp"` + -- — macOS libc++ static-init-order workaround (mcpp#336). -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. generated_files = { ["mcpp_generated/boost.ut.cppm"] = [==[ module; // Global-module-fragment: every stdlib header ut.hpp needs, so the module -// TU sees the SAME std entities the library links (no `export import std;` -// → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix) -// AND GCC's module two-phase lookup finds the names it needs. +// TU sees the SAME std entities the library links, and GCC's module +// two-phase lookup finds the names it needs. (The macOS SIGSEGV is NOT an +// ODR split — it is libc++'s missing ios_base::Init guard, mcpp#336 — and +// is handled by the ios_base::Init construct below, before `#include "ut.hpp"`.) #include #include #include @@ -224,6 +242,21 @@ using std::size_t; #define __argv ((const char**)nullptr) #endif +// ---- mcpp-index compat shim 3/3: macOS libc++ static-init order ------------ +// ut.hpp:2247 declares `cfg` as an `inline` variable, so under module +// attachment its dynamic initializer is emitted in THIS module TU — and it +// reads std::cout.rdbuf() (ut.hpp:1620) inside reporter_junit's constructor. +// On macOS, libc++'s carries no ios_base::Init guard of its own +// and Mach-O has no priority-ordered init section (mcpp-community/mcpp#336): +// the stream initializer pulled from libc++.a lands AFTER this TU's, leaving +// std::cout all-zero when cfg's init runs (exit 139). Constructing a +// std::ios_base::Init object HERE — declared before ut.hpp, hence before +// cfg's init in this TU's initializer order — forces libc++'s +// __start_std_streams() to run first, so std::cout is live when cfg's init +// reads it. Harmless no-op on libstdc++/MSVC STL (they guard iostreams +// themselves). Drop once mcpp >= 2026.8.3.1 is required (engine fix). +std::ios_base::Init __mcpp_boost_ext_ut_ios_init; + #define BOOST_UT_CXX_MODULES 1 #include "ut.hpp" @@ -239,8 +272,9 @@ using std::size_t; // `expect`) only IMPLICITLY instantiable. Explicitly instantiating // them forces emission — upstream's fix for a Clang module linkage gap. // NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()` -// ODR split) — dev 1 (no `export import std;`) is the macOS fix. Both stay: -// dev 1 is the ODR fix, dev 3 is upstream's verbatim linkage-gap fix. +// reads a never-constructed libc++ stream — mcpp#336) — the ios_base::Init +// construct above (shim 3/3) is the macOS workaround. Both stay: +// shim 3/3 is the init-order fix, dev 3 is upstream's verbatim linkage-gap fix. // Once a >2.3.1 release ships this block, switch `sources` to // `*/include/boost/ut.cppm` and drop `generated_files`; these lines come // back with the verbatim upstream cppm. From 52ba1fe6beef01612fab2190e755198ba8f828eb Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Mon, 3 Aug 2026 12:14:49 +0800 Subject: [PATCH 21/26] ci(validate): bump MCPP_VERSION to 2026.8.3.1 (mcpp#336 macOS init-order fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan B per review: the macOS crash is an mcpp engine bug fixed in 2026.8.3.1 (links a generated object first whose ctor forces libc++'s streams up), so bump only the CI pin — index.toml min_mcpp stays at 0.0.109 (syntax contract; a behavior fix must not E0006-gate older-mcpp consumers). Synced the three workspace matrix mcpp_version entries (keep in sync with env.MCPP_VERSION). --- .github/workflows/validate.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index b937a58..229f5cc 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,6 +14,12 @@ on: workflow_dispatch: env: + # 2026.8.3.1: fixes the macOS static-init-order crash of module packages + # that construct std streams (mcpp#336) — mcpp links a generated object + # first whose constructor forces libc++'s streams up, so boost-ext.ut's + # macOS leg needs no package-side workaround. This pin is a CI-only move: + # index.toml's `min_mcpp` is a *syntax* contract and is NOT bumped for a + # behavior fix (it would E0006-gate every older-mcpp consumer of the index). # 0.0.109: a bare dependency's wire address takes BOTH halves from the # descriptor the identity gate accepted (mcpp#286). This is the client-side # other half of the SPEC-001 migration below: mcpp used to take the NAME from @@ -75,7 +81,7 @@ env: # 0.0.94 fixed feature-gated `sources` under `mcpp test` (mcpp#218); 0.0.91 # added standard = "c++fly" to the resolver grammar, so c++fly descriptors # get the lint WARN below, not a hard grammar-parse rejection. - MCPP_VERSION: "0.0.109" + MCPP_VERSION: "2026.8.3.1" jobs: lint: @@ -240,21 +246,21 @@ jobs: ext: tar.gz mcpp: bin/mcpp xlings: registry/bin/xlings - mcpp_version: "0.0.109" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION - platform: macos os: macos-15 suffix: macosx-arm64 ext: tar.gz mcpp: bin/mcpp xlings: registry/bin/xlings - mcpp_version: "0.0.109" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION - platform: windows os: windows-latest suffix: windows-x86_64 ext: zip mcpp: bin/mcpp.exe xlings: registry/bin/xlings.exe - mcpp_version: "0.0.109" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION env: MCPP_EFFECTIVE: ${{ matrix.mcpp_version }} steps: From 1800642941ecb21eee6f13ce8e2474f5504bbfab Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Mon, 3 Aug 2026 12:15:59 +0800 Subject: [PATCH 22/26] =?UTF-8?q?Revert=20"fix(boost-ext.ut):=20construct?= =?UTF-8?q?=20ios=5Fbase::Init=20before=20ut.hpp=20=E2=80=94=20macOS=20ini?= =?UTF-8?q?t-order=20workaround=20(mcpp#336)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fbf1ad0c45e57bf3aa23b020665c281a0313ab5a. --- pkgs/b/boost-ext.ut.lua | 84 ++++++++++++----------------------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index 5104628..afbc29c 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -28,24 +28,20 @@ -- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles -- the verbatim file (WITH `export import std;`) fine but the resulting -- test binary SIGSEGVs (exit 139) during static init of --- `cfg::runner>` — no "Suite '...'" output. +-- `cfg::runner>` — no "Suite '...'" output. A +-- macOS lldb backtrace pins it exactly: +-- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 +-- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) -- ut.hpp:1620 is the member-init `std::streambuf* cout_save = --- std::cout.rdbuf();` — the fault address 0xffffffffffffffe8 (-0x18) --- means `std::cout`'s vptr is ZERO: the stream object was never --- constructed. Root cause (verified from mcpp's link line, `__init_offsets` --- and a disassembly of iostream.cpp.o; filed as mcpp-community/mcpp#336, --- fixed in mcpp 2026.8.3.1): Mach-O has no priority-ordered initializer --- section, so the initializer pulled out of `libc++.a` lands LAST in link --- order; libc++'s `` carries no `ios_base::Init` guard of its --- own (libstdc++ and the MSVC STL do — which is exactly why only macOS --- broke); and `std::ios_base::Init` is only forward-declared in libc++'s --- headers, so the standard's remedy for static-init order is not writable --- by users on libc++. This is NOT an ODR split: `nm` shows exactly one --- `std::cout` definition, and the crash reproduces with plain --- `#include ` + a global object, no modules involved at all. --- mcpp 2026.8.3.1 fixes it engine-side by linking a generated object FIRST --- whose constructor forces the streams up; until that mcpp is required, --- this descriptor works around it package-side (dev 4 below). +-- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is +-- ZERO — the stream object was NEVER constructed. Root cause: +-- `export import std;` inside the module makes `std::cout` refer to the +-- module's OWN copy of the std stream entities, NOT libc++'s — an ODR +-- split between "the std module's std::cout" and "the libc++ library's +-- std::cout" (Apple libc++ does not merge module std entities the way +-- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the +-- library copy; the module's copy stays all-zero, so +-- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr. -- -- The FIX (matching how every other successful C++23 module package in this -- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built): @@ -72,10 +68,11 @@ -- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` -- block ut.hpp opens at line 111 exports everything — with these deviations: -- --- dev 1. `export import std;` is DROPPED, and instead the global-module- --- fragment `#include`s every stdlib header ut.hpp needs (its own --- includes at lines 73-104 still run, the GMF list just guarantees --- they're all present and visible for GCC's two-phase lookup). +-- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above), +-- and instead the global-module-fragment `#include`s every stdlib +-- header ut.hpp needs (its own includes at lines 73-104 still run, +-- the GMF list just guarantees they're all present and visible for +-- GCC's two-phase lookup). -- dev 2. Two compiler-compat shims: -- shim a: `using std::size_t;` — lifts `std::size_t` into the -- global namespace so the unqualified `size_t` at ut.hpp @@ -89,20 +86,8 @@ -- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from -- upstream `master`. It is upstream's fix for a Clang module -- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT --- address the macOS crash (dev 4 does). Kept because upstream +-- address the macOS ODR-split (dev 1 does). Kept because upstream -- added it for a reason and it is cheap to carry. --- dev 4. A `std::ios_base::Init` object declared in the module purview --- BEFORE `#include "ut.hpp"` — the package-side macOS workaround for --- the libc++ static-init-order bug above (mcpp#336). `cfg` --- (ut.hpp:2247) is an `inline` variable, so under module attachment --- its initializer is emitted in THIS module TU; being declared --- earlier makes this construct's initializer precede cfg's in this --- object's `__init_offsets`, forcing libc++'s stream construction --- (`__start_std_streams()` via DoIOSInit) before cfg's member-init --- reads `std::cout.rdbuf()` (ut.hpp:1620). On libstdc++/MSVC STL it --- is a harmless no-op (they carry their own ios_base::Init guard), --- and it also protects macOS consumers still on older mcpp. Once --- mcpp ≥ 2026.8.3.1 is required, dev 4 can be dropped. -- -- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — -- the shims add NO code of our own beyond what the compiler/runtime had to @@ -172,23 +157,20 @@ package = { -- at the top of this descriptor: -- dev 1: `export import std;` DROPPED — global-module-fragment -- `#include`s of every stdlib header ut.hpp needs instead - -- (see header comment). + -- (macOS ODR-split fix; see header comment). -- dev 2: two compiler-compat shims (`using std::size_t;` for GCC; -- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI). -- dev 3: post-v2.3.1 explicit-template-instantiation block (from -- upstream `master`) appended after `#include "ut.hpp"`. - -- dev 4: `std::ios_base::Init` construct BEFORE `#include "ut.hpp"` - -- — macOS libc++ static-init-order workaround (mcpp#336). -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. generated_files = { ["mcpp_generated/boost.ut.cppm"] = [==[ module; // Global-module-fragment: every stdlib header ut.hpp needs, so the module -// TU sees the SAME std entities the library links, and GCC's module -// two-phase lookup finds the names it needs. (The macOS SIGSEGV is NOT an -// ODR split — it is libc++'s missing ios_base::Init guard, mcpp#336 — and -// is handled by the ios_base::Init construct below, before `#include "ut.hpp"`.) +// TU sees the SAME std entities the library links (no `export import std;` +// → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix) +// AND GCC's module two-phase lookup finds the names it needs. #include #include #include @@ -242,21 +224,6 @@ using std::size_t; #define __argv ((const char**)nullptr) #endif -// ---- mcpp-index compat shim 3/3: macOS libc++ static-init order ------------ -// ut.hpp:2247 declares `cfg` as an `inline` variable, so under module -// attachment its dynamic initializer is emitted in THIS module TU — and it -// reads std::cout.rdbuf() (ut.hpp:1620) inside reporter_junit's constructor. -// On macOS, libc++'s carries no ios_base::Init guard of its own -// and Mach-O has no priority-ordered init section (mcpp-community/mcpp#336): -// the stream initializer pulled from libc++.a lands AFTER this TU's, leaving -// std::cout all-zero when cfg's init runs (exit 139). Constructing a -// std::ios_base::Init object HERE — declared before ut.hpp, hence before -// cfg's init in this TU's initializer order — forces libc++'s -// __start_std_streams() to run first, so std::cout is live when cfg's init -// reads it. Harmless no-op on libstdc++/MSVC STL (they guard iostreams -// themselves). Drop once mcpp >= 2026.8.3.1 is required (engine fix). -std::ios_base::Init __mcpp_boost_ext_ut_ios_init; - #define BOOST_UT_CXX_MODULES 1 #include "ut.hpp" @@ -272,9 +239,8 @@ std::ios_base::Init __mcpp_boost_ext_ut_ios_init; // `expect`) only IMPLICITLY instantiable. Explicitly instantiating // them forces emission — upstream's fix for a Clang module linkage gap. // NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()` -// reads a never-constructed libc++ stream — mcpp#336) — the ios_base::Init -// construct above (shim 3/3) is the macOS workaround. Both stay: -// shim 3/3 is the init-order fix, dev 3 is upstream's verbatim linkage-gap fix. +// ODR split) — dev 1 (no `export import std;`) is the macOS fix. Both stay: +// dev 1 is the ODR fix, dev 3 is upstream's verbatim linkage-gap fix. // Once a >2.3.1 release ships this block, switch `sources` to // `*/include/boost/ut.cppm` and drop `generated_files`; these lines come // back with the verbatim upstream cppm. From d1a35eb630cc3be620db57d0f92c12c995d37943 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 3 Aug 2026 19:09:38 +0800 Subject: [PATCH 23/26] chore(ci): drop the temporary macOS crash-diagnostic workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diag-ut-macos.yml was a one-shot forensics harness for the boost-ext.ut macOS SIGSEGV — lldb disassembly, __init_offsets symbolization, a dozen relink variants — triggered on push to this branch. The crash is diagnosed (mcpp-community/mcpp#336, fixed in mcpp 2026.8.3.1), so the harness has done its job. On main it would never fire and only burn macos-15 minutes on manual dispatch. Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/diag-ut-macos.yml | 155 ---------------------------- 1 file changed, 155 deletions(-) delete mode 100644 .github/workflows/diag-ut-macos.yml diff --git a/.github/workflows/diag-ut-macos.yml b/.github/workflows/diag-ut-macos.yml deleted file mode 100644 index ef03d2b..0000000 --- a/.github/workflows/diag-ut-macos.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: diag-ut-macos - -on: - push: - branches: [add-boost-ext-ut] - workflow_dispatch: - -jobs: - diag: - runs-on: macos-15 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Download mcpp 0.0.109 - shell: bash - env: - MCPP_ARCHIVE: mcpp-0.0.109-macosx-arm64.tar.gz - MCPP_ROOT: mcpp-0.0.109-macosx-arm64 - run: | - curl -L -fsS -o "$MCPP_ARCHIVE" \ - "https://github.com/mcpp-community/mcpp/releases/download/v0.0.109/${MCPP_ARCHIVE}" - tar -xzf "$MCPP_ARCHIVE" - root="$PWD/$MCPP_ROOT" - mkdir -p "$HOME/.mcpp/registry" - cp -a "$root/registry/." "$HOME/.mcpp/registry/" - echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" - echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" - echo "$root/bin" >> "$GITHUB_PATH" - - name: mcpp test (collect crash) - shell: bash - env: - MCPP_INDEX_MIRROR: GLOBAL - run: | - "$MCPP" --version - "$MCPP" test -p boost-ext.ut || true - bin=$(find tests/examples/boost-ext.ut/target -name 'ut' -type f | head -1) - echo "BIN=$bin" - echo "=== init_offsets entries (raw) ===" - otool -s __TEXT __init_offsets "$bin" 2>/dev/null || true - echo "=== symbolize __init_offsets (ASLR off) ===" - offs=$(otool -s __TEXT __init_offsets "$bin" 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') - printf 'settings set target.disable-aslr true\nrun\n' > /tmp/x.lldb - for off in $offs; do - addr=$((16#$off)) - printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/x.lldb - done - lldb -b -s /tmp/x.lldb -- "$bin" 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true - - name: relink matrix + guard test - shell: bash - env: - MCPP_INDEX_MIRROR: GLOBAL - run: | - clang=$(python3 -c "import json;print(json.load(open('tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'][0])") - pkgroot=$(dirname $(dirname $clang)) - d=$(ls -d "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/target/aarch64-macos"/*/ | head -1) - cd "$d" - C="$clang -nostdinc++ -isystem $pkgroot/include/c++/v1 --sysroot=$(xcrun --show-sdk-path) -mmacosx-version-min=14.0" - stdpcm=$(ls pcm.cache/std.pcm 2>/dev/null) - cppm=$(find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut" -path '*generated*/boost.ut.cppm' | head -1) - echo "CPPM=$cppm STD=$stdpcm" - runit() { if ./"$1" >/tmp/out.txt 2>&1; then echo "$2 exit: 0 :: $(head -1 /tmp/out.txt)"; else echo "$2 exit: $? :: $(head -1 /tmp/out.txt)"; fi; } - $C -o ut_f obj/ut.o obj/boost.ut.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true - runit ut_f "F(static plain)" - $C -o ut_g obj/ut.o obj/boost.ut.m.o -Wl,-no_dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true - runit ut_g "G(no_dead_strip)" - $C -o ut_d obj/ut.o obj/boost.ut.m.o -Wl,-dead_strip "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true - runit ut_d "D(dead_strip explicit)" - $C -o ut_m obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi 2>&1 | tail -2 || true - otool -L ut_m 2>/dev/null | grep -i c++ || true - runit ut_m "M(xpkg dylib)" - tmp=$(mktemp -d) - (cd "$tmp" && ar -t "$pkgroot/lib/libc++.a" | grep -i iostream && ar -x "$pkgroot/lib/libc++.a" $(ar -t "$pkgroot/lib/libc++.a" | grep -i iostream) 2>/dev/null) || true - ls "$tmp" || true - $C -o ut_i obj/ut.o obj/boost.ut.m.o "$tmp"/*.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -2 || true - runit ut_i "I(force iostream.o)" - echo "=== K2: cppm GMF-guard (external linkage, before export module), static link ===" - inc=$(python3 -c "import json;args=json.load(open('$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/compile_commands.json'))[0]['arguments'];print(' '.join(a for a in args if a.startswith('-I')))") - echo "INC=$inc" - cp "$cppm" /tmp/guarded2.cppm - python3 -c "p='/tmp/guarded2.cppm'; s=open(p).read(); g='\n// GMF guard: force libc++ stream construction before ut cfg (static-libc++ init-order workaround)\nstd::ios_base::Init ut_ensure_std_streams_ready_global;\n'; m='export module boost.ut;'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('gmf guard injected')" - $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded2.cppm -o obj/guarded2.m.o 2>&1 | tail -10 || true - $C -o ut_k2 obj/ut.o obj/guarded2.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true - runit ut_k2 "K2(GMF guard static plain)" - $C -o ut_k3 obj/ut.o obj/guarded2.m.o 2>&1 | tail -5 || true - runit ut_k3 "K3(GMF guard dylib)" - echo "=== K2 init_offsets symbolization ===" - offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') - printf 'settings set target.disable-aslr true\nrun\n' > /tmp/y.lldb - for off in $offs; do addr=$((16#$off)); printf 'image lookup -a 0x%x\n' $((0x100000000 + addr)) >> /tmp/y.lldb; done - lldb -b -s /tmp/y.lldb -- ./ut_k2 2>&1 | grep -E "image lookup -a|Summary:|Function:" | head -80 || true - echo "=== K2 guard init (.97) disasm ===" - printf 'settings set target.disable-aslr true\nrun\n' > /tmp/w.lldb - printf 'image lookup -a 0x%x\ndisassemble -s 0x%x -c 30\n' 0x0 0x0 >> /tmp/w.lldb - offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') - i=0 - for off in $offs; do - i=$((i+1)) - if [ $i -eq 3 ]; then printf 'disassemble -s 0x%x -c 30\n' $((0x100000000 + 16#$off)) >> /tmp/w.lldb; fi - done - lldb -b -s /tmp/w.lldb -- ./ut_k2 2>&1 | grep -E "ut_k2\`|bl |0x100" | head -40 || true - echo "=== S: __attribute__((constructor)) fn in GMF, static link ===" - cp "$cppm" /tmp/guarded3.cppm - python3 -c "p='/tmp/guarded3.cppm'; s=open(p).read(); g='\n// GMF ctor: force libc++ stream construction before ut cfg\n__attribute__((constructor)) static void ut_force_ios_init() { std::ios_base::Init i; }\n'; m='export module boost.ut;'; assert m in s; open(p,'w').write(s.replace(m, g+m)); print('ctor injected')" - $C -std=c++23 -fmodule-file=std=$stdpcm -fmodule-file=std.compat=pcm.cache/std.compat.pcm -fprebuilt-module-path=pcm.cache -O0 -g --no-default-config -Wno-template-body $inc -c /tmp/guarded3.cppm -o obj/guarded3.m.o 2>&1 | tail -6 || true - $C -o ut_s obj/ut.o obj/guarded3.m.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -4 || true - runit ut_s "S(GMF ctor static)" - echo "=== STREAMTEST: plain TU + ios_base::Init guard, static libc++ ===" - printf '#include \nstatic std::ios_base::Init g_ios;\nint main() { return std::cout.rdbuf() != nullptr ? 0 : 1; }\n' > /tmp/streamtest.cpp - $C -O0 -g -c /tmp/streamtest.cpp -o /tmp/streamtest.o 2>&1 | tail -3 || true - $C -o /tmp/streamtest /tmp/streamtest.o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" 2>&1 | tail -3 || true - if /tmp/streamtest >/tmp/st.out 2>&1; then echo "streamtest exit: 0 (guard WORKS in plain TU)"; else echo "streamtest exit: $? (guard FAILS in plain TU)"; fi - echo " streamtest DoIOSInit present?"; nm -nm /tmp/streamtest 2>/dev/null | grep -iE "DoIOS|start_std" || echo " (NOT PRESENT)" - echo "=== .97 disasm (fixed) ===" - offs=$(otool -s __TEXT __init_offsets ut_k2 2>/dev/null | awk 'NR>2 {for(i=1;i<=NF;i++) if ($i ~ /^[0-9a-f]{8}$/) print $i}') - printf 'settings set target.disable-aslr true\nrun\n' > /tmp/w.lldb - i=0 - for off in $offs; do - i=$((i+1)) - if [ $i -eq 3 ]; then printf 'disassemble -s 0x%x -c 25\n' $((0x100000000 + 16#$off)) >> /tmp/w.lldb; fi - done - lldb -b -s /tmp/w.lldb -- ./ut_k2 2>&1 | sed -n '/disassemble/,$p' | head -35 || true - echo "=== mcpp link command (verbose full) ===" - "$MCPP" test -p boost-ext.ut -v > /tmp/mcpp_v.log 2>&1 || true - grep -nE "clang\+\+.*(-o|\.o)|\.a |libc\+\+|static" /tmp/mcpp_v.log | head -20 || echo "(no link line found)" - echo "--- full tail of verbose log ---" - tail -30 /tmp/mcpp_v.log || true - echo "=== mcpp build artifacts ===" - find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -maxdepth 3 -name "*link*" -o -maxdepth 3 -name "*build*" -o -maxdepth 3 -name "*.ninja" 2>/dev/null | head -10 || true - echo "=== ninja/build log for link cmd ===" - find "$GITHUB_WORKSPACE/tests/examples/boost-ext.ut/.mcpp" -name "*.log" 2>/dev/null | head -5 || true - echo "=== M3: system dylib ONLY via mcpp-style link (sanity: E variant) ===" - $C -o ut_e2 obj/ut.o obj/boost.ut.m.o 2>&1 | tail -3 || true - runit ut_e2 "E2(dylib only)" - echo "=== O: libc++.a BEFORE user objects ===" - $C -o ut_o "$pkgroot/lib/libc++.a" "$pkgroot/lib/libc++abi.a" obj/ut.o obj/boost.ut.m.o 2>&1 | tail -5 || true - runit ut_o "O(archive first)" - echo "=== P: force_load libc++.a ===" - $C -o ut_p obj/ut.o obj/boost.ut.m.o -Wl,-force_load,"$pkgroot/lib/libc++.a" -Wl,-force_load,"$pkgroot/lib/libc++abi.a" 2>&1 | tail -5 || true - runit ut_p "P(force_load)" - echo "=== M2: xpkg dylib + rpath ===" - $C -o ut_m2 obj/ut.o obj/boost.ut.m.o -L"$pkgroot/lib" -lc++ -lc++abi -Wl,-rpath,"$pkgroot/lib" 2>&1 | tail -5 || true - runit ut_m2 "M2(xpkg dylib+rpath)" - echo "=== K backtrace + guard disasm ===" - printf 'settings set target.disable-aslr true\nrun\nbt\nregister read pc x0 x1\n' > /tmp/z.lldb - lldb -b -s /tmp/z.lldb -- ./ut_k 2>&1 | tail -45 || true - echo "=== K guard init disasm (0x100020534 = first guarded.cppm init) ===" - printf 'settings set target.disable-aslr true\nrun\ndisassemble -s 0x100020534 -c 25\n' > /tmp/w.lldb - lldb -b -s /tmp/w.lldb -- ./ut_k 2>&1 | tail -40 || true - echo "=== O runtime init order ===" - DYLD_PRINT_INITIALIZERS=1 ./ut_o 2>&1 | grep -v "^dyld\[" | head -8 || true - DYLD_PRINT_INITIALIZERS=1 ./ut_o 2>&1 | grep "^dyld\[" | head -40 || true - echo "=== O abort backtrace ===" - printf 'settings set target.disable-aslr true\nrun\nbt all\n' > /tmp/v.lldb - lldb -b -s /tmp/v.lldb -- ./ut_o 2>&1 | tail -50 || true From 7d2f21d2afbe1b6455f25613c5f5f09405d55d79 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 3 Aug 2026 19:09:56 +0800 Subject: [PATCH 24/26] chore(ci): pin mcpp 2026.8.3.3 + move the index floor; bypass the global build cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## The pin macOS: a global object that touches std::cout during static init crashes on sight. Mach-O has no priority-ordered init section and libc++'s carries no ios_base::Init guard of its own, so the streams are still all-zero when an archive member's initializer runs. That is mcpp-community/mcpp#336, fixed in mcpp 2026.8.3.1 by linking a generated object FIRST whose constructor brings them up. boost-ext.ut trips exactly this: ut.hpp:1620 member-initializes `std::streambuf* cout_save = std::cout.rdbuf();` inside the statically constructed `cfg::runner>`. It is not fixable package-side — std::ios_base::Init is only forward-declared in libc++'s — which this branch established the hard way: dropping `export import std;` (3eff289) and force-constructing ios_base::Init in the package (fbf1ad0) both still exit 139 on 0.0.109. The pin is what turns macOS green. index.toml's min_mcpp/latest_mcpp move with the pin as they always have (#125 moved 0.0.108 -> 0.0.109). Here they especially should: a consumer below the floor gets a segfault with no diagnostic, which is worse than E0006. 2026.8.3.3 rather than .3.1 — .3.2/.3.3 are cross-compilation fixes (PE artifact naming, -static decided by host instead of target) that no leg of this matrix exercises, so taking the newest of the train costs nothing. ## MCPP_BUILD_CACHE: local mcpp >= 2026.7.30.2 added a global package build cache. It is unusable here until mcpp-community/mcpp#344 lands: mcpp#233's object-path disambiguation fires on basename collisions across the WHOLE build dir — i.e. on which packages the CONSUMER pulls in — while the cache key deliberately covers only the dependency itself. So one entry can hold two different layouts. tests/examples/archive pulls zlib AND bzip2 (both ship compress.c), disambiguates, and stores obj/compat_zlib/zlib-1.3.2/compress.o; every zlib consumer without bzip2 then asks the same key for a flat obj/compress.o and ninja dies at graph time with "missing and no known rule to make it". Reproduced both ways round on 2026.8.3.3 — whichever member runs second fails, in the mirror-image direction. That is what took 13/47 (linux), 11/47 (macos) and 8/47 (windows) members down on this branch's first full-workspace run: eui-neo*, opencv-module*, freetype, libpng, sdl2, vulkan — all zlib/ffmpeg consumers with a package set different from whichever member primed the cache. `local` still caches the std BMI, the expensive one; only package entries are bypassed. Drop the env once mcpp#344 is fixed. Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/validate.yml | 42 ++++++++++++++++++++++++++-------- index.toml | 4 ++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 229f5cc..79ac841 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,12 +14,19 @@ on: workflow_dispatch: env: - # 2026.8.3.1: fixes the macOS static-init-order crash of module packages - # that construct std streams (mcpp#336) — mcpp links a generated object - # first whose constructor forces libc++'s streams up, so boost-ext.ut's - # macOS leg needs no package-side workaround. This pin is a CI-only move: - # index.toml's `min_mcpp` is a *syntax* contract and is NOT bumped for a - # behavior fix (it would E0006-gate every older-mcpp consumer of the index). + # 2026.8.3.1: on macOS, a global object that touches std::cout during static + # init crashes on sight (mcpp#336). Mach-O has no priority-ordered init + # section and libc++'s carries no ios_base::Init guard of its own, + # so the streams are still all-zero when an archive member's initializer + # runs. mcpp now links a generated object FIRST whose constructor brings them + # up. It is not fixable package-side — std::ios_base::Init is only + # forward-declared in libc++'s — so boost-ext.ut genuinely requires + # this floor on macOS, and min_mcpp/latest_mcpp move with the pin as they + # always have (a consumer below the floor would get a segfault with no + # diagnostic, which is worse than E0006). + # 2026.8.3.3 is the pin rather than .3.1: .3.2/.3.3 are cross-compilation + # fixes (PE artifact naming, -static host-vs-target) that no leg of this + # matrix exercises, so taking the newest of the train costs nothing. # 0.0.109: a bare dependency's wire address takes BOTH halves from the # descriptor the identity gate accepted (mcpp#286). This is the client-side # other half of the SPEC-001 migration below: mcpp used to take the NAME from @@ -81,7 +88,7 @@ env: # 0.0.94 fixed feature-gated `sources` under `mcpp test` (mcpp#218); 0.0.91 # added standard = "c++fly" to the resolver grammar, so c++fly descriptors # get the lint WARN below, not a hard grammar-parse rejection. - MCPP_VERSION: "2026.8.3.1" + MCPP_VERSION: "2026.8.3.3" jobs: lint: @@ -246,21 +253,21 @@ jobs: ext: tar.gz mcpp: bin/mcpp xlings: registry/bin/xlings - mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.3" # keep in sync with env.MCPP_VERSION - platform: macos os: macos-15 suffix: macosx-arm64 ext: tar.gz mcpp: bin/mcpp xlings: registry/bin/xlings - mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.3" # keep in sync with env.MCPP_VERSION - platform: windows os: windows-latest suffix: windows-x86_64 ext: zip mcpp: bin/mcpp.exe xlings: registry/bin/xlings.exe - mcpp_version: "2026.8.3.1" # keep in sync with env.MCPP_VERSION + mcpp_version: "2026.8.3.3" # keep in sync with env.MCPP_VERSION env: MCPP_EFFECTIVE: ${{ matrix.mcpp_version }} steps: @@ -406,6 +413,21 @@ jobs: shell: bash env: MCPP_INDEX_MIRROR: GLOBAL + # Dependencies build inside each member's own target/ instead of + # through the global package build cache (mcpp >= 2026.7.30.2). + # That cache is unusable here: mcpp#233's object-path disambiguation + # fires on basename collisions across the WHOLE build dir — i.e. on + # which packages the CONSUMER pulls in — while the cache key covers + # only the dependency itself, so one entry can hold two different + # layouts. `tests/examples/archive` pulls zlib AND bzip2 (both ship + # compress.c) and stores obj/compat_zlib/zlib-1.3.2/compress.o; + # every zlib consumer without bzip2 then asks the same key for a + # flat obj/compress.o and ninja dies at graph time with + # "missing and no known rule to make it". Reproduced both ways round + # on 2026.8.3.3 and filed as mcpp-community/mcpp#344; drop this once + # it lands. `local` still caches the std BMI, which is the expensive + # one — only package entries are bypassed. + MCPP_BUILD_CACHE: local run: | "$MCPP" --version # No `timeout` wrapper: absent on macOS runners; job-level timeout-minutes bounds it. diff --git a/index.toml b/index.toml index 2915961..2793f7f 100644 --- a/index.toml +++ b/index.toml @@ -7,5 +7,5 @@ # "floor first, new grammar after" rollout rule mechanically. [index] spec = "1" -min_mcpp = "0.0.109" -latest_mcpp = "0.0.109" +min_mcpp = "2026.8.3.3" +latest_mcpp = "2026.8.3.3" From 8ee73642b9490670bb54405d582a93c59df724d8 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 3 Aug 2026 19:10:21 +0800 Subject: [PATCH 25/26] fix(boost-ext.ut): use upstream's ut.cppm verbatim but for the Clang-on-MSVC shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper had grown three deviations from upstream v2.3.1's include/boost/ut.cppm. Two of them turned out to be self-inflicted, and this reduces the file to upstream's own bytes plus exactly one shim. ## Dropped: the `export import std;` removal + 24 hand-written GMF includes Introduced as the fix for the macOS SIGSEGV, on the theory that `export import std;` gave the module its own copy of the std stream entities, ODR-split from libc++'s. The branch's own CI refutes it: at 3eff289, with `export import std;` already gone, macOS still reported `ut ... FAIL (exit 139)` on 0.0.109. The real cause is toolchain-side (mcpp#336) and is addressed by the pin. Its cost was real, though: without `export import std;` GCC 16.1 rejected ut.hpp with a cascade of -Wtemplate-body errors (unqualified `size_t`, `std::empty`, the literals using-block), which is what forced `using std::size_t;` and `cxxflags = { "-Wno-template-body" }`. Those existed only to repair the damage. Verified cold on linux/gcc 16.1.0, on mcpp 2026.8.3.3 AND 0.0.109: with upstream's `export import std;` kept, the module compiles and the member passes with no cxxflags at all — the compile line carries neither -Wno-template-body nor -w. ## Dropped: the post-v2.3.1 explicit-template-instantiation block Lifted from upstream master and carried as a second macOS candidate fix. It was already present at 61b9441 while macOS still exited 139, so it does not address anything this index hits, and it ships in no release tag. Out of the trust path. ## Kept: the __argc / __argv shim ut.hpp:687 is `#if defined(_MSC_VER)` and reaches for the MSVC builtins __argc / __argv. Clang on the MSVC ABI sets _MSC_VER but does not provide them; upstream's neighbouring guards at 291 / 311 / 1147 already gate clang out and 687 missed it. This index's Windows default toolchain IS Clang on the MSVC ABI (llvm@20.1.7, *-pc-windows-msvc — not mingw, not cl.exe), so the shim is load-bearing. Double-gated on `_MSC_VER && __clang__`: real MSVC never enters it, no non-Windows target ever sees it, and cfg::largc/largv are reassigned from main()'s argv at runtime so the stand-in values are never read. ## Docs README (both languages) claimed the cppm was "used verbatim, no generated_files" while the descriptor did the opposite — corrected to what the file now actually is. The design doc is rewritten around the evidence, including a section on the three refuted approaches so the next reader does not retry them. Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- .../docs/2026-08-02-add-boost-ext-ut-plan.md | 265 ++++++++---------- README.md | 2 +- README.zh-CN.md | 2 +- pkgs/b/boost-ext.ut.lua | 251 +++++------------ 4 files changed, 192 insertions(+), 328 deletions(-) diff --git a/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md index 0c592ed..59199b8 100644 --- a/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md +++ b/.agents/docs/2026-08-02-add-boost-ext-ut-plan.md @@ -1,169 +1,144 @@ # 收录 [Boost::ext].UT(boost-ext.ut)2.3.1 -> 日期:2026-08-02 -> 分支:`add-boost-ext-ut`(本地提交,未推送、未发 PR) +> 日期:2026-08-02(2026-08-03 依 CI 与本地实测结论重写) +> 分支:`add-boost-ext-ut` / PR #142 > 仓库:[boost-ext/ut](https://github.com/boost-ext/ut) -> 背景:用户要求将 boost-ext 组织(注意:不是 boost 官方)的 UT 单头测试框架收录进 mcpp-index, -> 自带 C++ 模块支持,模块单元位于 `include/boost/ut.cppm`,声明 `export module boost.ut;`。 +> 背景:把 boost-ext 组织(**不是** boost 官方)的 UT 单头测试框架收录进 mcpp-index。 +> 上游自带模块单元 `include/boost/ut.cppm`,宣告 `export module boost.ut;`。 ## 1. 来源与形态判定 -- 来源:**(a) 第三方上游库**。上游 boost-ext/ut 不提供 mcpp 支持,但其 release tarball **自带**官方模块单元 - `include/boost/ut.cppm`(`export module boost.ut;`),因此本仓不需自行从零合成 module wrapper, - 只需对上游 cppm 做最小兼容补丁(见 §2)。 +- 来源:**(a) 第三方上游库**。上游不提供 mcpp 支持,但 release tarball **自带**官方模块单元。 - 版本:`v2.3.1`(截至 2026-08-02 的最新 release tag)。 -- License:Boost Software License 1.0,SPDX 标识 `BSL-1.0`。 -- 布局:tarball 顶层 wrap 目录为 `ut-2.3.1/`,模块单元位于 `include/boost/ut.cppm`, - 头文件 `include/boost/ut.hpp`(3378 行,定义 `namespace boost::inline ext::ut::inline v2_3_1`)。 -- 形态:**C++23 module(generated wrapper)** —— 因上游 cppm 无法逐字编译(见 §2),采用 - `generated_files` 内嵌一份带两处最小 shim 的等价物,与 `marzer.tomlplusplus` 同类。 +- License:Boost Software License 1.0,SPDX `BSL-1.0`。 +- 布局:tarball 顶层 wrap 目录 `ut-2.3.1/`,模块单元 `include/boost/ut.cppm`, + 头文件 `include/boost/ut.hpp`(3378 行,`namespace boost::inline ext::ut::inline v2_3_1`)。 +- 形态:**C++23 module(generated wrapper)**,与 `nlohmann.json` / `marzer.tomlplusplus` 同类。 + 但与它们不同的是,本包的 wrapper 是上游 cppm 的**逐字复制**,只加一处 shim(见 §2)。 -判据:逐字复用上游 cppm 的尝试在 mcpp 0.0.109 默认 toolchain 下均失败,详见 §2。 +身份:`namespace = "boost-ext"`、`name = "ut"`。**不是** `boost`、**不是** `compat` —— +该项目并非 boost 官方库,不应占用 `compat` 习惯位置,也不应进 boost 命名空间。 +mcpp 命名空间用 `-` 分层,避免与 `.` 点号寻址约定碰撞。 -身份:`namespace = "boost-ext"`、`name = "ut"`。**NOT** `boost`、**NOT** `compat`: -用户明确指出该项目并非 boost 官方库,因此不能占用 `compat` 习惯位置,也不能放进 boost 命名空间, -更不能与 boost::ext 这一第三方组织混同于 mcpplibs 的既有命名空间。`boost-ext` 这一 mcpp 命名空间 -用 `-` 分层,避免与 mcpp 既有 `.` 点号寻址约定碰撞(与 `nlohmann.json` 走点号、 -`marzer.tomlplusplus` 走点号属同一道理下的反向取舍)。 +## 2. 唯一的偏离:Clang-on-MSVC 的 `__argc` / `__argv` -## 2. 关键注意事项:上游 ut.cppm 不能逐字内嵌 - -与 `nlohmann.json`(可 VERBATIM 内嵌)与 `marzer.tomlplusplus`(删一行后 VERBATIM 内嵌)不同, -上游 `ut-2.3.1/include/boost/ut.cppm` 在 mcpp 0.0.109 的三条 CI 平台 default toolchain 上 -**均无法直接使用**: - -### 2.1 GCC 16.1(`-std=c++23`):-Wtemplate-body 拒绝无修饰 `size_t` +`generated_files` 内嵌的 cppm 在上游 v2.3.1 `ut.cppm` 之上**只加一处**,不删任何一行, +连 `export import std;` 都原样保留: ``` -ut.hpp:1916:5: error: 'size_t' was not declared in this scope; - did you mean 'std::size_t'? [-Wtemplate-body] - 1916 | size_t n_tests = 0; +ut.hpp:688:29: error: use of undeclared identifier '__argc'; did you mean 'largc'? +ut.hpp:689:63: error: use of undeclared identifier '__argv'; did you mean 'largv'? ``` -根因:ut.cppm 把 `#include "ut.hpp"` 放在 purview 内,只做了 `export import std;`。 -在 named-module purview 中,`size_t` 并不通过 `export import std;` 进入作用域 -(只有 `std::size_t` 进入)。ut.hpp 在多处用无修饰 `size_t`(行 1916/1917/1936/1937、 -reporter_junit 模板体内),非模块编译时这条由前置 `` 等头链间接带入,模块下则显式缺失。 +`ut.hpp:687` 是 `#if defined(_MSC_VER)`,引用 MSVC 内建 `__argc` / `__argv`。 +Clang 在 MSVC ABI 上设了 `_MSC_VER` 但不提供这两个内建 —— 上游相邻分支(行 291 / 311 / 1147) +已用 `&& !defined(__clang__)` 排除 clang,行 687 漏了同一守卫。属上游的 clang-on-windows 适配缺口。 -### 2.2 Clang 22.1(MSVC ABI / x86_64-windows-msvc):`__argc`/`__argv` 未声明 +这一条对本仓是必需的,因为本索引的 **Windows 默认工具链就是 LLVM/Clang + MSVC ABI** +(`.agents/docs/2026-07-19-full-platform-support-plan.md`:`llvm@20.1.7`,NOT mingw,NOT cl.exe), +CI 日志里也是 `Resolved llvm@20.1.7 → …/clang++.exe`。shim 用 `_MSC_VER && __clang__` 双重门控, +真 MSVC 永远不进入;`cfg::largc` / `cfg::largv` 在运行期由 `main()` 的 argv 重新赋值,替身值不会被读。 -``` -ut.hpp:688:29: error: use of undeclared identifier '__argc'; did you mean 'largc'? - 688 | static inline int largc = __argc; -ut.hpp:689:63: error: use of undeclared identifier '__argv'; did you mean 'largv'? -``` +## 3. 曾经走过的弯路(留档,避免重走) -根因:ut.hpp 行 687 `#if defined(_MSC_VER)` 引用 MSVC 内建 `__argc`/`__argv`。Clang 在 MSVC ABI -上设了 `_MSC_VER`,但不提供这两个内建 —— 上游相邻分支(行 291 / 311 / 1147)已通过 -`&& !defined(__clang__)` 排除 clang,行 687 漏了同一守卫。属上游 clang-on-windows 模块适配缺口。 +PR #142 的前 22 个 commit 里做过三处后来被**证伪并回退**的改动。记在这里,是因为它们的 +"理由"看上去都很有说服力: -### 2.3 Clang 20.1.7(macOS CI 自装的 default,macos-15):运行期 SIGSEGV(exit 139) +### 3.1 删 `export import std;` + 手写 24 个 GMF `#include` -verbatim ut.cppm 在 macOS CI 上**能编译**,但 `mcpp test` 运行测试二进制立刻 exit 139 -(SIGSEGV),`bin/ut` 输出全无,无 `Suite '...'` 字样。根因: +**动机**:macOS 上 verbatim 模块在静态初始化期 SIGSEGV,lldb 停在 `ut.hpp:1620` 的 +`std::cout.rdbuf()`,address `-0x18` 说明 `std::cout` 的 vptr 为零。当时判断是 +`export import std;` 让模块持有自己那份 std 流实体,与 libc++ 库里的那份 ODR 分裂。 -```text -$ Running bin/ut -ut ... FAIL (exit 139) -``` +**证伪**:commit `3eff289`(已删 `export import std;`)在 mcpp 0.0.109 上,macOS 仍然 +`ut ... FAIL (exit 139)`。后续的 `ios_base::Init` 强制构造(`fbf1ad0`)同样失败。 +macOS 唯一转绿的变更是把 mcpp pin 提到 2026.8.3.1。 + +**真因**:mcpp-community/mcpp#336 —— Mach-O 没有按优先级排序的初始化段,libc++ 的 +`` 也不像 libstdc++ / MSVC STL 那样自带 `ios_base::Init` 守卫,于是归档成员的 +初始化器跑到时流还是全零。**包侧无法修复**:`std::ios_base::Init` 在 libc++ 的 `` 里 +只有前向声明。mcpp 2026.8.3.1 起生成一个极小 C 翻译单元排在链接行最前,由它先把流顶起来。 + +**副作用**:删掉 `export import std;` 之后,GCC 会爆出一连串 `-Wtemplate-body` 错误 +(无修饰 `size_t`、`std::empty`、literal using 块),于是又被迫补 `using std::size_t;` +和 `cxxflags = { "-Wno-template-body" }`。**这些全部是自造的问题** —— 保留上游原样就没有。 +本地实测(mcpp 2026.8.3.3 **与** 0.0.109,linux / gcc 16.1.0,冷跑):上游 cppm 逐字、 +不加任何 `cxxflags`,`mcpp test -p boost-ext.ut` 通过,编译行里确认没有 `-Wno-template-body`。 + +### 3.2 追加上游 `master` 的显式模板实例化块 + +**动机**:当作 macOS SIGSEGV 的修复引进。 + +**证伪**:它在 `61b9441` 就已就位,macOS 仍 exit 139。它是上游为某个 Clang module linkage +gap 加的,且**不在任何 release tag 里**。既然不解决本仓遇到的问题,就不进信任路径。 + +### 3.3 `.github/workflows/diag-ut-macos.yml` + +排 macOS 崩溃时的一次性诊断 workflow(lldb 反汇编 / `__init_offsets` 符号化 / relink 矩阵), +`on: push: branches: [add-boost-ext-ut]`。问题定位后已删除。 -`cfg::runner>` 静态对象的 `reporter_junit` 成员默认初始化器 -`active_scope_ = &results_[active_suite_]` 触发 `std::unordered_map::operator[]("global")`,该条路径在 `-fmodules` 下顺道访问的几个 -`reporter_junit<>::on<...>` / `test::operator=<>` / `expect` 模板成员 -在 v2.3.1 ut.cppm 中只有*隐式实例化*路径 —— 缺乏把模板代码从 module 接口单元挤到 -消费方 .o 的导出声明,Apple-Clang 20.1.7 在该路径上的 module BMI 未把这些成员的 -定义带进可执行文件,首次静态初始化的 dispatch 落入未实例化 slot → 段错误。 - -### 2.4 解法:generated_files 内嵌 + 三处最小偏离 - -`generated_files` 内嵌的 cppm 在 v2.3.1 ut.cppm 字面之上 **追加/调整** 三处,无任何行被删除: - -| 偏离 | 作用 | 守卫/来源 | -|---|---|---| -| **dev 1**:v2.3.1 ut.cppm body 逐字保留 | `#include "ut.hpp"` | 全部 | -| **dev 2a**:purview 顶层 `using std::size_t;` | 修复 GCC `-Wtemplate-body` 无修饰 `size_t` | 无条件 | -| **dev 2b**:`#define __argc 0` / `#define __argv nullptr` | 修复 Clang-on-Windows 内建缺失 | `_MSC_VER && __clang__`(MSVC 自身不进入) | -| **dev 3**:文件末尾追加 explicit template instantiation 块 | 修复 §2.3 macOS Clang 20.1.7 SIGSEGV | 8 条 `template ...` 声明,**逐字取自上游 master 的 ut.cppm**(尚未进 v2.3.1;见 §演进) | - -要点: - -- 三处偏离都**加**而**不删**,除 dev 2 的两条 `#define`/`undef` 是对称宏外, - 其它均为新增内容,不动 ut.hpp、不动 ut.cppm 既有字节。 -- dev 3 的 8 行 explicit-template-instantiation **逐字**来自上游 `master` 分支当前的 - `include/boost/ut.cppm` 文件末尾(由用户在 review 时提供并指向),仅为做 macOS - module-linkage gap 的 forward-port —— 与本仓 `marzer.tomlplusplus` 把 master 的 - cppm 删一行后内嵌属同一"最小 forward-port 自 master"形态,信任链上只多引一段 - 上游自己写的明示实例化。 -- 演进条件:一旦 ut 出 >2.3.1 release 且 release tarball 的 `include/boost/ut.cppm` - 自带 dev 3 那块实例化,把 `sources` 切回 `*/include/boost/ut.cppm`、 - 删 `generated_files` 即可(只保留两个 dev-2 shim 时会顺带回落到 generated wrapper; - 若 >2.3.1 release 还顺带修了 §2.1/§2.2,`generated_files` 整块可移除)。 -- `export import std;` **保留**:试过删它会在 GCC 上引爆一连串 `-Wtemplate-body` - error(`std::empty` 在 `gherkin::steps::next`、`utility::match` 在同一处、 - `literals::operator""_test` 等 literal `using` 块在 ut.hpp:3311-3360)。 - 显式追加 ``/`` 等 `#include` 也不能消完, - 根因实为 GCC 在 module purview 下的两阶段名字查找对未通过 `import std` 进 purview - 的 stdlib 适配较保守。原作者的 `export import std;` 是当前唯一干净写法,予以保留。 - 其 transitive 行为(消费者 `import boost.ut;` 即可见 stdlib符号)与 `import_std = false` - 协调:后者仅避免 mcpp 在 wrapper TU 中再注入一次 `import std;`。 - -`include_dirs = { "*/include/boost" }` 让 wrapper 内的 `#include "ut.hpp"` 解析到 verdir 下的 -实际 ut.hpp;同时消费者若直接 `#include ` 也能解析(与 nlohmann/marzer 同形式)。 -generated cppm 路径 `mcpp_generated/boost.ut.cppm` 为 verdir 相对(无 glob),与既有同形态惯例一致。 - -## 3. 描述符 - -- 路径:`pkgs/b/boost-ext.ut.lua`(目录取**完整包名首字母** `b`)。 -- 命名:`boost-ext.ut`,避免占用 `compat` 习惯位置与 `boost` 官方命名空间。 -- 三平台 `xpm` 共用同一 url 与 sha256(GLOBAL-only;无 `mcpp-res` 写权限故走 plain-string url, - lint 允许,CN 用户回退至上游源由维护者后续补充)。 -- 版本号采用裸版本 `"2.3.1"`;URL 中保留上游 `…/v2.3.1.tar.gz` 拼写。 - -## 4. feature 评估 - -**不实现 feature**。ut 为纯头文件 + 模块单元,无"额外的可编译源码"可供门控。 -其可选行为(`BOOST_UT_CONFIG_DISABLE_EXCEPTIONS` 等)均为编译期 **define**, -而 `features` 表当前仅能门控 `sources`,无法携带 define。 -与 Eigen 的 `EIGEN_MPL2_ONLY`、toml++ 的 `TOML_EXCEPTIONS` 属同类限制, -待 mcpp 支持 define/cflags 后再评估。 - -## 5. CN 镜像 - -未配置。本地无 `mcpp-res` 写权限,lint 规则允许 plain-string 形式的 `url` -(见 `tests/check_mirror_urls.lua`:plain url 直接放行)。CN 镜像由维护者后续补充。 - -## 6. 验证结论 - -测试工程 `tests/examples/boost-ext.ut/`,已登记进根 `mcpp.toml` 的 `[workspace].members` -(本仓测试面为 `mcpp test -p ` / `mcpp test --workspace`)。断言覆盖 UDL 语法 -(`"..."_test` = lambda { ... })、函数语法(`test("...") = lambda { ... }`)与 -`expect()` 断言两路,直接由 [Boost::ext].UT 的 runner 输出判通过/失败: -`"Suite 'global': all tests passed (3 asserts in 2 tests)"`。 +## 4. mcpp pin 与 index.toml floor + +`MCPP_VERSION` 从 `0.0.109` 提到 `2026.8.3.3`,`index.toml` 的 `min_mcpp` / `latest_mcpp` 同步。 + +- 提 pin 是**必需**的:macOS 的崩溃修在 mcpp 侧(§3.1),包侧无解。 +- floor 同步跟随 pin 是本仓一贯做法(见 #125 把 0.0.108 → 0.0.109)。这里尤其应该同步: + 低于 floor 的 macOS 消费者装上本包会**直接段错误且无任何诊断**,比 E0006 更糟。 +- 取 `.3.3` 而非 `.3.1`:`.3.2` / `.3.3` 只有交叉编译相关修复(PE 产物命名、`-static` + 按主机而非目标判定),本 CI 矩阵不走那些路径,取最新一档零成本。 + +## 5. CI:`MCPP_BUILD_CACHE: local` + +mcpp >= 2026.7.30.2 引入了全局包构建缓存。它在本仓的全量 workspace 下有 bug, +必须先绕开(已上报 **mcpp-community/mcpp#344**): + +- mcpp#233 的 obj 路径消歧按**整个 build dir 内的 basename 冲突**触发,也就是按 + **消费方拉了哪些包**触发;而 cache key 只覆盖依赖包自身(v2026.7.30.2 的设计明确 + "不含 root 的身份与 flags")。 +- `tests/examples/archive` 同时拉 zlib 与 bzip2(两者都有 `compress.c`)→ 触发消歧, + 写进缓存的是 `obj/compat_zlib/zlib-1.3.2/compress.o`;而只拉 zlib 的消费者 + (`libpng` / `freetype` / `eui-neo*` / `sdl2` / `vulkan`)按同一个 key 去要平的 + `obj/compress.o` → `ninja: error: … missing and no known rule to make it`。 +- 本地在 2026.8.3.3 上**双向对称复现**(谁第二个跑谁挂),`--cache local` / + `MCPP_BUILD_CACHE=local` 可绕过,且 `local` 仍然缓存 std BMI(贵的那份),只跳过包条目。 + +mcpp#344 修好后删掉这个 env 即可。 + +## 6. feature 评估 + +**不实现 feature**。ut 为纯头文件 + 单个模块单元,无"额外的可编译源码"可门控。 +其可选行为(`BOOST_UT_CONFIG_*`)均为编译期 **define**,而 `features` 表当前仅能门控 `sources`。 +与 Eigen 的 `EIGEN_MPL2_ONLY`、toml++ 的 `TOML_EXCEPTIONS` 属同类限制。 + +## 7. CN 镜像 + +未配置。无 `mcpp-res` 写权限,lint 允许 plain-string `url`(`tests/check_mirror_urls.lua` +对字符串形式直接放行),CN 用户回退至上游源,镜像由维护者后续补充。 + +## 8. 测试工程与验证 + +`tests/examples/boost-ext.ut/`,已登记进根 `mcpp.toml` 的 `[workspace].members`(按字母序)。 +成员级 `[indices] boost-ext = { path = "../../.." }` 恰好一条 —— 它**替换**而非合并根级继承的 +`compat` 表,把项目级 index repo 保持在一个。 + +断言覆盖 UDL 语法(`"..."_test = []{}`)与函数语法(`test("...") = []{}`)两路,通过 +`expect()` 判定。测试 `main()` 返回 0 是安全的:ut 的 `~runner()`(ut.hpp:2032)在 +`fails_` 非零时 `std::exit(-1)`,静态析构期生效,所以断言失败会让 `mcpp test` 非零退出。 | 检查 | 结果 | |---|---| -| `mcpp xpkg parse pkgs/b/boost-ext.ut.lua`(本地 2026.8.2.1 与 CI pin 0.0.109) | ✅ parse OK | -| `mcpp test -p boost-ext.ut`(gcc 16.1.0,Windows x86_64-windows-gnu 默认 target,dev 1+2+3 后) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | -| `mcpp test -p boost-ext.ut`(llvm 22.1.8,Windows x86_64-windows-msvc,dev 1+2+3 后) | ✅ `all tests passed (3 asserts in 2 tests)` / `test result ok. 1 passed` | -| `workspace (linux)` / `workspace (windows)` CI(PR #142 第 1 次推,dev 1+2 仅有) | ✅ pass | -| `workspace (macos)` CI(PR #142 第 1 次推,dev 1+2 仅有,Clang 20.1.7) | ❌ SIGSEGV exit 139(§2.3)→ 推动 dev 3 | -| `tests/check_mirror_urls.lua`(手算:plain-string url 直接放行) | ✅ 通过 | -| `tests/check_package_name.lua`(手算:`name = "ut"` 单一原子段) | ✅ 通过 | -| 前导 v 版本号 lint(手算:`"2.2.3"` 裸版本) | ✅ 通过 | - -冷验证前已 `rm -rf tests/examples/boost-ext.ut/{target,.mcpp,mcpp.lock,compile_commands.json}`, -自干净状态走完"拉 tarball → 编译 wrapper → 编译/链接测试 → 运行"完整管线。 - -dev 3(explicit template instantiation 块逐字取自上游 master)为 PR #142 CI macOS leg 失败 -(exit 139)的直接修复,根因分析与最终选型见 §2.3/§2.4。修复后本地在 Windows 的两条 default -toolchain 上均仍通过,显示 dev 3 不引入回归;macOS CI 复检结果以 PR #142 第二次 CI 为准。 - -## 7. 其它 - -- 描述符注释里说明了与 `nlohmann.json`/`marzer.tomlplusplus` 的同与不同: - 上游 cppm 不能 VERBATIM 内嵌,但**无需**像 marzer 那样删去某一行, - shim 只**追加**两处兼容补丁(verbatim 部分 + 两条 shim),不删除上游 cppm 任何字节, - 也不替换任何行;`generated_files` payload 不含 `]==]`。 -- 提交策略:遵循用户指示,在新分支 `add-boost-ext-ut` 上 commit 一次,**不**推送、**不**发 PR/Issue。 - CI 自然不会运行;本设计文档与本地 `mcpp test` 输出作为等价证据保留。 \ No newline at end of file +| tarball sha256 二次核对 | ✅ `e51bf187…ea9b1` | +| `tests/check_package_name.lua` | ✅ pass | +| `tests/check_mirror_urls.lua` | ✅ pass | +| `tests/check_cross_package_refs.lua` | ✅ pass | +| 前导 v 版本号 lint | ✅ `"2.3.1"` 裸版本 | +| `mcpp test -p boost-ext.ut`(linux / gcc 16.1.0 / mcpp 2026.8.3.3,冷跑) | ✅ `all tests passed (3 asserts in 2 tests)` | +| macOS / Windows | 由 PR #142 的 CI 判定 | + +冷验证前均已 `rm -rf tests/examples/boost-ext.ut/{target,.mcpp,mcpp.lock,compile_commands.json}`, +自干净状态走完「拉 tarball → 编译 wrapper → 编译/链接测试 → 运行」完整管线。 + +## 9. 演进条件 + +一旦 ut 发出 >2.3.1 的 release,且其 ut.hpp 在 687 行补上 `&& !defined(__clang__)` 守卫, +`generated_files` 整块即可删除,`sources` 切回 `*/include/boost/ut.cppm` 直接用上游文件。 diff --git a/README.md b/README.md index 61ada3d..1f9f605 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Two kinds of packages live here: | Multiple majors in one package (shape switches with the version) | [`compat.catch2`](pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) | | External build system (`install()` builds from source) | [`compat.openblas`](pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | | Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua) (upstream already ships `include/boost/ut.cppm` declaring `export module boost.ut;` — used verbatim, no `generated_files`; namespace `boost-ext` since it is NOT an official Boost library) | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) | ### Adding a package diff --git a/README.zh-CN.md b/README.zh-CN.md index 965b85e..daba711 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -47,7 +47,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | | 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua)(上游已自带 `include/boost/ut.cppm`,宣告 `export module boost.ut;` —— 逐字复用,无需 `generated_files`;命名空间取 `boost-ext`,因其并非 boost 官方库) | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) | ### 新增一个包 diff --git a/pkgs/b/boost-ext.ut.lua b/pkgs/b/boost-ext.ut.lua index afbc29c..bf3e57b 100644 --- a/pkgs/b/boost-ext.ut.lua +++ b/pkgs/b/boost-ext.ut.lua @@ -4,112 +4,73 @@ -- `boost`). Exposed as the C++23 module `boost.ut` so users can write -- `import boost.ut;` out of the box. -- --- The upstream release tarball DOES ship an official module interface unit --- at `include/boost/ut.cppm` (`export module boost.ut;`), but it cannot be --- used VERBATIM on this index's three CI platforms: +-- The upstream release tarball ships an official module interface unit at +-- `include/boost/ut.cppm` (`export module boost.ut;`). This descriptor +-- reproduces that file through `generated_files` with EXACTLY ONE deviation — +-- everything else, including `export import std;`, is upstream's own bytes in +-- upstream's own order. -- --- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`: --- ut.hpp:1916:5: error: 'size_t' was not declared in this scope; --- did you mean 'std::size_t'? --- GCC's two-phase lookup in the module purview is strict: names used at --- namespace scope of templates must be visible where the template is --- DEFINED. ut.hpp uses unqualified `size_t` at namespace scope (lines --- 1916/1917/1936/1937) and relies on `std::empty`, `utility::match`, --- the literal `using` block, etc. — all of which only resolve if the --- standard library is fully visible in the purview. +-- The one deviation: Clang on the MSVC ABI (`*-pc-windows-msvc`, this index's +-- Windows default toolchain — llvm@20.1.7, NOT mingw, NOT cl.exe) rejects +-- upstream's file with +-- ut.hpp:688:29: error: use of undeclared identifier '__argc' +-- ut.hpp:689:63: error: use of undeclared identifier '__argv' +-- ut.hpp line 687 is `#if defined(_MSC_VER)` and reaches for the MSVC builtins +-- `__argc` / `__argv`. Clang DOES set `_MSC_VER` on that ABI but does NOT +-- provide those builtins — the neighbouring upstream guards at lines 291 / 311 +-- / 1147 already gate clang out, line 687 missed it. So the wrapper defines the +-- two names as macros around the `#include`, under `_MSC_VER && __clang__` +-- only: real MSVC never enters the guard and keeps its builtins, and no +-- non-Windows target ever sees the macros. `cfg::largc` / `cfg::largv` are +-- reassigned from `main()`'s argv at runtime, so the stand-in values are never +-- read. -- --- * Clang 22.1 on the MSVC ABI (Windows default target) rejects the verbatim --- file with `use of undeclared identifier '__argc'` (and `__argv`): --- ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC --- builtins `__argc` / `__argv`. Clang DOES set `_MSC_VER` on the MSVC --- ABI but does NOT provide those builtins (the adjacent branches at --- lines 291 / 311 / 1147 already gate clang out — line 687 was missed). +-- That is the whole delta. In particular this descriptor does NOT: +-- * drop `export import std;` — keeping upstream's spelling is what makes +-- GCC 16.1 accept ut.hpp's unqualified `size_t` (ut.hpp:1916 et al) and +-- the `literals` using-block with no `-Wno-template-body` and no +-- hand-written global-module-fragment include list; +-- * carry the post-v2.3.1 explicit-template-instantiation block from +-- upstream `master` — it is in no release tag, and the macOS crash it was +-- tried against turned out to be a toolchain-side problem (below). -- --- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles --- the verbatim file (WITH `export import std;`) fine but the resulting --- test binary SIGSEGVs (exit 139) during static init of --- `cfg::runner>` — no "Suite '...'" output. A --- macOS lldb backtrace pins it exactly: --- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31 --- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8) --- ut.hpp:1620 is the member-init `std::streambuf* cout_save = --- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is --- ZERO — the stream object was NEVER constructed. Root cause: --- `export import std;` inside the module makes `std::cout` refer to the --- module's OWN copy of the std stream entities, NOT libc++'s — an ODR --- split between "the std module's std::cout" and "the libc++ library's --- std::cout" (Apple libc++ does not merge module std entities the way --- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the --- library copy; the module's copy stays all-zero, so --- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr. +-- macOS: the verbatim module used to SIGSEGV (exit 139) during static init of +-- `cfg::runner>`, at ut.hpp:1620's member-init +-- `std::streambuf* cout_save = std::cout.rdbuf();` — libc++'s `std::cout` had +-- not been constructed yet. That is mcpp-community/mcpp#336, fixed in mcpp +-- 2026.8.3.1: Mach-O has no priority-ordered init section and libc++'s +-- `` carries no `ios_base::Init` guard of its own, so mcpp now links +-- a generated object first whose constructor brings the streams up. It is not +-- fixable package-side (`std::ios_base::Init` is only forward-declared in +-- libc++'s ``), which is why this package needs mcpp >= 2026.8.3.1 on +-- macOS — covered by the floor this index declares in index.toml. -- --- The FIX (matching how every other successful C++23 module package in this --- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built): --- the module TU must NOT `export import std;`. It pulls stdlib in via --- `#include` only, so every `std::*` symbol inside ut.hpp is the library's --- own (same ODR entity the linker binds). Consumers `import std;` themselves --- (the test member does), which is the established pattern and works on all --- three CI platforms. Removing `export import std;` alone was NOT enough --- though — it used to explode on GCC with a cascade of `-Wtemplate-body` --- errors (`std::empty`, `utility::match`, `call_steps_`, the literal `using` --- block, …) because GCC's two-phase lookup could not see the stdlib names --- from `#include` in the module purview. Two things make it work now: --- 1. the module's GLOBAL-MODULE-FRAGMENT `#include`s (below, before --- `export module`) make all the stdlib headers VISIBLE to the module --- TU (GMF declarations are visible to the purview); and --- 2. `cxxflags = { "-Wno-template-body" }` silences GCC's remaining --- two-phase-lookup pedantry inside ut.hpp's templates. GCC 16.1 --- accepts `-Wno-template-body` (verified), and ut.hpp compiles + runs --- green on gcc 16.1 / x86_64-windows-gnu AND llvm 20.1.7 / --- x86_64-windows-msvc. +-- `include_dirs` exposes `*/include/boost` so the wrapper's `#include +-- "ut.hpp"` resolves (and `#include ` stays available to +-- consumers who want the header form). That path is a GLOB — the leading `*` +-- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm path +-- is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus. -- --- So this descriptor is a `generated_files` wrapper over upstream's --- `include/boost/ut.cppm` INTENT — ut.hpp included in the module purview --- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` --- block ut.hpp opens at line 111 exports everything — with these deviations: +-- `import_std` stays false: the module TU must not get an injected `import +-- std;` (upstream's file does its own `export import std;`), and consumers +-- `import std;` themselves exactly like the other module packages. -- --- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above), --- and instead the global-module-fragment `#include`s every stdlib --- header ut.hpp needs (its own includes at lines 73-104 still run, --- the GMF list just guarantees they're all present and visible for --- GCC's two-phase lookup). --- dev 2. Two compiler-compat shims: --- shim a: `using std::size_t;` — lifts `std::size_t` into the --- global namespace so the unqualified `size_t` at ut.hpp --- namespace scope resolves (GCC template-body pedantry; --- std::size_t is available because is in the GMF). --- shim b: `#define __argc 0` / `#define __argv nullptr` gated to --- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows --- (`__argc` / `__argv` builtins missing under `_MSC_VER`); --- MSVC itself never enters the guard. --- dev 3. The post-v2.3.1 explicit-template-instantiation block (see below) --- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from --- upstream `master`. It is upstream's fix for a Clang module --- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT --- address the macOS ODR-split (dev 1 does). Kept because upstream --- added it for a reason and it is cheap to carry. +-- No features. ut is header-only plus a single module unit, with no extra +-- compilable sources to gate; its optional `BOOST_UT_CONFIG_*` toggles are +-- compile-time defines, which the `features` table cannot carry today (the +-- same limitation as compat.eigen's `EIGEN_MPL2_ONLY` and toml++'s +-- `TOML_EXCEPTIONS`). -- --- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag — --- the shims add NO code of our own beyond what the compiler/runtime had to --- see anyway, and the appended instantiation block is upstream master's own --- byte-for-byte fix. --- --- include_dirs exposes `*/include/boost` so the wrapper's `#include "ut.hpp"` --- resolves (and `#include ` remains available to consumers who --- want the header form). The upstream path is a GLOB — the leading `*` --- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm --- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus. --- --- `import_std` stays false: the module TU must not get an injected --- `import std;` (mcpp would otherwise add one), and consumers are expected --- to `import std;` themselves exactly like the other module packages. +-- No CN mirror: plain-string upstream URLs, which tests/check_mirror_urls.lua +-- accepts as-is. CN users fall back to the GLOBAL source until a maintainer +-- backfills the gitcode release. -- -- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0. -- --- Package identity: `namespace = "boost-ext"`, `name = "ut"`; the C++20 --- namespace the library actually declares is `boost::ext::ut::v2_3_1` — --- unrelated to the mcpp namespace, which uses `-` precisely because `.` would --- collide with mcpp's own `.` addressing convention. +-- Package identity: `namespace = "boost-ext"`, `name = "ut"`. The C++ +-- namespace the library declares is `boost::ext::ut::v2_3_1` — unrelated to +-- the mcpp namespace, which uses `-` precisely because `.` would collide with +-- mcpp's own `.` addressing convention. package = { spec = "1", namespace = "boost-ext", @@ -146,79 +107,29 @@ package = { import_std = false, modules = { "boost.ut" }, include_dirs = { "*/include/boost" }, - -- GCC 16.1's module two-phase lookup (the -Wtemplate-body errors) - -- needs every stdlib name visible at template-definition time; the - -- GMF includes below provide that. The remaining pedantry inside - -- ut.hpp's templates (unqualified `size_t`, `std::empty`, the - -- literals using-block, ...) is silenced with -Wno-template-body. - -- Clang 20.1.7 / 22.1 do NOT need this flag and ignore it. - cxxflags = { "-Wno-template-body" }, - -- Upstream's v2.3.1 ut.cppm reproduced with the deviations documented - -- at the top of this descriptor: - -- dev 1: `export import std;` DROPPED — global-module-fragment - -- `#include`s of every stdlib header ut.hpp needs instead - -- (macOS ODR-split fix; see header comment). - -- dev 2: two compiler-compat shims (`using std::size_t;` for GCC; - -- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI). - -- dev 3: post-v2.3.1 explicit-template-instantiation block (from - -- upstream `master`) appended after `#include "ut.hpp"`. - -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus. + -- Upstream's v2.3.1 include/boost/ut.cppm, reproduced verbatim apart + -- from the __argc / __argv shim documented at the top of this file. + -- Verdir-relative path, no glob. generated_files = { ["mcpp_generated/boost.ut.cppm"] = [==[ module; -// Global-module-fragment: every stdlib header ut.hpp needs, so the module -// TU sees the SAME std entities the library links (no `export import std;` -// → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix) -// AND GCC's module two-phase lookup finds the names it needs. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #if __has_include() and __has_include() #include #include #endif export module boost.ut; +export import std; -// ---- mcpp-index compat shim 1/2: GCC template-body fix ------------------- -// ut.hpp uses `size_t` unqualified at namespace scope (e.g. line 1916); -// GCC's module two-phase lookup needs it resolvable where used. -// (in the GMF above) brings std::size_t; this `using` lifts it to the -// global namespace so the unqualified name resolves inside the exported -// namespace block ut.hpp opens below. -using std::size_t; - -// ---- mcpp-index compat shim 2/2: Clang-on-Windows fix --------------------- -// ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC -// builtins `__argc` / `__argv`. Clang on the MSVC ABI (x86_64-windows-msvc) -// sets `_MSC_VER` but does NOT provide those builtins — the neighboring -// upstream guards at lines 291 / 311 / 1147 already gate clang out, but -// line 687 missed it. Provide macros as stand-ins (cfg::largc is reassigned -// from main() args at runtime anyway, so the value is unused). MSVC itself -// never enters this guard, so its real builtins are untouched. +// ---- the only mcpp-index deviation from upstream v2.3.1's ut.cppm --------- +// ut.hpp:687 is `#if defined(_MSC_VER)` and references the MSVC builtins +// __argc / __argv. Clang on the MSVC ABI (this index's Windows default, +// *-pc-windows-msvc) sets _MSC_VER but does not provide them; the adjacent +// upstream guards at lines 291 / 311 / 1147 already gate clang out, 687 +// missed it. cfg::largc / cfg::largv are reassigned from main()'s argv at +// runtime, so these stand-in values are never read. MSVC itself never enters +// the guard and keeps its own builtins. #if defined(_MSC_VER) and defined(__clang__) #define __argc 0 #define __argv ((const char**)nullptr) @@ -231,32 +142,10 @@ using std::size_t; #undef __argc #undef __argv #endif - -// ---- mcpp-index deviation 3: post-v2.3.1 explicit template instantiations -- -// Lifted VERBATIM from upstream `master`'s include/boost/ut.cppm. v2.3.1's -// ut.cppm stops at `#include "ut.hpp"`, which leaves the member templates -// the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`, -// `expect`) only IMPLICITLY instantiable. Explicitly instantiating -// them forces emission — upstream's fix for a Clang module linkage gap. -// NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()` -// ODR split) — dev 1 (no `export import std;`) is the macOS fix. Both stay: -// dev 1 is the ODR fix, dev 3 is upstream's verbatim linkage-gap fix. -// Once a >2.3.1 release ships this block, switch `sources` to -// `*/include/boost/ut.cppm` and drop `generated_files`; these lines come -// back with the verbatim upstream cppm. -template class boost::ut::reporter_junit; -template void boost::ut::reporter_junit::on(boost::ut::events::log); -template void boost::ut::reporter_junit::on(boost::ut::events::assertion_pass); -template void boost::ut::reporter_junit::on(boost::ut::events::assertion_fail); -template auto boost::ut::detail::test::operator=<>(test_location _test); -template auto boost::ut::expect(const bool&expr,const reflection::source_location&); -template void boost::ut::reporter_junit<>::on>(events::assertion_fail>); -template void boost::ut::reporter_junit<>::on>(events::assertion_pass>); -template void boost::ut::reporter_junit<>::on>(events::log>); ]==], }, sources = { "mcpp_generated/boost.ut.cppm" }, targets = { ["boost_ut"] = { kind = "lib" } }, deps = { }, }, -} \ No newline at end of file +} From b5d8378292634465c8b5c66ae65ff8b9287b347e Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 3 Aug 2026 19:10:22 +0800 Subject: [PATCH 26/26] chore: sort the new workspace member, add the missing trailing newlines tests/examples/boost-ext.ut went in at the head of [workspace].members; the list is alphabetical, so it belongs between asio-ssl and build-mcpp. The two new member files were also missing their final newline, unlike every comparable file in the tree. Co-authored-by: SPeak Agent <248744407+speak-agent@users.noreply.github.com> --- mcpp.toml | 2 +- tests/examples/boost-ext.ut/mcpp.toml | 2 +- tests/examples/boost-ext.ut/tests/ut.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mcpp.toml b/mcpp.toml index 8aa0909..47323c5 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -7,10 +7,10 @@ # mcpp .agents/docs/2026-06-30-workspace-test-and-zero-shell-index-design.md. [workspace] members = [ - "tests/examples/boost-ext.ut", "tests/examples/archive", "tests/examples/asio-module", "tests/examples/asio-ssl", + "tests/examples/boost-ext.ut", "tests/examples/build-mcpp", "tests/examples/catch2", "tests/examples/catch2-main", diff --git a/tests/examples/boost-ext.ut/mcpp.toml b/tests/examples/boost-ext.ut/mcpp.toml index bc6515d..6521207 100644 --- a/tests/examples/boost-ext.ut/mcpp.toml +++ b/tests/examples/boost-ext.ut/mcpp.toml @@ -12,4 +12,4 @@ name = "boost-ext-ut-tests" version = "0.1.0" [dependencies.boost-ext] -ut = "2.3.1" \ No newline at end of file +ut = "2.3.1" diff --git a/tests/examples/boost-ext.ut/tests/ut.cpp b/tests/examples/boost-ext.ut/tests/ut.cpp index 4fc8da9..b139c13 100644 --- a/tests/examples/boost-ext.ut/tests/ut.cpp +++ b/tests/examples/boost-ext.ut/tests/ut.cpp @@ -20,4 +20,4 @@ int main() { }; return 0; -} \ No newline at end of file +}