Skip to content

Commit 3a6d46f

Browse files
committed
feat: add compat.godot-cpp 4.5.0 (godot-4.5-stable)
godot-cpp's tag archives are only half a source tree: the ~1000 GDExtension engine classes and every builtin Variant type (gen/include, gen/src) are emitted by upstream's own binding_generator.py at build time, and ship in no upstream archive or release. Running that generator on the consumer side would make Python a hard dependency of every build on every platform, so it runs once offline instead and the result is published as an immutable mirror archive: upstream's tree byte-for-byte plus gen/. tools/godot-cpp/repack.sh is the only source of that archive and verifies it -- it diffs the regenerated tree against a fresh extraction of the upstream archive and refuses to publish if any upstream file differs, then packs deterministically (sorted, fixed mtime, gzip -n). Two runs hash identically. GLOBAL github.com/xlings-res/godot-cpp 4.5.0 CN gitcode.com/mcpp-res/godot-cpp 4.5.0 sha256 b0c36e77f02c4181352cdd7547b209b93a833be1ad6197f8c650d92987221a00 GDEXTENSION is upstream's PUBLIC compile definition, so it rides on a default feature and reaches consumer TUs. DEBUG_ENABLED / DEV_ENABLED / HOT_RELOAD_ENABLED are deliberately not features (each re-keys the store into a second full 1022-TU build, and HOT_RELOAD_ENABLED changes the Wrapped layout); REAL_T_IS_DOUBLE cannot be one at all, since it needs a gen/ tree generated with precision=double. The test member asserts on both halves and can fail on either: Vector2:: length(), Basis::orthonormalized(), Color::to_rgba32() and AABB::get_volume() are defined in src/variant/*.cpp, so they only resolve if the library really compiled and linked, while Node, Node::PROCESS_MODE_*, godot::OK and Variant::OBJECT exist only in gen/. Everything a running Godot process would be needed for (String, Array, class registration) is out of scope. Verified locally with the CI-pinned mcpp 2026.8.3.3: `mcpp test -p godot-cpp` -> test result ok. 1 passed; 0 failed (106.54s).
1 parent 160c389 commit 3a6d46f

8 files changed

Lines changed: 391 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# 收录 godot-cpp:compat.godot-cpp(本 PR)与模块层 godotengine.godot-cpp(后续 PR)
2+
3+
日期:2026-08-04
4+
上游:<https://github.com/godotengine/godot-cpp> · 版本 `godot-4.5-stable` → 索引版本 `4.5.0`
5+
6+
## 1. 形态判定
7+
8+
godot-cpp 是 Godot GDExtension 的 C++ 绑定库。表面上属于「C++ 源码 compat」,但它有一个决定性特征:
9+
10+
**上游 tag 归档只是半棵源码树。** 另一半 —— 约 1000 个引擎类与全部 builtin Variant 类型,即
11+
`gen/include/``gen/src/` —— 由上游自带的 `binding_generator.py`
12+
`gdextension/extension_api.json` 在构建时生成,任何上游 tag 归档或 release 都不含它:
13+
14+
```
15+
上游归档: include/ 67 个 .hpp src/ 32 个 .cpp
16+
生成之后: +1010 个 .hpp +990 个 .cpp (共 18 MB)
17+
```
18+
19+
于是形态判定的真正问题不是模板选哪个,而是**这一步 codegen 在哪里跑**。三个选项:
20+
21+
| 方案 | 结论 |
22+
|---|---|
23+
| 消费侧 `install()` 钩子跑 python | 否决。会让 Python 成为每个消费者、每个平台的硬依赖,与本索引既有做法相悖(compat.ffmpeg 的 config 快照、opencv 包的 gen/ 快照都是「消费侧不跑 codegen」) |
24+
| 把 gen/ 塞进 `generated_files` | 否决。18 MB 文本进描述符 |
25+
| **离线跑一次,产物随镜像归档发布** | **采用** |
26+
27+
因此下载地址不是上游归档,而是 `xlings-res/godot-cpp` 的重打包归档 —— 与
28+
[[windows-symlink-archives]] 里 asio 的 repack 惯例同源,只是这次加的是 `gen/` 而不是去符号链接。
29+
30+
`tools/godot-cpp/repack.sh` 是该归档的唯一来源与验证器:
31+
32+
1. 拉上游 tag 归档(`ac78539c…e339c`);
33+
2.**未修改**的上游 `binding_generator.py`(默认配置:64 位、`precision=single``template_get_node` 开);
34+
3. 把重新解包的上游树与生成后的树做 `diff -r`,**`gen/` 之外任何差异即拒绝打包**;
35+
4. 确定性打包(`--sort=name`、固定 mtime、`gzip -n`)。
36+
37+
连跑两次 sha256 相同:`b0c36e77f02c4181352cdd7547b209b93a833be1ad6197f8c650d92987221a00`
38+
39+
### 版本号
40+
41+
上游 tag 是 `godot-4.5-stable`,索引版本取裸版本 `4.5.0`(lint 拦前导 `v`,且给 4.5.x 留位)。
42+
43+
## 2. 镜像
44+
45+
| 区域 | 地址 |
46+
|---|---|
47+
| GLOBAL | `https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz` |
48+
| CN | `https://gitcode.com/mcpp-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz` |
49+
50+
两侧 sha256 一致,均等于本地打包结果(下载回来逐一核过)。仓库 README 记录了复现方式与上游归档 sha。
51+
52+
三平台共用同一份 OS 中立归档:godot-cpp 是可移植 C++,没有按平台切换的源码集合(平台差异在 Godot 本体里,
53+
`gdextension_interface.h` 这层 C ABI 挡住了)。
54+
55+
## 3. 描述符要点
56+
57+
- `include_dirs = { "*/include", "*/gen/include", "*/gdextension" }` —— 与上游 cmake 暴露的三个根一致。
58+
- `sources` 逐层枚举(`*/src/*.cpp``*/src/*/*.cpp``*/gen/src/*/*.cpp`)而非 `**`:上游自带的
59+
`test/src/*.cpp` 是它自己的示例扩展,不能被扫进库里。
60+
- 目标名 `godot-cpp`,kind = lib,共 1022 个 TU。
61+
- `src/core/object.cpp``gen/src/classes/object.cpp` **basename 撞名**。mcpp ≥ 0.0.98 的 obj 路径消歧
62+
(mcpp#233/#240)已覆盖这种同包内撞名,本地实测链接正常;这是本包对客户端版本下限的隐含依赖,
63+
index.toml 现有 floor 远高于它。
64+
65+
### define 与 feature 评估
66+
67+
`GDEXTENSION` 是上游 cmake 挂在 target INTERFACE 上的 **PUBLIC** define,库与消费者 TU 必须一致,
68+
所以走 `default = { implies = { "gdextension" } }`(feature 的 `defines` 才到得了消费端,而
69+
`default.implies` 无条件生效 —— 与 compat.curl 的 `CURL_STATICLIB` 同一解法)。
70+
71+
以下 define **有意不做成 feature**:
72+
73+
- `DEBUG_ENABLED` / `DEV_ENABLED`:额外检查,上游 release 默认关。
74+
- `HOT_RELOAD_ENABLED`:会改 `Wrapped` 的布局 —— 属于 ABI,不是开关。
75+
- `REAL_T_IS_DOUBLE`:需要用 `precision=double` 重新生成的另一棵 `gen/` 树,归档里没有,
76+
所以它根本不可能是本包的 feature;真要支持是另一个版本/包。
77+
78+
共同理由:每个都会把 store 重新 key 一次,等于把同一个库的 1000 TU 再全量编一遍。
79+
80+
## 4. 测试成员
81+
82+
`tests/examples/godot-cpp/`(根 `[indices] compat` 继承,成员不再声明)。断言分两类,都能真失败:
83+
84+
- **链接类**:`Vector2::length()``Basis::orthonormalized()``Color::to_rgba32()``AABB::get_volume()`
85+
在头里只有声明,定义在 `src/variant/*.cpp` —— 跑通即证明这 1022 个 TU 真的编了并链进来了
86+
(对照 [[verify-obj-count-not-green-ci]]:绿 CI 不等于包被编译)。
87+
- **生成绑定类**:`<godot_cpp/classes/node.hpp>``Node::PROCESS_MODE_*``godot::OK`
88+
`godot::ERR_FILE_NOT_FOUND``Variant::OBJECT` —— 这些只存在于 `gen/`,缺了就编不过。
89+
90+
**不能测什么**:`String`/`Array` 等一切要走 `gdextension_interface_*` 函数指针的 API,以及类注册
91+
(`GDREGISTER_CLASS`),都需要一个已加载该扩展的 Godot 进程。纯数学那半边不需要,所以断言全落在那里。
92+
93+
## 5. 本地验证
94+
95+
与 CI 同版本(`.github/workflows/validate.yml``MCPP_VERSION = 2026.8.3.3`),冷验证:
96+
97+
```
98+
$ mcpp test -p godot-cpp
99+
Compiling compat.godot-cpp v4.5.0
100+
Running bin/godot_cpp
101+
vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
102+
godot_cpp ... ok (0.12s)
103+
test result ok. 1 passed; 0 failed; finished in 106.54s (build 55.60s + run 0.02s)
104+
```
105+
106+
另外用 gcc 13 单独全量编过一遍 1022 个 TU(`-std=c++23`,零告警失败),并把全部 .o 与测试 main
107+
直接链接跑通 —— 用来提前确认「依赖 .o 全量入链」下没有未解析符号(不需要 `-ldl`/`-lpthread`)。
108+
CPU 时间约 16 分钟,4 核 runner 上折合 4~5 分钟。
109+
110+
## 6. 后续:模块层 godotengine.godot-cpp
111+
112+
模块包****放在本索引里内联(避免索引变重,也避免把 1000 TU 再编一遍):按 Form A 走外部仓
113+
`mcpplibs/godot-cpp-m`,其 `mcpp.toml` 依赖 `compat.godot-cpp`,提供 `import godot_cpp;`
114+
(单段模块名,与 `#include <godot_cpp/...>` 和库名对应;点号在本索引里留给子模块,如 `opencv.cv`)。
115+
116+
**顺序是硬约束**:模块成员的测试工程只能声明一条 `[indices]`,给 `godotengine`;它的传递依赖
117+
`compat.godot-cpp` 于是从**已发布**的远端索引解析(tests/examples/ffmpeg-module 的注释写的就是这件事)。
118+
所以必须先合并本 PR、`publish-artifact` 重新发布 artifact,第二个 PR 的 CI 才可能绿。
119+
120+
模块 wrapper 的生成方式已验证可行:1077 个头在单个 TU 里全部编过只要 3.5 秒 / 728 MB RSS,
121+
按命名空间作用域声明批量产出 `export using ::godot::X;` 即可。**宏不在其中** —— `GDCLASS`
122+
`GDREGISTER_CLASS``memnew``ERR_*``GDVIRTUAL_*` 是预处理器构造,模块带不走,做类注册的 TU
123+
仍需 `#include` 对应头;这一条要在 godot-cpp-m 的 README 与描述符注释里写明。

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Two kinds of packages live here:
4242
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4343
| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4444
| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) |
45+
| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (the ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) |
4546
| Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) |
4647
| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) |
4748
| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_<NAME>` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` |

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
3939
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
4040
| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
4141
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) |
42+
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
4243
| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) |
4344
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
4445
| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_<NAME>`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` |

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ members = [
2929
"tests/examples/ffmpeg",
3030
"tests/examples/ffmpeg-module",
3131
"tests/examples/fmtlib.fmt",
32+
"tests/examples/godot-cpp",
3233
"tests/examples/gui-stack",
3334
"tests/examples/imgui",
3435
"tests/examples/imgui-module",

pkgs/c/compat.godot-cpp.lua

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
-- compat.godot-cpp -- godot-cpp (the C++ bindings for Godot's GDExtension API)
2+
-- as an ordinary mcpp source package: `#include <godot_cpp/...>` works out of
3+
-- the box, no SCons, no CMake and no Python on the consumer side.
4+
--
5+
-- Why the download is NOT the upstream tag archive
6+
-- godot-cpp is only half a source tree. The other half -- ~1000 engine
7+
-- classes and every builtin Variant type, i.e. gen/include + gen/src -- is
8+
-- produced by upstream's own binding_generator.py from
9+
-- gdextension/extension_api.json, and no upstream tag archive or release
10+
-- carries it. A package that ran that generator at install time would make
11+
-- a Python toolchain a hard runtime dependency of every consumer, on every
12+
-- platform, which is exactly what this index avoids elsewhere (the frozen
13+
-- configure snapshots in compat.ffmpeg / the opencv module package).
14+
--
15+
-- So the generator runs ONCE, offline, and the result is published as an
16+
-- immutable mirror archive: upstream's tree byte-for-byte (the repack
17+
-- verifies this and refuses otherwise) plus the gen/ tree that upstream's
18+
-- unmodified binding_generator.py emitted for it. Recipe and verification
19+
-- live in tools/godot-cpp/repack.sh; upstream's own archive for
20+
-- godot-4.5-stable hashes to
21+
-- ac78539c0042554c494ea419549d2de88758d448721aeb0e5d41129aa87e339c.
22+
--
23+
-- Bindings are generated for the default configuration -- 64-bit,
24+
-- precision=single, template_get_node on -- which is what `scons` and
25+
-- `cmake` give you by default. A double-precision build needs a different
26+
-- gen/ tree, so it cannot be a feature over this archive; it would be a
27+
-- second version/package.
28+
--
29+
-- Defines
30+
-- GDEXTENSION is upstream's PUBLIC compile definition (cmake sets it on the
31+
-- godot-cpp target's INTERFACE), so it rides on a default feature: the lib
32+
-- and every consumer TU must agree. The layout-affecting ones are left
33+
-- undefined on both sides, which is upstream's release default:
34+
-- DEBUG_ENABLED / DEV_ENABLED (extra checks), HOT_RELOAD_ENABLED (changes
35+
-- the Wrapped layout) and REAL_T_IS_DOUBLE (needs the double-precision
36+
-- gen/ tree above). They are deliberately NOT features: each would re-key
37+
-- the store into a second full ~1000-TU build of the same library.
38+
--
39+
-- Consuming
40+
-- compat.godot-cpp is the plain-header form. `import godot_cpp;` is the
41+
-- module package godotengine.godot-cpp (mcpplibs/godot-cpp-m), which builds
42+
-- on top of this one.
43+
package = {
44+
spec = "1",
45+
namespace = "compat",
46+
name = "godot-cpp",
47+
description = "C++ bindings for the Godot GDExtension API (pre-generated bindings, no Python/SCons needed)",
48+
licenses = {"MIT"},
49+
repo = "https://github.com/godotengine/godot-cpp",
50+
type = "package",
51+
52+
-- One OS-neutral archive: godot-cpp is portable C++ with no per-platform
53+
-- source selection (the platform split lives in Godot itself, behind the
54+
-- gdextension_interface.h ABI).
55+
xpm = {
56+
linux = {
57+
["4.5.0"] = {
58+
url = {
59+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
60+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
61+
},
62+
sha256 = "b0c36e77f02c4181352cdd7547b209b93a833be1ad6197f8c650d92987221a00",
63+
},
64+
},
65+
macosx = {
66+
["4.5.0"] = {
67+
url = {
68+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
69+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
70+
},
71+
sha256 = "b0c36e77f02c4181352cdd7547b209b93a833be1ad6197f8c650d92987221a00",
72+
},
73+
},
74+
windows = {
75+
["4.5.0"] = {
76+
url = {
77+
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
78+
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
79+
},
80+
sha256 = "b0c36e77f02c4181352cdd7547b209b93a833be1ad6197f8c650d92987221a00",
81+
},
82+
},
83+
},
84+
85+
mcpp = {
86+
schema = "0.1",
87+
language = "c++23",
88+
import_std = false,
89+
-- Three roots, exactly as upstream's build systems expose them:
90+
-- hand-written headers, generated headers, and the GDExtension C ABI
91+
-- header (gdextension_interface.h) that both of them include.
92+
include_dirs = { "*/include", "*/gen/include", "*/gdextension" },
93+
-- Enumerated rather than `**`: upstream's own test project ships a
94+
-- test/src/*.cpp that must not be swept into the library, and the two
95+
-- source roots are only ever one and two levels deep.
96+
sources = {
97+
"*/src/*.cpp",
98+
"*/src/*/*.cpp",
99+
"*/gen/src/*/*.cpp",
100+
},
101+
targets = { ["godot-cpp"] = { kind = "lib" } },
102+
features = {
103+
["default"] = { implies = { "gdextension" } },
104+
["gdextension"] = { defines = { "GDEXTENSION" } },
105+
},
106+
deps = { },
107+
},
108+
}

tests/examples/godot-cpp/mcpp.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# compat.godot-cpp test project: builds the pre-generated GDExtension bindings
2+
# and asserts on the part of the API that runs without a Godot process --
3+
# the Variant math types, whose out-of-line definitions live in the library's
4+
# src/variant/*.cpp, so a passing run proves the ~1000-TU library really did
5+
# compile and link.
6+
#
7+
# `compat` is redirected to this checkout by the workspace-root [indices],
8+
# which every member inherits.
9+
10+
[package]
11+
name = "godot-cpp-tests"
12+
version = "0.1.0"
13+
14+
[dependencies.compat]
15+
godot-cpp = "4.5.0"

0 commit comments

Comments
 (0)