Skip to content

Commit c9af2f3

Browse files
committed
Add separate crate which handles command-line interaction with Godot engine
1 parent afa2e59 commit c9af2f3

File tree

15 files changed

+232
-772
lines changed

15 files changed

+232
-772
lines changed

godot-codegen/Cargo.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,15 @@ keywords = ["gamedev", "godot", "engine", "codegen"]
88
categories = ["game-engines", "graphics"]
99

1010
[features]
11+
default = []
1112
codegen-fmt = []
1213
codegen-full = []
1314
double-precision = []
15+
custom-godot = ["godot-input/custom-godot"]
1416

1517
[dependencies]
16-
quote = "1"
17-
proc-macro2 = "1"
18-
which = "4"
18+
godot-input = { path = "../godot-input" }
1919
heck = "0.4"
20-
21-
# Version >= 1.5.5 for security: https://blog.rust-lang.org/2022/03/08/cve-2022-24713.html
22-
# 'unicode-gencat' needed for \d, see: https://docs.rs/regex/1.5.5/regex/#unicode-features
23-
regex = { version = "1.5.5", default-features = false, features = ["std", "unicode-gencat"] }
2420
nanoserde = "0.1.29"
21+
proc-macro2 = "1"
22+
quote = "1"

godot-codegen/input/gdextension_interface.h

Lines changed: 0 additions & 643 deletions
This file was deleted.

godot-codegen/src/api_parser.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
#![allow(dead_code)]
99
#![allow(clippy::question_mark)] // in #[derive(DeJson)]
1010

11-
use crate::{godot_exe, StopWatch};
12-
1311
use nanoserde::DeJson;
1412

