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
93 changes: 93 additions & 0 deletions .agents/docs/2026-08-04-add-godot-cpp-10.0.0-rc1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# compat.godot-cpp 增加 10.0.0-rc1(Godot 4.6),并补上 MSVC ABI 缺的 define

日期:2026-08-04 · 承接 [2026-08-04-add-godot-cpp-plan.md](2026-08-04-add-godot-cpp-plan.md)(4.5.0,PR #143)

## 1. 版本线:godot-cpp 有了自己的版本号

上游 godot-cpp 的 tag 从「跟 Godot 走」(`godot-4.5-stable`)换成了**自己的版本线**:
[`10.0.0-rc1`](https://github.com/godotengine/godot-cpp/releases/tag/10.0.0-rc1)。它绑定的引擎版本写在
`gdextension/extension_api.json` 的 header 里:

```json
{ "version_major": 4, "version_minor": 6, "version_patch": 0, "version_status": "stable",
"version_full_name": "Godot Engine v4.6.stable.official" }
```

即 **10.0.0-rc1 = Godot 4.6**。索引里两条版本并存,消费者按需选:

| 索引版本 | 上游 tag | 引擎 |
|---|---|---|
| `4.5.0` | `godot-4.5-stable` | Godot 4.5 |
| `10.0.0-rc1` | `10.0.0-rc1` | Godot 4.6 |

`mcpp xpkg parse` 接受带预发布后缀的 `10.0.0-rc1`,lint 也只拦前导 `v`,无需特殊处理。

## 2. repack 脚本要兼容两代 API

10.x 改了两处,`tools/godot-cpp/repack.sh` 按**实际签名/实际文件**判定而不是按 tag 判定:

- `generate_bindings()` 多了 `interface_filepath` 参数(用 `inspect.signature` 探测);
- `gdextension_interface.h` **不再签入**,改为由 `gdextension/gdextension_interface.json` 生成到
`gen/include/`(存在哪个就传哪个,与 cmake 的 `GODOTCPP_GDEXTENSION_INTERFACE_FILE` 同逻辑)。

改完后重跑 4.5.0 仍得到同一个 sha `b0c36e77…`,即向后兼容;10.0.0-rc1 连跑两次得
`aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e`。

## 3. 描述符:两种布局取并集

10.x 的 `gen/src/` 多了一个直接位于其下的 `.cpp`(4.5 只有 `classes/`、`variant/` 两层),故 sources
加一条 `*/gen/src/*.cpp`。**匹配不到的 glob 会被跳过**,这是 compat.catch2 已经在用的做法(v2 走
`single_include`、v3 走 `src`,另一个空着)。`include_dirs` 同时保留 `*/gdextension` 与 `*/gen/include`,
因为那个 C ABI 头在两代里位置不同。

## 4. TYPED_METHOD_BIND —— MSVC ABI 上不是可选项

`godot-cpp-m` 的 Windows CI 暴露出来的:任何 `ClassDB::bind_method` 调用在 clang-cl 下直接编译失败

```
error: cannot reinterpret_cast from member pointer type 'double (TestSprite::*)() const'
to member pointer type 'double (_gde_UnexistingClass::*)() const' of different size
```

`method_bind.hpp` 在 `#ifndef TYPED_METHOD_BIND` 时把成员指针 cast 成一个**前向声明**的
`_gde_UnexistingClass`;MSVC ABI 下成员指针的大小取决于该类的继承模型,不完整类型只能按最一般形式
假定,于是尺寸对不上、cast 非法。上游 `cmake/windows.cmake` 正是为此在 MSVC 下把
`TYPED_METHOD_BIND` 设为 **PUBLIC**。

本包把它挂在默认 feature 上(与 `GDEXTENSION` 同处),**不按平台分**:它是个改
`MethodBindT` 模板参数表的**头文件开关**,库与消费者必须一致,统一一个答案比按 OS 分更容易保证。
非 MSVC 侧的代价只是多一些模板实例化,无行为差异。上游一起设的 `WINDOWS_ENABLED` / `NOMINMAX`
**不需要** —— 在 4.5 与 10.x 的头文件和源码里都一次都没出现过。

## 5. 测试补了 GDCLASS + bind_method

两个成员(`godot-cpp`、`godot-cpp-v10`)都加了一个 `GDCLASS` 子类,带两个 `ClassDB::bind_method`
绑定和一次对 GDCLASS 生成物的 ODR-use。**这正是之前 Windows 绿得没有意义的原因**:老测试只碰纯数学,
根本没走到 bind_method,所以上面那个必现的编译错误一次都没被 CI 看见。断言是「编得过且链得上」——
不能真调用,ClassDB/StringName 都要走 `gdextension_interface_*` 函数指针。

`godot-cpp-v10` 另外断言 `GODOT_VERSION_MAJOR/MINOR == 4/6` 且 4.6 才有的 `EditorDock` 存在,
用来证明拿到的确实是 10.x 那套绑定而不是 4.5 的。

## 6. 本地验证

```
$ mcpp test -p godot-cpp # 4.5.0
bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
test result ok. 1 passed; 0 failed; finished in 59.15s

$ mcpp test -p godot-cpp-v10 # 10.0.0-rc1
version=1 bind=1 vec2=1 vec3=1 basis=1 color=1 aabb=1 gen=1
test result ok. 1 passed; 0 failed; finished in 52.72s
```

另外 10.0.0-rc1 的 1075 个 TU 用 gcc 13 `-std=c++23 -fPIC` 全量编过,零失败。

## 7. 镜像

| 区域 | 地址 |
|---|---|
| GLOBAL | `https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz` |
| CN | `https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz` |

两侧下载回来核过 sha,与本地打包一致。
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Two kinds of packages live here:
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| 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) |
| 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) |
| 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) |
| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. 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) |
| 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) |
| 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) |
| 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` |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| 运行时 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) |
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) |
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) |
| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) |
| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) |
| 互斥后端(同包多后端二选一) | [`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` |
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ members = [
"tests/examples/ffmpeg-module",
"tests/examples/fmtlib.fmt",
"tests/examples/godot-cpp",
"tests/examples/godot-cpp-v10",
"tests/examples/gui-stack",
"tests/examples/imgui",
"tests/examples/imgui-module",
Expand Down
52 changes: 49 additions & 3 deletions pkgs/c/compat.godot-cpp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,25 @@
-- Defines
-- GDEXTENSION is upstream's PUBLIC compile definition (cmake sets it on the
-- godot-cpp target's INTERFACE), so it rides on a default feature: the lib
-- and every consumer TU must agree. The layout-affecting ones are left
-- and every consumer TU must agree.
--
-- TYPED_METHOD_BIND rides along, and is not optional on the MSVC ABI.
-- Without it, method_bind.hpp reinterpret_casts member pointers through a
-- FORWARD-DECLARED `_gde_UnexistingClass`; under the MSVC ABI a
-- pointer-to-member's size depends on the class's inheritance model, so for
-- an incomplete class clang-cl rejects the cast outright ("cannot
-- reinterpret_cast ... to member pointer type of different size") and every
-- ClassDB::bind_method call fails to compile. Upstream's cmake sets it
-- PUBLIC for exactly this reason ($<${IS_MSVC}: TYPED_METHOD_BIND ...> in
-- cmake/windows.cmake). It is set unconditionally rather than per-OS
-- because it is a HEADER switch that changes MethodBindT's template
-- parameter list -- library and consumer must agree on it, and one uniform
-- answer is cheaper to guarantee than an OS-conditional one. The cost off
-- MSVC is some extra template instantiation, which is why upstream keeps
-- the untyped path as its default there; there is no behavioural
-- difference. (WINDOWS_ENABLED and NOMINMAX, which upstream sets alongside,
-- are NOT needed: neither appears anywhere in the shipped headers or
-- sources.) The layout-affecting ones are left
-- undefined on both sides, which is upstream's release default:
-- DEBUG_ENABLED / DEV_ENABLED (extra checks), HOT_RELOAD_ENABLED (changes
-- the Wrapped layout) and REAL_T_IS_DOUBLE (needs the double-precision
Expand All @@ -54,6 +72,13 @@ package = {
-- gdextension_interface.h ABI).
xpm = {
linux = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
},
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
Expand All @@ -63,6 +88,13 @@ package = {
},
},
macosx = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
},
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
Expand All @@ -72,6 +104,13 @@ package = {
},
},
windows = {
["10.0.0-rc1"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
CN = "https://gitcode.com/mcpp-res/godot-cpp/releases/download/10.0.0-rc1/godot-cpp-10.0.0-rc1.tar.gz",
},
sha256 = "aaafbf50d4b8469d610fdb2eb76c6f58d758dbabbc6b013f60464d99b20ceb6e",
},
["4.5.0"] = {
url = {
GLOBAL = "https://github.com/xlings-res/godot-cpp/releases/download/4.5.0/godot-cpp-4.5.0.tar.gz",
Expand All @@ -88,20 +127,27 @@ package = {
import_std = false,
-- Three roots, exactly as upstream's build systems expose them:
-- hand-written headers, generated headers, and the GDExtension C ABI
-- header (gdextension_interface.h) that both of them include.
-- header that both of them include. Where that header lives moved
-- between the two versions -- 4.5 checks in gdextension/
-- gdextension_interface.h, 10.x generates it into gen/include/ from
-- gdextension_interface.json -- so both roots stay listed.
include_dirs = { "*/include", "*/gen/include", "*/gdextension" },
-- Enumerated rather than `**`: upstream's own test project ships a
-- test/src/*.cpp that must not be swept into the library, and the two
-- source roots are only ever one and two levels deep.
-- Union of both layouts, catch2-style: a glob that matches nothing on
-- a given version is simply skipped. 10.x adds one .cpp directly under
-- gen/src/ that 4.5 does not have.
sources = {
"*/src/*.cpp",
"*/src/*/*.cpp",
"*/gen/src/*.cpp",
"*/gen/src/*/*.cpp",
},
targets = { ["godot-cpp"] = { kind = "lib" } },
features = {
["default"] = { implies = { "gdextension" } },
["gdextension"] = { defines = { "GDEXTENSION" } },
["gdextension"] = { defines = { "GDEXTENSION", "TYPED_METHOD_BIND" } },
},
deps = { },
-- A GDExtension IS a shared library, so this static library's objects
Expand Down
15 changes: 15 additions & 0 deletions tests/examples/godot-cpp-v10/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# compat.godot-cpp 10.0.0-rc1 (Godot 4.6) test project.
#
# Separate member rather than a second dependency in the 4.5 one: the two
# versions are different bindings of different engine releases, and each has to
# be built and asserted on its own -- same shape as catch2 / catch2-v2.
#
# `compat` is redirected to this checkout by the workspace-root [indices],
# which every member inherits.

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

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