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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions .agents/docs/2026-08-04-add-godot-cpp-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,44 @@ godot_cpp ... ok (0.12s)
直接链接跑通 —— 用来提前确认「依赖 .o 全量入链」下没有未解析符号(不需要 `-ldl`/`-lpthread`)。
CPU 时间约 16 分钟,4 核 runner 上折合 4~5 分钟。

## 6. 后续:模块层 godotengine.godot-cpp
## 6. 后续:模块层 godotengine.godot-cpp-m

模块包**不**放在本索引里内联(避免索引变重,也避免把 1000 TU 再编一遍):按 Form A 走外部仓
`mcpplibs/godot-cpp-m`,其 `mcpp.toml` 依赖 `compat.godot-cpp`,提供 `import godot_cpp;`
`mcpplibs/godot-cpp-m`(版本号**与上游对齐**,即 4.5.0 —— 只做 API 重导出的模块层没有自己的版本值得编,
消费者从 `godot-cpp = "4.5.0"` 需要知道的就是它对着哪个 Godot),其 `mcpp.toml` 依赖 `compat.godot-cpp`,提供 `import godot_cpp;`
(单段模块名,与 `#include <godot_cpp/...>` 和库名对应;点号在本索引里留给子模块,如 `opencv.cv`)。

**顺序是硬约束**:模块成员的测试工程只能声明一条 `[indices]`,给 `godotengine`;它的传递依赖
`compat.godot-cpp` 于是从**已发布**的远端索引解析(tests/examples/ffmpeg-module 的注释写的就是这件事)。
所以必须先合并本 PR、`publish-artifact` 重新发布 artifact,第二个 PR 的 CI 才可能绿。

模块 wrapper 的生成方式已验证可行:1077 个头在单个 TU 里全部编过只要 3.5 秒 / 728 MB RSS,
按命名空间作用域声明批量产出 `export using ::godot::X;` 即可。**宏不在其中** —— `GDCLASS`、
`GDREGISTER_CLASS`、`memnew`、`ERR_*`、`GDVIRTUAL_*` 是预处理器构造,模块带不走,做类注册的 TU
仍需 `#include` 对应头;这一条要在 godot-cpp-m 的 README 与描述符注释里写明。
### 已完成(mcpplibs/godot-cpp-m 0.0.1)

`src/godot_cpp.cppm` 由 `tools/gen_module_cppm.py` **生成**:扫描头文件(带花括号/命名空间状态机),
把 `godot` 命名空间作用域的全部声明批量产出为 `export using ::godot::X;`,约 1750 个名字 ——
全部引擎类、全部 builtin Variant 类型、全局枚举**连同枚举量**(所以 `godot::OK` 拼法不变)、
`godot::Math`、模板。手工罗列这个面积不现实,且每个 Godot 版本都会变,故生成 + 编译器校验。

踩到的两类坑:

- **宏带不走**。`GDCLASS`、`GDREGISTER_CLASS`、`GDVIRTUAL_*`、`D_METHOD`、`memnew`、`ERR_*` 是预处理器
构造,而 GDExtension 代码正是用它们写的。解法照搬 ffmpeg-m:附一个侧头文件
`<godot-cpp-m/macros.h>`,与 import 并排包含。godot-cpp 的头在模块的 GMF 里,两种拼法指向同一批
global-module 实体,混用是良构的 —— `tests/godot_cpp_macros.cpp` 就是这个形状,通过。
- **TU-local 暴露是硬错误,不是告警**。`HashMap`/`HashSet` 及其默认 hasher 的内联体会走到
`hash_murmur3_one_float/double`,这两个既是 `static`,体内又声明了匿名 union;从模块接口暴露一个
TU-local **类型**是 error(不是 `-Wexpose-global-module-tu-local` 那条 warning)。用二分法在 1759 个
导出名里定位到 7 个(含 `PairHash`),排除即可 —— 它们是 godot-cpp 内部容器,扩展代码用
Dictionary/Array/TypedArray,头文件里也仍然拿得到。另外那 3300 条 warning 级暴露用
`-Wno-expose-global-module-tu-local` 静掉,是上游头的形态,包一层改变不了。

