Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ members = [
"tests/examples/ffmpeg",
"tests/examples/ffmpeg-module",
"tests/examples/fmtlib.fmt",
"tests/examples/ftxui-module",
"tests/examples/gmp",
"tests/examples/gmp-gmpxx",
"tests/examples/godot-cpp",
Expand Down
100 changes: 99 additions & 1 deletion pkgs/c/compat.ftxui.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-- M6.x glob-aware Form B descriptor for FTXUI 6.1.9 and 7.0.3.
--
-- Pure C++ library (no C++23 modules); compiled sources + public headers.
-- Compiled sources + public headers by default; 7.0.3 additionally offers
-- upstream's named modules behind the opt-in `modules` feature (see below).
-- Uses mcpp 0.0.4's glob exclusion (`!` prefix) to skip the
-- *_test.cpp / *_fuzzer.cpp files that live alongside the library
-- sources in the same directories (6.1.9: ~30 test / ~16 fuzzer;
Expand All @@ -14,6 +15,69 @@
-- FTXUI_BUILD_MODULES, off by default); the `*.cpp` globs never match them,
-- and the plain .cpp sources still compile header-only style.
--
-- The `modules` feature (7.0.3+)
-- ------------------------------
-- 7.x ships upstream's own named modules — an `ftxui` umbrella that
-- `export import`s four sub-modules (ftxui.component/.dom/.screen/.util),
-- each of which textually includes the matching public headers in its global
-- module fragment. `features.modules` adds exactly the five files upstream's
-- cmake/ftxui_modules.cmake lists, so `import ftxui;` becomes available
-- without touching the header surface: the module units carry no definitions
-- of their own (they are `export namespace ftxui { using ... }` re-exports),
-- so they layer ON TOP of the same libftxui.a the default build produces.
-- Both surfaces coexist in one archive; a consumer picks either.
--
-- OFF BY DEFAULT — but NOT because the units fail to build. They build and
-- run under gcc 16.1.0 and llvm 22.1.8 alike, verified on the mcpp version CI
-- pins. The reason is cost and choice: the module surface is five extra TUs
-- and their BMIs that no header consumer of 7.0.3 should pay for unasked,
-- upstream itself defaults FTXUI_BUILD_MODULES to OFF, and there is a
-- consumer-side constraint below that only the consumer can honour.
--
-- ⚠️ THE CONSTRAINT IS IN THE CONSUMER'S TU, NOT IN THIS PACKAGE, AND #292
-- IS WHY IT IS WORTH SPELLING OUT. Because each sub-module puts the public
-- headers — and transitively libstdc++ — into a global module fragment, a
-- consumer TU that writes `import ftxui;` and ALSO textually `#include`s a
-- standard header hands gcc two copies of the standard library's
-- declarations. gcc 16 refuses, at volume:
--
-- c++config.h:355:15: error: redefinition of 'void std::__terminate()'
-- memoryfwd.h:68:11: error: conflicting declaration of template
-- 'template<class> struct std::allocator'
-- stringfwd.h:55:12: error: conflicting declaration of template
-- 'template<class _CharT> struct std::char_traits'
--
-- That is exactly what sank #292's first attempt at this. Its smoke TU wrote
-- `import ftxui;` above `#include <string>` and `#include <gtest/gtest.h>`,
-- and the linux gcc leg died on those three errors (and ~16k more) while
-- llvm, macOS and windows stayed green — clang accepts the mixed TU. The one
-- object that failed in that run was the smoke TU's own (`obj/module.o`); the
-- package's five module units had already compiled.
--
-- A consumer that stays ON the module surface — `import std;` beside
-- `import ftxui;`, no textual includes, the discipline tests/examples/
-- asio-module already documents — builds clean on both compilers. That is
-- what tests/examples/ftxui-module asserts, and it is why that member can run
-- on both of CI's linux legs rather than needing a toolchain pin.
--
-- Upstream's own module CI is llvm-only (`test_modules` in
-- .github/workflows/build.yaml: a one-entry ubuntu + llvm matrix carrying
-- `# TODO add gcc / msvc`), and ftxui_modules.cmake still forces
-- `-fmodules-ts` under CMAKE_COMPILER_IS_GNUCXX above a bare
-- `# TODO: Explain why this is needed.`. So gcc is UNTESTED upstream — worth
-- knowing before trusting the combination far — but, as measured here, it is
-- not broken.
--
-- On 6.1.9 the feature's glob matches nothing (no .cppm before 7.0.0), which
-- is a warning rather than an error — the same union-of-layouts tolerance
-- compat.catch2 and compat.redis-plus-plus rely on. `modules` below is the
-- declared export set, in the same spelling every other module package in
-- this index uses. Note what it does NOT buy here: mcpp validates
-- `[modules].exports` against the scanner only for the PRIMARY manifest of a
-- build, so a DEPENDENCY's list is never checked — measured by deleting
-- `ftxui.util` from it and rebuilding with the package cache bypassed, which
-- built and passed. It is documentation and metadata, not a guard.
--
-- ONE version skew the globs cannot express (no per-version build blocks,
-- mcpp-community/mcpp#290): FTXUI 7 moved Loop's method definitions from
-- loop.cpp into app.cpp and dropped loop.cpp from the CMake build, but the
Expand Down Expand Up @@ -98,6 +162,40 @@ package = {
"!*/src/ftxui/**/*_fuzzer.cpp", -- fuzz targets (16 in 6.1.9, 6 in 7.0.3)
},
targets = { ["ftxui"] = { kind = "lib" } },
-- The export set of the `modules` feature, in the spelling every other
-- module package in this index uses. Documentation and metadata only:
-- mcpp checks `[modules].exports` against the scanner for the PRIMARY
-- manifest of a build, never for a dependency's, so nothing here is
-- enforced at a consumer's build (measured — see the header comment).
-- Order follows upstream's ftxui_modules.cmake.
modules = {
"ftxui",
"ftxui.component",
"ftxui.dom",
"ftxui.screen",
"ftxui.util",
},
features = {
-- Upstream's five module units, verbatim from
-- cmake/ftxui_modules.cmake. `*.cppm` cannot collide with the base
-- `**/*.cpp` globs (different extension), so this is a pure
-- ADDITION — no `!` exclusion is involved and the base source set
-- is untouched whether the feature is on or off. That matters:
-- a `!` exclusion in mcpp is global and would out-rank a feature
-- entry naming the same file, so "exclude in base, add back in the
-- feature" is not an expressible shape.
--
-- No `include_dirs` here (features cannot carry them, and none is
-- needed): the GMF `#include <ftxui/...>` resolve through the
-- package-level `*/include`, which mcpp applies to the package's
-- own TUs as well as to consumers.
--
-- Consumer-side rule, gcc only: don't mix `import ftxui;` with a
-- textual `#include` in one TU. See the header comment.
["modules"] = {
sources = { "*/src/ftxui/*.cppm" },
},
},
deps = { },
windows = {
cxxflags = { "-DUNICODE", "-D_UNICODE" },
Expand Down
28 changes: 28 additions & 0 deletions tests/examples/ftxui-module/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# compat.ftxui's `modules` feature: upstream's own named modules for 7.0.3 —
# the `ftxui` umbrella plus ftxui.component/.dom/.screen/.util. The sibling
# member tests/examples/core covers the default HEADER surface at the same
# version and requests no feature; this member exists to prove the feature.
#
# Part of the self-referential workspace: the dependency resolves to the
# checked-in descriptor (pkgs/c/compat.ftxui.lua) through the workspace-root
# `[indices]` redirect, which this member inherits.
#
# ⚠️ EVERY TU HERE STAYS ON THE MODULE SURFACE — `import std;` beside
# `import ftxui...;`, and not one textual `#include`. That is not style, it is
# the thing the member is guarding. A consumer TU that mixes `import ftxui;`
# with a textual `#include` of a standard header hands gcc two copies of
# libstdc++'s declarations through the sub-modules' global module fragments,
# and gcc 16 rejects it with thousands of `redefinition of 'void
# std::__terminate()'` / `conflicting declaration of template ... std::allocator`
# errors. That is what took down #292's first attempt at compiling these units,
# and it is a CONSUMER-side constraint, not a defect in the package. clang
# accepts the mixed TU, which is why only the linux gcc leg went red there.
#
# Kept unmixed, the feature builds and runs on gcc 16.1.0 and llvm 22.1.8
# alike, so this member needs no toolchain pin and runs on both linux legs.
[package]
name = "ftxui-module-tests"
version = "0.1.0"

[dependencies.compat]
ftxui = { version = "7.0.3", features = ["modules"] }
21 changes: 21 additions & 0 deletions tests/examples/ftxui-module/tests/sub_component.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// ftxui.component alone: Component/Event/Button and event routing.
import std;
import ftxui.component;

int main() {
using namespace ftxui;
int clicked = 0;
Component button = Button("go", [&] { ++clicked; });

Component container = Container::Vertical({button});
if (!container->OnEvent(Event::Return)) return 1;
if (clicked != 1) return 2;

const Event a = Event::Character('a');
if (!a.is_character() || a.character() != "a") return 3;
if (Event::Return == a) return 4;

std::println("clicked={}", clicked);
std::println("ftxui.component OK");
return 0;
}
16 changes: 16 additions & 0 deletions tests/examples/ftxui-module/tests/sub_dom.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ftxui.dom alone: elements + Render + Screen, no umbrella, no headers.
import std;
import ftxui.dom;

int main() {
using namespace ftxui;
Element document = vbox({text("dom-only"), separator(), text("row2")});
auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));
Render(screen, document);
const std::string rendered = screen.ToString();
std::println("dom rendered: [{}]", rendered);
if (rendered.find("dom-only") == std::string::npos) return 1;
if (rendered.find("row2") == std::string::npos) return 2;
std::println("ftxui.dom OK");
return 0;
}
26 changes: 26 additions & 0 deletions tests/examples/ftxui-module/tests/sub_screen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ftxui.screen alone: Screen/Pixel/Color/Terminal and the _rgb literal.
import std;
import ftxui.screen;

