use path_slash::PathExt;
use std::path::{Path, PathBuf};
#[derive(Copy, Clone, Debug)]
pub enum BuildMode {
Debug,
Release,
}
#[derive(Copy, Clone, Debug)]
pub enum LibFormat {
Gdnlib,
Tres,
}
#[derive(Default)]
pub struct Builder {
godot_project_dir: Option<PathBuf>,
godot_resource_output_dir: Option<PathBuf>,
target_dir: Option<PathBuf>,
lib_name: Option<String>,
build_mode: Option<BuildMode>,
lib_format: Option<LibFormat>,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn with_godot_project_dir(&mut self, dir: impl AsRef<Path>) {
let dir = dir.as_ref().to_path_buf();
self.godot_project_dir = Some(dir);
}
pub fn godot_project_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.with_godot_project_dir(dir);
self
}
pub fn with_godot_resource_output_dir(&mut self, dir: impl AsRef<Path>) {
let dir = dir.as_ref().to_path_buf();
self.godot_resource_output_dir = Some(dir);
}
pub fn godot_resource_output_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.with_godot_resource_output_dir(dir);
self
}
pub fn with_target_dir(&mut self, dir: impl AsRef<Path>) {
let dir = dir.as_ref().to_path_buf();
self.target_dir = Some(dir);
}
pub fn target_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.with_target_dir(dir);
self
}
pub fn with_lib_format(&mut self, lib_format: LibFormat) {
self.lib_format = Some(lib_format);
}
pub fn lib_format(mut self, lib_format: LibFormat) -> Self {
self.with_lib_format(lib_format);
self
}
pub fn with_lib_name(&mut self, name: impl AsRef<str>) {
let name = name.as_ref().to_string();
self.lib_name = Some(name);
}
pub fn lib_name(mut self, name: impl AsRef<str>) -> Self {
self.with_lib_name(name);
self
}
pub fn with_build_mode(&mut self, mode: BuildMode) {
self.build_mode = Some(mode);
}
pub fn build_mode(mut self, mode: BuildMode) -> Self {
self.with_build_mode(mode);
self
}
pub fn build(self, classes: crate::scan::Classes) -> Result<(), std::io::Error> {
let lib_name = self
.lib_name
.or_else(|| std::env::var("CARGO_PKG_NAME").ok())
.expect("Package name not given and unable to find");
let godot_project_dir = self
.godot_project_dir
.and_then(|path| dunce::canonicalize(path).ok())
.expect("Godot project dir not given");
let godot_resource_output_dir = self
.godot_resource_output_dir
.and_then(|path| dunce::canonicalize(path).ok())
.unwrap_or_else(|| godot_project_dir.join("native"));
let target_dir = self
.target_dir
.and_then(|path| dunce::canonicalize(path).ok())
.or_else(|| {
let dir = std::env::var("CARGO_TARGET_DIR").ok()?;
dunce::canonicalize(PathBuf::from(dir)).ok()
})
.or_else(|| {
let dir = std::env::var("OUT_DIR").ok()?;
let out_path = PathBuf::from(&dir);
dunce::canonicalize(out_path.join("../../../../")).ok()
})
.expect("Target dir not given and unable to find");
let build_mode = self
.build_mode
.or_else(|| {
let profile = std::env::var("PROFILE").ok()?;
match profile.as_str() {
"release" => Some(BuildMode::Release),
"debug" => Some(BuildMode::Debug),
_ => None,
}
})
.expect("Build mode not given and unable to find");
std::fs::create_dir_all(&godot_resource_output_dir)?;
let lib_ext = match self.lib_format {
Some(LibFormat::Gdnlib) | None => "gdnlib",
Some(LibFormat::Tres) => "tres",
};
let gdnlib_path = godot_resource_output_dir.join(format!("{}.{}", lib_name, lib_ext));
{
let target_base_path = target_dir;
let target_rel_path = pathdiff::diff_paths(&target_base_path, &godot_project_dir)
.expect("Unable to create relative path between Godot project and library output");
let prefix;
let output_path;
if target_rel_path.starts_with("../") {
prefix = "";
output_path = target_base_path;
} else {
prefix = "res://";
output_path = target_rel_path;
};
let binaries = common_binary_outputs(&output_path, build_mode, &lib_name);
let file_exists = gdnlib_path.exists() && gdnlib_path.is_file();
if !file_exists {
let content = match self.lib_format {
Some(LibFormat::Gdnlib) | None => generate_gdnlib(prefix, binaries),
Some(LibFormat::Tres) => generate_tres(prefix, binaries),
};
std::fs::write(&gdnlib_path, content)?;
}
}
let rel_gdnlib_path = pathdiff::diff_paths(&gdnlib_path, &godot_project_dir)
.expect("Unable to create relative path between Godot project and library output");
let prefix;
let output_path;
if rel_gdnlib_path.starts_with("../") {
prefix = "";
output_path = &gdnlib_path;
} else {
prefix = "res://";
output_path = &rel_gdnlib_path;
};
for name in classes {
let path = godot_resource_output_dir.join(format!("{}.gdns", &name));
let file_exists = path.exists() && path.is_file();
if !file_exists {
let content = generate_gdns(&prefix, &output_path, &name);
std::fs::write(&path, content)?;
}
}
Ok(())
}
}
struct Binaries {
x11: PathBuf,
osx: PathBuf,
windows: PathBuf,
android_aarch64: PathBuf,
android_armv7: PathBuf,
android_x86: PathBuf,
android_x86_64: PathBuf,
}
fn common_binary_outputs(target: &Path, mode: BuildMode, name: &str) -> Binaries {
let mode_path = match mode {
BuildMode::Debug => "debug",
BuildMode::Release => "release",
};
let name = name.replace("-", "_");
Binaries {
x11: target.join(mode_path).join(format!("lib{}.so", name)),
osx: target.join(mode_path).join(format!("lib{}.dylib", name)),
windows: target.join(mode_path).join(format!("{}.dll", name)),
android_armv7: target
.join("armv7-linux-androideabi")
.join(mode_path)
.join(format!("lib{}.so", name)),
android_aarch64: target
.join("aarch64-linux-android")
.join(mode_path)
.join(format!("lib{}.so", name)),
android_x86: target
.join("i686-linux-android")
.join(mode_path)
.join(format!("lib{}.so", name)),
android_x86_64: target
.join("x86_64-linux-android")
.join(mode_path)
.join(format!("lib{}.so", name)),
}
}
fn generate_tres(path_prefix: &str, binaries: Binaries) -> String {
format!(
r#"[gd_resource type="GDNativeLibrary" format=2]
[resource]
entry/Android.armeabi-v7a="{prefix}{android_armv7}"
entry/Android.arm64-v8a="{prefix}{android_aarch64}"
entry/Android.x86="{prefix}{android_x86}"
entry/Android.x86_64="{prefix}{android_x86_64}"
entry/X11.64="{prefix}{x11}"
entry/OSX.64="{prefix}{osx}"
entry/Windows.64="{prefix}{win}"
dependency/Android.armeabi-v7a=[ ]
dependency/Android.arm64-v8a=[ ]
dependency/Android.x86=[ ]
dependency/Android.x86_64=[ ]
dependency/X11.64=[ ]
dependency/OSX.64=[ ]
"#,
prefix = path_prefix,
android_armv7 = binaries.android_armv7.to_slash_lossy(),
android_aarch64 = binaries.android_aarch64.to_slash_lossy(),
android_x86 = binaries.android_x86.to_slash_lossy(),
android_x86_64 = binaries.android_x86_64.to_slash_lossy(),
x11 = binaries.x11.to_slash_lossy(),
osx = binaries.osx.to_slash_lossy(),
win = binaries.windows.to_slash_lossy(),
)
}
fn generate_gdnlib(path_prefix: &str, binaries: Binaries) -> String {
format!(
r#"[entry]
Android.armeabi-v7a="{prefix}{android_armv7}"
Android.arm64-v8a="{prefix}{android_aarch64}"
Android.x86="{prefix}{android_x86}"
Android.x86_64="{prefix}{android_x86_64}"
X11.64="{prefix}{x11}"
OSX.64="{prefix}{osx}"
Windows.64="{prefix}{win}"
[dependencies]
Android.armeabi-v7a=[ ]
Android.arm64-v8a=[ ]
Android.x86=[ ]
Android.x86_64=[ ]
X11.64=[ ]
OSX.64=[ ]
[general]
singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true"#,
prefix = path_prefix,
android_armv7 = binaries.android_armv7.to_slash_lossy(),
android_aarch64 = binaries.android_aarch64.to_slash_lossy(),
android_x86 = binaries.android_x86.to_slash_lossy(),
android_x86_64 = binaries.android_x86_64.to_slash_lossy(),
x11 = binaries.x11.to_slash_lossy(),
osx = binaries.osx.to_slash_lossy(),
win = binaries.windows.to_slash_lossy(),
)
}
fn generate_gdns(path_prefix: &str, gdnlib_path: &Path, name: &str) -> String {
format!(
r#"[gd_resource type="NativeScript" load_steps=2 format=2]
[ext_resource path="{prefix}{gdnlib}" type="GDNativeLibrary" id=1]
[resource]
class_name = "{name}"
script_class_name = "{name}"
library = ExtResource( 1 )
"#,
prefix = path_prefix,
gdnlib = gdnlib_path.to_slash_lossy(),
name = name,
)
}