Skip to content
Closed
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
14 changes: 11 additions & 3 deletions .github/workflows/build-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,21 @@ jobs:
working-directory: generator
run: cargo build -vv

- name: "Run generator tests"
working-directory: generator
run: cargo test

- name: "Move generated files + rename according to platform"
env:
BUILD_CONFIG: linux
run: |
mv .generated/* output/
mv output/gdextension_interface.rs output/gdextension_interface_${BUILD_CONFIG}.rs
rm output/*stats*
mv .generated/gdextension_interface.h output/
# Rename 64-bit and 32-bit bindings with platform prefix
mv .generated/gdextension_interface_64.rs output/gdextension_interface_${BUILD_CONFIG}_64.rs
mv .generated/gdextension_interface_32.rs output/gdextension_interface_${BUILD_CONFIG}_32.rs
rm -f .generated/*stats* output/*stats*
echo "Output directory:"
ls -la output/

# -----------------------------------------------------------------------------------------------------------------------------------------
# JSON spec
Expand Down
32 changes: 26 additions & 6 deletions generator/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,36 @@

use std::path::Path;

fn main() {
let mut watch = godot_bindings::StopWatch::start();
use godot_bindings::TargetPointerWidth;

fn main() {
let gen_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.generated"));
let header_c_path = gen_path.join("gdextension_interface.h");
let header_rs_path = gen_path.join("gdextension_interface.rs");

// Generate bindings for both 64-bit and 32-bit targets.
// This allows proper layout tests on both architectures without runtime stripping.
// See: https://github.com/godot-rust/gdext/issues/347

// Output paths - CI workflow will rename these with platform prefix (e.g., linux_64, linux_32).
// Windows and macOS only need 64-bit; Linux/wasm needs both.
let header_rs_64_path = gen_path.join("gdextension_interface_64.rs");
let header_rs_32_path = gen_path.join("gdextension_interface_32.rs");

// Note: do not call clear_dir(), as we pass in the C header into the gen directory.
//godot_bindings::clear_dir(gen_path, &mut watch);
godot_bindings::write_gdextension_headers_from_c(&header_c_path, &header_rs_path, &mut watch);

watch.write_stats_to(&gen_path.join("godot-ffi-stats.txt"));
// Generate 64-bit bindings.
godot_bindings::write_gdextension_headers_for_target(
&header_c_path,
&header_rs_64_path,
TargetPointerWidth::Bits64,
);
println!("Generated 64-bit bindings: {}", header_rs_64_path.display());

// Generate 32-bit bindings.
godot_bindings::write_gdextension_headers_for_target(
&header_c_path,
&header_rs_32_path,
TargetPointerWidth::Bits32,
);
println!("Generated 32-bit bindings: {}", header_rs_32_path.display());
}
75 changes: 74 additions & 1 deletion generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
*/

// Output of generated code. Mimics the file structure, symbols are re-exported.
// We now generate separate bindings for 64-bit and 32-bit targets (Linux/wasm only for 32-bit).
// See: https://github.com/godot-rust/gdext/issues/347

// The generator runs on 64-bit Linux and produces both _64.rs and _32.rs files.
// For local builds, we select based on host pointer width.
#[rustfmt::skip]
#[allow(
dead_code,
Expand All @@ -15,7 +20,21 @@
non_upper_case_globals,
clippy::redundant_static_lifetimes,
)]
#[path = "../../.generated/gdextension_interface.rs"]
#[cfg(target_pointer_width = "64")]
#[path = "../../.generated/gdextension_interface_64.rs"]
mod gdextension_interface;

#[rustfmt::skip]
#[allow(
dead_code,
deref_nullptr,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
clippy::redundant_static_lifetimes,
)]
#[cfg(target_pointer_width = "32")]
#[path = "../../.generated/gdextension_interface_32.rs"]
mod gdextension_interface;


Expand All @@ -27,3 +46,57 @@ trait Distinct {}
impl Distinct for gdextension_interface::GDExtensionVariantPtr {}
impl Distinct for gdextension_interface::GDExtensionTypePtr {}
impl Distinct for gdextension_interface::GDExtensionConstTypePtr {}

