feat(ftxui): a modules feature for 7.0.3's named modules - #331
Merged
Conversation
FTXUI 7 ships upstream's own module units — an `ftxui` umbrella re-exporting ftxui.component/.dom/.screen/.util — and mcpplibs#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. mcpplibs#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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
FTXUI 7.0.0 added named modules beside the headers it has always shipped: an
ftxuiumbrella that re-exports four sub-modules —
ftxui.component,ftxui.dom,ftxui.screenandftxui.util. #292 brought 7.0.3 into the index but left thoseunits out of the build, after an attempt to compile them unconditionally went red on
the linux gcc leg. This adds them back behind an opt-in
modulesfeature, with atest member, and with a correction to what that red leg was actually telling us.
The descriptor change
The feature adds one glob,
*/src/ftxui/*.cppm, and nothing else. Those five filesare precisely the list in upstream's
cmake/ftxui_modules.cmake, so the set tracksthe release rather than a transcription of it.
The units carry no definitions. Each sub-module textually includes the matching
public headers in its global module fragment and then re-exports the names with
export namespace ftxui { using ... }; the umbrella is fourexport importlines.The module surface therefore layers on top of the same
libftxui.athe default buildalready produces rather than replacing it — both surfaces live in one archive and a
consumer picks either. Measured on the built package, that is 81 compiled units with
the feature and 76 without, a difference of exactly the five module units; the base
source set does not move.
That shape is also why the feature needs no
include_dirsof its own: the#include <ftxui/...>in those global module fragments resolve through thepackage-level
*/include, which mcpp applies to the package's own translation unitsas well as to consumers.
Two mechanism edges are worth naming, because the feature was shaped to avoid them.
A
!exclusion in mcpp is global and out-ranks a feature entry naming the same file,so "exclude in the base, add back in the feature" is not expressible; here it never
arises, because
*.cppmcannot be matched by the base**/*.cppglobs and thefeature is a pure addition. And a feature cannot carry
include_dirs, which as aboveis not needed.
modulesin the descriptor is the declared export set, in the same spelling everyother module package in this index uses. It is worth being clear about what it does
not buy: mcpp validates
[modules].exportsagainst the scanner only for the primarymanifest of a build, never for a dependency's, so nothing in that list is enforced at
a consumer's build. Measured, by deleting
ftxui.utilfrom it and rebuilding with thepackage cache bypassed — which built and passed. It is documentation, not a guard.
What #292's gcc leg was actually reporting
#292's summary reads that GCC 16 cannot consume the module units. Reproducing it says
something narrower, and the difference matters enough to correct here.
The failure is in the CONSUMER's translation unit, not in the package. Each sub-module
puts the public headers — and transitively libstdc++ — into a global module fragment.
A consumer TU that writes
import ftxui;and then also textually#includes astandard header hands gcc two copies of the standard library's declarations, and gcc
16 rejects that at volume:
That is what the reverted commit's smoke TU did —
import ftxui;above#include <string>and#include <gtest/gtest.h>— and the errors above are the samethree, at the same file and line, that its
workspace (linux default)job printedbefore the ~16k that followed. clang accepts the mixed TU, which is exactly why llvm,
macOS and windows stayed green while only the gcc leg failed.
Kept off the textual surface, the module units are fine on gcc. A consumer that pairs
import std;withimport ftxui;and includes nothing textually builds and runs ongcc 16.1.0 and llvm 22.1.8 alike, on both the mcpp version CI pins and the current
release. So the feature is not llvm-only; it carries a consumer-side rule, and that
rule is the same discipline
tests/examples/asio-modulealready documents for its ownmodule consumers.
Upstream's own module CI is llvm-only —
test_modulesin.github/workflows/build.yamlis a one-entry ubuntu plus llvm matrix carrying
# TODO add gcc / msvc, andftxui_modules.cmakestill forces-fmodules-tsunderCMAKE_COMPILER_IS_GNUCXXabove a bare
# TODO: Explain why this is needed.So gcc is untested upstream, whichis worth knowing before leaning on the combination hard. It is not, as far as this
index can measure, broken.
Why it is still off by default
Not because it fails anywhere, but because it is an addition nobody should pay for
unasked: five extra translation units and their BMIs for every consumer of 7.0.3, most
of whom want the headers they already use. Upstream defaults
FTXUI_BUILD_MODULEStoOFF for the same reason, and the consumer-side rule above is something only the
consumer can honour.
On 6.1.9 the feature's glob matches nothing, since there are no
.cppmfiles before7.0.0. That is a warning rather than an error — the same union-of-layouts tolerance
compat.catch2andcompat.redis-plus-plusalready rely on.The test member
tests/examples/ftxui-moduleconsumes the feature and asserts it:import ftxui;with a real render, plus each of the four sub-modules imported alone in a translation
unit of its own, so nothing leans on the umbrella or on a header. Every TU stays on
the module surface, which is the property the member exists to guard as much as the
imports themselves; the file says so, at length, next to the reason.
Because the units build under both compilers, the member needs no toolchain pin and
runs on both linux legs as an ordinary member.
tests/examples/corekeeps coveringthe default header surface at the same version with no feature requested.
Verification
Local, against this checkout, on mcpp 2026.8.27.2 — the version CI pins — with
2026.9.1.1 agreeing throughout.
The member, on both of CI's linux toolchains:
The umbrella, rendering an
hboxof twotextelements around aseparatorlaid outwith
Dimension::Fit, and each sub-module alone:The negative direction, which is the one that decides whether the feature is a feature
at all — the same
import ftxui;source with the feature not requested:The mixed-TU rule, in both directions, on the shape the reverted commit used:
The existing header path, unchanged, with no feature requested, and with the feature
on since the two surfaces share one archive:
And
mcpp test -p corestays green.Lints
check_mirror_urls,check_package_name,check_platform_version_parity,check_duplicate_versionsandcheck_cross_package_refsall pass, andmcpp xpkg parseis clean over all 175 descriptors with 2026.8.27.2, so no indexfloor bump is needed.