feat(mcpp): 用 mcpp.toml 支持多平台构建(与 xmake 并存) - #1
Draft
Sunrisepeak wants to merge 14 commits into
Draft
Conversation
Adds an mcpp build description for XRGUI without touching xmake.lua --
the two coexist, xmake stays authoritative for Windows/MSVC.
Design and every trade-off: .agents/docs/2026-08-12-mcpp-adaptation-design.md
WHAT LANDS
- mcpp.toml (root) + three thin packages under mcpp/pkgs/ that carry the
submodules as path dependencies. The thin packages reach their sources
through `../../../external/...` globs so nothing has to be committed to
the three upstream repos yet; upstreaming later is a one-line change to
`sources` per package.
- Four header-only dependencies vendored as submodules (gtl, nanosvg,
miniaudio, spirv_reflect); magic_enum switches to the index package.
- Source-conformance fixes: 21 in this repo, 11 more in the submodules
exported to mcpp/patches/ with an apply.sh (they live in other repos,
so a commit here cannot carry them).
STATUS
utility (26k LOC / 63 modules) green
vulkan_wrapper (9k LOC / 33 modules) green
react_flow (3k LOC / 11 modules) blocked by a GCC 16 modules bug
xrgui root source issues cleared; blocked
only on 3 unpackaged libraries
The remaining root-package blockers are harfbuzz, msdfgen and mimalloc --
the "heavy library" work the design scoped to P1 as local index packages.
WHY THE SOURCE CHANGES ARE NOT MCPP-ONLY PATCHES
Every fix is a portability fix that MSVC accepts identically, so they are
gain for the xmake build too rather than a private fork:
- unnamed lambdas used as types in module interfaces expose TU-local
entities ([basic.link]p14) -- named consteval helpers instead
- `static` helpers referenced from module-linkage templates, same rule
- `export` on std:: template specializations is ill-formed
- std::exception(const char*) is an MSVC extension -- std::runtime_error
is the portable base (this also fixes reading past a non-NUL-terminated
string_view via .data())
- members named after a type used earlier in the same class
(-Wchanges-meaning) -- qualify the type
- bare size_t under `import std;`
- an interface partition must be `export import`ed by its primary
ONE CHANGE THAT AFFECTS THE MSVC PATH -- NEEDS VERIFICATION ON WINDOWS
mcpp's scanner forbids conditional imports AND header units outright, so
the `#ifdef XRGUI_FUCK_MSVC_INCLUDE_CPP_HEADER_IN_MODULE` /
`import <plf_hive.h>;` branch cannot survive under mcpp in any form. The
`#include` branch is now unconditional in all 16 files and the header-unit
branch is gone. xmake still defines the macro but nothing reads it, so
MSVC now takes the include path. xmake.lua:102 already said "msvc 新版好像
没这问题了,哪天删了" -- this effectively does that, and it should be
re-checked on Windows.
…bmodule [generated_files] was writing external/nanosvg/nanosvg/*.h into the nanosvg submodule's working tree, leaving it permanently dirty. A build must not dirty someone else's repository. The forwarding headers now land in mcpp/shims/ (gitignored, regenerated every build, content still enters the fingerprint) and include_dirs points there instead.
The root .gitignore anchored the pattern to the repo root, so the three thin packages' generated compile_commands.json got committed. Un-anchor it.
I blamed mcpp's scanner for `error: module already declared` on `module : private;`. Reproduced with a 5-line file using mcpp's exact scan command: the error is GCC's. GCC 16 has not implemented private module fragments at all -- plain compilation says "sorry, unimplemented: private module fragment", while the P1689 scan path (-fdeps-format=p1689r5 ... -E) reports the same thing as the far more confusing "module already declared". mcpp only passes it through. The usage was also ill-formed independently: a module unit with a private-module-fragment must be the ONLY module unit of its module ([module.unit]), and mo_yanxi.font also has font.cpp (`module mo_yanxi.font;`). IFNDR, so no diagnostic was owed. Removing the line fixes a real defect rather than working around a tool. Filed mcpp-community/mcpp#421 for the scanner's M1 limits (conditional imports, header units) with this diagnostic passthrough as a minor item.
`git diff` in mo_yanxi_vulkan_wrapper included its own submodule pointer, so the patch led with -Subproject commit fbdb5287... +Subproject commit fbdb5287...-dirty which is not a commit anyone can check out -- on a fresh clone apply.sh would have tried to move the pointer to a bogus revision. Regenerated both patches with --ignore-submodules=all and verified the round trip: reverse- apply clears the four files, apply.sh puts them back and correctly reports the already-applied one as such.
Sunrisepeak
added a commit
to mcpplibs/mcpp-index
that referenced
this pull request
Aug 11, 2026
These three are the only compiled dependencies still missing from the index for XRGUI (Sunrisepeak/xrgui#1), whose root build stops at exactly three lines -- hb.h, msdfgen/msdfgen-ext.h, mimalloc.h -- and nothing else. All three are general-purpose libraries in their own right: text shaping, SDF generation, and an allocator. SHAPES compat.mimalloc A 18 TUs, upstream's own mi_sources compat.harfbuzz A one TU, upstream's own amalgamation compat.msdfgen A + E C++ sources + a generated config header TWO OPPOSITE LESSONS ABOUT AMALGAMATIONS harfbuzz ships src/harfbuzz.cc as its supported "compile one file" path, so `sources` is a single line that cannot drift from a release -- reproducing meson here would mean tracking ~137 .cc files plus a generated config. The backends it also #includes (CoreText/DirectWrite/GDI/GLib/Graphite2) each sit behind their own HAVE_* gate, so naming only HAVE_FREETYPE selects the FreeType bridge and compiles the rest to nothing. mimalloc ships one too, and using it would be wrong. `src/*.c` is wrong three ways and each is a LINK error rather than a compile error: static.c duplicates every symbol, and free.c / alloc-override.c are #included BY alloc.c rather than being TUs. The list is upstream's mi_sources. msdfgen: THE CONFIG IS NOT OPTIONAL core/base.h opens with `#include <msdfgen/msdfgen-config.h>`, which CMake generates -- without it nothing compiles, not even core/. Generating it rather than passing -D flags is also what makes the library and its consumers agree BY CONSTRUCTION: base.h is reached from every public header, so that file is the single place saying which of SVG/PNG/Skia exist. A package's own cxxflags are private and a consumer including <msdfgen/msdfgen-ext.h> would disagree about which declarations exist. THREE KNOBS DELIBERATELY LEFT OFF, EACH FOR THE SAME REASON HB_NO_MT removes HarfBuzz's atomics -- sound only under a single-threading promise a shared package cannot make on its consumers' behalf MI_MALLOC_OVERRIDE a dependency silently taking over the process allocator is not a package's call MSDFGEN_USE_CPP11 adds move constructors to Bitmap, so it changes the layout of a type that crosses the library boundary; a package cannot guarantee every consumer defines it identically CN MIRRORS All three mirrored under gitcode mcpp-res with byte-identical tarballs; sha256 computed twice and the GLOBAL/CN round trip verified (200 + equal digest) for each. VERIFICATION Cold `mcpp test -p <member>` with the CI-pinned mcpp 2026.8.10.3: mimalloc test result ok. 1 passed; 0 failed harfbuzz test result ok. 1 passed; 0 failed msdfgen test result ok. 1 passed; 0 failed Every test asserts behavior, not linkage: mimalloc checks that usable_size reflects mimalloc's own bookkeeping and that zalloc really zeroes; harfbuzz shapes a real buffer and asserts one glyph per input codepoint with clusters tracking offsets; msdfgen generates an MSDF and asserts the field is signed (inside > 0.5, outside < 0.5 -- a blank bitmap fails both) and that the three channels actually differ, which a plain SDF would not. Local lint: check_mirror_urls, check_package_name, check_platform_version_parity, check_cross_package_refs and `mcpp xpkg parse` all pass. Co-authored-by: Sunrisepeak <x.d2learn.org@gmail.com>
mcpplibs/mcpp-index#206 landed compat.harfbuzz, compat.msdfgen and compat.mimalloc, so the root package no longer stops at three missing headers. Getting past them exposed a whole layer those headers had been hiding: 13 more source issues, all now fixed. mcpp build -> 1332 objects, 626 BMIs only react_flow's :endpoint and :modifier still fail Source conformance is CLEARED except for react_flow, which is a GCC 16 modules bug rather than a defect in this tree. DEPENDENCIES ARE ONE TABLE NOW [dependencies.compat] / [dependencies.neargye] / [dependencies.marzer] are folded into a single [dependencies] using dotted `ns.name` selectors. The sub-table form scattered 12 dependencies across 4 sections whose only difference was the namespace -- not the role -- so nothing showed you what this project actually depends on. `compat.glfw = "3.4"` resolves to the same exact identity `(compat, glfw)`. compat.gtest stays under [feature-deps.tests]: that IS a role difference -- it is not resolved at all unless the feature is on. WHAT THE 13 FIXES WERE Same character as the first round -- portability fixes MSVC accepts identically, not mcpp-only patches: - export on std::hash / std::formatter specializations (color.ixx) - `struct ::std::hash<X>{...}` -- GCC rejects the global qualification - friend function templates defined inside a class template, redefined by every instantiation (gui.alloc.ixx, both allocators) - a `using` naming the injected-class-name instead of the base - std::exception(const char*) and std::ifstream(const wchar_t*), both MSVC extensions - `msdf::` qualification used INSIDE namespace mo_yanxi::graphic::msdf - a dependent name missing `typename` - an import reaching a module purview through a #include (allocator2d -- the two imports move into the .ixx, and the header gains a gate so a caller can say it already did them) Two deserve their own note: quad_group's scalar constructor was constrained on `!std::convertible_to<const Ty&, quad_group>` -- deciding whether something converts to quad_group has to consider that very constructor, so the constraint referred to itself. GCC rejects it outright. The replacement is non-recursive; what is genuinely lost is a Ty carrying its own `operator quad_group<T>()`, which nothing here does, and there is no way to ask that question without asking it of this constructor. Noted in the source. -Wchanges-meaning went from per-site fixes to a flag. Naming a member after its type (`vk::fence fence;`, `interp interp{};`) is pervasive in this codebase; it is ill-formed NDR that MSVC allows. Five sites were qualified by hand in the first round and stay that way, but the reachable surface is now dozens, all in someone else's style. GCC ships an opt-out for exactly this legacy shape. Qualifying every site remains the right fix -- upstream's. The extreme case of the same family is #11: a constructor parameter sharing the name of a member made GCC fail to PARSE `vk::allocator&` at all. LINUX CI .github/workflows/mcpp-linux.yml, alongside build_and_dispatch.yml (Windows/MSVC via xmake) -- the two build systems stay independent. utility and vulkan_wrapper are required jobs. react_flow and the root build are continue-on-error, which is a statement about one known blocker rather than a way to ignore failures: the steps still run and still print what failed, so new breakage stays visible in the log; it just cannot gate the branch on a compiler bug. They become required the day react_flow builds. Also caches ~/.mcpp (the GCC 16 toolchain plus compat.* sources are ~500 MB and only move when a pin does) and installs libfontconfig1-dev for src/platform/font.ixx's Linux backend.
THE CLANG EXPERIMENT ANSWERED THE BIG QUESTION Switching the Linux toolchain to llvm proves `recursive lazy load` is a GCC bug and not a defect in this tree: clang compiles react_flow completely, cold, both partition PCMs produced. It then clears the ENTIRE tree -- all 196 modules and every dependency -- and stops only at a missing generated header. But it is not a usable workaround. Once build.mcpp supplies that header, clang runs into libc++ instead: ~100 uses of C++23 library facilities libc++ has not implemented (move_only_function 33, views::enumerate 46, views::stride 11, views::slide 3, is_pointer_interconvertible_with_class 4, const_iterator 3 -- P2278's global alias template, not the member typedef). llvm 20 and 22 are both short. That is a standard library gap, not something to shim. So GCC stays the only viable Linux toolchain and react_flow stays blocked -- but the bug now has a confirmed owner, which is what a GCC report needs. A REAL MCPP SCANNER BUG FELL OUT OF IT clang first failed with `unable to open output file ''` -- an EMPTY path. The cause is a UTF-8 BOM: 8 files here start with EF BB BF, so mcpp's scanner does not recognize the `export module` that follows, the module name comes out empty, and clang's -fmodule-output gets "". It only ever shows up under clang; GCC names BMIs by module name into gcm.cache and never takes that path. BOMs stripped from the 5 files that participate in the build (the 3 under legacy/ do not); GCC re-verified green. Reported on mcpp#421. build.mcpp The asset pipeline from the design's §8.3: scan the normalized SVGs, bin2c each, aggregate assets_summary.h -- all into MCPP_OUT_DIR, never the source tree. bin2c is a dozen lines rather than a dependency on xmake's utils.binary.bin2c. Output is sorted because directory iteration order is unspecified and an unstable summary would flap its content hash and rebuild every consumer. The summary is emitted even with zero SVGs, which is the state of a fresh checkout -- gui.assets.cpp only needs the header to exist, and an empty one compiles. That is what lets the tree build with no Python, Node or slangc. DEPENDENCIES GROUPED BY ORIGIN Still one [dependencies] table with dotted identities, now split into internal (the path-dependency thin packages, which move with this repo) and external (pinned index packages, where an upgrade is its own decision). That is a role difference; namespace was not.
The GCC defect is in partition BMI streaming, so remove the partition. The
contents of manager.ixx fold into :node_interface, which every affected
partition already imported, and `:manager` ceases to exist.
mcpp build --cache off -> Finished in 4.30s, 74 BMIs, no errors
`recursive lazy load` is gone -- not suppressed, not worked around. The
structure that triggered it is not there any more.
This overturns what I wrote in the design doc's §16, which said the project
side could only fix part of it. It could fix all of it; I had stopped at
"remove the unnecessary imports" instead of asking whether the partition
needed to exist.
TWO REAL DEPENDENCY DEFECTS FELL OUT OF THE SAME INVESTIGATION
Neither has anything to do with the compiler bug, and both are gain for the
xmake build too:
- :endpoint and :modifier never needed :manager. `manager` appears only in
`friend manager;` and as a reference parameter of on_pulse_received --
whose three bodies never touch it -- and :node_interface has carried
`export struct manager;` all along.
- Seven files use vk::allocator / vk::allocator_usage without importing
mo_yanxi.vk.util. Those names are in the mo_yanxi::vk NAMESPACE but come
from the mo_yanxi.vk.util MODULE, which mo_yanxi.vk does not re-export.
MSVC let it pass; GCC does not.
COST
:node_interface grows from 780 to ~1190 lines -- a partition boundary is
genuinely lost. The patch is self-contained
(mcpp/patches/mo_yanxi_react_flow.patch), so it reverts cleanly if GCC ever
fixes the underlying defect.
None of this makes the GCC bug go away: 17.0.0 trunk still reproduces, and it
should still be reported. It means xrgui no longer has to wait for it.
With react_flow unblocked the root build reaches far more of the tree, and
the next batch of MSVC-only constructs surfaced. All are portability fixes
that MSVC accepts identically:
- seven files use vk::allocator / vk::allocator_usage without importing
mo_yanxi.vk.util. Those names live in the mo_yanxi::vk NAMESPACE but are
provided by the mo_yanxi.vk.util MODULE, which mo_yanxi.vk does not
re-export.
- key_binding.ixx static_cast's a void* to a function pointer three times.
That conversion needs reinterpret_cast; static_cast between object and
function pointers is ill-formed. MSVC allows it as an extension.
- pipeline_manager.ixx names a non-static data member bare inside a
deducing-this member template. With an explicit object parameter there is
no implicit `this`, so it has to go through `self` -- which the two
preceding lines already do.
[toolchain] windows moves from llvm to msvc@system. This project is MSVC-first
and upstream's xmake MSVC CI is green (Yuria-Shikibe/xrgui), so there is no
reason for the mcpp path to pick an unverified frontend there; mcpp's
x86_64-windows-msvc target probes an installed VS/BuildTools.
STATUS: 333 objects, one translation unit still failing -- and not on a source
defect. image_atlas.util.ixx dies in the ASSEMBLER:
Error: symbol '_ZN8mo_yanxi4mathW8mo_yanxiW4mathW7vector2eqERK...vector2IjEES7_'
is already defined
a module name-mangling collision emitting operator== for
math::vector2<unsigned> twice in one TU. Another GCC modules defect, distinct
from the lazy-load one, and not yet investigated.
vector2's operator== was a defaulted HIDDEN FRIEND inside a class template:
FORCE_INLINE friend constexpr bool operator==(const vector2&, const vector2&)
noexcept = default;
GCC emits that twice for the same specialization under modules, and the
assembler rejects the second:
Error: symbol '_ZN8mo_yanxi4mathW...vector2eqERK...vector2IjEES7_'
is already defined
The member form is the C++20 idiom, synthesises the same == and !=, and does
not duplicate. Same family as the gui.alloc.ixx friend-template fix earlier.
pipeline_manager's deducing-this member templates also become ordinary
const/non-const pairs. That was a hypothesis about the remaining failure and it
did NOT fix it -- kept because the pairs are correct and because one of them
carried a real defect (a non-static member named bare where there is no
implicit `this`).
STATE: clean build gives 334 objects, one failing TU.
renderer.components.ixx cannot read pipeline_manager's BMI:
failed to read compiled module cluster 87: Bad file data
and this is NOT a stale-artifact problem -- it reproduces from rm -rf target.
Bisected as far as: the BMI reads fine on its own, and reads fine under
renderer.components' complete import list. Only the file's own body triggers
it. So it is a third GCC modules defect, distinct from the lazy-load one and
from the mangling collision above, and not yet narrowed further.
The three submodule manifests move out of mcpp/pkgs/ and into the libraries
themselves, so `sources` becomes "src/**/*.ixx" instead of
"../../../external/mo_yanxi_react_flow/src/**/*.ixx".
The reason they were collected in one place was "do not touch the upstream
repos". That reason is gone: mcpp/patches/ already carries four patches, and
the react_flow one deletes a whole partition. Once you are already editing
upstream sources, putting the manifest there costs nothing.
Three things this buys:
- the ../../../ escaping globs disappear
- so does mcpp/pkgs/, a directory whose name reads as "package index" when
it never was one. (The local index planned in the design's §7 was never
built either -- the three heavy libraries went straight to mcpp-index as
compat.* packages instead.)
- upstreaming becomes a no-op: the manifest is already where it belongs
Manifests ship through the patches, so apply.sh stays a required step on a
fresh checkout -- which it already was.
Build behaviour is unchanged: same single failing TU (the GCC BMI cluster
defect in pipeline_manager), same object count.
Sunrisepeak
force-pushed
the
feat/mcpp-build
branch
from
August 11, 2026 23:00
3c8a117 to
823563c
Compare
Sunrisepeak
added a commit
to mcpplibs/mcpp-index
that referenced
this pull request
Aug 11, 2026
…ocator, gtl and plf-hive (#207) These six were the libraries XRGUI (Sunrisepeak/xrgui#1) still reached through git submodules and -I flags rather than through a package manager. Packaging them is not an mcpp-specific accommodation: xmake.lua:44 already reads add_requires("nanosvg", "spirv-reflect", "gtl", "glfw", "miniaudio") so the project's own primary build gets four of them from xrepo packages, and the external/ submodules beside them are vestigial. Two are Khronos/AMD official (SPIRV-Reflect, VulkanMemoryAllocator); the rest are widely used in their own right. THREE SHAPES, ONE OF THEM NEW TO THIS INDEX A header-only + anchor TU gtl, plf-hive B single header + GENERATED impl nanosvg, vulkan-memory-allocator C one upstream TU is the library miniaudio, spirv-reflect Shape B is the new one. Both are stb-style -- the implementation hides behind a macro and upstream ships no .c to instantiate it. Leaving that to the consumer would make the package a header drop rather than something linkable, and would hand every consumer the same duplicate-symbol hazard, so the package generates the TU once and both descriptors state the rule that follows: do not define the macro again (it is a LINK error, so it surfaces late). VMA FORCED A POLICY CHOICE, NOT JUST A SHAPE VMA defaults to VMA_STATIC_VULKAN_FUNCTIONS 1, which references vkBindBufferMemory2, vkGetPhysicalDeviceProperties2 and six more BY NAME. Against a headers-only dependency that is eight undefined symbols -- observed at link time, not predicted. Adding compat.vulkan would have made it link and would have been wrong: it forces a Vulkan loader on every consumer of a memory allocator and fights anyone dispatching through volk. The generated TU selects VMA_DYNAMIC_VULKAN_FUNCTIONS instead, so VMA resolves everything through VmaVulkanFunctions and the package needs the Vulkan headers alone. TWO SMALLER JUDGEMENTS WORTH KEEPING miniaudio links -ldl -lpthread -lm and deliberately NOT -lasound/-lpulse: it dlopen()s its backends, so hard-linking them would break the package on a machine that has neither, for no gain. spirv-reflect exposes BOTH `*` and `*/include`, because spirv_reflect.h:35-37 picks between <spirv/unified1/spirv.h> and the bundled "./include/spirv/unified1/spirv.h". Exposing both makes the two spellings resolve to the SAME header, so a consumer that defines SPIRV_REFLECT_USE_SYSTEM_SPIRV_H cannot silently get a different SPIR-V revision than this .c was written against. VERSIONING THE UNTAGGED TWO nanosvg and plf_hive cut no tags. Following compat.khrplatform (which mirrors the untagged EGL-Registry), each pins a commit archive under a DATE version from that commit's date. spirv-reflect has the opposite problem -- it tags in lockstep with the Vulkan SDK -- so its key drops the `vulkan-sdk-` prefix and lines up with compat.vulkan-headers of the same SDK. TESTS ASSERT BEHAVIOUR, NOT LINKAGE Every test can fail. hive checks that element ADDRESSES survive erasing their neighbours; nanosvg checks rasterized pixel coverage is 2300-2700 for a 50x50 rect and links both halves of the generated TU so a half-instantiated package fails there; miniaudio round-trips a sine through WAV and also checks the peak so silence cannot pass vacuously; spirv-reflect reflects a REAL glslc-compiled shader (embedded as words, no compiler needed on the runner) and adds a negative case so an always-succeed stub cannot pass; VMA drives the virtual allocator and asserts no two live allocations overlap. VERIFIED LOCALLY - six `mcpp test` runs, 1 passed / 0 failed each - lint scripts pass on the six; cross-package refs pass across all 96 - `mcpp xpkg parse` passes for ALL 96 descriptors under the PINNED CI version 2026.8.10.3, downloaded for the purpose rather than trusting the newer local build -- that check is what enforces "floor first, grammar after" - all six CN mirrors return 200 and are byte-identical to GLOBAL, which is what mirror-cn-reachable checks
…d VMA from the index
mcpp-index#207 landed, so these six no longer have to be submodules plus -I
flags. This is not an mcpp-specific accommodation -- xmake.lua:44 already reads
add_requires("nanosvg", "spirv-reflect", "gtl", "glfw", "miniaudio")
so the project's own primary build gets four of them from xrepo packages and
the same-named checkouts under external/ were never what it compiled against.
This catches mcpp up to that and drops the five now-dead submodules.
FOUR PLACES HAD TO STOP INSTANTIATING THE IMPLEMENTATION
These are stb-style libraries: the implementation hides behind a macro, and the
packages now compile it. Defining the macro here as well duplicates every
symbol -- a LINK error, so it surfaces late.
src.backends/miniaudio/audio.cpp drop MINIAUDIO_IMPLEMENTATION
vulkan_wrapper .../util/vma.cpp drop VMA_IMPLEMENTATION
external/spirv_reflect/spirv_reflect.cpp deleted (it existed to compile the
.c as C++)
mcpp.toml sources drop external/spirv_reflect/spirv_reflect.c
TWO INTERFACE DIFFERENCES
nanosvg: this repo wrote <nanosvg/nanosvg.h>, which is a directory some package
managers wrap around it, not upstream's layout -- upstream keeps the headers in
src/ and its own examples write "nanosvg.h". The package follows upstream, so
msdf.cpp now says <nanosvg.h> and the mcpp/shims/ forwarding pair is gone.
VMA: the package builds with VMA_DYNAMIC_VULKAN_FUNCTIONS so it needs the
Vulkan headers alone rather than forcing a loader on everything that allocates
memory. VMA then resolves nothing by name, so vma.ixx's constructor fills in
pVulkanFunctions with vkGetInstanceProcAddr / vkGetDeviceProcAddr when the
caller left it null and lets VMA fetch the rest. It goes in the constructor
rather than at the call sites because the raw VmaAllocatorCreateInfo
constructor is public, so call sites cannot be covered exhaustively.
external/ drops from 12 submodules to 7. VulkanMemoryAllocator is
vulkan_wrapper's own nested submodule -- someone else's repo -- so it is only
removed from the build, not from its .gitmodules.
450 objects build. The one remaining failure is unchanged: renderer.components
still hits the GCC modules defect recorded in the design doc.
…ger one load path
renderer.components imports both mo_yanxi.backend.vulkan.pipeline_manager and
mo_yanxi.graphic.g2d.batch.backend.vulkan. That makes the special members of
vk::descriptor_layout / vk::dynamic_descriptor_buffer reachable through two
independent module paths, and GCC 16.1.0 cannot merge the two views:
pipeline_manager: error: failed to read compiled module cluster 87: Bad file data
Bisected to the exact trigger: the `mr::vector<descriptor_slots>` member of
pipeline_manager_base. Instantiating ANY container over descriptor_slots does
it -- std::vector reproduces it identically, so this is not about mr::vector --
while a plain `descriptor_slots` member does not, because the container is what
forces the element type's special members to be instantiated and serialized.
Letting pipeline_manager see batch.frontend collapses the two paths into one
and the merge never happens. The import is unused by this module, and the
comment in the source says so rather than pretending otherwise.
RULED OUT, each verified by rebuilding the failing TU:
- splitting pipeline_manager into base/graphic/compute modules -- the failure
simply followed whichever module ended up holding pipeline_manager_base
- moving config::create and descriptor_slots' destructor/move out of line
into an implementation unit
- replacing the deduced `auto` return types on get_pipelines /
get_custom_descriptor with explicit ones
- reordering imports so mo_yanxi.vk is loaded first
- -fno-module-lazy (moves the failure to g2d.batch.frontend)
- --param=lazy-modules=1000 (ulimit -n is 1048576; not the constraint)
- narrower shared imports: vk.cmd, vk.sync_processor, type_register,
raw_byte_buffer, g2d.batch.common -- only batch.frontend works
Also drops a duplicate `import mo_yanxi.audio;` from gui/infrastructure/scene.ixx:
the module was imported twice, once plain and once `export import`. That is a
defect on its own terms. It is not what causes the two remaining failures --
removing it changes nothing there -- but it should not stand.
Build goes from 1 failing TU to 2. That is not a regression: ui_manager and
element were never reached before, because ninja stopped at the first failure.
Their BMI closure does not contain pipeline_manager at all (verified against
the .ddi.dd), so this change cannot have caused them. They are the same defect
class at a different site -- string_hash_map<scene_resources> in ui_manager,
where std::unordered_map reproduces it identically, so gtl is not implicated
either.
… two open sites Characterises the defect precisely (container-over-imported-type plus two independent module paths), corrects the earlier claim that this was a disguised recursive-lazy-load, and lists every ruled-out approach with its result so the next attempt does not repeat them.
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.
给 XRGUI 增加一套
mcpp.toml构建描述,不动xmake.lua—— 两套并存,xmake 仍是 Windows/MSVC 的权威路径。完整设计与每一处取舍:
.agents/docs/2026-08-12-mcpp-adaptation-design.mdDraft,用于记录进展。
当前状态
除 react_flow 外,源码符合性问题已全部清零。 剩下的那一个不是「还没修」,是一个七种规避全部无效的 GCC 16 模块 bug。
mo_yanxi_utilitymo_yanxi_vulkan_wrappermo_yanxi_react_flowxrgui根包三个重库(harfbuzz / msdfgen / mimalloc)已进 mcpp-index:mcpplibs/mcpp-index#206(CI 全绿,已合入)。
落地了什么
mcpp.toml× 4 —— 根包 +mcpp/pkgs/下三个薄包,把 submodule 作为 path 依赖引入。薄包用../../../external/...越界 glob 指回 submodule 源码,三个上游仓库一行都不用改;将来上游化时每个包只改sources一行。gtl/nanosvg/miniaudio/spirv_reflect;magic_enum改用索引包。.github/workflows/mcpp-linux.yml—— 与既有build_and_dispatch.yml(Windows/MSVC via xmake)并行。mcpp/patches/*.patch+apply.sh(它们属于别的仓库,本仓库的提交带不走)。新检出的完整流程:
依赖表用点式
ns.name统一成一张原先按命名空间拆成四段(
[dependencies]+.compat+.neargye+.marzer)。现在合成一张:拆表把 12 条依赖散成 4 段,而它们的差别只是命名空间、不是角色 —— 一眼看不全这个工程到底依赖什么。点式 selector 表达的是同一个精确身份(
compat.glfw⇒(compat, glfw))。[feature-deps.tests]里的compat.gtest保持独立:那是角色差异(feature 未激活时完全不解析),不是命名空间差异。为什么这些源码改动不是「mcpp 专用补丁」
每一处都是可移植性修复,MSVC 语义完全等价,对 xmake 侧同样是净收益:
consteval辅助函数static辅助函数被模块链接的模板引用staticexport了std::模板特化 / 用全局限定名做定义export、去掉前导::std::exception(const char*)、std::ifstream(const wchar_t*)std::runtime_error,后者经std::filesystem::pathnamespace …::msdf内又写msdf::限定typename、裸size_tusing写成自身注入类名而非基类#include引入import.ixx,header 加一道门两处值得单独说:
自指约束。
quad_group的标量广播构造带!std::convertible_to<const Ty&, quad_group>—— 判定「能否转成 quad_group」必须先判定这个构造函数本身。GCC 直接拒绝。换成非自指形式后,真正损失的是「Ty 自带operator quad_group<T>()」这种 exotic 情形:本代码库无此类型,而且要问这个问题就绕不开问这个构造函数。已在源码注释写明。-Wchanges-meaning从逐个修改为整体开关。vk::fence fence;、interp interp{};这种「成员名与其类型同名」在本代码库是普遍写法,属 ill-formed NDR 而 MSVC 放行。第一轮我按「类型写限定名」逐个修了 5 处(保留),但接上三个重库后命中面扩大到几十处、且全在别人的代码风格里。GCC 为这个具体的历史写法提供了-Wno-changes-meaning。逐个限定仍是正解,但该由上游做。同族的极端表现:
sub_page的构造参数与成员同名,导致 GCC 在vk::allocator&处直接解析失败(expected ')' before '&')而不是给 changes-meaning 诊断。参数已改名。mcpp 的扫描器同时禁止条件 import 和头单元,所以
#ifdef XRGUI_FUCK_MSVC_INCLUDE_CPP_HEADER_IN_MODULE里那条import <plf_hive.h>;路径在 mcpp 下没有任何保留余地。16 个文件里的
#include分支已改为无条件,头单元分支删除。xmake 仍然定义那个宏,但现在没有代码引用它,于是 MSVC 会走#include而不是头单元。xmake.lua:102的注释本来就写着「msvc 新版好像没这问题了,哪天删了」—— 这次等于替它删了,但需要在 Windows 上确认。Linux CI
.github/workflows/mcpp-linux.yml。任务刻意分两档:mo_yanxi.utility与mo_yanxi.vulkan_wrapper:它们是绿的,必须保持绿。react_flow与根包:这不是「忽略失败」,而是一句关于单一已知阻塞的陈述。步骤照常运行、照常打印失败内容,新增的破坏在日志里可见;只是不能拿一个编译器 bug 去 gate 分支。react_flow 能编的那天就改成 required。另外缓存了
~/.mcpp(GCC 16 工具链加 compat.* 源码约 500 MB,只在 pin 变动时才变),并装libfontconfig1-dev(src/platform/font.ixx的 Linux 字体后端)。✅ react_flow 的 GCC bug 已解决 —— 在项目侧
那个
recursive lazy load不用等 GCC 了。做法很直接:bug 出在分区 BMI 的流式化上,所以消掉那个分区 —— 把manager.ixx并进:node_interface(相关分区本来就 import 它),删掉:manager。不是抑制、不是绕过:触发它的结构不存在了。补丁自包含(
mcpp/patches/mo_yanxi_react_flow.patch),GCC 修好后可原样回滚。代价是:node_interface从 780 行涨到约 1190 行,少了一个分区边界。这不改变「GCC 有 bug」这个事实 —— 17.0.0 trunk 仍复现,材料在
mcpp-community/mcpp-gcc(含插桩、trace、被测试套件否决的补丁分支)。改变的是 xrgui 不必等它。编译器侧那条路为什么走不通
为查清这条,建了
mcpp-community/mcpp-gcc,从源码编了 GCC 17 并加插桩。结论:lazy_load_binding持有(不是我最初以为的分区 bulk 读)finish_member_declaration,"Don't add decls after definition"),且g++.dg/modules从 4 个失败涨到 17 个守卫同时守着 section 顺序和类定义期状态,只放宽前者会让成员被塞进半成品类。
又一层可移植性缺陷(已修)
react_flow 通了之后构建推进到更深处,暴露出下一批 MSVC-only 写法,全部是 MSVC 语义等价的可移植性修复:
import mo_yanxi.vk.util;——vk::allocator/vk::allocator_usage在mo_yanxi::vk命名空间却由mo_yanxi.vk.util模块提供,主接口不重导出key_binding.ixx用static_cast把void*转函数指针(3 处)—— 必须reinterpret_castpipeline_manager.ixx在 deducing-this 模板里裸用非静态成员 —— 有显式对象参数就没有隐式thisWindows 默认改为 MSVC
[toolchain] windows从llvm@20.1.7改为msvc@system。理由是你指出的:这个项目本来就是 MSVC-first,上游 xmake 的 MSVC CI 是绿的,mcpp 侧没理由在 Windows 上换一个未经验证的前端。image_atlas.util.ixx死在汇编阶段:模块 name-mangling 碰撞,
math::vector2<unsigned>::operator==在同一个 TU 里被发射了两次。又一个 GCC 模块缺陷,与 lazy-load 那个无关,尚未调查。关于 Windows 的 xmake CI:本 PR 上失败,但未改动的 master 上以逐字相同的错误失败(同文件、同行号 61/94/234/257-259),所以不是本 PR 的回归。该 workflow 从
aka.ms/vs/18/insiders拉 preview MSVC 14.52,是个移动靶。clang 实验的结论(llvm@20 / llvm@22,Linux)
recursive lazy load是 GCC 独有的。 clang 冷构建完整编过 react_flow,两个分区 PCM 都产出;继续往下越过了整棵树(196 个模块 + 全部依赖),只停在当时还没实现的资产管线。这把归属钉死了 —— 是 GCC 16 模块实现的 bug,不是本仓代码问题。但 clang 不能作为绕道。 补上
build.mcpp之后,撞的是 libc++:std::move_only_functionstd::views::enumeratestd::views::stridestd::views::slidestd::is_pointer_interconvertible_with_classstd::const_iterator(P2278 全局别名模板)约 100 处,llvm 20 与 22 都一样缺。这是标准库缺口、不是能补 shim 的量级。GCC 仍是 Linux 上唯一可行的工具链。
若将来 mcpp 支持 clang + libstdc++(Linux 经典搭配),这个结论要重算 —— 那时这些设施由 libstdc++ 提供,而 GCC 的模块 bug 又不在 clang 前端里。
顺带查出一个 mcpp scanner 的真 bug:UTF-8 BOM
clang 最初报的是
unable to open output file ''—— 空输出路径。追下去是 UTF-8 BOM:仓库里 8 个源文件以EF BB BF开头,mcpp 扫描器因此认不出紧随的export module,模块名为空,clang 的-fmodule-output=拿到空串。只在 clang 下暴露 —— GCC 按模块名把 BMI 存进
gcm.cache/,不走这条路径。已剥掉参与构建的 5 个文件的 BOM,GCC 侧复验仍绿。已补进 mcpp-community/mcpp#421。build.mcpp:资产管线已落地扫
properties/assets_raw/gen/**/*.svg→ bin2c → 聚合assets_summary.h,全部写进MCPP_OUT_DIR,不碰源码树。没有 SVG 时也生成空 summary —— 那正是全新检出的状态,gui.assets.cpp只要求这个头存在,空的能编过,这是让整棵树不需要 Python/Node/slangc 就能构建的前提。剩余阻塞:react_flow / GCC 16
recursive lazy load已定位触发点:
:manager分区在导出的类里声明了std::vector<...>成员。任何同时import :manager且随后惰性需要std::vector的分区都会递归。诊断实验:把import :manager;从node.spec.ixx去掉,该 TU 立刻编过。试过并全部无效的 7 种规避:
import std提前 / 强制触碰std::vector/ GMF#include <vector>/-fno-module-lazy/ 给 manager 补import std/--param=lazy-modules=1000/ 降级 gcc@15(更糟,gcc 15 连import std都预编译不出来)。xrgui 有三处
export import mo_yanxi.react_flow;(scene.ixx/overlay_manager.ixx/text_tree.react_flow.ixx),都在核心 GUI 层,绕不过去。下一步只有两条:给 GCC 提 bug(需先剥最小复现),或把 react_flow 的 6 个分区拆成独立命名模块。后者不是机械改动 —— 同模块的分区之间可以看到彼此的非导出实体,拆成独立模块后只有
export的才可见,需逐个核对跨分区用法。相关
slang包(shader 再生成需要slangc)更正:
module : private;的归因PR 初版把
error: module already declared归给了 mcpp 的扫描器。用 5 行文件逐字复现 mcpp 的扫描命令后确认,这是 GCC 的错误:GCC 16 根本没实现私有模块片段,编译路径给的是sorry, unimplemented: private module fragment,P1689 扫描路径对同一件事给的是误导性的module already declared。mcpp 只是透传。而且这处用法本身就不合规:private module fragment 是 C++20 特性([module.unit]),但「带私有片段的模块单元必须是该模块唯一的模块单元」——
mo_yanxi.font还有font.cpp(module mo_yanxi.font;)。IFNDR,所以谁都没义务报。删掉那行修的是真问题。