diff --git a/.agents/docs/2026-08-04-add-godot-cpp-plan.md b/.agents/docs/2026-08-04-add-godot-cpp-plan.md index 8f50646..0a4e30c 100644 --- a/.agents/docs/2026-08-04-add-godot-cpp-plan.md +++ b/.agents/docs/2026-08-04-add-godot-cpp-plan.md @@ -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 ` 和库名对应;点号在本索引里留给子模块,如 `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:附一个侧头文件 + ``,与 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。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 79ac841..e96666b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -409,6 +409,30 @@ jobs: echo "MEMBERS=$sel" >> "$GITHUB_ENV" echo "selected members: ${sel:-}" + # ── 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 (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: diff --git a/README.md b/README.md index db0f245..5140234 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 907e957..d6c4f10 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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 官方库) | ### 新增一个包 diff --git a/mcpp.toml b/mcpp.toml index e67aabf..023726e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -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", diff --git a/pkgs/g/godotengine.godot-cpp-m.lua b/pkgs/g/godotengine.godot-cpp-m.lua new file mode 100644 index 0000000..3336fe4 --- /dev/null +++ b/pkgs/g/godotengine.godot-cpp-m.lua @@ -0,0 +1,127 @@ +-- Form A descriptor: the godot-cpp module package ships its own mcpp.toml. +-- mcpp's default lookup finds /*/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 ` 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 ` 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 `` 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 /*/mcpp.toml". Reproducible with a namespace unrelated to either +-- package: leave a single -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 '", +-- 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 /*/mcpp.toml) +} diff --git a/tests/examples/godot-cpp-module-v10/mcpp.toml b/tests/examples/godot-cpp-module-v10/mcpp.toml new file mode 100644 index 0000000..9a8d314 --- /dev/null +++ b/tests/examples/godot-cpp-module-v10/mcpp.toml @@ -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" diff --git a/tests/examples/godot-cpp-module-v10/tests/godot_cpp_module_v10.cpp b/tests/examples/godot-cpp-module-v10/tests/godot_cpp_module_v10.cpp new file mode 100644 index 0000000..a1012e2 --- /dev/null +++ b/tests/examples/godot-cpp-module-v10/tests/godot_cpp_module_v10.cpp @@ -0,0 +1,76 @@ +// Behavioral test for the godotengine.godot-cpp-m 10.0.0-rc1 module package (Godot 4.6): godot-cpp's +// API reached with `import godot_cpp;` and no #include at all. +// +// The numbers are deliberately the same ones tests/examples/godot-cpp asserts +// through headers -- that member is the header spelling of this one, so a +// divergence here means the module re-export changed behaviour. +// +// Both halves can fail: +// * Vector2::length(), Basis::orthonormalized(), Color::to_rgba32() and +// AABB::get_volume() are DEFINED in compat.godot-cpp's src/variant/*.cpp, +// so they only resolve if that dependency really compiled and linked. +// * Node, Node2D, Node::PROCESS_MODE_*, godot::OK and Variant::OBJECT exist +// only in the pre-generated gen/ tree. +// +// Not covered here, by nature: anything routed through the +// gdextension_interface_* function pointers (String, Array, class +// registration) needs a Godot process that has loaded the extension. The +// GDCLASS/macro shape is covered by the package's own test suite. + +import std; +import godot_cpp; + +namespace { + +bool close(double a, double b) { + return std::fabs(a - b) < 1e-5; +} + +} // namespace + +int main() { + using namespace godot; + + const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) && + close(Vector2(3, 4).normalized().length(), 1.0); + + const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0)); + const bool vec3_ok = cross == Vector3(0, 0, 1) && + close(Vector3(2, 3, 6).length(), 7.0); + + const bool basis_ok = close(Basis().orthonormalized().determinant(), 1.0); + + const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu; + + const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4)); + const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4)); + const bool aabb_ok = close(box.get_volume(), 24.0) && + box.intersects(other) && + close(box.intersection(other).get_volume(), 6.0); + + // These bindings are Godot 4.6. The 4.5 member checks GODOT_VERSION_MAJOR + // / _MINOR, but those are MACROS from version.hpp and a module cannot + // export macros -- so the version is asserted through types instead: + // EditorDock and AHashMap exist in 4.6 and not in 4.5. + const bool version_ok = sizeof(EditorDock) > 0 && + sizeof(AHashMap) > 0; + + const bool gen_ok = sizeof(Node) > 0 && + sizeof(Node2D) > 0 && + Node::PROCESS_MODE_INHERIT == 0 && + Node::PROCESS_MODE_DISABLED == 4 && + godot::OK == 0 && + godot::ERR_FILE_NOT_FOUND == 7 && + godot::SIDE_LEFT == 0 && + Variant::OBJECT != Variant::NIL; + + const bool math_ok = Math::is_equal_approx(1.0f, 1.0f) && + !Math::is_zero_approx(1.0f) && + close(Math::lerp(0.0, 10.0, 0.25), 2.5); + + const bool ok = version_ok && vec2_ok && vec3_ok && basis_ok && color_ok && + aabb_ok && gen_ok && math_ok; + std::println("version={} vec2={} vec3={} basis={} color={} aabb={} gen={} math={}", + version_ok, vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok, math_ok); + return ok ? 0 : 1; +} diff --git a/tests/examples/godot-cpp-module/mcpp.toml b/tests/examples/godot-cpp-module/mcpp.toml new file mode 100644 index 0000000..5dedd25 --- /dev/null +++ b/tests/examples/godot-cpp-module/mcpp.toml @@ -0,0 +1,24 @@ +# godotengine.godot-cpp (module layer) test project: consumes `import godot_cpp;` +# and asserts the same numbers the compat.godot-cpp member asserts through +# headers -- if the module re-export changed behaviour, these would move. +# +# Overrides the workspace-root redirect: root declares `compat`, this member +# needs `godotengine`. A member-level [indices] REPLACES the inherited table +# rather than merging with it, which is what keeps this to ONE project index +# repo -- two repos pointing at the same tree make every lookup ambiguous. +# +# The module package's own transitive dep (compat.godot-cpp) therefore resolves +# from the GLOBAL published index, not from this checkout; the compat +# descriptor is pre-merge-validated by its own member (tests/examples/godot-cpp). +[indices] +godotengine = { path = "../../.." } + +[package] +name = "godot-cpp-module-tests" +version = "0.1.0" + +# Dotted spelling on purpose: CI maps a changed pkgs//.lua to the +# members whose mcpp.toml mentions , and here is the whole +# `godotengine.godot-cpp-m`. +[dependencies] +godotengine.godot-cpp-m = "4.5.0" diff --git a/tests/examples/godot-cpp-module/tests/godot_cpp_module.cpp b/tests/examples/godot-cpp-module/tests/godot_cpp_module.cpp new file mode 100644 index 0000000..3022cf7 --- /dev/null +++ b/tests/examples/godot-cpp-module/tests/godot_cpp_module.cpp @@ -0,0 +1,69 @@ +// Behavioral test for the godotengine.godot-cpp-m module package: godot-cpp's +// API reached with `import godot_cpp;` and no #include at all. +// +// The numbers are deliberately the same ones tests/examples/godot-cpp asserts +// through headers -- that member is the header spelling of this one, so a +// divergence here means the module re-export changed behaviour. +// +// Both halves can fail: +// * Vector2::length(), Basis::orthonormalized(), Color::to_rgba32() and +// AABB::get_volume() are DEFINED in compat.godot-cpp's src/variant/*.cpp, +// so they only resolve if that dependency really compiled and linked. +// * Node, Node2D, Node::PROCESS_MODE_*, godot::OK and Variant::OBJECT exist +// only in the pre-generated gen/ tree. +// +// Not covered here, by nature: anything routed through the +// gdextension_interface_* function pointers (String, Array, class +// registration) needs a Godot process that has loaded the extension. The +// GDCLASS/macro shape is covered by the package's own test suite. + +import std; +import godot_cpp; + +namespace { + +bool close(double a, double b) { + return std::fabs(a - b) < 1e-5; +} + +} // namespace + +int main() { + using namespace godot; + + const bool vec2_ok = close(Vector2(3, 4).length(), 5.0) && + close(Vector2(3, 4).normalized().length(), 1.0); + + const Vector3 cross = Vector3(1, 0, 0).cross(Vector3(0, 1, 0)); + const bool vec3_ok = cross == Vector3(0, 0, 1) && + close(Vector3(2, 3, 6).length(), 7.0); + + const bool basis_ok = close(Basis().orthonormalized().determinant(), 1.0); + + const bool color_ok = Color(1.0f, 0.0f, 0.0f, 1.0f).to_rgba32() == 0xff0000ffu; + + const AABB box(Vector3(0, 0, 0), Vector3(2, 3, 4)); + const AABB other(Vector3(1, 1, 1), Vector3(4, 4, 4)); + const bool aabb_ok = close(box.get_volume(), 24.0) && + box.intersects(other) && + close(box.intersection(other).get_volume(), 6.0); + + const bool gen_ok = sizeof(Node) > 0 && + sizeof(Node2D) > 0 && + Node::PROCESS_MODE_INHERIT == 0 && + Node::PROCESS_MODE_DISABLED == 4 && + godot::OK == 0 && + godot::ERR_FILE_NOT_FOUND == 7 && + godot::SIDE_LEFT == 0 && + Variant::OBJECT != Variant::NIL; + + const bool math_ok = Math::is_equal_approx(1.0f, 1.0f) && + !Math::is_zero_approx(1.0f) && + close(Math::lerp(0.0, 10.0, 0.25), 2.5); + + const bool ok = vec2_ok && vec3_ok && basis_ok && color_ok && aabb_ok && + gen_ok && math_ok; + std::println("vec2={} vec3={} basis={} color={} aabb={} gen={} math={}", + vec2_ok, vec3_ok, basis_ok, color_ok, aabb_ok, gen_ok, math_ok); + return ok ? 0 : 1; +}