验证:`mcpp test` 两条全过(模块面 + 宏面),`examples/summator`(真 GDExtension,`kind = "shared"`)
构建出 `libsummator.so` 且导出 `summator_library_init`。

### 本 PR(索引侧)可验证到哪一步

`godotengine.godot-cpp` 是 Form A,索引侧只有描述符;成员的 `[indices]` 给了 `godotengine`,
所以传递依赖 `compat.godot-cpp` 必须从**已发布**的索引解析。合并前本地伪造 published index 来验证
是走不通的:mcpp 在解析不到时会主动 refresh 一次,把注入的状态直接冲掉(参见
[[stale-global-index-masks-descriptor-bugs]] 的反面)。因此这一步的唯一真实验证就是
compat PR 合并 + artifact 重新发布之后的 CI。
24 changes: 24 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,30 @@ jobs:
echo "MEMBERS=$sel" >> "$GITHUB_ENV"
echo "selected members: ${sel:-<none>}"

# ── Refresh the PUBLISHED index before testing ────────────────────
# Most members resolve everything from this checkout, but a member that
# redirects a namespace other than `compat` gets the REST from the
# published index — and nothing here ever refreshed it. The snapshot in
# play is whatever the pinned mcpp release vendored (the Download step
# `cp -a`s the release's registry/ over ~/.mcpp/registry, on top of the
# restored cache), so it is by construction older than main, and it
# never moves: the cache is saved with that same stale copy inside it.
#
# mcpp does refresh on a miss for a DIRECT dependency, which is why this
# went unnoticed — the gap is a Form-A package's TRANSITIVE dependency.
# tests/examples/godot-cpp-module hit it head-on: the module package's
# own compat.godot-cpp dep resolved against a snapshot predating the
# commit that added it, and failed with `index: local index <sha> (never
# refreshed)` even though the artifact had already been republished.
# (Older members never noticed: their compat packages have been in the
# index far longer than any snapshot.)
- name: Refresh the published package index
shell: bash
env:
MCPP_INDEX_MIRROR: GLOBAL
run: |
"$MCPP" index update

- name: mcpp test (workspace or affected members)
shell: bash
env:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Two kinds of packages live here:
| Multiple majors in one package (shape switches with the version) | [`compat.catch2`](pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) |
| External build system (`install()` builds from source) | [`compat.openblas`](pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) |
| Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) |
| Module layer over a compat source build (external Form-A repo) | [`godotengine.godot-cpp-m`](pkgs/g/godotengine.godot-cpp-m.lua) (two versions tracking upstream: `10.0.0-rc1` = Godot 4.6, `4.5.0` = Godot 4.5. `import godot_cpp;` re-exports the whole `godot` namespace, ~1800 names GENERATED from the headers rather than curated; the 1022-TU build stays in `compat.godot-cpp`, so the index carries only this descriptor. Macros — `GDCLASS`, `GDREGISTER_CLASS`, `memnew`, `ERR_*` — are the one thing a named module cannot export, so the package ships a side header to include next to the import. It also ships a generated `hashfuncs.hpp` shim — upstream's header minus `static` on two functions whose bodies declare an unnamed union — without which GCC refuses the module interface outright, a hard error no `-W` flag reaches) |
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) |

### Adding a package
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) |
| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) |
| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) |
| 模块层叠在 compat 源码构建之上(外部 Form-A 仓) | [`godotengine.godot-cpp-m`](pkgs/g/godotengine.godot-cpp-m.lua)(两个版本与上游对齐:`10.0.0-rc1` 对应 Godot 4.6,`4.5.0` 对应 Godot 4.5。`import godot_cpp;` 重导出整个 `godot` 命名空间,约 1800 个名字由头文件**生成**而非手工罗列;1022 个 TU 的构建留在 `compat.godot-cpp`,索引侧只留这一个描述符。宏 —— `GDCLASS`、`GDREGISTER_CLASS`、`memnew`、`ERR_*` —— 是具名模块唯一带不走的东西,故包内附一个与 import 并排包含的侧头文件。另外还带一份生成的 `hashfuncs.hpp` 遮蔽头 —— 上游那个头去掉两个函数的 `static`(它们体内声明了匿名 union)—— 否则 GCC 直接拒绝该模块接口,且是任何 `-W` 开关都够不到的硬错误) |
| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) |

