Skip to content

Commit 853ac27

Browse files
committed
Allow users to generate API from user-provided JSON
- Remove `GODOT4_GDEXTENSION_HEADERS` – it can be introduced as separate feature (after someone presents solid usecase for that).
1 parent 0ffa937 commit 853ac27

File tree

8 files changed

+68
-78
lines changed

8 files changed

+68
-78
lines changed

.github/composite/godot-itest/action.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ runs:
9494
with-llvm: ${{ inputs.with-llvm }}
9595
cache-key: ${{ inputs.rust-cache-key }}
9696

97+
# if
9798
- name: "Patch prebuilt version ({{ inputs.godot-prebuilt-patch }})"
9899
if: inputs.godot-prebuilt-patch != ''
99100
env:
@@ -132,7 +133,7 @@ runs:
132133

133134
# else if
134135
- name: "Dump extension api"
135-
if: inputs.godot-indirect-json == 'true' && inputs.godot-prebuilt-patch == ''
136+
if: inputs.godot-prebuilt-patch == '' && inputs.godot-indirect-json == 'true'
136137
run: |
137138
$GODOT4_BIN --headless --dump-extension-api
138139
mv extension_api.json $RUNNER_DIR/godot_bin/extension_api.json

godot-bindings/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ gdextension-api = { workspace = true }
4343
bindgen = { workspace = true, optional = true }
4444
regex = { workspace = true, optional = true }
4545
which = { workspace = true, optional = true }
46-
# required by `api-custom-json` to parse the extension api header (to get the Godot version).
46+
# Required by `api-custom-json` to parse the extension API JSON (to get the Godot version).
4747
nanoserde = { workspace = true, optional = true }
4848

4949
[dev-dependencies]

godot-bindings/src/godot_exe.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
//! Commands related to Godot executable
99
1010
use crate::godot_version::{parse_godot_version, validate_godot_version};
11-
use crate::header_gen::{generate_rust_binding, patch_c_header};
11+
use crate::header_gen::generate_rust_binding;
1212
use crate::watch::StopWatch;
1313
use crate::GodotVersion;
1414

