1use path_slash::PathExt;
2use std::path::{Path, PathBuf};
3
4#[derive(Copy, Clone, Debug)]
6pub enum BuildMode {
7 Debug,
8 Release,
9}
10
11#[derive(Copy, Clone, Debug)]
13pub enum LibFormat {
14 Gdnlib,
15 Tres,
16}
17
18#[derive(Default)]
21pub struct Builder {
22 godot_project_dir: Option<PathBuf>,
23 godot_resource_output_dir: Option<PathBuf>,
24 target_dir: Option<PathBuf>,
25 lib_name: Option<String>,
26 build_mode: Option<BuildMode>,
27 lib_format: Option<LibFormat>,
28}
29
30impl Builder {
31 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn with_godot_project_dir(&mut self, dir: impl AsRef<Path>) {
38 let dir = dir.as_ref().to_path_buf();
39
40 self.godot_project_dir = Some(dir);
41 }
42
43 pub fn godot_project_dir(mut self, dir: impl AsRef<Path>) -> Self {
45 self.with_godot_project_dir(dir);
46 self
47 }
48
49 pub fn with_godot_resource_output_dir(&mut self, dir: impl AsRef<Path>) {
52 let dir = dir.as_ref().to_path_buf();
53
54 self.godot_resource_output_dir = Some(dir);
55 }
56
57 pub fn godot_resource_output_dir(mut self, dir: impl AsRef<Path>) -> Self {
60 self.with_godot_resource_output_dir(dir);
61 self
62 }
63
64 pub fn with_target_dir(&mut self, dir: impl AsRef<Path>) {
67 let dir = dir.as_ref().to_path_buf();
68
69 self.target_dir = Some(dir);
70 }
71
72 pub fn target_dir(mut self, dir: impl AsRef<Path>) -> Self {
75 self.with_target_dir(dir);
76 self
77 }
78
79 pub fn with_lib_format(&mut self, lib_format: LibFormat) {
81 self.lib_format = Some(lib_format);
82 }
83
84 pub fn lib_format(mut self, lib_format: LibFormat) -> Self {
86 self.with_lib_format(lib_format);
87 self
88 }
89
90 pub fn with_lib_name(&mut self, name: impl AsRef<str>) {
92 let name = name.as_ref().to_string();
93
94 self.lib_name = Some(name);
95 }
96
97 pub fn lib_name(mut self, name: impl AsRef<str>) -> Self {
99 self.with_lib_name(name);
100 self
101 }
102
103 pub fn with_build_mode(&mut self, mode: BuildMode) {
107 self.build_mode = Some(mode);
108 }
109
110 pub fn build_mode(mut self, mode: BuildMode) -> Self {
114 self.with_build_mode(mode);
115 self
116 }
117
118 pub fn build(self, classes: crate::scan::Classes) -> Result<(), std::io::Error> {
124 let lib_name = self
125 .lib_name
126 .or_else(|| std::env::var("CARGO_PKG_NAME").ok())
127 .expect("Package name not given and unable to find");
128 let godot_project_dir = self
129 .godot_project_dir
130 .and_then(|path| dunce::canonicalize(path).ok())
131 .expect("Godot project dir not given");
132 let godot_resource_output_dir = self
133 .godot_resource_output_dir
134 .and_then(|path| dunce::canonicalize(path).ok())
135 .unwrap_or_else(|| godot_project_dir.join("native"));
136 let target_dir = self
137 .target_dir
138 .and_then(|path| dunce::canonicalize(path).ok())
139 .or_else(|| {
140 let dir = std::env::var("CARGO_TARGET_DIR").ok()?;
141 dunce::canonicalize(PathBuf::from(dir)).ok()
142 })
143 .or_else(|| {
144 let dir = std::env::var("OUT_DIR").ok()?;
145 let out_path = PathBuf::from(&dir);
146
147 dunce::canonicalize(out_path.join("../../../../")).ok()
149 })
150 .expect("Target dir not given and unable to find");
151 let build_mode = self
152 .build_mode
153 .or_else(|| {
154 let profile = std::env::var("PROFILE").ok()?;
155 match profile.as_str() {
156 "release" => Some(BuildMode::Release),
157 "debug" => Some(BuildMode::Debug),
158 _ => None,
159 }
160 })
161 .expect("Build mode not given and unable to find");
162
163 std::fs::create_dir_all(&godot_resource_output_dir)?;
164
165 let lib_ext = match self.lib_format {
166 Some(LibFormat::Gdnlib) | None => "gdnlib",
167 Some(LibFormat::Tres) => "tres",
168 };
169 let gdnlib_path = godot_resource_output_dir.join(format!("{}.{}", lib_name, lib_ext));
170
171 {
172 let target_base_path = target_dir;
173
174 let target_rel_path = pathdiff::diff_paths(&target_base_path, &godot_project_dir)
175 .expect("Unable to create relative path between Godot project and library output");
176
177 let prefix;
178 let output_path;
179
180 if target_rel_path.starts_with("../") {
181 prefix = "";
183 output_path = target_base_path;
184 } else {
185 prefix = "res://";
187 output_path = target_rel_path;
188 };
189
190 let binaries = common_binary_outputs(&output_path, build_mode, &lib_name);
191
192 let file_exists = gdnlib_path.exists() && gdnlib_path.is_file();
193
194 if !file_exists {
195 let content = match self.lib_format {
196 Some(LibFormat::Gdnlib) | None => generate_gdnlib(prefix, binaries),
197 Some(LibFormat::Tres) => generate_tres(prefix, binaries),
198 };
199 std::fs::write(&gdnlib_path, content)?;
200 }
201 }
202
203 let rel_gdnlib_path = pathdiff::diff_paths(&gdnlib_path, &godot_project_dir)
204 .expect("Unable to create relative path between Godot project and library output");
205
206 let prefix;
207 let output_path;
208
209 if rel_gdnlib_path.starts_with("../") {
210 prefix = "";
212 output_path = &gdnlib_path;
213 } else {
214 prefix = "res://";
216 output_path = &rel_gdnlib_path;
217 };
218
219 for name in classes {
220 let path = godot_resource_output_dir.join(format!("{}.gdns", &name));
221
222 let file_exists = path.exists() && path.is_file();
223
224 if !file_exists {
225 let content = generate_gdns(&prefix, &output_path, &name);
226 std::fs::write(&path, content)?;
227 }
228 }
229
230 Ok(())
231 }
232}
233
234struct Binaries {
235 x11: PathBuf,
236 osx: PathBuf,
237 windows: PathBuf,
240 android_aarch64: PathBuf,
241 android_armv7: PathBuf,
242 android_x86: PathBuf,
243 android_x86_64: PathBuf,
244}
245
246fn common_binary_outputs(target: &Path, mode: BuildMode, name: &str) -> Binaries {
247 let mode_path = match mode {
248 BuildMode::Debug => "debug",
249 BuildMode::Release => "release",
250 };
251
252 let name = name.replace("-", "_");
255
256 Binaries {
257 x11: target.join(mode_path).join(format!("lib{}.so", name)),
258 osx: target.join(mode_path).join(format!("lib{}.dylib", name)),
259
260 windows: target.join(mode_path).join(format!("{}.dll", name)),
261 android_armv7: target
262 .join("armv7-linux-androideabi")
263 .join(mode_path)
264 .join(format!("lib{}.so", name)),
265 android_aarch64: target
266 .join("aarch64-linux-android")
267 .join(mode_path)
268 .join(format!("lib{}.so", name)),
269 android_x86: target
270 .join("i686-linux-android")
271 .join(mode_path)
272 .join(format!("lib{}.so", name)),
273 android_x86_64: target
274 .join("x86_64-linux-android")
275 .join(mode_path)
276 .join(format!("lib{}.so", name)),
277 }
278}
279
280fn generate_tres(path_prefix: &str, binaries: Binaries) -> String {
281 format!(
282 r#"[gd_resource type="GDNativeLibrary" format=2]
283
284[resource]
285entry/Android.armeabi-v7a="{prefix}{android_armv7}"
286entry/Android.arm64-v8a="{prefix}{android_aarch64}"
287entry/Android.x86="{prefix}{android_x86}"
288entry/Android.x86_64="{prefix}{android_x86_64}"
289entry/X11.64="{prefix}{x11}"
290entry/OSX.64="{prefix}{osx}"
291entry/Windows.64="{prefix}{win}"
292dependency/Android.armeabi-v7a=[ ]
293dependency/Android.arm64-v8a=[ ]
294dependency/Android.x86=[ ]
295dependency/Android.x86_64=[ ]
296dependency/X11.64=[ ]
297dependency/OSX.64=[ ]
298"#,
299 prefix = path_prefix,
300 android_armv7 = binaries.android_armv7.to_slash_lossy(),
301 android_aarch64 = binaries.android_aarch64.to_slash_lossy(),
302 android_x86 = binaries.android_x86.to_slash_lossy(),
303 android_x86_64 = binaries.android_x86_64.to_slash_lossy(),
304 x11 = binaries.x11.to_slash_lossy(),
305 osx = binaries.osx.to_slash_lossy(),
306 win = binaries.windows.to_slash_lossy(),
307 )
308}
309
310fn generate_gdnlib(path_prefix: &str, binaries: Binaries) -> String {
311 format!(
312 r#"[entry]
313Android.armeabi-v7a="{prefix}{android_armv7}"
314Android.arm64-v8a="{prefix}{android_aarch64}"
315Android.x86="{prefix}{android_x86}"
316Android.x86_64="{prefix}{android_x86_64}"
317X11.64="{prefix}{x11}"
318OSX.64="{prefix}{osx}"
319Windows.64="{prefix}{win}"
320
321[dependencies]
322
323Android.armeabi-v7a=[ ]
324Android.arm64-v8a=[ ]
325Android.x86=[ ]
326Android.x86_64=[ ]
327X11.64=[ ]
328OSX.64=[ ]
329
330[general]
331
332singleton=false
333load_once=true
334symbol_prefix="godot_"
335reloadable=true"#,
336 prefix = path_prefix,
337 android_armv7 = binaries.android_armv7.to_slash_lossy(),
338 android_aarch64 = binaries.android_aarch64.to_slash_lossy(),
339 android_x86 = binaries.android_x86.to_slash_lossy(),
340 android_x86_64 = binaries.android_x86_64.to_slash_lossy(),
341 x11 = binaries.x11.to_slash_lossy(),
342 osx = binaries.osx.to_slash_lossy(),
343 win = binaries.windows.to_slash_lossy(),
344 )
345}
346
347fn generate_gdns(path_prefix: &str, gdnlib_path: &Path, name: &str) -> String {
348 format!(
349 r#"[gd_resource type="NativeScript" load_steps=2 format=2]
350
351[ext_resource path="{prefix}{gdnlib}" type="GDNativeLibrary" id=1]
352
353[resource]
354class_name = "{name}"
355script_class_name = "{name}"
356library = ExtResource( 1 )
357"#,
358 prefix = path_prefix,
359 gdnlib = gdnlib_path.to_slash_lossy(),
360 name = name,
361 )
362}