Skip to content

feat: 新增 compat.harfbuzz / compat.msdfgen / compat.mimalloc - #206

Merged
Sunrisepeak merged 1 commit into
mainfrom
feat/add-xrgui-deps
Aug 11, 2026
Merged

feat: 新增 compat.harfbuzz / compat.msdfgen / compat.mimalloc#206
Sunrisepeak merged 1 commit into
mainfrom
feat/add-xrgui-deps

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

补齐 XRGUI 适配 mcpp 时索引里仅剩的三个编译型依赖。该项目的源码符合性问题已全部清零,根包构建恰好停在且仅停在这三行:

fatal error: mimalloc.h: No such file or directory
fatal error: hb.h: No such file or directory
fatal error: msdfgen/msdfgen-ext.h: No such file or directory

三者本身也都是通用库(分配器 / 文本整形 / SDF 生成),不是为单个下游定制的。适配记录见 Sunrisepeak/xrgui#1

设计文档:.agents/docs/2026-08-12-add-harfbuzz-msdfgen-mimalloc-plan.md

形态

版本 形态 要点
compat.mimalloc 3.4.5 A 18 个 TU,取上游 mi_sources
compat.harfbuzz 14.3.0 A 单 TU,用上游自带 amalgamation
compat.msdfgen 1.13 A + E C++ 源码 + 必须生成 config

关于 amalgamation 的两个相反教训

harfbuzz 该用。 上游是 meson,在此复刻意味着跟踪 ~137 个 .cc 加一份生成 config。src/harfbuzz.cc 是上游自己支持的「只编一个文件」路径,于是 sources 只有一行且不会与 release 脱节。它同时 #include 的 CoreText / DirectWrite / GDI / GLib / Graphite2 各在自己的 HAVE_* 门后,所以只声明 HAVE_FREETYPE 就能精确选中 FreeType 桥接,其余编译为空。

mimalloc 不该用。 它也带 amalgamation(src/static.c),但 src/*.c 会在三处出错,且每处都是链接期而非编译期报错static.c 让每个符号重复;free.calloc-override.c 是被 alloc.c #include 的、并非独立 TU。故源列表取上游 mi_sourcesCMakeLists.txt:75-93)。

msdfgen:config 不是可选项

core/base.h 第 7 行就是 #include <msdfgen/msdfgen-config.h>,由 CMake 生成 —— 不生成它连 core/ 都编不了

选择生成它而非传 -D,还有更重要的一层:它让库与消费者天然一致base.h 被每个公开头间接包含,所以这份文件是「SVG / PNG / Skia 哪些存在」的唯一出处。包自己的 cxxflags 是私有的,消费者 #include <msdfgen/msdfgen-ext.h> 时看不到,两边会对「有哪些声明」产生分歧。

ext/ 四个单元只编 import-font.cpp,其余三个各需一个本索引没有的库(Skia / TinyXML2 / libpng-或-LodePNG),其声明经同一份生成 config 一并消失。

另外补了 mcpp_generated/msdfgen/*.h 把约定的 msdfgen/ 前缀装回来 —— 上游把两个根头放在仓库根,但所有打包方式(vcpkg / xmake-repo / 发行版)都装到 include/msdfgen/,消费者源码写的也是这个。

三个刻意不开的开关

理由是同一条:包不能替消费者做它无法保证的承诺。

开关 不开的原因
HB_NO_MT 去掉 HarfBuzz 的原子操作与锁,仅在单线程使用下成立
MI_MALLOC_OVERRIDE 让依赖悄悄接管进程分配器,不该由包决定
MSDFGEN_USE_CPP11 Bitmap 加移动构造,改变跨库边界类型的布局,而直接 #include <msdfgen.h> 的消费者看不到任何 shim 里的定义

CN 镜像

三个都已在 gitcode mcpp-res 建仓发 release,上传与 GLOBAL 字节一致的 tarball:

slug 版本 sha256
mimalloc 3.4.5 19a43af0645c57d348e729d5b31e23e912582911bb1047f795790834d3416221
harfbuzz 14.3.0 566e996a1b40486954fb7110ffe6eb88a0f7958bb466cdb023b0302618acea4a
msdfgen 1.13 93cd1ad8918c1a78c5c96e82d4f4c77f0eb86c2e7e8579a0967e54196c4b7167

sha256 按 SOP 算两遍确认稳定;闭环校验(CN 返回 200 且与 GLOBAL 字节一致)三个全过。

feature

三个都没有实现 feature,各有其因:

  • mimalloc —— 可选项(override / secure / valgrind / 统计档位)全是编译期 define,而 features 表仅能门控 sources,按 SKILL 的判定准则不可门控。
  • harfbuzz —— 其余后端确实是可编译源码,但已在 amalgamation 内部,无法按源文件切分;开关同样是 define。
  • msdfgen —— ext/ 三个单元形式上符合定义,但每个都需要本索引尚无的库,且开关还要同时写进生成的 config 才能让消费者看见。等这些依赖进索引后可再评估。

验证

冷验证(先删 target/.mcpp/),用 validate.yml 里钉的 mcpp 2026.8.10.3

mimalloc    test result ok. 1 passed; 0 failed; finished in 4.32s
harfbuzz    test result ok. 1 passed; 0 failed; finished in 31.35s
msdfgen     test result ok. 1 passed; 0 failed; finished in 14.63s

每个测试都断言行为而非链接:

  • mimalloc —— mi_usable_size 必须 ≥ 请求大小(证明指针来自 mimalloc 自己的簿记而非 fallback)、mi_zalloc 必须真的清零、对齐必须生效、私有 heap 必须 mi_heap_contains 自己发出的块。
  • harfbuzz —— 真的整形一段文本,glyph_count 必须等于输入长度(若 hb-ot-shape.cc 没编进 amalgamation 这里会是 0)、cluster 必须跟踪输入偏移;再取 hb_ft_face_create_referenced 的地址,证明 FreeType 后端确实编进来了而不只是被声明。
  • msdfgen —— 生成真实 MSDF,断言场是有符号的(内 > 0.5、外 < 0.5,全零位图两条都过不了)且三通道确有差异(否则 edge coloring 没跑,"多通道"没发生);再经 initializeFreetype 证明 ext/import-font 在位、传递依赖到达链接行。测试用 msdfgen/ 前缀 include,让生成的 shim 也被 CI 覆盖。

本地 lint 全过:check_mirror_urlscheck_package_namecheck_platform_version_paritycheck_cross_package_refsmcpp xpkg parse

已知事项

  • harfbuzz 的 tarball 36 MB,是本索引里较大的一个;CI 拉取耗时可见(构建本身很快,单 TU)。
  • macOS / Windows 只做了静态校验与描述符层面的处理,未在真机跑过(手头只有 Linux x86_64)—— 交给 CI 的 workspace(macos|windows) 腿验证。

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.
@Sunrisepeak
Sunrisepeak merged commit 451ba00 into main Aug 11, 2026
11 checks passed
Sunrisepeak pushed a commit to Sunrisepeak/xrgui that referenced this pull request Aug 11, 2026
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 Yuria-Shikibe#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants