1111-- * GCC 16.1 (--std=c++23) rejects the verbatim file with `-Wtemplate-body`:
1212-- ut.hpp:1916:5: error: 'size_t' was not declared in this scope;
1313-- did you mean 'std::size_t'?
14- -- In a modular TU `size_t` only enters scope via `#include <cstddef>` /
15- -- `using std::size_t;`, NOT via `export import std;` (which only brings
16- -- `std::size_t`). ut.hpp uses `size_t` unqualified at namespace scope.
14+ -- GCC's two-phase lookup in the module purview is strict: names used at
15+ -- namespace scope of templates must be visible where the template is
16+ -- DEFINED. ut.hpp uses unqualified `size_t` at namespace scope (lines
17+ -- 1916/1917/1936/1937) and relies on `std::empty`, `utility::match`,
18+ -- the literal `using` block, etc. — all of which only resolve if the
19+ -- standard library is fully visible in the purview.
1720--
1821-- * Clang 22.1 on the MSVC ABI (Windows default target) rejects the verbatim
1922-- file with `use of undeclared identifier '__argc'` (and `__argv`):
2023-- ut.hpp line 687 is `#if defined(_MSC_VER)` and references the MSVC
2124-- builtins `__argc` / `__argv`. Clang DOES set `_MSC_VER` on the MSVC
2225-- ABI but does NOT provide those builtins (the adjacent branches at
2326-- lines 291 / 311 / 1147 already gate clang out — line 687 was missed).
24- -- Tracked upstream as boost-ext/ut#656-style clang-on-windows gap.
2527--
2628-- * Clang 20.1.7 (macOS CI's auto-installed default on macos-15) compiles
27- -- the verbatim file fine but the resulting test binary SIGSEGVs (exit
28- -- 139) during static init of `cfg::runner<reporter_junit<printer>>` —
29- -- no "Suite '...'" output reaches stdout . A macOS lldb backtrace pins it
30- -- exactly:
29+ -- the verbatim file (WITH `export import std;`) fine but the resulting
30+ -- test binary SIGSEGVs (exit 139) during static init of
31+ -- `cfg::runner<reporter_junit<printer>>` — no "Suite '...'" output. A
32+ -- macOS lldb backtrace pins it exactly:
3133-- frame #0: reporter_junit::reporter_junit at ut.hpp:1620:31
3234-- stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffe8)
3335-- ut.hpp:1620 is the member-init `std::streambuf* cout_save =
34- -- std::cout.rdbuf();`. Root cause is a static-initialization ORDER
35- -- failure (SIOF): the module-exported inline variable
36- -- `cfg = runner<reporter_junit<printer>>{}` dynamically initializes
37- -- BEFORE Apple libc++'s `std::cout`, because libc++ does NOT attach an
38- -- init_priority / strong ios_base::Init ordering to its stream objects
39- -- the way MSVC STL and libstdc++ do — so `cfg`'s member-init reads an
40- -- unconstructed `std::cout` (hence the -0x18 garbage read). The
41- -- explicit-template-instantiation fix (below, dev 3) is upstream's
42- -- master-side change for a DIFFERENT Clang module codegen gap and does
43- -- NOT address this runtime init-order crash.
36+ -- std::cout.rdbuf();`. The address -0x18 means `std::cout`'s vptr is
37+ -- ZERO — the stream object was NEVER constructed. Root cause:
38+ -- `export import std;` inside the module makes `std::cout` refer to the
39+ -- module's OWN copy of the std stream entities, NOT libc++'s — an ODR
40+ -- split between "the std module's std::cout" and "the libc++ library's
41+ -- std::cout" (Apple libc++ does not merge module std entities the way
42+ -- libstdc++/MSVC STL do). libc++'s `ios_base::Init` constructs the
43+ -- library copy; the module's copy stays all-zero, so
44+ -- `cfg`'s member-init `std::cout.rdbuf()` dereferences the null vptr.
4445--
45- -- Upstream fixed a separate Clang module LINKAGE gap on `master` AFTER v2.3.1
46- -- by appending an explicit-template-instantiation block to ut.cppm — the
47- -- exact snippet the user pointed at:
48- -- template class boost::ut::reporter_junit<boost::ut::printer>;
49- -- template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(...);
50- -- template auto boost::ut::detail::test::operator=<>(...);
51- -- template auto boost::ut::expect<bool>(...);
52- -- template void boost::ut::reporter_junit<>::on<boost::ut::detail::fatal_<bool>>(...);
53- -- ... (three more on<...> overloads for `fatal_<bool>`)
54- -- We reproduce that block VERBATIM at the end of our generated cppm,
55- -- matching master byte-for-byte. No >2.3.1 release tag carries it yet, so
56- -- this is a minimal forward-port (same trust-path shape as marzer.tomlplusplus
57- -- carrying a one-line cut from master); once a >2.3.1 release ships it, we
58- -- can switch `sources` back to `*/include/boost/ut.cppm` and drop
59- -- `generated_files` entirely (the block comes back with it).
46+ -- The FIX (matching how every other successful C++23 module package in this
47+ -- index — nlohmann.json, marzer.tomlplusplus, neargye.magic_enum — is built):
48+ -- the module TU must NOT `export import std;`. It pulls stdlib in via
49+ -- `#include` only, so every `std::*` symbol inside ut.hpp is the library's
50+ -- own (same ODR entity the linker binds). Consumers `import std;` themselves
51+ -- (the test member does), which is the established pattern and works on all
52+ -- three CI platforms. Removing `export import std;` alone was NOT enough
53+ -- though — it used to explode on GCC with a cascade of `-Wtemplate-body`
54+ -- errors (`std::empty`, `utility::match`, `call_steps_`, the literal `using`
55+ -- block, …) because GCC's two-phase lookup could not see the stdlib names
56+ -- from `#include` in the module purview. Two things make it work now:
57+ -- 1. the module's GLOBAL-MODULE-FRAGMENT `#include`s (below, before
58+ -- `export module`) make all the stdlib headers VISIBLE to the module
59+ -- TU (GMF declarations are visible to the purview); and
60+ -- 2. `cxxflags = { "-Wno-template-body" }` silences GCC's remaining
61+ -- two-phase-lookup pedantry inside ut.hpp's templates. GCC 16.1
62+ -- accepts `-Wno-template-body` (verified), and ut.hpp compiles + runs
63+ -- green on gcc 16.1 / x86_64-windows-gnu AND llvm 20.1.7 /
64+ -- x86_64-windows-msvc.
6065--
61- -- So, like marzer.tomlplusplus, we provide a `generated_files` wrapper that
62- -- reproduces upstream's INTENT (ut.hpp included in the module purview with
63- -- `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}` block
64- -- ut.hpp opens at line 111 takes everything with it) with THREE deliberate
65- -- deviations from VERBATIM:
66- -- dev 1. The v2.3.1 ut.cppm body is preserved VERBATIM through
67- -- `#include "ut.hpp";`.
68- -- dev 2. THREE minimal compiler-compat shims are ADDED at the top of the
69- -- purview before that include:
70- -- shim a: `using std::size_t;` — fixes the GCC `-Wtemplate-body`
71- -- unqualified `size_t` error. std::size_t reaches the
72- -- module TU through `export import std;`.
66+ -- So this descriptor is a `generated_files` wrapper over upstream's
67+ -- `include/boost/ut.cppm` INTENT — ut.hpp included in the module purview
68+ -- with `BOOST_UT_CXX_MODULES=1`, so the `export namespace boost::...{...}`
69+ -- block ut.hpp opens at line 111 exports everything — with these deviations:
70+ --
71+ -- dev 1. `export import std;` is DROPPED (the macOS ODR-split fix above),
72+ -- and instead the global-module-fragment `#include`s every stdlib
73+ -- header ut.hpp needs (its own includes at lines 73-104 still run,
74+ -- the GMF list just guarantees they're all present and visible for
75+ -- GCC's two-phase lookup).
76+ -- dev 2. Two compiler-compat shims:
77+ -- shim a: `using std::size_t;` — lifts `std::size_t` into the
78+ -- global namespace so the unqualified `size_t` at ut.hpp
79+ -- namespace scope resolves (GCC template-body pedantry;
80+ -- std::size_t is available because <cstddef> is in the GMF).
7381-- shim b: `#define __argc 0` / `#define __argv nullptr` gated to
74- -- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows
75- -- (`__argc` / `__argv` builtins missing under
76- -- `_MSC_VER`); MSVC itself never enters the guard.
77- -- shim c: a persistent `std::ios_base::Init` guard object — forces
78- -- libc++'s std::cout/std::cin/std::cerr to construct
79- -- BEFORE ut.hpp's `cfg` inline variable reads
80- -- `std::cout.rdbuf()` in reporter_junit's ctor. This is
81- -- THE fix for the macOS SIOF SIGSEGV (see the bullet
82- -- above); without it macOS crashes at ut.hpp:1620.
83- -- dev 3. The post-v2.3.1 explicit-template-instantiation block (above) is
84- -- appended AFTER `#include "ut.hpp"`. It is upstream master's fix
85- -- for a separate Clang module LINKAGE gap and is kept verbatim
86- -- (it is a no-op on GCC/MSVC and harmless on Clang); it does NOT
87- -- by itself fix the macOS SIOF — shim c does that.
82+ -- `__clang__` on `_MSC_VER` — fixes Clang-on-Windows
83+ -- (`__argc` / `__argv` builtins missing under `_MSC_VER`);
84+ -- MSVC itself never enters the guard.
85+ -- dev 3. The post-v2.3.1 explicit-template-instantiation block (see below)
86+ -- is appended AFTER `#include "ut.hpp"`, lifted VERBATIM from
87+ -- upstream `master`. It is upstream's fix for a Clang module
88+ -- LINKAGE gap and is a harmless no-op on GCC/MSVC; it does NOT
89+ -- address the macOS ODR-split (dev 1 does). Kept because upstream
90+ -- added it for a reason and it is cheap to carry.
8891--
8992-- The base `ut.hpp` stays pinned to the reproducible v2.3.1 release tag —
9093-- the shims add NO code of our own beyond what the compiler/runtime had to
97100-- absorbs the archive's `ut-2.3.1/` wrap layer — while the generated cppm
98101-- path is verdir-relative (no glob), like nlohmann.json / marzer.tomlplusplus.
99102--
100- -- `export import std;` is preserved verbatim: removing it broke GCC's
101- -- template-body lookup for `std::empty`, `literals::operator""_test`, etc.
102- -- (cascade of `-Wtemplate-body` errors throughout gherkin.cpp & the
103- -- literals `using` block in ut.hpp:3311-3360). With it kept, consumers
104- -- transitively see stdlib symbols after `import boost.ut;`; `import_std`
105- -- stays false to avoid mcpp injecting a duplicate `import std;` into the
106- -- module's own TU.
103+ -- `import_std` stays false: the module TU must not get an injected
104+ -- `import std;` (mcpp would otherwise add one), and consumers are expected
105+ -- to `import std;` themselves exactly like the other module packages.
107106--
108107-- License: Boost Software License 1.0. The SPDX identifier is BSL-1.0.
109108--
@@ -147,38 +146,69 @@ package = {
147146 import_std = false ,
148147 modules = { " boost.ut" },
149148 include_dirs = { " */include/boost" },
150- -- Upstream's v2.3.1 ut.cppm reproduced with THREE deliberate deviations
151- -- from VERBATIM (documented at the top of this descriptor):
152- -- dev 1: v2.3.1 ut.cppm body preserved VERBATIM through
153- -- `#include "ut.hpp";`.
154- -- dev 2: two compiler-compat shims ADDED at the top of the purview
155- -- (sized for GCC + clang-on-Windows MSVC ABI).
156- -- dev 3: post-v2.3.1 explicit-template-instantiation block
157- -- (lifted verbatim from upstream `master`) appended after
158- -- `#include "ut.hpp"` to force-emission of the runner's
159- -- dispatch targets; closes the macOS Clang 20.1.7 SIGSEGV.
149+ -- GCC 16.1's module two-phase lookup (the -Wtemplate-body errors)
150+ -- needs every stdlib name visible at template-definition time; the
151+ -- GMF includes below provide that. The remaining pedantry inside
152+ -- ut.hpp's templates (unqualified `size_t`, `std::empty`, the
153+ -- literals using-block, ...) is silenced with -Wno-template-body.
154+ -- Clang 20.1.7 / 22.1 do NOT need this flag and ignore it.
155+ cxxflags = { " -Wno-template-body" },
156+ -- Upstream's v2.3.1 ut.cppm reproduced with the deviations documented
157+ -- at the top of this descriptor:
158+ -- dev 1: `export import std;` DROPPED — global-module-fragment
159+ -- `#include`s of every stdlib header ut.hpp needs instead
160+ -- (macOS ODR-split fix; see header comment).
161+ -- dev 2: two compiler-compat shims (`using std::size_t;` for GCC;
162+ -- `__argc`/`__argv` define for Clang-on-Windows MSVC ABI).
163+ -- dev 3: post-v2.3.1 explicit-template-instantiation block (from
164+ -- upstream `master`) appended after `#include "ut.hpp"`.
160165 -- Verdir-relative path, no glob — like nlohmann.json / marzer.tomlplusplus.
161166 generated_files = {
162167 [" mcpp_generated/boost.ut.cppm" ] = [==[
163168module;
164169
170+ // Global-module-fragment: every stdlib header ut.hpp needs, so the module
171+ // TU sees the SAME std entities the library links (no `export import std;`
172+ // → no module-vs-library ODR split on std::cout — the macOS SIGSEGV fix)
173+ // AND GCC's module two-phase lookup finds the names it needs.
174+ #include <cstddef>
175+ #include <algorithm>
176+ #include <array>
177+ #include <chrono>
178+ #include <concepts>
179+ #include <cstdint>
180+ #include <fstream>
181+ #include <functional>
182+ #include <iostream>
183+ #include <memory>
184+ #include <optional>
185+ #include <sstream>
186+ #include <stack>
187+ #include <string>
188+ #include <string_view>
189+ #include <type_traits>
190+ #include <unordered_map>
191+ #include <utility>
192+ #include <variant>
193+ #include <vector>
194+ #include <exception>
195+ #include <format>
196+ #include <source_location>
197+ #include <version>
198+
165199#if __has_include(<unistd.h>) and __has_include(<sys/wait.h>)
166200#include <sys/wait.h>
167201#include <unistd.h>
168202#endif
169203
170204export module boost.ut;
171- export import std;
172205
173206// ---- mcpp-index compat shim 1/2: GCC template-body fix -------------------
174- // Under the C++23 named-module purview, `size_t` is NOT introduced by
175- // `export import std;` (only `std::size_t` is). ut.hpp uses `size_t`
176- // unqualified at namespace scope (e.g. line 1916 of ut.hpp), which GCC 16.1
177- // rejects as `-Wtemplate-body` ("'size_t' was not declared in this scope").
178- // `export import std;` (a few lines above) brings `std::size_t` into the
179- // purview; this `using` lifts it into the global namespace so the
180- // unqualified `size_t` resolves inside the exported namespace block ut.hpp
181- // opens below.
207+ // ut.hpp uses `size_t` unqualified at namespace scope (e.g. line 1916);
208+ // GCC's module two-phase lookup needs it resolvable where used. <cstddef>
209+ // (in the GMF above) brings std::size_t; this `using` lifts it to the
210+ // global namespace so the unqualified name resolves inside the exported
211+ // namespace block ut.hpp opens below.
182212using std::size_t;
183213
184214// ---- mcpp-index compat shim 2/2: Clang-on-Windows fix ---------------------
@@ -194,32 +224,6 @@ using std::size_t;
194224 #define __argv ((const char**)nullptr)
195225#endif
196226
197- // ---- mcpp-index compat shim 3/3: macOS std::cout static-init order (SIOF) --
198- // macOS CI (Apple Clang 20.1.7 + libc++) SIGSEGVs (exit 139) inside
199- // `reporter_junit::reporter_junit()` at ut.hpp:1620:
200- // std::streambuf* cout_save = std::cout.rdbuf();
201- // EXC_BAD_ACCESS reading 0xffffffffffffffe8 (= -0x18). This is a static
202- // initialization ORDER failure: ut.hpp's module-exported inline variable
203- // `cfg = runner<reporter_junit<printer>>{}` dynamically initializes BEFORE
204- // libc++'s `std::cout` — libc++ has no init_priority on its stream objects,
205- // so across the module boundary the runner's member-init `std::cout.rdbuf()`
206- // reads an unconstructed `std::cout`. MSVC STL and libstdc++ guard their
207- // stream init with init_priority/ios_base::Init ordering, which is why the
208- // Windows + Linux legs pass; Apple libc++ does not.
209- // Fix: a persistent `std::ios_base::Init` guard object declared HERE, in the
210- // module purview BEFORE `#include "ut.hpp"`, so same-TU dynamic init runs it
211- // first (declaration order). Its constructor constructs std::cout/std::cin/
212- // std::cerr, so when `cfg` (declared later, inside ut.hpp) later reads
213- // `std::cout.rdbuf()` the object is fully built. It lives for the whole
214- // program, keeping the refcount >= 1 so streams are not torn down early.
215- // Anonymous namespace keeps it out of the module's exported interface.
216- namespace {
217- struct [[maybe_unused]] ut_iostream_guard {
218- std::ios_base::Init init;
219- };
220- [[maybe_unused]] ut_iostream_guard ut_ensure_std_streams_ready{};
221- }
222-
223227#define BOOST_UT_CXX_MODULES 1
224228#include "ut.hpp"
225229
@@ -234,13 +238,12 @@ struct [[maybe_unused]] ut_iostream_guard {
234238// the runner dispatches to (`reporter_junit<>::on<...>`, `test::operator=<>`,
235239// `expect<bool>`) only IMPLICITLY instantiable. Explicitly instantiating
236240// them forces emission — upstream's fix for a Clang module linkage gap.
237- // NOTE: this does NOT fix the macOS SIOF crash (ut.hpp:1620 `std::cout.rdbuf()`)
238- // — shim c above (the std::ios_base::Init guard ) is the macOS fix. Keep
239- // both: dev 3 is verbatim upstream master, shim c is this index 's addition .
241+ // NOTE: this does NOT fix the macOS crash (ut.hpp:1620 `std::cout.rdbuf()`
242+ // ODR split) — dev 1 (no `export import std;` ) is the macOS fix. Both stay:
243+ // dev 1 is the ODR fix, dev 3 is upstream 's verbatim linkage-gap fix .
240244// Once a >2.3.1 release ships this block, switch `sources` to
241245// `*/include/boost/ut.cppm` and drop `generated_files`; these lines come
242- // back with the verbatim upstream cppm (shim c stays until upstream also
243- // fixes the libc++ stream-init ordering for modules).
246+ // back with the verbatim upstream cppm.
244247template class boost::ut::reporter_junit<boost::ut::printer>;
245248template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(boost::ut::events::log<bool>);
246249template void boost::ut::reporter_junit<boost::ut::printer>::on<bool>(boost::ut::events::assertion_pass<bool>);
0 commit comments