### 新增一个包
Expand Down
2 changes: 2 additions & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ members = [
"tests/examples/ffmpeg-module",
"tests/examples/fmtlib.fmt",
"tests/examples/godot-cpp",
"tests/examples/godot-cpp-module",
"tests/examples/godot-cpp-module-v10",
"tests/examples/godot-cpp-v10",
"tests/examples/gui-stack",
"tests/examples/imgui",
Expand Down
127 changes: 127 additions & 0 deletions pkgs/g/godotengine.godot-cpp-m.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
-- Form A descriptor: the godot-cpp module package ships its own mcpp.toml.
-- mcpp's default lookup finds <verdir>/*/mcpp.toml inside the GitHub source
-- tarball wrap.
--
-- The package is the thin C++23 module layer over godot-cpp's unchanged C++
-- API: `import godot_cpp;` re-exports the whole public `godot` namespace, so
-- code that used to open with a stack of `#include <godot_cpp/...>` opens with
-- one import and is otherwise the same. godot-cpp's sources and its
-- pre-generated GDExtension bindings arrive through the package's own
-- compat.godot-cpp dependency (1022-TU source build -- see
-- pkgs/c/compat.godot-cpp.lua).
--
-- Module name: ONE segment, `godot_cpp`, matching the library and the
-- `#include <godot_cpp/...>` root users already type. The dotted spelling is
-- reserved here for packages that expose several submodules (opencv.cv,
-- ffmpeg.av); this one has a single interface unit.
--
-- What the module cannot carry: MACROS. GDCLASS, GDREGISTER_CLASS,
-- GDVIRTUAL_*, D_METHOD, memnew/memdelete and the ERR_* family are
-- preprocessor constructs, and GDExtension code is written in them. The
-- package ships `<godot-cpp-m/macros.h>` for exactly that, to be included
-- next to the import; godot-cpp's headers sit in the module's global module
-- fragment, so the two spellings denote the same entities and mixing them is
-- well-formed. The package's own tests cover both shapes.
--
-- Also not re-exported (upstream's shape, not a wrapper choice): the internal
-- container templates HashMap/HashSet and their default hashers. Their inline
-- bodies reach `hash_murmur3_one_float/double`, which are `static` AND declare
-- an unnamed union -- and exposing a TU-local TYPE from a module interface is
-- a hard error, not the -Wexpose-global-module-tu-local warning. Extension
-- code uses Dictionary/Array/TypedArray; the headers still have the rest.
--
-- The version TRACKS UPSTREAM rather than counting the wrapper's own
-- iterations: 4.5.0 is the godot-cpp release it wraps, and the version
-- compat.godot-cpp carries, so the version tells a consumer the one thing
-- that matters -- which Godot they are targeting.
--
-- The name is `godot-cpp-m`, NOT `godot-cpp`, and that is load-bearing rather
-- than cosmetic. This package and its own compat.godot-cpp dependency are
-- always resolved together, and mcpp's installed-package lookup matches on
-- (name, version) WITHOUT the namespace: with both named `godot-cpp` at the
-- same version -- which "the version tracks upstream" guarantees -- resolving
-- this one lands on compat's unpacked directory and fails with "no mcpp.toml
-- at <verdir>/*/mcpp.toml". Reproducible with a namespace unrelated to either
-- package: leave a single <anything>-x-godot-cpp/4.5.0 in the store and the
-- resolution goes to it; remove it and the same descriptor installs fine. The
-- store DIRECTORIES are namespaced (ns-x-name) -- the lookup is not. Distinct
-- short names sidestep it, keep the version tracking upstream, and `-m`
-- matches the repository name. `import godot_cpp;` is unaffected.
--
-- Versions track upstream godot-cpp one for one, as compat.godot-cpp's do:
-- 10.0.0-rc1 binds Godot 4.6, 4.5.0 binds Godot 4.5.
--
-- 10.0.0-rc1 additionally ships a generated `hashfuncs.hpp` shim -- upstream's
-- header with `static` dropped from two functions -- without which GCC refuses
-- the module interface outright ("exposes TU-local entity 'union <unnamed>'",
-- a hard error no -W flag reaches). It is scoped to the package's own include
-- path, so compat.godot-cpp still compiles upstream's copy untouched. See the
-- package README.
--
-- CN tags carry an `-m` suffix: the gitcode mirror repo is shared with the
-- compat.godot-cpp archives, which already hold the bare-version tags.
--
-- Three platforms, one OS-neutral tarball: godot-cpp is portable C++ and
-- compat.godot-cpp covers all three.
package = {
spec = "1",
name = "godot-cpp-m",
namespace = "godotengine",
description = "C++23 module package for godot-cpp (import godot_cpp) — Godot GDExtension API, C++ API unchanged",
licenses = {"MIT"}, -- module layer; upstream godot-cpp is MIT as well
repo = "https://github.com/mcpplibs/godot-cpp-m",
type = "package",

xpm = {
linux = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v10.0.0-rc1-m/godot-cpp-m-10.0.0-rc1.tar.gz",
},
sha256 = "895975f32456d821b1297dbd5298ea3e9b925b00557a187927f3dacf2f035220",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v4.5.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v4.5.0-m/godot-cpp-m-4.5.0.tar.gz",
},
sha256 = "d91794f46ec4a74c1f8c61c894efdecceaf54feff103b9b139fd6a8f16e43051",
},
},
macosx = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v10.0.0-rc1-m/godot-cpp-m-10.0.0-rc1.tar.gz",
},
sha256 = "895975f32456d821b1297dbd5298ea3e9b925b00557a187927f3dacf2f035220",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v4.5.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v4.5.0-m/godot-cpp-m-4.5.0.tar.gz",
},
sha256 = "d91794f46ec4a74c1f8c61c894efdecceaf54feff103b9b139fd6a8f16e43051",
},
},
windows = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v10.0.0-rc1-m/godot-cpp-m-10.0.0-rc1.tar.gz",
},
sha256 = "895975f32456d821b1297dbd5298ea3e9b925b00557a187927f3dacf2f035220",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/godot-cpp-m/archive/refs/tags/v4.5.0.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/v4.5.0-m/godot-cpp-m-4.5.0.tar.gz",
},
sha256 = "d91794f46ec4a74c1f8c61c894efdecceaf54feff103b9b139fd6a8f16e43051",
},
},
},

-- (no `mcpp` field -- default lookup will find <verdir>/*/mcpp.toml)
}
20 changes: 20 additions & 0 deletions tests/examples/godot-cpp-module-v10/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# godotengine.godot-cpp-m 10.0.0-rc1 (Godot 4.6) module-layer test project.
#
# Separate member from godot-cpp-module for the same reason compat has two:
# the versions are different bindings of different engine releases, and each
# has to be built and asserted on its own.
#
# Overrides the workspace-root redirect: root declares `compat`, this member
# needs `godotengine`. A member-level [indices] REPLACES the inherited table,
# so the module package's own compat.godot-cpp dependency resolves from the
# GLOBAL published index -- that descriptor is pre-merge-validated by
# tests/examples/godot-cpp-v10.
[indices]
godotengine = { path = "../../.." }

[package]
name = "godot-cpp-module-v10-tests"
version = "0.1.0"

[dependencies]
godotengine.godot-cpp-m = "10.0.0-rc1"
Loading
Loading