Skip to content

Commit e98b3fc

Browse files
committed
Move version detection out of user crate
This should make the `custom-godot` feature silence the warning properly. Currently, the version requirement is hard-coded, but it should be easy to later introduce a mechanism where the versions requirements can be collected from `-sys` and `-bindings` and combined, for better usefulness and less maintenance work in the future. Close #1023
1 parent 9050211 commit e98b3fc

File tree

6 files changed

+149
-25
lines changed

6 files changed

+149
-25
lines changed

gdnative-core/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rust-version = "1.63"
1515
default = []
1616
gd-test = []
1717
type-tag-fallback = []
18+
custom-godot = []
1819

1920
[dependencies]
2021
gdnative-sys = { path = "../gdnative-sys", version = "=0.11.3" }
@@ -26,11 +27,12 @@ atomic-take = "1"
2627
bitflags = "1"
2728
glam = "0.22"
2829
indexmap = "1"
30+
inventory = { version = "0.3", optional = true }
2931
libc = "0.2"
3032
once_cell = "1"
3133
parking_lot = "0.12"
34+
semver = "1"
3235
serde = { version = "1", features = ["derive"], optional = true }
33-
inventory = { version = "0.3", optional = true }
3436

3537
[dev-dependencies]
3638
gdnative = { path = "../gdnative" } # for doc-tests

gdnative-core/src/init/diagnostics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
//! any problems were found. This is so that they can be freely improved without compatibility
66
//! concerns.
77
8+
mod godot_version_mismatch;
89
mod missing_manual_registration;
910
mod missing_suggested_diagnostics;
1011

12+
#[doc(inline)]
13+
pub use godot_version_mismatch::godot_version_mismatch;
14+
1115
#[doc(inline)]
1216
pub use missing_manual_registration::missing_manual_registration;
1317

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use semver::{BuildMetadata, Prerelease};
2+
3+
use crate::core_types::Dictionary;
4+
use crate::core_types::FromVariant;
5+
use crate::object::ownership::Unique;
6+
use crate::private::{get_api, EngineMethodTable};
7+
8+
/// Checks the version number of the host Godot instance to see if it matches the generated API.
9+
/// Returns `true` if the test isn't applicable, or if no mismatch was found.
10+
#[inline]
11+
pub fn godot_version_mismatch() -> bool {
12+
let ret = check_godot_version_mismatch();
13+
if !ret {
14+
godot_warn!(concat!(
15+
"gdnative-core: GDNative version mismatches may lead to subtle bugs, undefined behavior or crashes at runtime.\n",
16+
"Apply the 'custom-godot' feature if you want to use current godot-rust with another Godot engine version.",
17+
));
18+
}
19+
20+
ret
21+
}
22+
23+
#[cfg(feature = "custom-godot")]
24+
fn check_godot_version_mismatch() -> bool {
25+
true
26+
}
27+
28+
#[cfg(not(feature = "custom-godot"))]
29+
fn check_godot_version_mismatch() -> bool {
30+
use semver::VersionReq;
31+
32+
let Some(version) = godot_version() else {
33+
godot_warn!("gdnative-core: failed to get version info from the engine.");
34+
return false;
35+
};
36+
37+
let version_req = VersionReq::parse("~3.5.1").unwrap();
38+
39+
if version_req.matches(&version) {
40+
true
41+
} else {
42+
godot_warn!("This godot-rust version is only compatible with Godot `{version_req}`; detected version `{version}`.");
43+
false
44+
}
45+
}
46+
47+
fn godot_version() -> Option<semver::Version> {
48+
let version = unsafe {
49+
let api = get_api();
50+
let engine = (api.godot_global_get_singleton)(b"Engine\0".as_ptr() as *mut _);
51+
52+
let mut dictionary = sys::godot_dictionary::default();
53+
54+
(api.godot_method_bind_ptrcall)(
55+
EngineMethodTable::get(api).get_version_info,
56+
engine,
57+
[].as_mut_ptr() as *mut _,
58+
&mut dictionary as *mut _ as *mut _,
59+
);
60+
61+
Dictionary::<Unique>::from_sys(dictionary)
62+
};
63+
64+
let major = u64::from_variant(&version.get("major")?).ok()?;
65+
let minor = u64::from_variant(&version.get("minor")?).ok()?;
66+
let patch = u64::from_variant(&version.get("patch")?).ok()?;
67+
68+
let pre = version
69+
.get("status")
70+
.and_then(|v| {
71+
let s = String::from_variant(&v).ok()?;
72+
if s == "stable" {
73+
return None;
74+
}
75+
76+
let s = s.chars().map(sanitize_for_semver).collect::<String>();
77+
Some(Prerelease::new(&s).expect("string sanitized"))
78+
})
79+
.unwrap_or(Prerelease::EMPTY);
80+
81+
let build = {
82+
let mut build_metadata = String::new();
83+
let mut sep = false;
84+
85+
if let Some(build_name) = version
86+
.get("build")
87+
.and_then(|v| String::from_variant(&v).ok())
88+
{
89+
build_metadata.extend(build_name.chars().map(sanitize_for_semver));
90+
sep = true;
91+
};
92+
93+
if let Some(hash) = version
94+
.get("hash")
95+
.and_then(|v| String::from_variant(&v).ok())
96+
{
97+
if sep {
98+
build_metadata.push('.');
99+
}
100+
build_metadata.extend(hash.chars().map(sanitize_for_semver));
101+
};
102+
103+
if build_metadata.is_empty() {
104+
BuildMetadata::EMPTY
105+
} else {
106+
BuildMetadata::new(&build_metadata).expect("build metadata sanitized")
107+
}
108+
};
109+
110+
Some(semver::Version {
111+
major,
112+
minor,
113+
patch,
114+
pre,
115+
build,
116+
})
117+
}
118+
119+
fn sanitize_for_semver(s: char) -> char {
120+
if s.is_ascii_alphanumeric() {
121+
s
122+
} else {
123+
'-'
124+
}
125+
}