1513
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -218,7 +216,7 @@ impl MethodReturn {
218216
// ----------------------------------------------------------------------------------------------------------------------------------------------
219217
// Implementation
220218

221-
pub fn load_extension_api(watch: &mut StopWatch) -> (ExtensionApi, &'static str) {
219+
pub fn load_extension_api(watch: &mut godot_input::StopWatch) -> (ExtensionApi, &'static str) {
222220
// For float/double inference, see:
223221
// * https://github.com/godotengine/godot-proposals/issues/892
224222
// * https://github.com/godotengine/godot-cpp/pull/728
@@ -227,9 +225,11 @@ pub fn load_extension_api(watch: &mut StopWatch) -> (ExtensionApi, &'static str)
227225
#[cfg(not(feature = "double-precision"))]
228226
let build_config = "float_64"; // TODO infer this
229227

230-
let json: String = godot_exe::load_extension_api_json(watch);
228+
// Use type inference, so we can accept both String (dynamically resolved) and &str (prebuilt).
229+
let json = godot_input::load_gdextension_json(watch);
231230

232-
let model: ExtensionApi = DeJson::deserialize_json(&json).expect("failed to deserialize JSON");
231+
let model: ExtensionApi =
232+
DeJson::deserialize_json(json.as_str()).expect("failed to deserialize JSON");
233233
watch.record("deserialize_json");
234234

235235
(model, build_config)

godot-codegen/src/lib.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ mod api_parser;
88
mod central_generator;
99
mod class_generator;
1010
mod context;
11-
mod godot_exe;
12-
mod godot_version;
1311
mod special_cases;
1412
mod util;
1513
mod utilities_generator;
16-
mod watch;
1714

1815
#[cfg(test)]
1916
mod tests;
@@ -25,22 +22,19 @@ use central_generator::{
2522
};
2623
use class_generator::{generate_builtin_class_files, generate_class_files};
2724
use context::Context;
28-
use util::ident;
25+
use util::{ident, to_pascal_case, to_snake_case};
2926
use utilities_generator::generate_utilities_file;
30-
use watch::StopWatch;
3127

32-
use crate::util::{to_pascal_case, to_snake_case};
3328
use proc_macro2::{Ident, TokenStream};
3429
use quote::{quote, ToTokens};
3530
use std::path::{Path, PathBuf};
3631

37-
pub fn generate_sys_files(sys_gen_path: &Path) {
32+
pub fn generate_sys_files(sys_gen_path: &Path, watch: &mut godot_input::StopWatch) {
3833
let mut out_files = vec![];
39-
let mut watch = StopWatch::start();
4034

4135
generate_sys_mod_file(sys_gen_path, &mut out_files);
4236

43-
let (api, build_config) = load_extension_api(&mut watch);
37+
let (api, build_config) = load_extension_api(watch);
4438
let mut ctx = Context::build_from_api(&api);
4539
watch.record("build_context");
4640

@@ -49,12 +43,11 @@ pub fn generate_sys_files(sys_gen_path: &Path) {
4943

5044
rustfmt_if_needed(out_files);
5145
watch.record("rustfmt");
52-
watch.write_stats_to(&sys_gen_path.join("codegen-stats.txt"));
5346
}
5447

5548
pub fn generate_core_files(core_gen_path: &Path) {
5649
let mut out_files = vec![];
57-
let mut watch = StopWatch::start();
50+
let mut watch = godot_input::StopWatch::start();
5851

5952
generate_core_mod_file(core_gen_path, &mut out_files);
6053

godot-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ trace = []
1313
codegen-fmt = ["godot-ffi/codegen-fmt"]
1414
codegen-full = ["godot-codegen/codegen-full"]
1515
double-precision = ["godot-codegen/double-precision"]
16+
custom-godot = ["godot-ffi/custom-godot", "godot-codegen/custom-godot"]
1617

1718
[dependencies]
1819
godot-ffi = { path = "../godot-ffi" }

godot-ffi/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ license = "MPL-2.0"
77
keywords = ["gamedev", "godot", "engine", "ffi"]
88
categories = ["game-engines", "graphics"]
99

10-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11-
1210
[features]
11+
custom-godot = ["dep:godot-input"]
1312
codegen-fmt = ["godot-codegen/codegen-fmt"]
1413
#codegen-full = ["godot-codegen/codegen-full"]
1514

1615
[dependencies]
1716
paste = "1"
1817

1918
[build-dependencies]
20-
bindgen = { version = "0.63.0", default-features = false, features = ["runtime"] }
19+
godot-input = { optional = true, path = "../godot-input" }
2120
godot-codegen = { path = "../godot-codegen" }

godot-ffi/build.rs

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

7-
use std::env;
87
use std::path::Path;
98

109
fn main() {
11-
// For custom path on macOS, iOS, Android etc: see gdnative-sys/build.rs
12-
let gen_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/gen/"));
10+
let mut watch = godot_input::StopWatch::start();
1311

12+
let gen_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/gen/"));
1413
if gen_path.exists() {
1514
std::fs::remove_dir_all(gen_path).unwrap_or_else(|e| panic!("failed to delete dir: {e}"));
15+
watch.record("delete_gen_dir");
1616
}
17+
std::fs::create_dir_all(gen_path).unwrap_or_else(|e| panic!("failed to create dir: {e}"));
1718

18-
run_bindgen(&gen_path.join("gdextension_interface.rs"));
19-
godot_codegen::generate_sys_files(gen_path);
20-
}
21-
22-
fn run_bindgen(out_file: &Path) {
23-
let header_path = "../godot-codegen/input/gdextension_interface.h";
24-
println!("cargo:rerun-if-changed={header_path}");
25-
26-
let builder = bindgen::Builder::default()
27-
.header(header_path)
28-
// Tell cargo to invalidate the built crate whenever any of the
29-
// included header files changed.
30-
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
31-
.prepend_enum_name(false);
32-
33-
std::fs::create_dir_all(
34-
out_file
35-
.parent()
36-
.expect("bindgen output file has parent dir"),
37-
)
38-
.expect("create bindgen output dir");
39-
40-
let bindings = configure_platform_specific(builder)
41-
.generate()
42-
.expect("failed generate gdextension_interface.h bindings");
43-
44-
// Write the bindings to the $OUT_DIR/bindings.rs file.
45-
bindings
46-
.write_to_file(out_file)
47-
.expect("failed write gdextension_interface.h bindings to file");
48-
}
49-
50-
//#[cfg(target_os = "macos")]
51-
fn configure_platform_specific(builder: bindgen::Builder) -> bindgen::Builder {
52-
let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
53-
if target_vendor == "apple" {
54-
eprintln!("Build selected for macOS.");
55-
let path = env::var("LLVM_PATH").expect("env var 'LLVM_PATH' not set");
56-
57-
builder
58-
.clang_arg("-I")
59-
// .clang_arg(format!("{path}/include"))
60-
.clang_arg(apple_include_path().expect("apple include path"))
61-
.clang_arg("-L")
62-
.clang_arg(format!("{path}/lib"))
63-
} else {
64-
eprintln!("Build selected for Linux/Windows.");
65-
builder
66-
}
67-
}
68-
69-
fn apple_include_path() -> Result<String, std::io::Error> {
70-
use std::process::Command;
71-
72-
let target = std::env::var("TARGET").unwrap();
73-
let platform = if target.contains("apple-darwin") {
74-
"macosx"
75-
} else if target == "x86_64-apple-ios" || target == "aarch64-apple-ios-sim" {
76-
"iphonesimulator"
77-
} else if target == "aarch64-apple-ios" {
78-
"iphoneos"
79-
} else {
80-
panic!("not building for macOS or iOS");
81-
};
19+
let rust_header_path = gen_path.join("gdextension_interface.rs");
20+
let header = godot_input::load_gdextension_rust_header(&rust_header_path, &mut watch);
21+
std::fs::write(rust_header_path, header).expect("failed to write extension header");
8222

83-
// run `xcrun --sdk iphoneos --show-sdk-path`
84-
let output = Command::new("xcrun")
85-
.args(["--sdk", platform, "--show-sdk-path"])
86-
.output()?
87-
.stdout;
88-
let prefix = std::str::from_utf8(&output)
89-
.expect("invalid output from `xcrun`")
90-
.trim_end();
91-
92-
let suffix = "usr/include";
93-
let directory = format!("{prefix}/{suffix}");
94-
95-
Ok(directory)
23+
godot_codegen::generate_sys_files(gen_path, &mut watch);
24+
watch.write_stats_to(&gen_path.join("godot-ffi-stats.txt"));
9625
}
97-
98-
// #[cfg(not(target_os = "macos"))]
99-
// fn configure_platform_specific(builder: Builder) -> Builder {
100-
// println!("Build selected for Linux/Windows.");
101-
// builder
102-
// }
103-
104-
/*fn rerun_if_any_changed(paths: &Vec<PathBuf>){
105-
for path in paths {
106-
println!("cargo:rerun-if-changed={}", path.display());
107-
}
108-
}*/

godot-input/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "godot-input"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.63"
6+
license = "MPL-2.0"
7+
keywords = ["gamedev", "godot", "engine", "ffi", "sys"]
8+
categories = ["game-engines", "graphics"]
9+
10+
[features]
11+
custom-godot = ["dep:bindgen", "dep:regex", "dep:which"]
12+
13+
[dependencies]
14+
godot4-artifacts = { git = "https://github.com/godot-rust/godot4-artifacts", branch = "4.0" }
15+
16+
# Version >= 1.5.5 for security: https://blog.rust-lang.org/2022/03/08/cve-2022-24713.html
17+
# 'unicode-gencat' needed for \d, see: https://docs.rs/regex/1.5.5/regex/#unicode-features
18+
bindgen = { optional = true, version = "0.64", default-features = false, features = ["runtime"] }
19+
regex = { optional = true, version = "1.5.5", default-features = false, features = ["std", "unicode-gencat"] }
20+
which = { optional = true, version = "4" }
File renamed without changes.

godot-codegen/src/godot_exe.rs renamed to godot-input/src/godot_exe.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@
55
*/
66

77
use crate::godot_version::parse_godot_version;
8-
use crate::StopWatch;
8+
use crate::header_gen::run_bindgen;
9+
use crate::watch::StopWatch;
910
use std::path::{Path, PathBuf};
1011
use std::process::Command;
1112

1213
/// Commands related to Godot executable
1314
14-
const GODOT_VERSION_PATH: &str =
15-
concat!(env!("CARGO_MANIFEST_DIR"), "/input/gen/godot_version.txt");
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");
1618

17-
const EXTENSION_API_PATH: &str =
18-
concat!(env!("CARGO_MANIFEST_DIR"), "/input/gen/extension_api.json");
19-
20-
pub fn load_extension_api_json(watch: &mut StopWatch) -> String {
21-
let json_path = Path::new(EXTENSION_API_PATH);
19+
pub fn load_gdextension_json(watch: &mut StopWatch) -> String {
20+
let json_path = Path::new(JSON_PATH);
2221
rerun_on_changed(json_path);
2322

2423
let godot_bin = locate_godot_binary();
@@ -31,15 +30,43 @@ pub fn load_extension_api_json(watch: &mut StopWatch) -> String {
3130
dump_extension_api(&godot_bin, json_path);
3231
update_version_file(&version);
3332

34-
watch.record("dump_extension_api");
33+
watch.record("dump_gdextension_json");
3534
}
3635

3736
let result = std::fs::read_to_string(json_path)
3837
.unwrap_or_else(|_| panic!("failed to open file {}", json_path.display()));
38+
3939
watch.record("read_json_file");
4040
result
4141
}
4242

43+
pub fn load_gdextension_rust_header(rust_out_path: &Path, watch: &mut StopWatch) -> String {
44+
let c_header_path = Path::new(HEADER_PATH);
45+
rerun_on_changed(c_header_path);
46+
47+
let godot_bin = locate_godot_binary();
48+
rerun_on_changed(&godot_bin);
49+
watch.record("locate_godot");
50+
51+
// Regenerate API JSON if first time or Godot version is different
52+
let version = read_godot_version(&godot_bin);
53+
if !c_header_path.exists() || has_version_changed(&version) {
54+
dump_header_file(&godot_bin, c_header_path);
55+
update_version_file(&version);
56+
57+
watch.record("dump_gdextension_header");
58+
}
59+
60+
run_bindgen(c_header_path, rust_out_path);
61+
watch.record("read_header_file");
62+
std::fs::read_to_string(rust_out_path).unwrap_or_else(|_| {
63+
panic!(
64+
"failed to read generated Rust file {}",
65+
rust_out_path.display()
66+
)
67+
})
68+
}
69+
4370
fn has_version_changed(current_version: &str) -> bool {
4471
let version_path = Path::new(GODOT_VERSION_PATH);
4572

@@ -92,7 +119,7 @@ fn read_godot_version(godot_bin: &Path) -> String {
92119
fn dump_extension_api(godot_bin: &Path, out_file: &Path) {
93120
let cwd = out_file.parent().unwrap();
94121
std::fs::create_dir_all(cwd).unwrap_or_else(|_| panic!("create directory '{}'", cwd.display()));
95-
println!("Dump extension API to dir '{}'...", cwd.display());
122+
println!("Dump GDExtension API JSON to dir '{}'...", cwd.display());
96123

97124
Command::new(godot_bin)
98125
.current_dir(cwd)
@@ -107,6 +134,27 @@ fn dump_extension_api(godot_bin: &Path, out_file: &Path) {
107134
)
108135
});
109136

137+
println!("Generated {}/gdextension_interface.h.", cwd.display());
138+
}
139+
140+
fn dump_header_file(godot_bin: &Path, out_file: &Path) {
141+
let cwd = out_file.parent().unwrap();
142+
std::fs::create_dir_all(cwd).unwrap_or_else(|_| panic!("create directory '{}'", cwd.display()));
143+
println!("Dump GDExtension header file to dir '{}'...", cwd.display());
144+
145+
Command::new(godot_bin)
146+
.current_dir(cwd)
147+
.arg("--headless")
148+
.arg("--dump-gdextension-interface")
149+
.arg(cwd)
150+
.output()
151+
.unwrap_or_else(|_| {
152+
panic!(
153+
"failed to invoke Godot executable '{}'",
154+
godot_bin.display()
155+
)
156+
});
157+
110158
println!("Generated {}/extension_api.json.", cwd.display());
111159
}
112160

0 commit comments

Comments
 (0)