Skip to content

Commit 0a2085b

Browse files
committed
Use target/godot-gen for generated files; streamline godot-bindings API
1 parent 091b9a7 commit 0a2085b

File tree

8 files changed

+177
-92
lines changed

8 files changed

+177
-92
lines changed

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

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,42 +60,43 @@ runs:
6060
echo "GODOT_BUILT_FROM=_Built from [\`$godotVer\`](https://github.com/godotengine/godot/commit/$gitSha)._" >> $GITHUB_ENV
6161
shell: bash
6262

63-
# Note: if this fails, run `git diff -R > tweaks.patch` after updating the file manually
63+
- name: "Install Rust"
64+
uses: ./.github/composite/rust
65+
with:
66+
rust: ${{ inputs.rust-toolchain }}
67+
with-llvm: ${{ inputs.with-llvm }}
68+
69+
- name: "Build gdext (itest)"
70+
run: |
71+
cargo build -p itest ${{ inputs.rust-extra-args }}
72+
shell: bash
73+
env:
74+
RUSTFLAGS: ${{ inputs.rust-env-rustflags }}
75+
76+
# Note: no longer fails, as we expect header to be forward-compatible; instead issues a warning
6477
- name: "Copy and compare GDExtension header"
6578
if: inputs.artifact-name == 'godot-linux'
79+
working-directory: target/godot-gen
6680
run: |
67-
mkdir -p godot-codegen/input
68-
cp $RUNNER_DIR/godot_bin/gdextension_interface.h godot-codegen/input/gdextension_interface.h
69-
git apply godot-codegen/input/tweak.patch -v
70-
git diff --exit-code --quiet || {
81+
mv $RUNNER_DIR/godot_bin/gdextension_interface.h gdextension_interface.h.bak
82+
git apply ../../godot-bindings/res/tweaks.patch
83+
git diff --no-index --exit-code --quiet gdextension_interface.h gdextension_interface.h.bak || {
7184
echo "OUTCOME=header-diff" >> $GITHUB_ENV
72-
echo "gdextension_interface.h is not up-to-date; abort."
85+
echo "::warning::gdextension_interface.h is not up-to-date."
7386
echo ""
74-
75-
echo "### :x: Outdated GDExtension API header" >> $GITHUB_STEP_SUMMARY
87+
88+
echo "### :warning: Outdated GDExtension API header" >> $GITHUB_STEP_SUMMARY
7689
echo "gdextension_interface.h contains the following differences:" >> $GITHUB_STEP_SUMMARY
7790
echo "\`\`\`diff" >> $GITHUB_STEP_SUMMARY
78-
git diff >> $GITHUB_STEP_SUMMARY
91+
git diff --no-index gdextension_interface.h gdextension_interface.h.bak >> $GITHUB_STEP_SUMMARY
7992
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
8093
echo "After manually updating file, run: \`git diff -R > tweak.patch\`." >> $GITHUB_STEP_SUMMARY
8194
82-
exit 1
95+
mv gdextension_interface.h.bak gdextension_interface.h
96+
#exit 1
8397
}
8498
shell: bash
8599

86-
- name: "Install Rust"
87-
uses: ./.github/composite/rust
88-
with:
89-
rust: ${{ inputs.rust-toolchain }}
90-
with-llvm: ${{ inputs.with-llvm }}
91-
92-
- name: "Build godot-rust"
93-
run: |
94-
cargo build -p itest ${{ inputs.rust-extra-args }}
95-
shell: bash
96-
env:
97-
RUSTFLAGS: ${{ inputs.rust-env-rustflags }}
98-
99100
- name: "Run Godot integration tests"
100101
# Aborts immediately if Godot outputs certain keywords (would otherwise stall until CI runner times out).
101102
# Explanation:

.github/workflows/full-ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,14 @@ jobs:
205205
godot-binary: godot.linuxbsd.editor.dev.double.x86_64
206206
rust-extra-args: --features double-precision
207207

208+
- name: linux-bindgen
209+
artifact-name: linux
210+
os: ubuntu-20.04
211+
rust-toolchain: stable
212+
godot-binary: godot.linuxbsd.editor.dev.x86_64
213+
rust-extra-args: --features custom-godot
214+
215+
208216
# Special Godot binaries compiled with AddressSanitizer/LeakSanitizer to detect UB/leaks.
209217
# Additionally, the Godot source is patched to make dlclose() a no-op, as unloading dynamic libraries loses stacktrace and
210218
# cause false positives like println!. See https://github.com/google/sanitizers/issues/89.
@@ -231,7 +239,7 @@ jobs:
231239
- name: "Run Godot integration test"
232240
uses: ./.github/composite/godot-itest
233241
with:
234-
artifact-name: godot-${{ matrix.name }}
242+
artifact-name: godot-${{ matrix.artifact-name || matrix.name }}
235243
godot-binary: ${{ matrix.godot-binary }}
236244
godot-args: ${{ matrix.godot-args }}
237245
rust-extra-args: ${{ matrix.rust-extra-args }}

godot-bindings/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ keywords = ["gamedev", "godot", "engine", "ffi", "sys"]
88
categories = ["game-engines", "graphics"]
99

1010
[features]
11+
default = ["prebuilt-godot"]
12+
prebuilt-godot = ["dep:godot4-prebuilt"]
1113
custom-godot = ["dep:bindgen", "dep:regex", "dep:which"]
14+
custom-godot-extheader = []
1215

1316
[dependencies]
14-
godot4-artifacts = { git = "https://github.com/godot-rust/godot4-artifacts", branch = "4.0" }
17+
godot4-prebuilt = { optional = true, git = "https://github.com/godot-rust/godot4-artifacts", branch = "4.0" }
1518

1619
# Version >= 1.5.5 for security: https://blog.rust-lang.org/2022/03/08/cve-2022-24713.html
1720
# 'unicode-gencat' needed for \d, see: https://docs.rs/regex/1.5.5/regex/#unicode-features

godot-bindings/res/tweak.patch

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
diff --git b/godot-bindings/gen/gdextension_interface.h a/godot-bindings/gen/gdextension_interface.h
1+
diff --git b/target/godot-gen/gdextension_interface.h a/target/godot-gen/gdextension_interface.h
22
index 0b7615f..6db266e 100644
3-
--- b/godot-bindings/gen/gdextension_interface.h
4-
+++ a/godot-bindings/gen/gdextension_interface.h
3+
--- b/target/godot-gen/gdextension_interface.h
4+
+++ a/target/godot-gen/gdextension_interface.h
55
@@ -139,22 +139,22 @@ typedef enum {
66

77
} GDExtensionVariantOperator;

godot-bindings/src/godot_exe.rs

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,28 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
//! Commands related to Godot executable
8+
79
use crate::godot_version::parse_godot_version;
810
use crate::header_gen::generate_rust_binding;
911
use crate::watch::StopWatch;
1012
use std::path::{Path, PathBuf};
11-
use std::process::Command;
12-
13-
/// Commands related to Godot executable
14-
15-
const GODOT_VERSION_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/gen/godot_version.txt");
16-
const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/gen/extension_api.json");
17-
const HEADER_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/gen/gdextension_interface.h");
13+
use std::process::{Command, Output};
14+
15+
// Note: CARGO_BUILD_TARGET_DIR and CARGO_TARGET_DIR are not set.
16+
// OUT_DIR would be standing to reason, but it's an unspecified path that cannot be referenced by CI.
17+
const GODOT_VERSION_PATH: &str = concat!(
18+
env!("CARGO_MANIFEST_DIR"),
19+
"/../target/godot-gen/godot_version.txt"
20+
);
21+
const JSON_PATH: &str = concat!(
22+
env!("CARGO_MANIFEST_DIR"),
23+
"/../target/godot-gen/extension_api.json"
24+
);
25+
const HEADER_PATH: &str = concat!(
26+
env!("CARGO_MANIFEST_DIR"),
27+
"/../target/godot-gen/gdextension_interface.h"
28+
);
1829
const RES_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res");
1930

2031
pub fn load_gdextension_json(watch: &mut StopWatch) -> String {
@@ -31,7 +42,7 @@ pub fn load_gdextension_json(watch: &mut StopWatch) -> String {
3142
dump_extension_api(&godot_bin, json_path);
3243
update_version_file(&version);
3344

34-
watch.record("dump_gdextension_json");
45+
watch.record("dump_json");
3546
// }
3647

3748
let result = std::fs::read_to_string(json_path)
@@ -41,34 +52,40 @@ pub fn load_gdextension_json(watch: &mut StopWatch) -> String {
4152
result
4253
}
4354

44-
pub fn load_gdextension_header_rs(rust_out_path: &Path, watch: &mut StopWatch) -> String {
45-
let c_header_path = Path::new(HEADER_PATH);
46-
let resource_path = Path::new(RES_PATH);
47-
rerun_on_changed(c_header_path);
48-
49-
let godot_bin = locate_godot_binary();
50-
rerun_on_changed(&godot_bin);
51-
watch.record("locate_godot");
55+
pub fn load_gdextension_header_rs(
56+
c_in_path: Option<&Path>,
57+
rust_out_path: &Path,
58+
watch: &mut StopWatch,
59+
) {
60+
let c_header_path;
5261

53-
// Regenerate API JSON if first time or Godot version is different
54-
let version = read_godot_version(&godot_bin);
55-
// if !c_header_path.exists() || has_version_changed(&version) {
56-
dump_header_file(&godot_bin, c_header_path);
57-
update_version_file(&version);
62+
if let Some(c_in_path) = c_in_path {
63+
// External C header file provided; we don't invoke Godot
64+
c_header_path = c_in_path;
65+
} else {
66+
// No external C header file: Godot binary is present, we use it to dump C header
67+
let godot_bin = locate_godot_binary();
68+
rerun_on_changed(&godot_bin);
69+
watch.record("locate_godot");
70+
71+
// Regenerate API JSON if first time or Godot version is different
72+
let version = read_godot_version(&godot_bin);
73+
c_header_path = Path::new(HEADER_PATH);
74+
75+
// if !c_header_path.exists() || has_version_changed(&version) {
76+
dump_header_file(&godot_bin, c_header_path);
77+
update_version_file(&version);
78+
// }
79+
};
80+
rerun_on_changed(c_header_path);
5881

59-
watch.record("dump_gdextension_header");
60-
// }
82+
watch.record("dump_header_c");
6183

62-
patch_c_header(&resource_path.join("tweak.patch"));
84+
let tweak_path = Path::new(RES_PATH).join("tweak.patch");
85+
patch_c_header(c_header_path, &tweak_path);
6386
generate_rust_binding(c_header_path, rust_out_path);
6487

65-
watch.record("read_header_file");
66-
std::fs::read_to_string(rust_out_path).unwrap_or_else(|_| {
67-
panic!(
68-
"failed to read generated Rust file {}",
69-
rust_out_path.display()
70-
)
71-
})
88+
watch.record("generate_header_rs");
7289
}
7390

7491
#[allow(dead_code)]
@@ -151,21 +168,30 @@ fn dump_header_file(godot_bin: &Path, out_file: &Path) {
151168
println!("Generated {}/extension_api.json.", cwd.display());
152169
}
153170

154-
fn patch_c_header(tweak_path: &Path) {
171+
fn patch_c_header(c_header_path: &Path, tweak_path: &Path) {
172+
// The C header path *must* be passed in by the invoking crate, as the path cannot be relative to this crate.
173+
// Otherwise, it can be something like `/home/runner/.cargo/git/checkouts/gdext-76630c89719e160c/efd3b94/godot-bindings`.
174+
let cwd = c_header_path.parent().unwrap().parent().unwrap();
175+
println!("cwd: {}", cwd.display());
176+
155177
// Note: patch must have paths relative to Git root (aka top-level dir), so cwd is root
156-
let cwd = tweak_path.parent().unwrap().parent().unwrap();
157178
rerun_on_changed(tweak_path);
158179

159180
let git = locate_git_binary();
160181
let mut cmd = Command::new(&git);
161182
cmd.current_dir(cwd).arg("apply").arg("-v").arg(tweak_path);
162183

163-
execute(cmd, "apply Git patch");
184+
let output = execute(cmd, "apply Git patch");
185+
let stderr = String::from_utf8(output.stderr).expect("convert Git patch output to UTF-8");
186+
187+
// `git patch` returns 0 even if it skips a patch because it's not applicable -- treat this as error
188+
assert!(!stderr.contains("Skipped"), "Git patch was skipped");
164189
}
165190

166191
fn locate_godot_binary() -> PathBuf {
167192
if let Ok(string) = std::env::var("GODOT4_BIN") {
168193
println!("Found GODOT4_BIN with path to executable: '{string}'");
194+
println!("cargo:rerun-if-env-changed=GODOT4_BIN");
169195
PathBuf::from(string)
170196
} else if let Ok(path) = which::which("godot4") {
171197
println!("Found 'godot4' executable in PATH: {}", path.display());
@@ -193,12 +219,23 @@ fn locate_git_binary() -> PathBuf {
193219
}
194220
}
195221

196-
fn execute(mut cmd: Command, error_message: &str) {
222+
fn execute(mut cmd: Command, error_message: &str) -> Output {
197223
let output = cmd
198224
.output()
199225
.unwrap_or_else(|_| panic!("failed to execute command: {error_message}"));
200226

201-
if !output.status.success() {
227+
if output.status.success() {
228+
println!(
229+
"[stdout] {}",
230+
String::from_utf8(output.stdout.clone()).unwrap()
231+
);
232+
println!(
233+
"[stderr] {}",
234+
String::from_utf8(output.stderr.clone()).unwrap()
235+
);
236+
println!("[status] {}", output.status);
237+
output
238+
} else {
202239
println!("[stdout] {}", String::from_utf8(output.stdout).unwrap());
203240
println!("[stderr] {}", String::from_utf8(output.stderr).unwrap());
204241
println!("[status] {}", output.status);
@@ -208,5 +245,4 @@ fn execute(mut cmd: Command, error_message: &str) {
208245

209246
fn rerun_on_changed(path: &Path) {
210247
println!("cargo:rerun-if-changed={}", path.display());
211-
println!("cargo:rerun-if-env-changed=GODOT4_BIN");
212248
}

godot-bindings/src/header_gen.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
use std::env;
88
use std::path::Path;
99

10-
pub(crate) fn generate_rust_binding(c_header_path: &Path, out_rust_path: &Path) {
11-
let c_header_path = c_header_path.display().to_string();
10+
pub(crate) fn generate_rust_binding(in_c_path: &Path, out_rust_path: &Path) {
11+
let c_header_path = in_c_path.display().to_string();
1212
println!("cargo:rerun-if-changed={}", c_header_path);
1313

1414
let builder = bindgen::Builder::default()
@@ -27,12 +27,24 @@ pub(crate) fn generate_rust_binding(c_header_path: &Path, out_rust_path: &Path)
2727

2828
let bindings = configure_platform_specific(builder)
2929
.generate()
30-
.expect("failed generate gdextension_interface.h bindings");
30+
.unwrap_or_else(|err| {
31+
panic!(
32+
"bindgen generate failed\n c: {}\n rs: {}\n err: {}\n",
33+
in_c_path.display(),
34+
out_rust_path.display(),
35+
err
36+
)
37+
});
3138

3239
// Write the bindings to the $OUT_DIR/bindings.rs file.
33-
bindings
34-
.write_to_file(out_rust_path)
35-
.expect("failed write gdextension_interface.h bindings to file");
40+
bindings.write_to_file(out_rust_path).unwrap_or_else(|err| {
41+
panic!(
42+
"bindgen write failed\n c: {}\n rs: {}\n err: {}\n",
43+
in_c_path.display(),
44+
out_rust_path.display(),
45+
err
46+
)
47+
});
3648
}
3749

3850
//#[cfg(target_os = "macos")]

0 commit comments

Comments
 (0)