Skip to content

Commit 91d0be4

Browse files
yspbwx2010claude
andcommitted
feat(ftxui): a modules feature for 7.0.3's named modules
FTXUI 7 ships upstream's own module units — an `ftxui` umbrella re-exporting ftxui.component/.dom/.screen/.util — and #292 left them out after an unconditional attempt went red on the linux gcc leg. They come back here behind an opt-in `modules` feature whose source list is exactly upstream's cmake/ftxui_modules.cmake: five .cppm, a pure addition that cannot collide with the base **/*.cpp globs, so the default build does not move (76 compiled units without the feature, 81 with). What that gcc leg was reporting is narrower than "GCC 16 cannot consume these modules". The sub-modules put the public headers, and transitively libstdc++, into a global module fragment, so a consumer TU that writes `import ftxui;` AND textually #includes a standard header hands gcc two copies of the standard library and it refuses — redefinition of std::__terminate, conflicting declaration of std::allocator / std::char_traits. #292's smoke TU did exactly that, and the only object that failed there was that TU's own (obj/module.o), after the package's five module units had already compiled. clang accepts the mixed TU, which is why only gcc went red. Kept off the textual surface, the units build and run on gcc 16.1.0 and llvm 22.1.8 alike, so tests/examples/ftxui-module needs no toolchain pin and runs on both linux legs: `import ftxui;` with a real render, plus each sub-module imported alone, every TU on the module surface and not one textual include. The `modules` export list is documentation, not a guard: mcpp validates [modules].exports only for a build's PRIMARY manifest, never for a dependency's — measured by deleting one name and rebuilding with the package cache bypassed, which passed. Verified (mcpp 2026.8.27.2, the version CI pins): ftxui-module 5 passed on both toolchains; `mcpp test -p core` green; the same import with the feature off fails with `module 'ftxui' not found`; 6.1.9 and the 7.0.3 header path unaffected; check_mirror_urls, check_package_name, check_platform_version_parity, check_duplicate_versions, check_cross_package_refs and `mcpp xpkg parse` over all 175 descriptors green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent bd08677 commit 91d0be4

8 files changed

Lines changed: 235 additions & 1 deletion

File tree

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ members = [
4545
"tests/examples/ffmpeg",
4646
"tests/examples/ffmpeg-module",
4747
"tests/examples/fmtlib.fmt",
48+
"tests/examples/ftxui-module",
4849
"tests/examples/gmp",
4950
"tests/examples/gmp-gmpxx",
5051
"tests/examples/godot-cpp",

pkgs/c/compat.ftxui.lua

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
-- M6.x glob-aware Form B descriptor for FTXUI 6.1.9 and 7.0.3.
22
--
3-
-- Pure C++ library (no C++23 modules); compiled sources + public headers.
3+
-- Compiled sources + public headers by default; 7.0.3 additionally offers
4+
-- upstream's named modules behind the opt-in `modules` feature (see below).
45
-- Uses mcpp 0.0.4's glob exclusion (`!` prefix) to skip the
56
-- *_test.cpp / *_fuzzer.cpp files that live alongside the library
67
-- sources in the same directories (6.1.9: ~30 test / ~16 fuzzer;
@@ -14,6 +15,69 @@
1415
-- FTXUI_BUILD_MODULES, off by default); the `*.cpp` globs never match them,
1516
-- and the plain .cpp sources still compile header-only style.
1617
--
18+
-- The `modules` feature (7.0.3+)
19+
-- ------------------------------
20+
-- 7.x ships upstream's own named modules — an `ftxui` umbrella that
21+
-- `export import`s four sub-modules (ftxui.component/.dom/.screen/.util),
22+
-- each of which textually includes the matching public headers in its global
23+
-- module fragment. `features.modules` adds exactly the five files upstream's
24+
-- cmake/ftxui_modules.cmake lists, so `import ftxui;` becomes available
25+
-- without touching the header surface: the module units carry no definitions
26+
-- of their own (they are `export namespace ftxui { using ... }` re-exports),
27+
-- so they layer ON TOP of the same libftxui.a the default build produces.
28+
-- Both surfaces coexist in one archive; a consumer picks either.
29+
--
30+
-- OFF BY DEFAULT — but NOT because the units fail to build. They build and
31+
-- run under gcc 16.1.0 and llvm 22.1.8 alike, verified on the mcpp version CI
32+
-- pins. The reason is cost and choice: the module surface is five extra TUs
33+
-- and their BMIs that no header consumer of 7.0.3 should pay for unasked,
34+
-- upstream itself defaults FTXUI_BUILD_MODULES to OFF, and there is a
35+
-- consumer-side constraint below that only the consumer can honour.
36+
--
37+
-- ⚠️ THE CONSTRAINT IS IN THE CONSUMER'S TU, NOT IN THIS PACKAGE, AND #292
38+
-- IS WHY IT IS WORTH SPELLING OUT. Because each sub-module puts the public
39+
-- headers — and transitively libstdc++ — into a global module fragment, a
40+
-- consumer TU that writes `import ftxui;` and ALSO textually `#include`s a
41+
-- standard header hands gcc two copies of the standard library's
42+
-- declarations. gcc 16 refuses, at volume:
43+
--
44+
-- c++config.h:355:15: error: redefinition of 'void std::__terminate()'
45+
-- memoryfwd.h:68:11: error: conflicting declaration of template
46+
-- 'template<class> struct std::allocator'
47+
-- stringfwd.h:55:12: error: conflicting declaration of template
48+
-- 'template<class _CharT> struct std::char_traits'
49+
--
50+
-- That is exactly what sank #292's first attempt at this. Its smoke TU wrote
51+
-- `import ftxui;` above `#include <string>` and `#include <gtest/gtest.h>`,
52+
-- and the linux gcc leg died on those three errors (and ~16k more) while
53+
-- llvm, macOS and windows stayed green — clang accepts the mixed TU. The one
54+
-- object that failed in that run was the smoke TU's own (`obj/module.o`); the
55+
-- package's five module units had already compiled.
56+
--
57+
-- A consumer that stays ON the module surface — `import std;` beside
58+
-- `import ftxui;`, no textual includes, the discipline tests/examples/
59+
-- asio-module already documents — builds clean on both compilers. That is
60+
-- what tests/examples/ftxui-module asserts, and it is why that member can run
61+
-- on both of CI's linux legs rather than needing a toolchain pin.
62+
--
63+
-- Upstream's own module CI is llvm-only (`test_modules` in
64+
-- .github/workflows/build.yaml: a one-entry ubuntu + llvm matrix carrying
65+
-- `# TODO add gcc / msvc`), and ftxui_modules.cmake still forces
66+
-- `-fmodules-ts` under CMAKE_COMPILER_IS_GNUCXX above a bare
67+
-- `# TODO: Explain why this is needed.`. So gcc is UNTESTED upstream — worth
68+
-- knowing before trusting the combination far — but, as measured here, it is
69+
-- not broken.
70+
--
71+
-- On 6.1.9 the feature's glob matches nothing (no .cppm before 7.0.0), which
72+
-- is a warning rather than an error — the same union-of-layouts tolerance
73+
-- compat.catch2 and compat.redis-plus-plus rely on. `modules` below is the
74+
-- declared export set, in the same spelling every other module package in
75+
-- this index uses. Note what it does NOT buy here: mcpp validates
76+
-- `[modules].exports` against the scanner only for the PRIMARY manifest of a
77+
-- build, so a DEPENDENCY's list is never checked — measured by deleting
78+
-- `ftxui.util` from it and rebuilding with the package cache bypassed, which
79+
-- built and passed. It is documentation and metadata, not a guard.
80+
--
1781
-- ONE version skew the globs cannot express (no per-version build blocks,
1882
-- mcpp-community/mcpp#290): FTXUI 7 moved Loop's method definitions from
1983
-- loop.cpp into app.cpp and dropped loop.cpp from the CMake build, but the
@@ -98,6 +162,40 @@ package = {
98162
"!*/src/ftxui/**/*_fuzzer.cpp", -- fuzz targets (16 in 6.1.9, 6 in 7.0.3)
99163
},
100164
targets = { ["ftxui"] = { kind = "lib" } },
165+
-- The export set of the `modules` feature, in the spelling every other
166+
-- module package in this index uses. Documentation and metadata only:
167+
-- mcpp checks `[modules].exports` against the scanner for the PRIMARY
168+
-- manifest of a build, never for a dependency's, so nothing here is
169+
-- enforced at a consumer's build (measured — see the header comment).
170+
-- Order follows upstream's ftxui_modules.cmake.
171+
modules = {
172+
"ftxui",
173+
"ftxui.component",
174+
"ftxui.dom",
175+
"ftxui.screen",
176+
"ftxui.util",
177+
},
178+
features = {
179+
-- Upstream's five module units, verbatim from
180+
-- cmake/ftxui_modules.cmake. `*.cppm` cannot collide with the base
181+
-- `**/*.cpp` globs (different extension), so this is a pure
182+
-- ADDITION — no `!` exclusion is involved and the base source set
183+
-- is untouched whether the feature is on or off. That matters:
184+
-- a `!` exclusion in mcpp is global and would out-rank a feature
185+
-- entry naming the same file, so "exclude in base, add back in the
186+
-- feature" is not an expressible shape.
187+
--
188+
-- No `include_dirs` here (features cannot carry them, and none is
189+
-- needed): the GMF `#include <ftxui/...>` resolve through the
190+
-- package-level `*/include`, which mcpp applies to the package's
191+
-- own TUs as well as to consumers.
192+
--
193+
-- Consumer-side rule, gcc only: don't mix `import ftxui;` with a
194+
-- textual `#include` in one TU. See the header comment.
195+
["modules"] = {
196+
sources = { "*/src/ftxui/*.cppm" },
197+
},
198+
},
101199
deps = { },
102200
windows = {
103201
cxxflags = { "-DUNICODE", "-D_UNICODE" },
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# compat.ftxui's `modules` feature: upstream's own named modules for 7.0.3 —
2+
# the `ftxui` umbrella plus ftxui.component/.dom/.screen/.util. The sibling
3+
# member tests/examples/core covers the default HEADER surface at the same
4+
# version and requests no feature; this member exists to prove the feature.
5+
#
6+
# Part of the self-referential workspace: the dependency resolves to the
7+
# checked-in descriptor (pkgs/c/compat.ftxui.lua) through the workspace-root
8+
# `[indices]` redirect, which this member inherits.
9+
#
10+
# ⚠️ EVERY TU HERE STAYS ON THE MODULE SURFACE — `import std;` beside
11+
# `import ftxui...;`, and not one textual `#include`. That is not style, it is
12+
# the thing the member is guarding. A consumer TU that mixes `import ftxui;`
13+
# with a textual `#include` of a standard header hands gcc two copies of
14+
# libstdc++'s declarations through the sub-modules' global module fragments,
15+
# and gcc 16 rejects it with thousands of `redefinition of 'void
16+
# std::__terminate()'` / `conflicting declaration of template ... std::allocator`
17+
# errors. That is what took down #292's first attempt at compiling these units,
18+
# and it is a CONSUMER-side constraint, not a defect in the package. clang
19+
# accepts the mixed TU, which is why only the linux gcc leg went red there.
20+
#
21+
# Kept unmixed, the feature builds and runs on gcc 16.1.0 and llvm 22.1.8
22+
# alike, so this member needs no toolchain pin and runs on both linux legs.
23+
[package]
24+
name = "ftxui-module-tests"
25+
version = "0.1.0"
26+
27+
[dependencies.compat]
28+
ftxui = { version = "7.0.3", features = ["modules"] }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// ftxui.component alone: Component/Event/Button and event routing.
2+
import std;
3+
import ftxui.component;
4+
5+
int main() {
6+
using namespace ftxui;
7+
int clicked = 0;
8+
Component button = Button("go", [&] { ++clicked; });
9+
10+
Component container = Container::Vertical({button});
11+
if (!container->OnEvent(Event::Return)) return 1;
12+
if (clicked != 1) return 2;
13+
14+
const Event a = Event::Character('a');
15+
if (!a.is_character() || a.character() != "a") return 3;
16+
if (Event::Return == a) return 4;
17+
18+
std::println("clicked={}", clicked);
19+
std::println("ftxui.component OK");
20+
return 0;
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ftxui.dom alone: elements + Render + Screen, no umbrella, no headers.
2+
import std;
3+
import ftxui.dom;
4+
5+
int main() {
6+
using namespace ftxui;
7+
Element document = vbox({text("dom-only"), separator(), text("row2")});
8+
auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));
9+
Render(screen, document);
10+
const std::string rendered = screen.ToString();
11+
std::println("dom rendered: [{}]", rendered);
12+
if (rendered.find("dom-only") == std::string::npos) return 1;
13+
if (rendered.find("row2") == std::string::npos) return 2;
14+
std::println("ftxui.dom OK");
15+
return 0;
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// ftxui.screen alone: Screen/Pixel/Color/Terminal and the _rgb literal.
2+
import std;
3+
import ftxui.screen;
4+
5+
int main() {
6+
using namespace ftxui;
7+
auto screen = Screen::Create(Dimensions{4, 2});
8+
screen.PixelAt(0, 0).character = "X";
9+
screen.PixelAt(3, 1).character = "Y";
10+
const std::string s = screen.ToString();
11+
std::println("screen: [{}]", s);
12+
if (s.find('X') == std::string::npos) return 1;
13+
if (s.find('Y') == std::string::npos) return 2;
14+
15+
const Color red = Color::Red;
16+
const Color rgb = Color::RGB(1, 2, 3);
17+
if (red == rgb) return 3;
18+
using namespace ftxui::literals;
19+
const Color lit = 0x0102ff_rgb;
20+
if (lit == red) return 4;
21+
22+
if (string_width("abc") != 3) return 5;
23+
if (to_string(to_wstring(std::string("mcpp"))) != "mcpp") return 6;
24+
std::println("ftxui.screen OK");
25+
return 0;
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// ftxui.util alone: Ref/ConstRef/StringRef and AutoReset.
2+
import std;
3+
import ftxui.util;
4+
5+
int main() {
6+
using namespace ftxui;
7+
int backing = 7;
8+
Ref<int> r(&backing);
9+
*r = 9;
10+
if (backing != 9) return 1;
11+
12+
ConstRef<int> cr(5);
13+
if (*cr != 5) return 2;
14+
15+
std::string text = "abc";
16+
StringRef sr(&text);
17+
*sr = "xyz";
18+
if (text != "xyz") return 3;
19+
20+
int guarded = 1;
21+
{ AutoReset<int> reset(&guarded, 42); if (guarded != 42) return 4; }
22+
if (guarded != 1) return 5;
23+
24+
std::println("ftxui.util OK");
25+
return 0;
26+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// `import ftxui;` — the umbrella module — plus a real dom render.
2+
import std;
3+
import ftxui;
4+
5+
int main() {
6+
using namespace ftxui;
7+
Element document = hbox({text("compat"), separator(), text("ftxui")});
8+
auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));
9+
Render(screen, document);
10+
const std::string rendered = screen.ToString();
11+
std::println("rendered: [{}]", rendered);
12+
if (rendered.find("compat") == std::string::npos) return 1;
13+
if (rendered.find("ftxui") == std::string::npos) return 2;
14+
// the separator must actually have drawn something between them
15+
if (rendered.find("compat") > rendered.find("ftxui")) return 3;
16+
std::println("umbrella OK");
17+
return 0;
18+
}

0 commit comments

Comments
 (0)