int main() {
using namespace ftxui;
auto screen = Screen::Create(Dimensions{4, 2});
screen.PixelAt(0, 0).character = "X";
screen.PixelAt(3, 1).character = "Y";
const std::string s = screen.ToString();
std::println("screen: [{}]", s);
if (s.find('X') == std::string::npos) return 1;
if (s.find('Y') == std::string::npos) return 2;

const Color red = Color::Red;
const Color rgb = Color::RGB(1, 2, 3);
if (red == rgb) return 3;
using namespace ftxui::literals;
const Color lit = 0x0102ff_rgb;
if (lit == red) return 4;

if (string_width("abc") != 3) return 5;
if (to_string(to_wstring(std::string("mcpp"))) != "mcpp") return 6;
std::println("ftxui.screen OK");
return 0;
}
26 changes: 26 additions & 0 deletions tests/examples/ftxui-module/tests/sub_util.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ftxui.util alone: Ref/ConstRef/StringRef and AutoReset.
import std;
import ftxui.util;

int main() {
using namespace ftxui;
int backing = 7;
Ref<int> r(&backing);
*r = 9;
if (backing != 9) return 1;

ConstRef<int> cr(5);
if (*cr != 5) return 2;

std::string text = "abc";
StringRef sr(&text);
*sr = "xyz";
if (text != "xyz") return 3;

int guarded = 1;
{ AutoReset<int> reset(&guarded, 42); if (guarded != 42) return 4; }
if (guarded != 1) return 5;

std::println("ftxui.util OK");
return 0;
}
18 changes: 18 additions & 0 deletions tests/examples/ftxui-module/tests/umbrella.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// `import ftxui;` — the umbrella module — plus a real dom render.
import std;
import ftxui;

int main() {
using namespace ftxui;
Element document = hbox({text("compat"), separator(), text("ftxui")});
auto screen = Screen::Create(Dimension::Fit(document), Dimension::Fit(document));
Render(screen, document);
const std::string rendered = screen.ToString();
std::println("rendered: [{}]", rendered);
if (rendered.find("compat") == std::string::npos) return 1;
if (rendered.find("ftxui") == std::string::npos) return 2;
// the separator must actually have drawn something between them
if (rendered.find("compat") > rendered.find("ftxui")) return 3;
std::println("umbrella OK");
return 0;
}
Loading