diff --git a/godot-bindings/src/import.rs b/godot-bindings/src/import.rs index 5b69f8fda..86940484a 100644 --- a/godot-bindings/src/import.rs +++ b/godot-bindings/src/import.rs @@ -63,3 +63,96 @@ pub use gdextension_api::version_4_5 as prebuilt; // [line] pub use gdextension_api::version_$snakeVersion as prebuilt; pub use gdextension_api::version_4_5 as prebuilt; // ]] + +// Platform-specific header loading for cross-compilation support. +// The prebuilt module is compiled for HOST (not TARGET) when used as a build-dependency, +// so we determine the platform at runtime based on CARGO_CFG_TARGET_* environment variables. +#[cfg(not(any(feature = "api-custom", feature = "api-custom-json")))] +pub(crate) mod prebuilt_platform { + use std::borrow::Cow; + + /// Load platform-specific prebuilt bindings based on the TARGET platform (not HOST). + /// + /// Since godot-bindings is a build-dependency, it's compiled for HOST, but we need bindings for TARGET. + /// We detect the target platform using CARGO_CFG_TARGET_* environment variables available during + /// build script execution, then read the corresponding file from the gdextension-api dependency. + pub fn load_gdextension_header_rs_for_target() -> Cow<'static, str> { + // Determine TARGET platform from environment variables + let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").ok(); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").ok(); + + // Select platform suffix matching gdextension-api's file naming + let platform = match (target_family.as_deref(), target_os.as_deref()) { + (Some("windows"), _) => "windows", + (_, Some("macos" | "ios")) => "macos", + _ => "linux", // Linux, Android, and other Unix-like systems + }; + + // Read the file from gdextension-api dependency in Cargo cache + load_platform_file_from_cache(platform) + } + + /// Load platform-specific bindings file from the gdextension-api dependency. + fn load_platform_file_from_cache(platform: &str) -> Cow<'static, str> { + let home = std::env::var("HOME") + .or_else(|_| std::env::var("USERPROFILE")) + .expect("HOME or USERPROFILE environment variable"); + + // Search git checkouts (for git dependencies) + if let Some(contents) = try_load_from_git(&home, platform) { + return Cow::Owned(contents); + } + + // Search registry (for crates.io dependencies) + if let Some(contents) = try_load_from_registry(&home, platform) { + return Cow::Owned(contents); + } + + panic!( + "Failed to locate gdextension-api dependency for platform '{}'.\n\ + The dependency should be in Cargo cache at:\n\ + - {}/.cargo/git/checkouts/godot4-prebuilt-*\n\ + - {}/.cargo/registry/src/*/gdextension-api-*", + platform, home, home + ); + } + + fn try_load_from_git(home: &str, platform: &str) -> Option { + let git_dir = format!("{home}/.cargo/git/checkouts"); + for entry in std::fs::read_dir(&git_dir).ok()?.flatten() { + let path = entry.path(); + if path.file_name()?.to_str()?.starts_with("godot4-prebuilt-") { + // Found godot4-prebuilt checkout, look for the hash subdirectory + for subdir in std::fs::read_dir(&path).ok()?.flatten() { + let file_path = subdir.path().join(format!( + "versions/4.5/res/gdextension_interface_{}.rs", + platform + )); + if let Ok(contents) = std::fs::read_to_string(&file_path) { + return Some(contents); + } + } + } + } + None + } + + fn try_load_from_registry(home: &str, platform: &str) -> Option { + let registry_dir = format!("{home}/.cargo/registry/src"); + for host_entry in std::fs::read_dir(®istry_dir).ok()?.flatten() { + for crate_entry in std::fs::read_dir(host_entry.path()).ok()?.flatten() { + let path = crate_entry.path(); + if path.file_name()?.to_str()?.starts_with("gdextension-api-") { + let file_path = path.join(format!( + "versions/4.5/res/gdextension_interface_{}.rs", + platform + )); + if let Ok(contents) = std::fs::read_to_string(&file_path) { + return Some(contents); + } + } + } + } + None + } +} diff --git a/godot-bindings/src/lib.rs b/godot-bindings/src/lib.rs index 5e3346010..7d4464646 100644 --- a/godot-bindings/src/lib.rs +++ b/godot-bindings/src/lib.rs @@ -129,7 +129,11 @@ mod depend_on_prebuilt { .unwrap_or_else(|e| panic!("failed to write gdextension_interface.h: {e}")); watch.record("write_header_h"); - let rs_contents = prebuilt::load_gdextension_header_rs(); + // CROSS-COMPILATION FIX: + // Since godot-bindings is a build-dependency, it and gdextension-api are compiled for the HOST platform. + // The #[cfg] attributes in gdextension-api::load_gdextension_header_rs() evaluate for HOST, not TARGET. + // We read CARGO_CFG_TARGET_* environment variables to select the correct bindings at runtime. + let rs_contents = crate::import::prebuilt_platform::load_gdextension_header_rs_for_target(); std::fs::write(rs_path, rs_contents.as_ref()) .unwrap_or_else(|e| panic!("failed to write gdextension_interface.rs: {e}")); watch.record("write_header_rs");