#![cfg_attr(published_docs, feature(doc_cfg))]
pub(crate) mod watch;
use std::path::Path;
pub use watch::StopWatch;
mod import;
#[derive(Eq, PartialEq, Debug)]
pub struct GodotVersion {
pub full_string: String,
pub major: u8,
pub minor: u8,
pub patch: u8,
pub status: String,
pub custom_rev: Option<String>,
}
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod godot_version;
#[cfg(feature = "api-custom")] #[cfg_attr(published_docs, doc(cfg(feature = "api-custom")))]
#[path = ""]
mod depend_on_custom {
use std::borrow::Cow;
use super::*;
pub(crate) mod godot_exe;
pub(crate) mod godot_version;
pub(crate) mod header_gen;
pub fn load_gdextension_json(watch: &mut StopWatch) -> Cow<'static, str> {
Cow::Owned(godot_exe::load_gdextension_json(watch))
}
pub fn write_gdextension_headers(h_path: &Path, rs_path: &Path, watch: &mut StopWatch) {
godot_exe::write_gdextension_headers(h_path, rs_path, false, watch);
}
#[cfg(feature = "api-custom-extheader")] #[cfg_attr(published_docs, doc(cfg(feature = "api-custom-extheader")))]
pub fn write_gdextension_headers_from_c(h_path: &Path, rs_path: &Path, watch: &mut StopWatch) {
godot_exe::write_gdextension_headers(h_path, rs_path, true, watch);
}
pub(crate) fn get_godot_version() -> GodotVersion {
godot_exe::read_godot_version(&godot_exe::locate_godot_binary())
}
}
#[cfg(feature = "api-custom")] #[cfg_attr(published_docs, doc(cfg(feature = "api-custom")))]
pub use depend_on_custom::*;
#[cfg(feature = "api-custom-json")] #[cfg_attr(published_docs, doc(cfg(feature = "api-custom-json")))]
#[path = ""]
mod depend_on_custom_json {
use std::borrow::Cow;
use super::*;
pub(crate) mod godot_json;
pub(crate) mod godot_version;
pub(crate) mod header_gen;
pub fn load_gdextension_json(watch: &mut StopWatch) -> Cow<'static, str> {
let result = godot_json::load_custom_gdextension_json();
watch.record("read_api_custom_json");
Cow::Owned(result)
}
pub fn write_gdextension_headers(h_path: &Path, rs_path: &Path, watch: &mut StopWatch) {
godot_json::write_gdextension_headers(h_path, rs_path, watch);
}
pub(crate) fn get_godot_version() -> GodotVersion {
godot_json::read_godot_version()
}
}
#[cfg(feature = "api-custom-json")] #[cfg_attr(published_docs, doc(cfg(feature = "api-custom-json")))]
pub use depend_on_custom_json::*;
#[cfg(not(any(feature = "api-custom", feature = "api-custom-json")))] #[cfg_attr(published_docs, doc(cfg(not(any(feature = "api-custom", feature = "api-custom-json")))))]
#[path = ""]
mod depend_on_prebuilt {
use super::*;
use crate::import::prebuilt;
pub fn load_gdextension_json(_watch: &mut StopWatch) -> std::borrow::Cow<'static, str> {
prebuilt::load_gdextension_json()
}
fn select_target_platform() -> prebuilt::TargetPlatform {
let target_os =
std::env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS must be set by Cargo");
match target_os.as_str() {
"windows" => prebuilt::TargetPlatform::Windows,
"ios" | "macos" => prebuilt::TargetPlatform::MacOS,
"android" | "dragonfly" | "freebsd" | "linux" | "netbsd" | "openbsd" => {
prebuilt::TargetPlatform::Linux
}
"emscripten" => prebuilt::TargetPlatform::Wasm,
other => panic!("Unsupported target OS `{other}`."),
}
}
pub fn write_gdextension_headers(h_path: &Path, rs_path: &Path, watch: &mut StopWatch) {
let h_contents = prebuilt::load_gdextension_header_h();
std::fs::write(h_path, h_contents.as_ref())
.unwrap_or_else(|e| panic!("failed to write gdextension_interface.h: {e}"));
watch.record("write_header_h");
let platform = select_target_platform();
let rs_contents = prebuilt::load_gdextension_header_rs_for_platform(platform);
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");
}
pub(crate) fn get_godot_version() -> GodotVersion {
let version: Vec<&str> = prebuilt::GODOT_VERSION_STRING
.split('.')
.collect::<Vec<_>>();
GodotVersion {
full_string: prebuilt::GODOT_VERSION_STRING.to_string(),
major: version[0].parse().unwrap(),
minor: version[1].parse().unwrap(),
patch: version
.get(2)
.and_then(|patch| patch.parse().ok())
.unwrap_or(0),
status: "stable".to_string(),
custom_rev: None,
}
}
}
#[cfg(not(any(feature = "api-custom", feature = "api-custom-json")))] #[cfg_attr(published_docs, doc(cfg(not(any(feature = "api-custom", feature = "api-custom-json")))))]
pub use depend_on_prebuilt::*;
pub fn clear_dir(dir: &Path, watch: &mut StopWatch) {
if dir.exists() {
remove_dir_all_reliable(dir);
watch.record("delete_gen_dir");
}
std::fs::create_dir_all(dir).unwrap_or_else(|e| panic!("failed to create dir: {e}"));
}
pub fn emit_godot_version_cfg() {
let all_versions = import::ALL_VERSIONS;
println!(r#"cargo:rustc-check-cfg=cfg(published_docs, values(none()))"#);
for (_, minor, patch) in all_versions.iter().copied() {
if minor > 0 && patch == 0 {
println!(r#"cargo:rustc-check-cfg=cfg(since_api, values("4.{minor}"))"#);
println!(r#"cargo:rustc-check-cfg=cfg(before_api, values("4.{minor}"))"#);
}
}
let GodotVersion {
major: _,
minor,
patch,
..
} = get_godot_version();
let upcoming_minor = all_versions.last().unwrap().1;
for m in 1..=minor {
println!(r#"cargo:rustc-cfg=since_api="4.{m}""#);
}
for m in minor + 1..=upcoming_minor {
println!(r#"cargo:rustc-cfg=before_api="4.{m}""#);
}
for (_, m, p) in all_versions.iter().copied() {
if (m, p) >= (minor, patch) {
println!(r#"cargo:rustc-cfg=since_patch_api="4.{m}.{p}""#);
} else {
println!(r#"cargo:rustc-cfg=before_patch_api="4.{m}.{p}""#);
}
}
}
pub fn emit_wasm_nothreads_cfg() {
println!(r#"cargo:rustc-check-cfg=cfg(wasm_nothreads, values(none()))"#);
#[cfg(feature = "experimental-wasm-nothreads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-wasm-nothreads")))]
if std::env::var("CARGO_CFG_TARGET_FAMILY")
.expect("target family environment variable")
.split(',')
.any(|family| family == "wasm")
{
println!(r#"cargo:rustc-cfg=wasm_nothreads"#);
}
}
pub fn remove_dir_all_reliable(path: &Path) {
let mut retry_count = 0;
while path.exists() {
match std::fs::remove_dir_all(path) {
Ok(_) => break,
Err(err) => {
assert_ne!(
retry_count,
5,
"cannot remove directory: {path_display} after 5 tries with error: {err}",
path_display = path.display()
);
retry_count += 1;
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
}
}
pub fn before_api(major_minor: &str) -> bool {
let queried_minor = major_minor
.strip_prefix("4.")
.expect("major version must be 4");
let queried_minor = queried_minor.parse::<u8>().expect("invalid minor version");
let godot_version = get_godot_version();
godot_version.minor < queried_minor
}
pub fn since_api(major_minor: &str) -> bool {
!before_api(major_minor)
}
pub fn emit_safeguard_levels() {
let mut safeguards_level = if cfg!(debug_assertions) { 2 } else { 1 };
#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
if cfg!(feature = "safeguards-dev-balanced") {
safeguards_level = 1;
}
#[cfg(not(debug_assertions))] #[cfg_attr(published_docs, doc(cfg(not(debug_assertions))))]
if cfg!(feature = "safeguards-release-disengaged") {
safeguards_level = 0;
}
println!(r#"cargo:rustc-check-cfg=cfg(safeguards_balanced)"#);
println!(r#"cargo:rustc-check-cfg=cfg(safeguards_strict)"#);
if safeguards_level >= 1 {
println!(r#"cargo:rustc-cfg=safeguards_balanced"#);
}
if safeguards_level >= 2 {
println!(r#"cargo:rustc-cfg=safeguards_strict"#);
}
}
#[cfg(any(feature = "api-custom", feature = "api-custom-json"))] #[cfg_attr(published_docs, doc(cfg(any(feature = "api-custom", feature = "api-custom-json"))))]
pub(crate) fn env_var_or_deprecated(
once: &'static std::sync::Once,
new_name: &str,
old_name: &str,
) -> Result<String, std::env::VarError> {
std::env::var(new_name).or_else(|_| {
let result = std::env::var(old_name);
if result.is_ok()
&& std::env::var("CARGO_PKG_NAME")
.map(|name| name == "godot-codegen")
.unwrap_or(false)
{
once.call_once(|| {
println!(
"cargo:warning=env var `{old_name}` is deprecated, use `{new_name}` instead."
);
});
}
result
})
}