Skip to content

Commit

Permalink
Try #838:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] committed Jan 1, 2022
2 parents 66b2dd7 + b9ac0ac commit fc5323c
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 24 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/full-ci.yml
Expand Up @@ -244,10 +244,9 @@ jobs:
godot: "3.4.1"
postfix: ' (msrv 1.51)'

# Test with older engine version
# Note: headless versions of Godot <= 3.3 may crash with a bug, see feature description in lib.rs
# Test with oldest supported engine version
- rust: stable
godot: "3.3.1"
godot: "3.2"
postfix: ''
build_args: '--features custom-godot'

Expand Down
1 change: 1 addition & 0 deletions gdnative-bindings/Cargo.toml
Expand Up @@ -25,3 +25,4 @@ bitflags = "1.3.2"
heck = "0.4.0"
gdnative_bindings_generator = { path = "../bindings_generator", version = "=0.9.3" }
which = { optional = true, version = "4.2.2" }
regex = "1.5.4"
82 changes: 61 additions & 21 deletions gdnative-bindings/build.rs
Expand Up @@ -154,42 +154,82 @@ fn format_file(output_rs: &Path) {
}
}

#[cfg(feature = "custom-godot")]
#[cfg(not(feature = "custom-godot"))]
fn generate_api_if_needed() -> bool {
let source: String;
let godot_bin: PathBuf;
false
}

if let Ok(string) = env::var("GODOT_BIN") {
source = format!("GODOT_BIN executable '{}'", string);
godot_bin = PathBuf::from(string);
#[cfg(feature = "custom-godot")]
fn generate_api_if_needed() -> bool {
let godot_bin: PathBuf = if let Ok(string) = env::var("GODOT_BIN") {
println!("Found GODOT_BIN with path to executable: '{}'", string);
PathBuf::from(string)
} else if let Ok(path) = which::which("godot") {
source = "executable 'godot'".to_string();
godot_bin = path;
println!("Found 'godot' executable in PATH: {}", path.display());
path
} else {
panic!(
"Feature 'custom-godot' requires an accessible 'godot' executable or \
a GODOT_BIN environment variable (with the path to the executable)."
);
};

// TODO call 'godot --version' and ensure >= 3.2 && < 4.0
let version = exec(1, Command::new(&godot_bin).arg("--version"));

let has_generate_bug = match godot_version::parse_version(&version) {
Ok(parsed) => {
assert!(
parsed.major == 3 && parsed.minor >= 2,
"Only Godot versions >= 3.2 and < 4.0 are supported."
);

// bug for versions < 3.3.1
parsed.major == 2 || parsed.major == 3 && parsed.minor == 0
}
Err(e) => {
// Don't treat this as fatal error
eprintln!("Warning, failed to parse version: {}", e);
true // version not known, conservatively assume bug
}
};

let status = Command::new(godot_bin)
.arg("--gdnative-generate-json-api")
.arg("api.json")
.status()
.unwrap_or_else(|err| panic!("Failed to invoke {}; error {}", source, err));
// Workaround for Godot bug, where the generate command crashes the engine.
// Try 10 times (should be reasonably high confidence that at least 1 run succeeds).
println!("Found Godot version < 3.3.1 with potential generate bug; trying multiple times...");

assert!(
status.success(),
"Custom Godot command exited with status {}",
status.code().map_or("?".to_string(), |f| f.to_string())
exec(
if has_generate_bug { 10 } else { 1 },
Command::new(&godot_bin)
.arg("--gdnative-generate-json-api")
.arg("api.json"),
);

true
}

#[cfg(not(feature = "custom-godot"))]
fn generate_api_if_needed() -> bool {
false
/// Executes a command and returns stdout. Panics on failure.
#[cfg(feature = "custom-godot")]
fn exec(attempts: i32, command: &mut Command) -> String {
let command_line = format!("{:?}", command);

for _attempt in 0..attempts {
match command.output() {
Ok(output) => return String::from_utf8(output.stdout).expect("parse UTF8 string"),
Err(err) => {
eprintln!(
"Godot command failed:\n command: {}\n error: {}",
command_line, err
)
}
}
}

panic!("Could not execute Godot command (see above).")
}

#[cfg(feature = "custom-godot")]
#[allow(unused_variables, dead_code)]
mod godot_version {
// Not very nice, but there's no idiomatic approach to split build.rs, and #[path] freezes build.rs
include!("src/godot_version.rs");
}
66 changes: 66 additions & 0 deletions gdnative-bindings/src/godot_version.rs
@@ -0,0 +1,66 @@
use regex::Regex;
use std::error::Error;

pub(crate) struct GodotVersion {
pub major: u8,
pub minor: u8,
pub patch: u8, //< 0 if none
pub stability: String, // stable|beta|dev
}

pub(crate) fn parse_version(version_str: &str) -> Result<GodotVersion, Box<dyn Error>> {
let regex = Regex::new("(\\d+)\\.(\\d+)(?:\\.(\\d+))?\\.(stable|beta|dev)")?;

let caps = regex.captures(version_str).ok_or("Regex capture failed")?;

let fail = || {
format!(
"Version substring could not be matched in '{}'",
version_str
)
};

Ok(GodotVersion {
major: caps.get(1).ok_or_else(fail)?.as_str().parse::<u8>()?,
minor: caps.get(2).ok_or_else(fail)?.as_str().parse::<u8>()?,
patch: caps
.get(3)
.map(|m| m.as_str().parse::<u8>())
.transpose()?
.unwrap_or(0),
stability: caps.get(4).ok_or_else(fail)?.as_str().to_string(),
})
}

#[test]
fn test_godot_versions() {
let good_versions = [
("3.0.stable.official", 3, 0, 0, "stable"),
("3.0.1.stable.official", 3, 0, 1, "stable"),
("3.2.stable.official", 3, 2, 0, "stable"),
("3.37.stable.official", 3, 37, 0, "stable"),
("3.4.stable.official.206ba70f4", 3, 4, 0, "stable"),
("3.4.1.stable.official.aa1b95889", 3, 4, 1, "stable"),
("3.5.beta.custom_build.837f2c5f8", 3, 5, 0, "beta"),
("4.0.dev.custom_build.e7e9e663b", 4, 0, 0, "dev"),
];

let bad_versions = [
"4.0.unstable.custom_build.e7e9e663b", // "unstable"
"4.0.3.custom_build.e7e9e663b", // no stability
"3.stable.official.206ba70f4", // no minor
];

for (full, major, minor, patch, stability) in good_versions {
let parsed: GodotVersion = parse_version(full).unwrap();
assert_eq!(parsed.major, major);
assert_eq!(parsed.minor, minor);
assert_eq!(parsed.patch, patch);
assert_eq!(parsed.stability, stability);
}

for full in bad_versions {
let parsed = parse_version(full);
assert!(parsed.is_err());
}
}

0 comments on commit fc5323c

Please sign in to comment.