From cf4ed87d5105cda78a7d0cdaae4fd25d86554f82 Mon Sep 17 00:00:00 2001 From: Allen Date: Thu, 9 Oct 2025 21:51:15 +0800 Subject: [PATCH 1/3] Fix cross build from macos to msvc --- godot-bindings/src/import.rs | 93 ++++++++++++++++++++++++++++++++++++ godot-bindings/src/lib.rs | 6 ++- 2 files changed, 98 insertions(+), 1 deletion(-) 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"); From 9a65a9d093cab3029d598bfe25a979ca1bc908a5 Mon Sep 17 00:00:00 2001 From: Allen Date: Mon, 13 Oct 2025 22:55:56 +0800 Subject: [PATCH 2/3] Read DEP_GDEXTENSION_API_ROOT env from gdextension-api's build script --- godot-bindings/build.rs | 2 +- godot-bindings/src/import.rs | 91 ++++++++++++------------------------ 2 files changed, 30 insertions(+), 63 deletions(-) diff --git a/godot-bindings/build.rs b/godot-bindings/build.rs index edceaa9f3..dbd915cf1 100644 --- a/godot-bindings/build.rs +++ b/godot-bindings/build.rs @@ -10,7 +10,7 @@ // It's the only purpose of this build.rs file. If a better solution is found, this file can be removed. #[rustfmt::skip] -fn main() { +fn main() { let mut count = 0; if cfg!(feature = "api-custom") { count += 1; } if cfg!(feature = "api-custom-json") { count += 1; } diff --git a/godot-bindings/src/import.rs b/godot-bindings/src/import.rs index 86940484a..708efe61e 100644 --- a/godot-bindings/src/import.rs +++ b/godot-bindings/src/import.rs @@ -60,8 +60,8 @@ pub use gdextension_api::version_4_5 as prebuilt; // ]] // [version-sync] [[ // [include] current.minor -// [line] pub use gdextension_api::version_$snakeVersion as prebuilt; -pub use gdextension_api::version_4_5 as prebuilt; +// [line] pub use gdextension_api as prebuilt; +pub use gdextension_api as prebuilt; // ]] // Platform-specific header loading for cross-compilation support. @@ -70,6 +70,7 @@ pub use gdextension_api::version_4_5 as prebuilt; #[cfg(not(any(feature = "api-custom", feature = "api-custom-json")))] pub(crate) mod prebuilt_platform { use std::borrow::Cow; + use std::path::PathBuf; /// Load platform-specific prebuilt bindings based on the TARGET platform (not HOST). /// @@ -88,71 +89,37 @@ pub(crate) mod prebuilt_platform { _ => "linux", // Linux, Android, and other Unix-like systems }; - // Read the file from gdextension-api dependency in Cargo cache - load_platform_file_from_cache(platform) + // Get the Godot version string from prebuilt module + let version = super::prebuilt::GODOT_VERSION_STRING; + + // Read the file from gdextension-api dependency + load_platform_file(platform, version) } /// 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 - ); - } + /// + /// Uses the DEP_GDEXTENSION_API_ROOT environment variable exported by gdextension-api's build script + /// to locate the correct platform-specific bindings file. + fn load_platform_file(platform: &str, _version: &str) -> Cow<'static, str> { + // Get the gdextension-api root directory from the build script + let dep_root = std::env::var("DEP_GDEXTENSION_API_ROOT") + .expect("DEP_GDEXTENSION_API_ROOT not set. This should be exported by gdextension-api's build script."); - 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 - } + // The 4.x branches have files directly in res/, not versions/{version}/res/ + let file_path = PathBuf::from(dep_root) + .join("res") + .join(format!("gdextension_interface_{}.rs", platform)); - 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); - } - } - } + match std::fs::read_to_string(&file_path) { + Ok(contents) => Cow::Owned(contents), + Err(e) => panic!( + "Failed to load platform-specific bindings for platform '{}'.\n\ + Tried to read: {}\n\ + Error: {}\n\ + \n\ + This is likely a cross-compilation issue or the gdextension-api version doesn't support this platform.", + platform, file_path.display(), e + ), } - None } } From 4c28af1993360257586c790388c89e26ceec3576 Mon Sep 17 00:00:00 2001 From: Allen Date: Tue, 14 Oct 2025 10:59:34 +0800 Subject: [PATCH 3/3] Refine the code and comments according to PR review comment --- godot-bindings/src/import.rs | 53 ++++++++++++++---------------------- godot-bindings/src/lib.rs | 6 ++-- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/godot-bindings/src/import.rs b/godot-bindings/src/import.rs index 708efe61e..6fe907958 100644 --- a/godot-bindings/src/import.rs +++ b/godot-bindings/src/import.rs @@ -60,66 +60,55 @@ pub use gdextension_api::version_4_5 as prebuilt; // ]] // [version-sync] [[ // [include] current.minor -// [line] pub use gdextension_api as prebuilt; -pub use gdextension_api 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. +// Cross-compilation support: +// Since godot-bindings is a build-dependency, it and the gdextension-api crate 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 platform-specific Rust bindings at runtime. #[cfg(not(any(feature = "api-custom", feature = "api-custom-json")))] pub(crate) mod prebuilt_platform { use std::borrow::Cow; use std::path::PathBuf; - /// Load platform-specific prebuilt bindings based on the TARGET platform (not HOST). + /// Load platform-specific Rust bindings (gdextension_interface_{platform}.rs) for the TARGET platform. /// - /// 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. + /// During cross-compilation, godot-bindings runs on the HOST, but needs to generate bindings for the TARGET. + /// This function reads CARGO_CFG_TARGET_* environment variables to determine the target platform, + /// then loads the appropriate gdextension_interface_{platform}.rs file from the gdextension-api crate's res/ directory. 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 }; - // Get the Godot version string from prebuilt module - let version = super::prebuilt::GODOT_VERSION_STRING; - - // Read the file from gdextension-api dependency - load_platform_file(platform, version) + load_platform_file(platform) } - /// Load platform-specific bindings file from the gdextension-api dependency. - /// - /// Uses the DEP_GDEXTENSION_API_ROOT environment variable exported by gdextension-api's build script - /// to locate the correct platform-specific bindings file. - fn load_platform_file(platform: &str, _version: &str) -> Cow<'static, str> { - // Get the gdextension-api root directory from the build script + /// Reads gdextension_interface_{platform}.rs from the gdextension-api crate using DEP_GDEXTENSION_API_ROOT. + fn load_platform_file(platform: &str) -> Cow<'static, str> { let dep_root = std::env::var("DEP_GDEXTENSION_API_ROOT") .expect("DEP_GDEXTENSION_API_ROOT not set. This should be exported by gdextension-api's build script."); - // The 4.x branches have files directly in res/, not versions/{version}/res/ let file_path = PathBuf::from(dep_root) .join("res") - .join(format!("gdextension_interface_{}.rs", platform)); + .join(format!("gdextension_interface_{platform}.rs")); - match std::fs::read_to_string(&file_path) { - Ok(contents) => Cow::Owned(contents), - Err(e) => panic!( - "Failed to load platform-specific bindings for platform '{}'.\n\ + std::fs::read_to_string(&file_path) + .map(Cow::Owned) + .unwrap_or_else(|e| panic!( + "Failed to load platform-specific Rust bindings for '{platform}'.\n\ Tried to read: {}\n\ - Error: {}\n\ + Error: {e}\n\ \n\ This is likely a cross-compilation issue or the gdextension-api version doesn't support this platform.", - platform, file_path.display(), e - ), - } + file_path.display() + )) } } diff --git a/godot-bindings/src/lib.rs b/godot-bindings/src/lib.rs index 7d4464646..c0698410c 100644 --- a/godot-bindings/src/lib.rs +++ b/godot-bindings/src/lib.rs @@ -129,10 +129,10 @@ mod depend_on_prebuilt { .unwrap_or_else(|e| panic!("failed to write gdextension_interface.h: {e}")); watch.record("write_header_h"); - // CROSS-COMPILATION FIX: - // Since godot-bindings is a build-dependency, it and gdextension-api are compiled for the HOST platform. + // Cross-compilation support: + // Since godot-bindings is a build-dependency, it and the gdextension-api crate 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. + // We read CARGO_CFG_TARGET_* environment variables to select the correct platform-specific Rust 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}"));