feat(mcpp): 基于上游的 mcpp 适配(review 用,勿合) - #8
Conversation
Unmodified master does not compile with the toolset its own workflow
installs. Measured, not inferred: a probe branch carrying one markdown file
and nothing else fails on 14.52.36629 with
typesetting.rich_text.argument.ixx: error C2039:
'vec2': is not a member of 'mo_yanxi::math'
plus the C4430 / C3646 / C2440 cascade behind it, and no other file in the
tree reports anything.
The file uses math::vec2 and nothing it already imports re-exports it:
graphic.color re-exports math.vector4, and vector4 only plain-imports
mo_yanxi.math. It built on 14.52.36510 because names leaked across the module
boundary there; 36629 does not leak them. So the import is written down.
Nothing to do with the rest of this branch — this is the one change
unmodified master needs today, and it is worth taking on its own.
…for it
xmake.lua asked for this: "msvc 新版好像没这问题了,哪天删了,,,".
The macro chose between `#include <gch/small_vector.hpp>` and
`import <gch/small_vector.hpp>;` in sixteen files, and add_xrgui_core_deps()
defined it — so xmake was taking the HEADER UNIT branch, and this commit
keeps the `#include` one. That is not a no-op, and the three source changes
here are its price rather than independent fixes:
A header unit's declarations are reachable to whoever imports the module; a
global module fragment's are discarded unless the purview names them. gch
spells the container's operator== and its iterator's operator-(a, b) as free
function templates rather than hidden friends, so once the header moves into
the GMF, an importer has neither.
typesetting.rich_text.ixx operator== written out, comparing features
through data()/size() rather than the
container. A defaulted one is synthesized at
each odr-use — in every module that compares a
layout_config — and fails there. Writing the
body is not enough on its own: gch::operator==
is itself a template and instantiates at the
call, dragging std::equal and its need for
operator-(a, b) along. `const hb_feature_t*`
has none of that.
typesetting.ixx assign(data(), data() + size()) for the same
reason: vector's range constructor sizes the
allocation with operator-(a, b).
typesetting.segmented_layout includes the header itself, rather than
hoping to inherit it from rich_text.
That these are consequences and not pre-existing breakage is measured: the
baseline probe (unmodified master, macro on) reports no C2243 and nothing out
of <xutility>, and its log shows small_vector.hpp.ifc — the header unit doing
exactly the job this commit gives up.
WHAT IT BUYS. Two things. The macro's live branch was the one nobody had
exercised in a long time — the CI on this branch is what says the surviving
one compiles. And header units are one of the two constructs mcpp's scanner
cannot follow, which a conditional does not help with: it refuses an `import`
inside one whether or not the branch is live.
The hardest part to place was not the fix but the report: cl blames the
template definition (ui.util.ixx:140) while the instantiation is in
text_edit.ixx, label.ixx, and one more for every future comparison — so
fixing it at the importers is whack-a-mole.
Two headers are compiled here by defining an implementation macro before
including them. Both become a problem when the library comes from a package
that already did it, so both get an opt-out — and NEITHER macro is defined by
xmake, so the xmake build is unchanged byte for byte.
XRGUI_MINIAUDIO_IMPL_PROVIDED MINIAUDIO_IMPLEMENTATION
compat.miniaudio compiles upstream's own miniaudio.c. Instantiating it
here as well defines every ma_* symbol twice — and only at LINK time,
which is a long way from the cause.
XRGUI_MSDFGEN_NO_CPP11 MSDFGEN_USE_CPP11
compat.msdfgen is built with it OFF and says so through the
msdfgen-config.h that every public header includes — which is precisely
how that package and its consumers agree on which declarations exist.
Defining it here anyway breaks that agreement one-sidedly:
Contour::addEdge(EdgeHolder&&) gets declared for this TU and exists
nowhere, surfacing as an unresolved external. xrepo's msdfgen IS built
with it, so xmake keeps the define.
Consumer-supplied rather than auto-detected: whether the implementation is
already compiled is a property of the build that provides the library, and
only that build knows it.
Four changes to the xmake workflow, all about it answering the same question
twice in a row. None of them is about mcpp.
runs-on: windows-2025 `windows-latest` currently maps to the VS 2026
image — currently. This build is sensitive to the toolchain, so it says
which one it wants.
assert VCToolsVersion `-vcvars_ver=14.52` is a request; nothing checked
the answer. Dropping to the image's 14.51 is a real failure (C1001 ICE
in template.cpp at vector2.ixx:67, "IFC import detected") and is
presumably why this workflow reached for a preview at all — so a 14.51
answer now fails the job. The exact build number moves whenever
aka.ms/vs/18/insiders does, which is Microsoft's schedule and not a
reason to fail a commit: that is a ::warning:: and the run continues,
while staying the first thing to suspect when this job reddens on an
unrelated change.
concurrency + cancel Each run installs VS 2026 Insider and the Vulkan
SDK before compiling anything. A superseded run is a very long slot
spent on an answer nobody wants.
pull_request (any base) It only ran for PRs targeting master, so a PR
into a topic branch got no build at all.
9259436 to
e78d4f4
Compare
review 修正:第 1 个 commit 原来的说法是错的原来的拆分把四处源码改动都算成「未改动 master 就编不过」。核实之后只有一处是。 拿 外加它的 C4430 / C3646 / C2440 级联,而且全树只有这一个文件报错。日志里:
原因很直接:那个宏在 于是重排成:
这样每个 commit 都能单独编过,而且第 1 个是上游现在就能拿走的最小改动。
|
Adds a second, independent way to build this project. Everything here is
additive — xmake.lua and the xmake workflow are untouched by this commit, and
the two builds share no state.
mcpp.toml a transcription of xmake.lua, target by target:
add_xrgui_target_options -> [target.windows.build],
add_xrgui_core_deps -> sources / include_dirs /
defines / dependencies, the three target() blocks
-> [targets.*]
build.mcpp what rule("media.svg_to_bin") + before_build did,
plus add_syslinks via mcpp::link_lib
mcpp/<submodule>/mcpp.toml the three submodules' own xmake.lua, likewise
mcpp/patches/ one six-line patch, applied by CI
.github/workflows/ the MSVC job: library + hello + example + tests
.agents/docs/ why each of the above says what it says
WHY THE MANIFESTS LIVE IN mcpp/ RATHER THAN IN THE SUBMODULES: those are
other people's repositories. The cost is that their globs have to reach back
out (`../../external/...`); moving each file into its submodule root and
dropping the prefix is the whole of what upstreaming them would take.
The dependency set is resolved from an index rather than vendored: the three
submodules stay submodules, and everything else (freetype, harfbuzz, msdfgen,
miniaudio, plf_hive, small_vector, magic_enum, ...) comes from packages.
magic_enum is taken from the index rather than from its submodule's
module/magic_enum.cppm, which wraps `import std;` in an #ifdef — the index
package carries a scan_overrides for exactly that, which is cheaper than
patching someone else's repo.
The MSVC toolset is pinned to 14.52.36629 (VS 2026 Insider) for the same
reason the xmake job pins one, and asserted the same way.
e78d4f4 to
6b4acf3
Compare
commit 1 单独能让上游 master 编过 —— 实测,不是推断
两个探针只差这一行。所以第 1 个 commit 是上游今天可以单独拿走、而且拿走就能修好构建的那一个。
|
CI 全绿,分支就绪(仍然:不合)修正后的系列(注释订正已折进第 5 个 commit,不另留一条"修我自己的注释"):
这个分支现在有什么
已知未测commits 2–4 的单独可编性没有单独跑过 CI —— 系列是按每个 commit 自洽排的, 提上游前还要做的
|
The job becomes a two-leg matrix over where the compiler came from. Same
toolset version on both (14.52.36629), so any difference between them is
about origin and nothing else.
system the machine's Visual Studio, via VSINSTALLDIR. Unchanged.
managed an xim payload. No Visual Studio installed, no vcvars, no
VSINSTALLDIR.
WHY BOTH RATHER THAN A SWITCH.
The system leg is the only place mcpp's "a declared answer outranks a probe"
precedence (mcpp#432/#434) can be posed at all: this runner has TWO Visual
Studios — the image's release-channel Enterprise 14.51 and the Insider this
job installs — and mcpp has to pick the one VSINSTALLDIR names while vswhere
still ranks the other first. mcpp's own CI has one VS and cannot ask that.
The managed leg gets something the system leg cannot have: a pin that is
real. `aka.ms/vs/18/insiders` always serves the newest Insider, so the system
leg can only ASSERT 14.52.36629 and warn when Microsoft moves it — it cannot
obtain it. The payload is content-addressed by sha256, so `14.52.36629` there
means the same bytes next month. It also drops a 4m06s install from that leg
(measured on run 31980964778) and replaces it with ~250 MB that the existing
~/.mcpp cache already covers.
TWO ORDERING DETAILS, both of which would have been silent:
The toolset install runs AFTER the cache step, not before. The payload lands
in ~/.mcpp, which is what the cache restores — installing first downloads a
quarter of a gigabyte and then has it overwritten.
The manifest rewrite ASSERTS that it changed something. Without that, a
manifest that stops saying `windows = "msvc@system"` — renamed key,
reformatted line — turns the managed leg into a second copy of the system
leg that passes while proving nothing.
Artifacts are uploaded per leg: upload-artifact@v4 refuses two uploads under
one name, and the binaries are not interchangeable anyway.
Records what the workflow comments say, in the place a reader looks for rationale rather than mechanics: the managed leg exists because the system leg's pin is an assertion rather than a pin, and the system leg stays because it is the only place mcpp#434's precedence can be posed against a real alternative — this runner has two Visual Studios, mcpp's own CI has one. Also the two ordering traps, both silent: installing the toolset before the cache step downloads a quarter of a gigabyte and then has it overwritten, and a manifest rewrite that does not assert it changed something turns the managed leg into a second system leg that passes while proving nothing.
`[toolchain] windows = "msvc@<toolset>"` auto-installs — it resolves through the same `autoInstall` path as any other dependency (prepare.cppm:1364) and drags in the `xim:windows-sdk` the package declares. So the explicit `mcpp toolchain install` step was not just redundant: it meant this leg would pass even if that path were broken, which is the one thing it exists to demonstrate. `mcpp why toolchain` goes through prepare_build, so the assertion step is where the toolset is obtained on a cold cache — which is also why it sits after the cache step rather than before it. Removing a step and getting a stronger claim out of it, rather than the other way round.
edcc244 to
1faea50
Compare
…tires mcpp 2026.9.9.1 (mcpp-community/mcpp#594) fixes four defects this port had been working around or was about to be broken by. Taking the version lets three things go away, and each is deleted here rather than left as dead weight. A FLOOR, not a preference. src/font/font.ixx uses the private module fragment `module : private;`, and from 2026.8.18.1 through 2026.9.8.1 mcpp read that as an implementation partition named ":" -- "file already provides module mo_yanxi.font\; cannot also provide ':'". A regression from mcpp#433. This job is green today only because it was pinned below the break, so the whole range is unusable and the pin has to move past it, not merely forward. The BOM strip is gone. mcpp now consumes a byte-order mark where bytes become lines, so the submodules keep theirs and this job no longer rewrites checked-out sources before building them. MSVC writes a BOM by default; the workaround was against ordinary input. The submodule patch is gone, replaced by a `[scan_overrides]` entry in mcpp/mo_yanxi_vulkan_wrapper/mcpp.toml. That is the escape hatch mcpp documents for this exact shape (its own example is fmt guarding `import std;` behind `#ifdef FMT_IMPORT_STD`), and mcpp audits the declaration against the compiler's P1689 scan every build, so it cannot drift from the source it describes. The declaration is true on MSVC, where __cpp_lib_stacktrace is defined; this manifest builds a Windows/MSVC target and does not claim more. The Windows system libraries move from build.mcpp to `[target.windows.runtime] libraries`. They were only ever expressed as a C++ program because a conditional table carrying just a `[target.<pred>.runtime]` was parsed and then discarded -- silently, so the natural spelling looked like it worked. mcpp#594 applies it and adds unknown-key reporting to `[runtime]` too. What stays in build.mcpp is vulkan-1, whose SEARCH PATH genuinely depends on what is installed on the machine and so cannot be written down in advance. Verified before pushing: xrgui configures clean on Linux for the first time -- 1062 compile commands, zero scanner errors. Previously the scan always ended on either font.ixx or stack_trace.cpp.
The previous commit replaced mcpp/patches/ with a `[scan_overrides]` entry.
That was wrong, and mcpp's own audit is what said so -- on clang/Linux:
error: module-graph divergence in obj/stack_trace.o:
planned : provides [<none>] imports [mo_yanxi.meta_programming]
compiler: provides [<none>] imports [mo_yanxi.stack_trace]
Two defects in one declaration. An implementation unit imports the module it
belongs to, which the declaration never named -- so it would have failed the
audit on MSVC too, where the guarded import IS live and the compiler reports
both. And the guard is __cpp_lib_stacktrace, which MSVC defines and clang here
does not, so no single fixed declaration is true on both.
`[scan_overrides]` is one answer, audited against the compiler every build. A
conditional import has two. The patch hoists the import out of the `#if`, which
is true on every compiler and keeps a Linux leg reachable -- and that is the
direction this port is going, so the patch stays. The real fix is the same hoist
upstream in mo_yanxi_vulkan_wrapper, after which neither is needed.
The version bump, the BOM-strip deletion and the `[target.windows.runtime]
libraries` move from the previous commit all stand.
…ility fixes Three changes, all aimed at making the mcpp build reach a compiler other than MSVC. Measured on Linux with clang 22.1.8 and GCC 16.1.0. build.mcpp applies mcpp/patches/ itself, during prepare and therefore before the module scan that one hunk exists to satisfy. It was a step in mcpp-windows.yml, which fixes the tree for one job on one platform and leaves a local checkout -- or a Linux/macOS leg -- with nothing. Idempotent via `git apply --check`, and a patch that passes --check and then fails to apply is reported rather than swallowed. Same reason the asset generators live there. The patch grows a second hunk, for util/exception.ixx in mo_yanxi_vulkan_wrapper. Three types derived from std::exception and called `std::exception(const char*)`, which is an MSVC extension -- the standard type has no such constructor, so the file did not compile on clang or GCC at all. They now derive from std::runtime_error, and the three `what()` overrides are deleted: each only forwarded to the base, and each was declared without noexcept over a noexcept base, which is separately ill-formed. This also fixes a real defect rather than only a portability one -- the argument was `msg.data()` on a std::string_view, not guaranteed NUL-terminated, so a view over part of a larger buffer produced a message that ran past its end. transparent_span.ixx uses two C++23 library features clang has not got: std::is_pointer_interconvertible_with_class -- a library function over a COMPILER builtin, and clang 22 has neither, so no choice of standard library supplies it (measured against libc++ and against GCC 16 libstdc++). The four requires-clauses now read one exported constant, so the branch is in one place and there is a single line to delete when clang grows the builtin. Where it cannot be checked the constructors stay available, which is a real loss and is said so in the comment. std::const_iterator -- P2278, which libc++ has not shipped, neither the alias nor basic_const_iterator. The two aliases and the four accessors over them are guarded on __cpp_lib_ranges_as_const. Nothing in this repository calls them, so a local reimplementation of basic_const_iterator would be iterator boilerplate written for no caller. Verified: with a clean submodule, `mcpp build` patches it and configures xrgui with 1062 compile commands and no scanner errors; a second run is a no-op.
…C was lenient
Driving the mcpp build to a second compiler, so the Linux and macOS legs have
something to run. Measured against clang 22.1.8 + libc++ on Linux; 1062 compile
edges, and this takes the failures from most of the project to one.
TWO KINDS OF CHANGE, and they are not the same kind.
(a) NON-PORTABLE CODE, rewritten to standard C++. No #if, no fallback, and MSVC
gets the same code -- in several cases a real defect goes with it. These are
not concessions to clang; they were bugs that only one compiler diagnosed.
std::exception(const char*) MSVC extension -> std::runtime_error.
Also fixes a string_view::data() that
was not NUL-terminated.
std::ifstream(const wchar_t*) MSVC extension -> std::filesystem::path
bare `size_t` relied on a transitive include
friend function template inside a redefined by every instantiation
class template (2 allocators)
`using <injected-class-name>::m` named its own class, meant the base
missing `typename` (2 sites) dependent name
missing `template` keyword dependent template name
std::conditional_t recursion instantiates BOTH arms; the tail ran
even when the head already matched
self-referential constraints (2) `!convertible_to<T, ThisClass>` cannot
be answered without answering itself
static_cast<fn-ptr>(void*) not a standard conversion;
reinterpret_cast is
unqualified member in an explicit there is no implicit `this`
object member function
implicit deduction guide forming built from every constructor whatever
`const void&` its requires-clause says
[[clang::musttail]] where the the attribute is a requirement, not a
signatures differ hint, and it could never hold here
missing `import` (8 files) module visibility is not transitive
(b) STANDARD CODE a library has not caught up with. The code is right; libc++
has not shipped the facility. These route through two shims that
mcpp/mo_yanxi_utility now adds -- `using std::X;` where the library has X and
a local definition only where it does not, so MSVC and libstdc++ compile no
fallback code at all, and every call site reverts to `std::` by deleting one
#if.
mo_yanxi.views enumerate, stride, slide, chunk (44 call sites)
plus ranges::range_const_reference_t
mo_yanxi.functional move_only_function (24 call sites)
The shims live in the LOWEST package: xrgui, mo_yanxi_vulkan_wrapper and
mo_yanxi_utility itself all need them, and tile.ixx RETURNS a stride/slide
view so it cannot fall back to an index loop. One copy, three consumers.
Also here: P2077 heterogeneous erase (3 sites, find-then-erase instead),
P2278 const_iterator / const_iterator_t (guarded, or spelled differently),
<stacktrace> (returns an empty trace, which every caller already handles),
and an aligned `operator new` clang reports as ambiguous between libc++'s
declaration and the one implicitly declared per TU (7 files include <new>).
Both shims were tested standalone against the real call shapes, on clang AND on
GCC, before a single call site moved: same test, same result, one running the
local implementations and one running std::.
The submodule patches carry the same split and say which hunk is which.
NOT DONE. One edge still fails: object_pool.ixx, through gtl's
`friend struct hashtable_debug_internal::HashtableDebugAccess` -- clang reports
no such struct in a namespace where it is plainly declared. gtl is an index
package (compat.gtl 1.2.0). The trigger is not isolated yet: gtl in two module
fragments compiles fine, and the three other modules using the same parallel_*
containers all compile. What is different about the failing one is that its
mapped type is a nested class of the class template being defined.
Windows is unverified for this commit. Every change above is
non-portable-to-portable, so MSVC should be at least as happy -- but that is
reasoning, not measurement, which is what this push is for.
The two shims test __cpp_lib_* macros to decide between `using std::X;` and a
local fallback. Those are PREPROCESSOR macros and come from <version>; `import
std;` does not define them, because a module exports no macros.
So every #if was false on every compiler and the fallback was always chosen --
the shim looked like a shim and never was one. Windows CI caught it:
views.ixx(184): error C2338: static assertion failed:
"enumerate: see shim_base_message"
MSVC has std::views::enumerate, but took the local one, whose forward+sized
requirement rejects the filter_view that stack_trace.cpp pipes into it. Linux
never showed this because the fallback is also what clang needs.
My standalone test for the shims did not catch it either, and could not have:
it ran the same expressions on both toolchains and checked the results matched,
which they did -- both were running the fallback. The test now asserts WHICH
path was taken:
#if defined(__cpp_lib_ranges_enumerate)
static_assert(std::is_same_v<decltype(mo_yanxi::views::enumerate),
decltype(std::views::enumerate)>);
#endif
and reports it: gcc/libstdc++ "defined -> aliased to std", clang/libc++
"absent -> local fallback". Before this fix gcc said the second.
Four files were missing the include -- all four written in this branch. Every
pre-existing file in the tree that tests a __cpp_lib_* macro already had it.
The rerun trigger covered mcpp/patches/*.patch only. But the patch also has to
be re-applied when the SUBMODULE SOURCES change under us -- `git checkout`
inside a submodule reverts it, and a cached build.mcpp then does not notice.
What that looks like is not "a patch is missing". It is the scanner refusing a
file for the hunk that exists to satisfy it:
error: scanner errors:
.../stack_trace.cpp:9: import statement inside conditional
preprocessor block (forbidden in M1)
with nothing pointing at the patch. CI never sees it -- it checks out fresh
every run -- so this is a defect only a developer meets, which is the kind that
survives longest.
…ordering fixes
THE GTL ONE, which is the interesting one.
gtl's raw_hash_set carries
friend struct hashtable_debug_internal::HashtableDebugAccess;
and clang could not find that name -- declared a few lines above it in
phmap.hpp, in the very namespace the diagnostic says it searched. Reduced to a
standalone project, it needs three things at once: a class template, a mapped
type that depends on that template's parameters, and the instantiation triggered
from a DIFFERENT translation unit than the one that included the header. Remove
any one and it compiles. Partition or not makes no difference -- an earlier
guess that it did was wrong and the reduction disproved it.
So the code is legal and the header is fine; this is a clang defect. Four
candidate fixes were measured on the reduction:
explicit instantiation in the including TU PASS
ODR-use of the specialisation in that TU PASS
consumer also includes phmap.hpp PASS
forward-declaring HashtableDebugAccess FAIL
The last one failing is what says this is not ordinary name lookup. What the
three passes have in common is where the INSTANTIATION happens, so the fix is to
move it there: one explicit instantiation of the single specialisation this
project uses, in object_pool.ixx, which is the only TU that includes phmap.hpp.
That keeps gtl. The alternatives were dropping to std::unordered_map, or pulling
phmap.hpp into every consumer's global module fragment.
THE OTHER THREE, all standard rules MSVC does not enforce:
scene.ixx update_entry::operator<=> returns std::strong_ordering rather than
`auto`. std::less uses the synthesised operator<, and a deduced
return type is not usable before the function is defined -- which
bites when the use is reached while instantiating something else
(here, ranges::sort). Two pointers compare as strong_ordering, so
naming it costs nothing.
scene.ixx util::update_insert / update_erase are declared `export`. Without
it the forward declarations had module linkage, the exported
definitions in :element were a redeclaration that cannot raise
that, and the friend declarations matched neither -- so the
definitions lost their access to scene's private members. Both
callers are in other modules, so exported is the side to agree on.
three files gained a missing import; module visibility is not transitive.
`mcpp build --features linux-desktop` completes, and the binary runs:
Platform subsystems initialized
Initializing audio system
Initializing render context
[Vulkan] Instance Create Succeed - 30ms
[Vulkan] Found 2 physical device(s), starting evaluation...
-> NVIDIA GeForce RTX 4080 (Score: 1163840)
[Fail] Missing Device Extensions: {"VK_KHR_maintenance9"}
-> llvmpipe (LLVM 20.1.2) (Score: 147456)
[Fail] Missing Device Extensions: {"VK_KHR_maintenance9"}
[Vulkan] Failed to find a suitable GPU
... teardown ... exit=0
It stops at a driver capability, not at anything this branch is about: this box
runs NVIDIA 550.144.03 / Vulkan 1.3.277 and no device here reports
VK_KHR_maintenance9. Platform init, fontconfig, GLFW, audio, the Vulkan instance
and device enumeration all work, and the shutdown path runs clean.
One line of that log is the best evidence in it:
teardown_stack::push(std::string, mo_yanxi::move_only_function<void ()>)
That is this branch's type-erasure implementation, in a frame, at run time.
WHAT THIS COMMIT FIXES, all standard rules MSVC does not enforce:
arrow_elem.ixx, file_selector.cpp
animator<float, 1/20.f, 0, 0> -- an int argument for a `float` non-type
parameter. Integral-to-floating is not allowed in a converted constant
expression, so the alias failed and took eleven cascading errors with it.
renderer.cpp
.clearValue = param, where clearValue is the VkClearValue UNION and param
converts to VkClearColorValue -- its `color` member. Now named.
function_manipulate.ixx
a lambda with no capture-default used an enclosing constexpr variable.
Passing an index_sequence by value odr-uses it even though the type
carries everything.
gui.alloc.ixx
the two allocators' operator== called mimalloc's operator==, which is
declared in the GLOBAL MODULE FRAGMENT and so is not visible where these
templates instantiate from another TU. They call is_equal now -- which is
what aligned_heap_allocator in the same file already did.
infrastructure.element.cpp
includes <gch/small_vector.hpp> itself. gch declares its iterator's
operator== as a namespace-scope template rather than a hidden friend, and
another TU's global module fragment does not carry it -- so a range-for
over a small_vector failed on `begin != end`.
elem_containers.ixx
extract() required `{ c.erase(c.begin()) } noexcept`. std::vector::erase
is not marked noexcept by the standard -- MSVC's library adds it, libc++
does not -- so the function simply disappeared. It now requires what
actually makes erase non-throwing: a nothrow-move-assignable value type.
eight files gained a missing import.
AND ONE DEPENDENCY. src/platform/font.ixx calls fontconfig under `__linux__`,
so the link needed it. It comes from the index (freedesktop.fontconfig 2.15.0),
not from the host, behind a `linux-desktop` feature -- mcpp has no
platform-conditional [dependencies] table, and that package's recipe has a linux
section only, so an unconditional entry does not resolve on Windows.
… run
Two defects in the asset generators, both found by running the built binary
rather than by building it.
`python` is not necessarily Python 3. This machine has 2.7.18 on PATH and 3.12
as python3; the generators are Python 3, so invoking `python` produced
SyntaxError: Non-ASCII character ... no encoding declared
and the failure was INVISIBLE. run_generator writes to stderr, mcpp echoes
build.mcpp's output only when it exits non-zero, and it does not. The build then
linked an application with no compiled shaders, which died at startup inside
createShaderModule -- a crash with no connection to its cause. CI never sees
this because xlings installs a `python` that is Python 3.
The second is recorded as a comment rather than fixed, because fixing it needs
a decision this file should not make on its own: a tool that RESOLVES is not a
tool that RUNS. xlings puts shims on PATH for packages declared by OTHER
projects, and running one prints "slangc is not installed in this subos" and
exits non-zero. have_tool() sees the shim and says yes; run_generator() reports
the failure to a stderr nobody reads; the build links an app with no shaders --
the same end as above, from a different start.
That is the third time the same shape has cost real time here: the check that
cannot fail. have_tool was already written to avoid `--version` probes because
slangc has none, and the note above it says so; this is the same trap wearing
different clothes.
build.mcpp needs Python and slangc. It used to look for them on PATH, and on
this machine PATH answered wrongly twice, in two different ways, both silently:
* `python` is 2.7.18 here while the generators are Python 3. The script died
on a SyntaxError.
* `slangc` was an xlings shim for a package a DIFFERENT project had declared.
`command -v` succeeded; running it printed "slangc is not installed in this
subos" and exited non-zero.
Neither was visible. run_generator writes to stderr, mcpp echoes build.mcpp
output only when the program exits non-zero, and it returns 0 either way. What
you got was an application linked with no compiled shaders, crashing at startup
inside createShaderModule -- a stack with no connection to its cause.
Both are now declared:
[xlings]
subos = "default"
[xlings.workspace]
"xim:python" = "3.13.12"
"xim:slang" = "2026.14.1"
and build.mcpp takes each executable from the directory mcpp delivers for it
(MCPP_XPKG_<NAME>_DIR) in preference to any PATH lookup. A shim cannot get in
front of that, and there is no machine on which `python` is the wrong Python,
because the version is a property of the project now. Falls back to a bare name
so a checkout that has not provisioned the environment still behaves as before.
Measured on a wiped asset tree: "Provisioning [xlings.workspace] entries
(xim:python@3.13.12, xim:slang@2026.14.1)", then 40 icons and 17 shaders, from a
machine whose own `python` cannot run either script.
The env var name was checked by running a probe rather than by reading the
source: mcpp exports BOTH MCPP_XPKG_PYTHON_DIR and MCPP_XPKG_XIM_PYTHON_DIR for
a `"xim:python"` entry.
…requirement nothing used, and a window shown before it was drawn
The demo now comes up on this machine: title, counter, button, slider and text
input, rendered by an RTX 4080, from `mcpp build --features linux-desktop`.
Three things stood between the build and that.
1. A COROUTINE OWNED BY TWO OBJECTS AT ONCE.
`~create_handle_base` resumes an unfinished coroutine and then destroys it. The
crash was a SEGV inside resume(), reached from the DESTROY of another frame:
~create_handle_base(A) -> hdl->destroy()
<frame>.destroy -> destructs an object inside the frame
~create_handle_base(B) -> !done() -> submit() -> resume() SEGV
B was `__coro_gro`, the slot clang uses for the coroutine's return object -- and
it lives INSIDE the frame. gdb, at the crash:
__coro_gro = {hdl = {handle = {__handle_ = 0x7fff3af50050}}}
which is the address of the frame being torn down. So the frame contained a
second owner of itself, and destroying it ran that owner's destructor.
get_return_object() now returns the bare `handle` rather than a
create_handle_base. std::coroutine_handle is trivially destructible, so the slot
owns nothing and runs no destructor; the caller's create_handle is the only
owner, reached through its existing converting constructor. Verified: the SEGV
is gone across five consecutive runs.
TWO EARLIER READINGS OF THIS WERE WRONG and are recorded because they cost time.
The first blamed clang's HALO frame elision -- the handle was a stack address,
which looked conclusive. It was not: the two frames sit in unrelated regions and
neither is nested in the other. The second tried to defeat elision with a custom
operator new on the promise; the handle stayed on the stack and the change was
reverted. The cause is a double-owner and has nothing to do with elision.
2. A REQUIRED EXTENSION NOTHING USED.
VK_KHR_MAINTENANCE_9 sat in the required device-extension list between four
commented-out entries. VkPhysicalDeviceMaintenance9Features appears nowhere in
the tree and nothing is gated on it. It is a Vulkan 1.4-era extension, and on
this machine no device has it -- an RTX 4080 on driver 550.144.03 and Mesa
25.2.8's llvmpipe both stop short. Device selection rejected every candidate and
the application exited without a window, for a capability it never called.
This IS a change to what hardware the application accepts, so it is a decision
rather than a cleanup, and the comment left in its place says so.
3. A WINDOW SHOWN 650 ms BEFORE ANYTHING WAS DRAWN IN IT.
GLFW maps a window at creation. Measured from the startup log, the first frame
lands ~650 ms later -- Vulkan device creation, assets and the UI thread all
happen in between -- so the window spent that time unpainted, which under a
compositor looks like a transparent rectangle that suddenly fills in.
It is created with GLFW_VISIBLE false and shown after the first present.
Measured: Map State is IsUnMapped at 0.35 s and IsViewable at 6.35 s.
RUNNING IT. The renderer needs the compiled shaders, which the earlier
[xlings.workspace] change made reliable, so:
mcpp build --features linux-desktop && ./target/.../bin/xrgui_hello
Software rendering is not a working path here. lavapipe 26.2.1 from the index
does provide maintenance9, but the run ends in a DRI3 BadDrawable and never
presents a frame. Two observations, and they are NOT known to be connected:
* The run prints 25 "libunwind: bad fde: FDE is really a CIE" warnings, only
once lavapipe JIT-compiles shaders, and only for a binary linked against
this toolchain's LLVM libunwind. vkcube -- gcc, libgcc's unwinder -- drives
the same lavapipe with none of them and runs fine.
* Exception handling still works after lavapipe is loaded, measured with a
throw/catch either side of device creation, so the warnings are not by
themselves evidence of a broken unwinder.
Whether the two are related is unestablished; the BadDrawable was not traced to
a cause. The real GPU has neither symptom, which is why the demo runs there.
558dea5 to
b3d308f
Compare
…at the Linux run cost
The tool lookup went through std::getenv("MCPP_XPKG_<NAME>_DIR"), building the
variable name by hand. mcpp exports that name, so it worked -- and it is the
wrong way to ask. The header says so where the interface is declared:
// An INTERFACE, not a naming convention. [...] a build.mcpp that
// reconstructed <home>/data/xpkgs/<ns>-x-<name>/<version> itself would be
// encoding store internals that mcpp is free to change
Reconstructing the ENV VAR name is the same mistake one level up. It is
mcpp::xpkg_dir("xim", "python") now, which is also how the manifest spells the
entry. Verified against a wiped asset tree: 40 icons and 17 shaders, from a
machine whose own `python` is 2.7.18 and whose `slangc` is a shim for another
project.
The design doc gains the Linux section: the coroutine double-owner and the two
wrong readings that preceded it, the required extension nothing used, the window
mapped 650 ms before its first frame -- and why software rendering is not a
fallback here.
That last one is now traced rather than guessed. Interposing on
xcb_dri3_fence_from_fd showed lavapipe passing drawable=0xb600012 while the
window is 0xb60000b: it is Mesa's own pixmap, and BadDrawable means the pixmap
was never created -- lavapipe hands a DRI3 buffer to an X server driven by the
NVIDIA proprietary driver, which cannot import it. Not xrgui's and not mcpp's;
the same lavapipe runs vkcube fine. MESA_VK_WSI_DEBUG=buffer takes DRI3 out of
the path and the error count goes to zero.
Also recorded: MESA_VK_WSI_DEBUG=sw, which earlier notes leaned on, is not a
valid value in Mesa 26 -- setting it does nothing. The accepted ones are
throttle / blit / buffer / dxgi / noshm, read out of libvulkan_lvp.so.
…ementing it found What the six mcpp releases since the pin and mcpp-plugins 0.7.0 let this tree delete, block by block, with the decisions taken during review and the record of what implementation measured differently from the plan.
…do by hand
Five blocks leave build.mcpp and three copies leave mcpp.toml; what stays is
what mcpp has no mechanism for.
runtime data the 71-line copy action (one command per OS, every output
named) is `[runtime] deploy` -- four files and
vk_layer_settings.txt -- and `mcpp pack` stages them too
shaders slang_builder.py, -j 30, --oneshot (every build recompiled
all 17) and a warning on failure become one call into
mcpp.rules.slang: one ninja edge per shader, -depfile for
what a shader imports, a failure that fails the build. The
flags are config.toml's, through extra_args. bloom.merge.slang
is excluded by name: slang 2026.14.1 refuses it, nothing
loads it, and the old flow only ever warned
Windows links gdi32 / psapi / bcrypt were copies of three index packages'
metadata, and vulkan-1's search directory was computed from
whatever the machine had. The packages spell them as
`runtime.libraries` now (mcpplibs/mcpp-index#402); the copies
and the 36-line search go
fontconfig the `linux-desktop` feature the Linux command line had to
name is `[target.linux.dependencies]`, evaluated against the
resolved target
tools node is declared beside python; `xim:slang` is declared by
the rule. No PATH probing is left, and a generator that is
skipped says so through mcpp::warning, the one channel a
green build prints
The manifest also gains what packaging needs: `[package]` metadata (the
dist members read it for the MSI version and the desktop entry), a Windows
icon cut from logo.png, `windows_subsystem = "windows"` on the two GUI
programs, the `llvm@22.1.8` the Linux port was done with, and the appimage /
wix members so `mcpp pack --format` has something to dispatch to.
CI: Windows installs mcpp and nothing else (the manifest provisions the
rest), asserts icons only (shaders are edges now), and packs a zip and an
MSI. A Linux leg builds with the pinned clang, runs the tests, and packs a
tarball and an AppImage. Both name the pack target, because two bin targets
are declared and only one is in the default feature set.
build.mcpp: 480 -> 379 lines, and the three mechanisms that go are the ones
that varied by machine: a shell copy per OS, PATH probing, a link search path
that depended on what was installed.
Under mcpp the shaders are compiled into the binary and reached as
xrgui::shaders::ui::draw::vert() -- a {code, size_bytes} payload straight into
the span constructor shader_module already has. Under xmake nothing changes:
the macro XRGUI_SHADER_SURFACE is defined by mcpp.toml only, and the #else
branches are the previous code verbatim.
The header is included from the global module fragment under the macro. It is
a header surface rather than a module one on purpose: render_context.cpp is a
module implementation unit, and a conditional `import` is the one shape the
module scanner refuses, while a conditional #include in the global module
fragment is not read by it at all.
Two names differ from the .spv files: `lane_merge` (config.toml aliased it
"ui/blit/basic") and `fullscreen_present_vert` / `_frag` (the stem's dot is
an underscore). main.cpp also imports mo_yanxi.vk.util, which it used through
another module's reachability; clang requires the import.
The Linux port covered the library and xrgui_hello; --features examples and
--features tests had never been compiled with clang. Four errors, all the
same kind the port fixed elsewhere -- standard rules MSVC does not enforce:
gui.examples.cpp names from mo_yanxi.math.interpolation and
mo_yanxi.math.matrix3 used without importing them
inout_animator.ixx simple_animator<T> passed `0` for a `T` non-type
template parameter; int -> float is not allowed in a
converted constant expression, so the alias failed to
instantiate in every test that used it
54 tests from 16 suites pass.
…ment mcpp clean leaves the global build cache in place, so both runs were served from it and the difference is cache misses from the changed fingerprint. A real reading needs [build] cache = "off"; neither knob is enabled here.
…ntime.libraries, not GNU ldflags (#402) All three declared their Windows system libraries as `ldflags` in the GNU spelling. `ldflags` reach the linker verbatim, and link.exe does not reject `-lgdi32` -- it drops it: LNK4044: unrecognized option '/lgdi32'; ignored and carries on, so the first sign is a consumer's link ending in unresolved externals (235 of them on xrgui). Every MSVC consumer has been re-declaring these three packages' libraries in its own manifest to compensate. `runtime.libraries` and `runtime.link_library_dirs` are the dialect-neutral half of a link line: rendered as gdi32.lib / /LIBPATH: for MSVC and -lgdi32 / -L for GNU. compat.libgbm already uses `link_library_dirs` in this form. compat.glfw gdi32 compat.mimalloc psapi shell32 user32 advapi32 bcrypt compat.vulkan vulkan-1, search dir lib/ Linux and macOS sections are unchanged. Payloads are unchanged, so no version moves. Consumer: Sunrisepeak/xrgui#8, which deletes its copies.
quick_install.sh reads from /dev/tty when its stdin is a pipe, and a runner has no tty: 'bash: line 375: /dev/tty: No such device or address'. The variable is the installer's own switch for that case.
xlings installs packages into the SubOS bin, not beside itself, and that directory did not exist when the install step exported PATH. 'command -v mcpp' failed right after a successful install.
MSVC 14.52 refuses to instantiate _Path_iterator's hidden-friend operator== inside a module interface unit that imports std, and 0.7.0's Slang rule reached it through lexically_normal(). Pinned by commit until the release carries the fix.
… conversion
.clearValue = {.color = param} compiles under clang (the conversion operator
to VkClearColorValue is chosen) and not under MSVC 14.52.36725, which tries
brace elision first and fails converting color_clear_value to float.
color_clear_value is a union that already holds the VkClearColorValue; naming
it needs no conversion on either compiler.
… payload root The Linux payload is bin/python3; the Windows one is python.exe at its root. The PATH fallback that used to cover the Windows miss is gone, and the miss showed as a build with no icons: an empty assets_summary.h, and gui.assets.cpp failing on every svgs::icons:: name.
Replaces the three commit-pinned probes. 0.7.0's host modules do not compile under MSVC 14.52 (36629 and 36725): the STL's own <filesystem> refuses _Path_iterator's hidden-friend operator== in any module that imports both std and a BMI already carrying that instantiation. 0.7.1 instantiates the iterator nowhere in the package.
… not see it The line was added for a clang defect (the gtl friend declaration not found when the specialisation is instantiated from another TU). Under MSVC 14.52 it has the opposite effect: with the specialisation explicitly instantiated in the interface, an importer calling its member template fails -- label.ixx(358): error C2672: 'any_pool<...>::acquire': no matching overloaded function found; note: syntax error: '<end Parse>' -- and the Windows legs had not compiled this tree since the port landed (last green: 5006775). Compiled for clang only; each compiler gets the tree it can build.
…e Linux port mo_yanxi.views and mo_yanxi.functional live beside the mcpp manifest (mcpp/mo_yanxi_utility/) and thirty files under src/ import them since 44aba92. The mcpp build compiles them through that package; xmake never saw them, and upstream's own workflow has failed on this branch since that commit with 'missing mo_yanxi.functional dependency for module mo_yanxi.gui.cfg.builtin.lifecycle'. They resolve to std:: where the library has the facility, which MSVC's does, so on that side they are two using-declarations.
…records the review
…de leave the build svg_normalize.py drove `npx oslllo-svg-fixer`, which RASTERISES an icon and traces the bitmap back with potrace -- a browser engine, node and python for an approximation of geometry. It was needed because msdf.cpp reads fills only and 38 of the 40 icons are strokes (width 3, round caps and joins). mcpp/svg_outline is a `kind = "bin"` path package, one file, depending only on compat.nanosvg (the same parser xrgui's runtime uses): flatten each path, emit one same-oriented capsule contour per segment, copy fills through. No union is computed; xrgui's generateMSDF runs with overlapSupport, under which a pile of same-oriented overlapping capsules IS the union, and round joins come from adjacent caps. `--embed` writes the bytes as the initializer list build.mcpp's bin2c used to write, so that moved too. Requested as `tools = ["svg-outline"]` on a [build-dependencies] edge: mcpp builds it for the host once per version x toolchain and build.mcpp declares one `source` action per icon (mcpp::dep_bin). The summary is written at prepare time from the known file set; the bytes arrive when the edges run. Measured: all 40 icons render identically to the stroked originals (two geometry bugs were caught that way: caps swept through the segment, and the control points of a decreasing-angle arc unflipped). Tolerance 0.15 keeps an icon around 5 KB. [xlings.workspace] is empty; the CI "icons exist" assertions are gone, a failing edge fails the build.
…ed leg rewrites it too A host-tool sub-build resolves the tool package's own [toolchain]; nothing is inherited from the consumer. Pinned for Linux only, on the Windows runner it took the machine's default -- an older clang against the MSVC 14.52 STL: 'STL1000: Unexpected compiler version, expected Clang 22 or newer', and the std module failed to precompile. The managed leg, which has no Visual Studio, rewrites the tool's manifest the way it rewrites the root's, and asserts both edits.
mcpp reads stdout and stderr through one merged pipe. On the Windows runner the forty action directives were still in stdout's buffer when the note went to unbuffered stderr, and the merged stream carried a JSON payload cut at '...build-cach' with the note spliced in: 'build.mcpp declared a malformed action'. Lines on stdout that do not start with mcpp: are ignored by the parser, so that is where notes belong.
用 mcpp 构建 XRGUI 的适配,基于上游而不是 fork 的 master 重新整理成 5 个 commit。
拆分标准只有一条:上游可以单独采纳哪一段。
fix(msvc): 写出 14.52 不再泄漏的名字refactor: 删掉XRGUI_FUCK_MSVC_INCLUDE_CPP_HEADER_IN_MODULEbuild: 让消费者提供单头实现ci: 钉住这个构建敏感的东西feat(mcpp): mcpp 构建,与 xmake 并存头四个 commit 和 mcpp 没有关系,而且第 1 个是上游现在就需要的:master 在
14.52.36629上编不过,而那正是它自己 workflow 装的 toolset。逐条 review 要点
1.
fix(msvc)—— 唯一改了语义写法的一处,以及为什么它在这里而不在调用方rich_text_fallback_style::operator==从= default改成写出函数体,并且按
data()/size()比较而不是按容器本身。gch 把容器的
operator==和迭代器的operator-都写成自由函数模板而不是hidden friend;而
<gch/small_vector.hpp>是在全局模块片段里 include 的 ——purview 从没点名过的声明会被丢弃、不写进 BMI。于是导入方两个都看不见。
难点在于报错位置和该改的文件不是同一个:cl 指向模板定义处
ui.util.ixx:140,而实例化点在
text_edit.ixx/label.ixx——每加一个比较layout_config的地方就多一处。所以修在定义处,不是在导入方逐个补 include(那是打地鼠)。
只写出函数体不够:
lhs.features == rhs.features调的gch::operator==自己也是模板,照样在导入方实例化,把
std::equal和它对operator-(a,b)的需求一起带过去。按
const hb_feature_t*比较才把整条链绕开。语义完全一样。2. 删
FUCK_MSVC为什么是安全的,而不只是"陈旧"那个
add_defines在add_xrgui_core_deps()里,也就是说 xmake 一直打开的是import <header>;那条分支……而这次删除保留的是#include那条。所以这里要看的是 CI,不是推理:本分支的 xmake job 绿,说明保留下来的那条分支
在 14.52.36629 上确实能编。两条分支里长期没被走的那条被删掉了。
3. 为什么两个守卫宏是"消费者提供"而不是自动探测
某个实现是不是已经被编过,是提供这个库的那个构建的属性,只有它知道。
compat.msdfgen正是通过每个公开头都会包含的msdfgen-config.h告诉消费者「
MSDFGEN_USE_CPP11是关的」—— 自己#define就单方面破坏了这个约定,而且要到链接期才以
unresolved external symbol Contour::addEdge(EdgeHolder&&)暴露。4.
mcpp/目录的位置是一个已知的取舍,写在 commit 里三个 submodule 的清单放在
mcpp/<name>/mcpp.toml而不是各自的 submodule 根,因为那是别人的仓库。代价是 glob 要越界指回
../../external/...。将来上游化就是把文件挪进 submodule 根、去掉前缀,其余不变。
验证
Sunrisepeak/xrgui@master逐字节一致(git diff master HEAD为空),而那个 commit 上两条 job 都是绿的(16m29s / 18m36s)
还没做的
mcpp/patches/mo_yanxi_vulkan_wrapper.patch由 CI 施加;真正的归宿是上游那个submodule(六行,把一条 import 提到
#if defined(__cpp_lib_stacktrace)之外)2026-09-12 追加:对齐 mcpp 2026.9.12.2,把 build.mcpp 手写的东西交还给 mcpp
方案与实施记录:
.agents/docs/2026-09-12-mcpp-build-optimization.md(第 10 节是实测偏离)。上游先行两步,都已进索引:mcpp-plugins 0.7.0(mcpp-community/mcpp-plugins#17,Slang 规则加
extra_args/per_file/storage)、mcpplibs/mcpp-index#402(glfw / mimalloc / vulkan 的 Windows 链接改runtime.libraries)。docs(mcpp)build(mcpp)[runtime] deploy取代 71 行拷贝 action;mcpp.rules.slang取代slang_builder.py(每个 shader 一条 ninja 边,失败即构建失败);三个索引包的系统库副本和 vulkan-1 搜索删除;linux-desktopfeature 改[target.linux.dependencies];声明xim:node,删 PATH 探测;[package]元数据、.ico、windows_subsystem、appimage / wix 打包成员;Windows CI 只装 mcpp 并打 zip + MSI;新增 Linux CI 腿(llvm@22.1.8,跑测试,打 tar + AppImage)port: shadersrender_context.cpp/src.examples/main.cpp在XRGUI_SHADER_SURFACE下走xrgui::shaders::...accessor;#else分支是原代码port: examples/testsbuild.mcpp 480 → 379 行;删掉的是三种机制:按平台分叉的 shell 拷贝、PATH 探测、依赖机器状态的链接搜索路径。
实测偏离(细节在文档第 10 节):调用面用
c_header不用module_(条件import被扫描器拒绝,条件#include在全局模块片段里可以);bloom.merge.slang按名排除(slang 2026.14.1 拒绝它、无人加载、旧流程只告警);
mcpp pack要点名xrgui_hello;[build-dependencies]要放在[dependencies]最后一条之后。追加(图标工具):
svg_normalize.py+npx oslllo-svg-fixer做的是栅格化再 potrace 描摹,python 和 node 只为这一步存在。换成mcpp/svg_outline/:一个kind = "bin"的 path 包,只依赖compat.nanosvg,每段一个胶囊轮廓、填充原样拷过去,靠 msdfgen 的overlapSupport做并集;tools = ["svg-outline"]让 mcpp 按宿主编译并缓存,build.mcpp每个图标一条sourceaction。40 个图标渲染对照与原始描边一致。[xlings.workspace]清空,build.mcpp285 行,CI 里「图标存在」断言删除。