gdnative-core/src/init/macros.rs

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,15 @@ macro_rules! godot_nativescript_init {
3535
return;
3636
}
3737

38-
// Compatibility warning if using in-house Godot version (not applicable for custom ones)
39-
#[cfg(not(feature = "custom-godot"))]
40-
{
41-
use $crate::core_types::Variant;
42-
43-
let engine = gdnative::api::Engine::godot_singleton();
44-
let info = engine.get_version_info();
45-
46-
if info.get("major").expect("major version") != Variant::new(3)
47-
|| info.get("minor").expect("minor version") != Variant::new(5)
48-
|| info.get("patch").expect("patch version") < Variant::new(1) {
49-
let string = info.get("string").expect("version str").to::<String>().expect("version str type");
50-
$crate::log::godot_warn!(
51-
"This godot-rust version is only compatible with Godot >= 3.5.1 and < 3.6; detected version {}.\n\
52-
GDNative mismatches may lead to subtle bugs, undefined behavior or crashes at runtime.\n\
53-
Apply the 'custom-godot' feature if you want to use current godot-rust with another Godot engine version.",
54-
string
55-
);
56-
}
57-
}
58-
5938
$crate::private::report_panics("nativescript_init", || {
60-
$crate::init::auto_register($crate::init::InitHandle::new(handle, $crate::init::InitLevel::AUTO));
61-
$callback($crate::init::InitHandle::new(handle, $crate::init::InitLevel::USER));
39+
$crate::init::auto_register($crate::init::InitHandle::new(
40+
handle,
41+
$crate::init::InitLevel::AUTO,
42+
));
43+
$callback($crate::init::InitHandle::new(
44+
handle,
45+
$crate::init::InitLevel::USER,
46+
));
6247

6348
$crate::init::diagnostics::missing_suggested_diagnostics();
6449
});
@@ -103,6 +88,8 @@ macro_rules! godot_gdnative_init {
10388
return;
10489
}
10590

91+
$crate::init::diagnostics::godot_version_mismatch();
92+
10693
$crate::private::report_panics("gdnative_init", || {
10794
let init_info = $crate::init::InitializeInfo::new(options);
10895
$callback(&init_info)

gdnative-core/src/private.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub unsafe fn bind_api(options: *mut sys::godot_gdnative_init_options) -> bool {
3535
ObjectMethodTable::get(get_api());
3636
ReferenceMethodTable::get(get_api());
3737
NativeScriptMethodTable::get(get_api());
38+
EngineMethodTable::get(get_api());
3839

3940
true
4041
}
@@ -326,3 +327,8 @@ make_method_table!(struct NativeScriptMethodTable for NativeScript {
326327
set_library,
327328
new,
328329
});
330+
331+
// `Engine` is known to the engine as `_Engine`.
332+
make_method_table!(struct EngineMethodTable for _Engine {
333+
get_version_info,
334+
});

gdnative/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ rust-version = "1.63"
1717
# Public
1818
default = []
1919
async = ["gdnative-async"]
20-
custom-godot = ["gdnative-bindings/custom-godot"]
20+
custom-godot = ["gdnative-bindings/custom-godot", "gdnative-core/custom-godot"]
2121
formatted = ["gdnative-bindings/formatted", "gdnative-bindings/one-class-one-file"]
2222
ptrcall = ["gdnative-bindings/ptrcall"]
2323
serde = ["gdnative-core/serde"]

0 commit comments

Comments
 (0)