#[cfg(test)]
mod tests {
use super::gdextension_interface::*;

/// Verify that key FFI types are exported and have the expected properties.
#[test]
fn test_ffi_types_exist() {
// These types should exist in the generated bindings
let _: GDExtensionVariantPtr;
let _: GDExtensionTypePtr;
let _: GDExtensionConstTypePtr;
let _: GDExtensionBool;
let _: GDExtensionInt;
}

/// Verify pointer types are distinct (type safety through patch).
#[test]
fn test_pointer_types_are_distinct() {
use std::any::TypeId;

// These should be different types (ensured by the Distinct trait above,
// but we also verify with TypeId for good measure)
let variant_ptr_id = TypeId::of::<GDExtensionVariantPtr>();
let type_ptr_id = TypeId::of::<GDExtensionTypePtr>();
let const_type_ptr_id = TypeId::of::<GDExtensionConstTypePtr>();

assert_ne!(variant_ptr_id, type_ptr_id, "VariantPtr and TypePtr should be distinct");
assert_ne!(variant_ptr_id, const_type_ptr_id, "VariantPtr and ConstTypePtr should be distinct");
assert_ne!(type_ptr_id, const_type_ptr_id, "TypePtr and ConstTypePtr should be distinct");
}

/// Verify that GDExtensionBool has the expected size (1 byte / u8).
#[test]
fn test_gdextension_bool_size() {
assert_eq!(
std::mem::size_of::<GDExtensionBool>(),
1,
"GDExtensionBool should be 1 byte"
);
}

/// Verify that GDExtensionInt has pointer-width size (platform-dependent).
#[test]
fn test_gdextension_int_size() {
// GDExtensionInt is int64_t on 64-bit and int32_t on 32-bit (pointer-sized)
let expected_size = std::mem::size_of::<isize>();
assert_eq!(
std::mem::size_of::<GDExtensionInt>(),
expected_size,
"GDExtensionInt should be pointer-sized"
);
}
}
60 changes: 58 additions & 2 deletions prebuilt-template/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,72 @@ pub const fn load_gdextension_header_h() -> CowStr {
CowStr::Borrowed(include_str!("../res/gdextension_interface.h"))
}

/// Returns the contents of the header file `gdextension_interface.rs`, generated for the corresponding platform.
/// Returns the contents of the header file `gdextension_interface.rs`, generated for the corresponding platform and pointer width.
///
/// The bindings are now generated separately for 32-bit and 64-bit targets to ensure
/// correct layout tests on both architectures. See: https://github.com/godot-rust/gdext/issues/347
///
/// **Important**: This function uses compile-time `#[cfg]` to select bindings, which means it
/// evaluates based on the *current compilation target*, not the cross-compilation target.
/// For cross-compilation scenarios (e.g., build scripts), use [`load_gdextension_header_rs_32`]
/// or [`load_gdextension_header_rs_64`] with `CARGO_CFG_TARGET_POINTER_WIDTH` instead.
// Kept for backward compatibility. New code should use load_gdextension_header_rs_32() or _64().
#[deprecated(
since = "0.3.1",
note = "Use load_gdextension_header_rs_32() or load_gdextension_header_rs_64() with CARGO_CFG_TARGET_POINTER_WIDTH for cross-compilation support"
)]
pub const fn load_gdextension_header_rs() -> CowStr {
// 64-bit platforms: use platform-specific bindings
#[cfg(all(windows, target_pointer_width = "64"))]
let s = include_str!("../res/gdextension_interface_windows.rs");

#[cfg(all(target_os = "macos", target_pointer_width = "64"))]
let s = include_str!("../res/gdextension_interface_macos.rs");

#[cfg(all(unix, not(target_os = "macos"), target_pointer_width = "64"))]
let s = include_str!("../res/gdextension_interface_linux_64.rs");

// 32-bit platforms: all use the same bindings (Linux, wasm32, etc.)
//
// Note: with this, in practce, we only support wasm32 and linux i686.
// Godot *has* support for other 32-bit platforms. However
// maintaining and offering *tested* exports for corner cases such
// as win32 is quite costly. Wasm32 is a real-world use case though.
#[cfg(target_pointer_width = "32")]
let s = include_str!("../res/gdextension_interface_linux_32.rs");

CowStr::Borrowed(s)
}

/// Returns the 32-bit bindings, regardless of current compilation target.
///
/// Use this in build scripts with `CARGO_CFG_TARGET_POINTER_WIDTH` to select
/// the correct bindings for cross-compilation.
pub const fn load_gdextension_header_rs_32() -> CowStr {
CowStr::Borrowed(include_str!("../res/gdextension_interface_linux_32.rs"))
}

/// Returns the 64-bit bindings for the current platform.
///
/// Use this in build scripts with `CARGO_CFG_TARGET_POINTER_WIDTH` to select
/// the correct bindings for cross-compilation.
///
/// Note: Returns platform-specific bindings (Windows/macOS/Linux) based on compile-time cfg.
/// For cross-compilation to a different OS, this may not be accurate, but struct layouts
/// are identical across 64-bit platforms.
pub const fn load_gdextension_header_rs_64() -> CowStr {
#[cfg(windows)]
let s = include_str!("../res/gdextension_interface_windows.rs");

#[cfg(target_os = "macos")]
let s = include_str!("../res/gdextension_interface_macos.rs");

#[cfg(all(unix, not(target_os = "macos")))]
let s = include_str!("../res/gdextension_interface_linux.rs");
let s = include_str!("../res/gdextension_interface_linux_64.rs");

// Fallback for non-unix, non-windows, non-macos (unlikely but safe)
#[cfg(not(any(windows, unix)))]
let s = include_str!("../res/gdextension_interface_linux_64.rs");

CowStr::Borrowed(s)
}
Expand Down