15+
use regex::Regex;
1516
use std::fs;
1617
use std::path::{Path, PathBuf};
1718
use std::process::{Command, Output};
18-
1919
// Note: CARGO_BUILD_TARGET_DIR and CARGO_TARGET_DIR are not set.
2020
// OUT_DIR would be standing to reason, but it's an unspecified path that cannot be referenced by CI.
2121
// const GODOT_VERSION_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/gen/godot_version.txt");
@@ -111,7 +111,7 @@ pub(crate) fn read_godot_version(godot_bin: &Path) -> GodotVersion {
111111

112112
let version = parse_godot_version(stdout)
113113
.unwrap_or_else(|err| panic!("failed to parse Godot version '{stdout}': {err}"));
114-
validate_godot_version(&version, stdout);
114+
validate_godot_version(&version);
115115

116116
// `--dump-extension-api`, `--dump-gdextension-interface` etc. are only available in Debug builds (editor, debug export template).
117117
// If we try to run them in release builds, Godot tries to run, causing a popup alert with an unhelpful message:
@@ -170,6 +170,53 @@ fn dump_header_file(godot_bin: &Path, out_file: &Path) {
170170
println!("Generated {}/gdextension_interface.h.", cwd.display());
171171
}
172172

173+
pub(crate) fn patch_c_header(in_h_path: &Path, out_h_path: &Path) {
174+
// The C header path *must* be passed in by the invoking crate, as the path cannot be relative to this crate.
175+
// Otherwise, it can be something like `/home/runner/.cargo/git/checkouts/gdext-76630c89719e160c/efd3b94/godot-bindings`.
176+
177+
println!("Patch C header '{}'...", in_h_path.display());
178+
179+
let mut c = fs::read_to_string(in_h_path)
180+
.unwrap_or_else(|_| panic!("failed to read C header file {}", in_h_path.display()));
181+
182+
// Detect whether header is legacy (4.0) format. This should generally already be checked outside.
183+
assert!(
184+
c.contains("GDExtensionInterfaceGetProcAddress"),
185+
"C header file '{}' seems to be GDExtension version 4.0, which is no longer support by godot-rust.",
186+
in_h_path.display()
187+
);
188+
189+
// Patch for variant converters and type constructors.
190+
c = c.replace(
191+
"typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionVariantPtr, GDExtensionTypePtr);",
192+
"typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr);"
193+
)
194+
.replace(
195+
"typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionTypePtr, GDExtensionVariantPtr);",
196+
"typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr);"
197+
)
198+
.replace(
199+
"typedef void (*GDExtensionPtrConstructor)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args);",
200+
"typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args);"
201+
);
202+
203+
// Use single regex with independent "const"/"Const", as there are definitions like this:
204+
// typedef const void *GDExtensionMethodBindPtr;
205+
let c = Regex::new(r"typedef (const )?void \*GDExtension(Const)?([a-zA-Z0-9]+?)Ptr;") //
206+
.expect("regex for mut typedef")
207+
.replace_all(&c, "typedef ${1}struct __Gdext$3 *GDExtension${2}${3}Ptr;");
208+
209+
// println!("Patched contents:\n\n{}\n\n", c.as_ref());
210+
211+
// Write the modified contents back to the file
212+
fs::write(out_h_path, c.as_ref()).unwrap_or_else(|_| {
213+
panic!(
214+
"failed to write patched C header file {}",
215+
out_h_path.display()
216+
)
217+
});
218+
}
219+
173220
pub(crate) fn locate_godot_binary() -> PathBuf {
174221
if let Ok(string) = std::env::var("GODOT4_BIN") {
175222
println!("Found GODOT4_BIN with path to executable: '{string}'");

godot-bindings/src/godot_json.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// Moving said types to `godot-bindings` would increase the cognitive overhead (since domain mapping is responsibility of `godot-codegen`, while godot-bindings is responsible for providing required resources & emitting the version).
1515
// In the future we might experiment with splitting said types into separate crates.
1616

17-
use crate::depend_on_custom_json::header_gen::{generate_rust_binding, patch_c_header};
17+
use crate::depend_on_custom_json::header_gen::generate_rust_binding;
1818
use crate::godot_version::validate_godot_version;
1919
use crate::{GodotVersion, StopWatch};
2020
use nanoserde::DeJson;
@@ -79,7 +79,7 @@ pub(crate) fn read_godot_version() -> GodotVersion {
7979
.expect("failed to deserialize JSON");
8080
let version = extension_api.header.into_godot_version();
8181

82-
validate_godot_version(&version, &version.full_string);
82+
validate_godot_version(&version);
8383

8484
version
8585
}
@@ -89,17 +89,10 @@ pub(crate) fn write_gdextension_headers(
8989
out_rs_path: &Path,
9090
watch: &mut StopWatch,
9191
) {
92-
// Allow to use custom gdextension headers in unlikely case if one uses heavily modified version of the engine.
93-
if let Ok(path) = std::env::var("GODOT4_GDEXTENSION_HEADERS") {
94-
let in_h_path = Path::new(&path);
95-
patch_c_header(in_h_path, out_h_path);
96-
watch.record("patch_header_h");
97-
} else {
98-
let h_contents = load_latest_gdextension_headers();
99-
fs::write(out_h_path, h_contents.as_ref())
100-
.unwrap_or_else(|e| panic!("failed to write gdextension_interface.h: {e}"));
101-
watch.record("write_header_h");
102-
}
92+
let h_contents = load_latest_gdextension_headers();
93+
fs::write(out_h_path, h_contents.as_ref())
94+
.unwrap_or_else(|e| panic!("failed to write gdextension_interface.h: {e}"));
95+
watch.record("write_header_h");
10396

10497
generate_rust_binding(out_h_path, out_rs_path);
10598
watch.record("generate_header_rs");

godot-bindings/src/godot_version.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
// This file is explicitly included in unit tests
99
// while all the functions included are used only with `custom-api` and `custom-api-json` features.
10-
#![allow(unused_variables, dead_code)]
10+
#![cfg_attr(not(feature = "api-custom"), allow(unused_variables, dead_code))]
1111

1212
use crate::GodotVersion;
1313
use regex::{Captures, Regex};
@@ -54,18 +54,17 @@ pub fn parse_godot_version(version_str: &str) -> Result<GodotVersion, Box<dyn Er
5454
})
5555
}
5656

57-
pub(crate) fn validate_godot_version(godot_version: &GodotVersion, version_str: &str) {
57+
pub(crate) fn validate_godot_version(godot_version: &GodotVersion) {
5858
assert_eq!(
59-
godot_version.major,
60-
4,
59+
godot_version.major, 4,
6160
"Only Godot versions with major version 4 are supported; found version {}.",
62-
version_str.trim()
61+
godot_version.full_string
6362
);
6463

6564
assert!(
6665
godot_version.minor > 0,
6766
"Godot 4.0 is no longer supported by godot-rust; found version {}.",
68-
version_str.trim()
67+
godot_version.full_string
6968
);
7069
}
7170

godot-bindings/src/header_gen.rs

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,8 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use regex::Regex;
8+
use std::env;
99
use std::path::Path;
10-
use std::{env, fs};
11-
12-
pub(crate) fn patch_c_header(in_h_path: &Path, out_h_path: &Path) {
13-
// The C header path *must* be passed in by the invoking crate, as the path cannot be relative to this crate.
14-
// Otherwise, it can be something like `/home/runner/.cargo/git/checkouts/gdext-76630c89719e160c/efd3b94/godot-bindings`.
15-
16-
println!("Patch C header '{}'...", in_h_path.display());
17-
18-
let mut c = fs::read_to_string(in_h_path)
19-
.unwrap_or_else(|_| panic!("failed to read C header file {}", in_h_path.display()));
20-
21-
// Detect whether header is legacy (4.0) format. This should generally already be checked outside.
22-
assert!(
23-
c.contains("GDExtensionInterfaceGetProcAddress"),
24-
"C header file '{}' seems to be GDExtension version 4.0, which is no longer support by godot-rust.",
25-
in_h_path.display()
26-
);
27-
28-
// Patch for variant converters and type constructors.
29-
c = c.replace(
30-
"typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionVariantPtr, GDExtensionTypePtr);",
31-
"typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr);"
32-
)
33-
.replace(
34-
"typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionTypePtr, GDExtensionVariantPtr);",
35-
"typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr);"
36-
)
37-
.replace(
38-
"typedef void (*GDExtensionPtrConstructor)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args);",
39-
"typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args);"
40-
);
41-
42-
// Use single regex with independent "const"/"Const", as there are definitions like this:
43-
// typedef const void *GDExtensionMethodBindPtr;
44-
let c = Regex::new(r"typedef (const )?void \*GDExtension(Const)?([a-zA-Z0-9]+?)Ptr;") //
45-
.expect("regex for mut typedef")
46-
.replace_all(&c, "typedef ${1}struct __Gdext$3 *GDExtension${2}${3}Ptr;");
47-
48-
// println!("Patched contents:\n\n{}\n\n", c.as_ref());
49-
50-
// Write the modified contents back to the file
51-
fs::write(out_h_path, c.as_ref()).unwrap_or_else(|_| {
52-
panic!(
53-
"failed to write patched C header file {}",
54-
out_h_path.display()
55-
)
56-
});
57-
}
5810

5911
pub(crate) fn generate_rust_binding(in_h_path: &Path, out_rs_path: &Path) {
6012
let c_header_path = in_h_path.display().to_string();

godot-bindings/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ mod depend_on_custom_json {
9292

9393
pub fn load_gdextension_json(watch: &mut StopWatch) -> Cow<'static, str> {
9494
let result = godot_json::load_custom_gdextension_json();
95-
watch.record("read_api_json");
95+
watch.record("read_api_custom_json");
9696
Cow::Owned(result)
9797
}
9898

godot/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,8 @@
7777
//!
7878
//! `api-custom` feature requires specifying `GODOT4_BIN` environment variable with a path to your Godot4 binary.
7979
//!
80-
//! `api-custom-json` feature requires specifying `GODOT4_GDEXTENSION_JSON` environment variable with a path
81-
//! to extension api json.
82-
//! By default, `api-custom-json` will fetch the latest GDExtension headers, which are backward-compatible and will work with all the up-to-date Godot versions.
83-
//! `GODOT4_GDEXTENSION_HEADERS` can be set to use custom, user-specified headers instead.<br><br>
80+
//! The `api-custom-json` feature requires specifying `GODOT4_GDEXTENSION_JSON` environment variable with a path
81+
//! to your custom-defined `extension_api.json`.<br><br>
8482
//!
8583
//! * **`double-precision`**
8684
//!

0 commit comments

Comments
 (0)