1mod actions;
4mod desktop;
5mod flatpak;
6mod gresources;
7mod gsettings;
8mod i18n;
9mod makefile;
10
11use std::{
12 env::current_dir,
13 fs::{create_dir, create_dir_all, remove_dir_all, remove_file, File},
14 io::Write,
15 path::{Path, PathBuf},
16 process::Command,
17};
18
19use actions::*;
20use flatpak::*;
21use fs_extra::{dir::CopyOptions, file::read_to_string};
22use gresources::*;
23use gsettings::*;
24use i18n::*;
25use makefile::*;
26
27use crate::parse_project_descriptor;
28
29pub fn target_dir() -> PathBuf {
30 let target_env = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".into());
31 PathBuf::new().join(target_env)
32}
33
34pub fn build(project_dir: Option<&std::path::Path>, output_dir: Option<&std::path::Path>) {
35 let project_dir = project_dir
36 .map(PathBuf::from)
37 .unwrap_or_else(|| current_dir().unwrap());
38 let app_toml = project_dir.join("App.toml");
39 let cargo_toml = project_dir.join("Cargo.toml");
40
41 if !app_toml.exists() {
42 eprintln!("\n[gra] App.toml does not exist. Did you setup your project correctly? Run `cargo gra init` or check https://gitlab.com/floers/gtk-rust-app/-/tree/main#getting-started-tldr for more information.\n");
43 return;
44 }
45
46 let project_descriptor = parse_project_descriptor(&cargo_toml, &app_toml);
47
48 if project_descriptor.is_err() {
49 eprintln!(
50 "[gra] Could not parse App.toml: {}",
51 project_descriptor.unwrap_err()
52 );
53 return;
54 }
55
56 let project_descriptor = project_descriptor.unwrap();
57
58 println!("{:#?}", project_descriptor.app);
59
60 let gra_gen_dir = output_dir.unwrap_or_else(|| std::path::Path::new("target/gra-gen"));
61 std::fs::create_dir_all(gra_gen_dir).expect("Could not create out dir.");
62
63 build_actions(&project_descriptor, gra_gen_dir);
64 build_gschema_settings(&project_descriptor, gra_gen_dir);
65 build_flatpak(&project_dir, &project_descriptor, gra_gen_dir);
66 build_gresources(&project_descriptor, gra_gen_dir);
67 build_makefile(&project_descriptor, gra_gen_dir);
68 build_gettext(&project_dir, &project_descriptor);
69}
70
71pub fn prepare_flatpak_temp(project_dir: &Path) -> Result<PathBuf, String> {
74 let target_dir = project_dir.join("target");
75 let flatpak_temp = target_dir.join("flatpak-temp");
76
77 if flatpak_temp.exists() {
80 remove_dir_all(&flatpak_temp).map_err(|e| e.to_string())?;
81 }
82
83 println!("[gra] mkdir target/flatpak-temp");
84 create_dir_all(&flatpak_temp).map_err(|e| e.to_string())?;
85 println!("[gra] mkdir target/flatpak-temp/target");
86 create_dir_all(flatpak_temp.join("target")).map_err(|e| e.to_string())?;
87 println!("[gra] mkdir target/flatpak-temp/.cargo");
88 create_dir_all(flatpak_temp.join(".cargo")).map_err(|e| e.to_string())?;
89
90 let mut options = CopyOptions::new();
91 options.overwrite = true;
92 options.copy_inside = true;
93
94 println!("[gra] cp -r src target/flatpak-temp");
95 fs_extra::dir::copy(project_dir.join("src"), &flatpak_temp, &options)
96 .map_err(|e| e.to_string())?;
97 println!("[gra] cp -r po target/flatpak-temp");
98 fs_extra::dir::copy(project_dir.join("po"), &flatpak_temp, &options)
99 .map_err(|e| e.to_string())?;
100 println!("[gra] cp Cargo.toml target/flatpak-temp");
101 std::fs::copy(
102 project_dir.join("Cargo.toml"),
103 flatpak_temp.join("Cargo.toml"),
104 )
105 .map_err(|e| e.to_string())?;
106 println!("[gra] cp App.toml target/flatpak-temp");
107 std::fs::copy(project_dir.join("App.toml"), flatpak_temp.join("App.toml"))
108 .map_err(|e| e.to_string())?;
109 println!("[gra] cp -r target/gra-gen target/flatpak-temp/target");
110 fs_extra::dir::copy(
111 project_dir.join("target/gra-gen"),
112 flatpak_temp.join("target"),
113 &options,
114 )
115 .map_err(|e| e.to_string())?;
116
117 println!("[gra] Vendoring sources...");
118 let c = Command::new("cargo")
119 .current_dir(&flatpak_temp)
120 .args(["vendor", "target/vendor"])
121 .output()
122 .map_err(|e| e.to_string())?;
123
124 if let Ok(e) = String::from_utf8(c.stderr) {
125 if !e.trim().is_empty() {
126 println!("[gra] {}", e);
127 }
128 }
129 let mut config =
130 File::create(flatpak_temp.join(".cargo").join("config.toml")).map_err(|e| e.to_string())?;
131 config.write_all(&c.stdout).map_err(|e| e.to_string())?;
132
133 Ok(flatpak_temp)
134}
135
136pub fn generate(project_dir: &Path) {
137 let target_dir = project_dir.join("target");
138 let gra_gen = target_dir.join("gra-gen");
139 create_dir_all(&gra_gen).expect("Could not create target/gra-gen dir");
140 build(Some(project_dir), Some(gra_gen.as_path()));
141}
142
143pub fn clean(project_dir: &Path) {
144 let target_dir = project_dir.join("target");
145 let flatpak_temp = target_dir.join("flatpak-temp");
146 if flatpak_temp.exists() {
147 remove_dir_all(&flatpak_temp).expect("Could not clean flatpak-temp");
148 }
149 let flatpak_build = target_dir.join("flatpak-build");
150 if flatpak_build.exists() {
151 remove_dir_all(&flatpak_build).expect("Could not clean flatpak-build");
152 }
153 let gra_gen = target_dir.join("gra-gen");
154 if gra_gen.exists() {
155 remove_dir_all(&gra_gen).expect("Could not clean gra-gen");
156 }
157}
158
159const TEMPLATE: &str = include_str!("../../App.toml.template");
160const PO: &str = include_str!("../../po.template");
161
162pub fn init(project_dir: &Path) {
163 let app_toml = project_dir.join("App.toml");
164
165 if app_toml.exists() {
166 println!("[gra] App.toml already exists!");
167 return;
168 }
169 match File::create(app_toml) {
170 Ok(mut at) => {
171 if let Err(e) = writeln!(at, "{}", TEMPLATE) {
172 eprintln!("[gra] Failed to write to App.toml: {}", e);
173 }
174 }
175 Err(e) => {
176 eprintln!("Failed to create App.toml: {}", e)
177 }
178 }
179
180 let po_dir = project_dir.join("po");
181
182 if !po_dir.exists() {
183 if let Err(e) = create_dir("po") {
184 eprintln!("Failed to create po dir: {}", e);
185 }
186 }
187 let linguas_file = po_dir.join("LINGUAS");
188
189 if !linguas_file.exists() {
190 match File::create(linguas_file) {
191 Ok(mut f) => {
192 if let Err(e) = writeln!(f, "en") {
193 eprintln!("[gra] Failed to write to App.toml: {}", e);
194 }
195 }
196 Err(e) => {
197 eprintln!("Failed to create App.toml: {}", e)
198 }
199 }
200 }
201
202 let en_file = po_dir.join("en.po");
203
204 if !en_file.exists() {
205 match File::create(en_file) {
206 Ok(mut f) => {
207 if let Err(e) = writeln!(f, "{}", PO) {
208 eprintln!("[gra] Failed to write to App.toml: {}", e);
209 }
210 }
211 Err(e) => {
212 eprintln!("Failed to create App.toml: {}", e)
213 }
214 }
215 }
216}
217pub struct FlatpakArgs {
218 pub release: Option<String>,
219 pub prepare_only: bool,
220 pub arch: Option<String>,
221}
222
223pub fn flatpak(project_dir: &Path, arguments: FlatpakArgs) -> std::io::Result<()> {
224 let target_dir = project_dir.join("target");
225 println!("[gra] Prepare flatpak build...");
226
227 let project_descriptor = match parse_project_descriptor(
228 &project_dir.join("Cargo.toml"),
229 &project_dir.join("App.toml"),
230 ) {
231 Err(e) => {
232 eprintln!("[gra] Could not parse App.toml for flatpak build: {}", e);
233 return Err(e);
234 }
235 Ok(pd) => pd,
236 };
237
238 let gra_gen = target_dir.join("gra-gen");
239
240 if arguments.release.is_some() {
241 clean(project_dir);
242 generate(project_dir);
243
244 let flatpak_temp = match prepare_flatpak_temp(&PathBuf::from(target_dir.parent().unwrap()))
245 {
246 Ok(f) => f,
247 Err(e) => {
248 eprintln!("Could not prepare flatpak-temp: {:?}", e);
249 std::process::exit(-1);
250 }
251 };
252
253 let manifest_yml = target_dir.join(format!("{}.yml", &project_descriptor.app.id));
254
255 remove_file(gra_gen.join(format!("data/{}.dev.yml", &project_descriptor.app.id)))?;
256 if let Err(e) = std::fs::rename(
257 gra_gen.join(format!("data/{}.yml", &project_descriptor.app.id)),
258 &manifest_yml,
259 ) {
260 eprintln!(
261 "[gra] Could not move target/gra-gen/data/{0}.yml to target/{0}.yml: {1}",
262 &project_descriptor.app.id, e
263 );
264 return Err(e);
265 }
266
267 let tar_file = target_dir.join(format!("{}.tar.xz", &project_descriptor.package.name));
268
269 println!("[gra] Call tar -C {:?} -cJf {:?} .", flatpak_temp, tar_file);
270 match Command::new("tar")
271 .args([
272 "-C",
273 flatpak_temp.to_str().unwrap(),
274 "-cJf",
275 tar_file.to_str().unwrap(),
276 ".",
277 ])
278 .spawn()
279 {
280 Err(e) => {
281 eprintln!("[gra] tar flatpak-temp resulted in error: {}", e);
282 return Err(e);
283 }
284 Ok(mut c) => {
285 if let Err(e) = c.wait() {
286 eprintln!("[gra] tar command failed: {}", e);
287 return Err(e);
288 }
289 }
290 }
291 println!("[gra] Call sha256sum {:?}", &tar_file);
292
293 let sha = Command::new("sha256sum").arg(&tar_file).output();
294 if let Err(e) = sha {
295 eprintln!(
296 "[gra] sha256sum for {:?} resulted in error: {}",
297 &tar_file, e
298 );
299 return Err(e);
300 }
301
302 let sha = String::from_utf8_lossy(&sha.unwrap().stdout)
303 .to_string()
304 .split_once(' ')
305 .unwrap()
306 .0
307 .trim()
308 .to_string();
309
310 let manifest_template = read_to_string(&manifest_yml).map_err(from_fs_to_io)?;
311 let mut sources = format!(
312 r#" - type: archive
313 url: "{}"
314 sha256: "{}""#,
315 arguments.release.as_ref().unwrap(),
316 &sha
317 );
318 if arguments.release.as_ref().unwrap() == "local" {
319 sources = format!(
320 r#" - type: archive
321 path: "{}.tar.xz""#,
322 &project_descriptor.package.name,
323 );
324 }
325 let manifest = manifest_template.replace("{sources}", &sources);
326
327 let sha_file = target_dir.join(format!("{}.sha256.txt", &project_descriptor.package.name));
328
329 match File::create(&sha_file) {
330 Ok(mut f) => {
331 f.write_all(sha.as_bytes())?;
332 }
333 Err(e) => {
334 eprintln!("[gra] Could not create {:?}", sha_file);
335 return Err(e);
336 }
337 }
338
339 match File::create(&manifest_yml) {
340 Ok(mut f) => {
341 f.write_all(manifest.as_bytes())?;
342 }
343 Err(e) => {
344 eprintln!("[gra] Could not write to release {:?}", manifest_yml);
345 return Err(e);
346 }
347 }
348
349 println!(
350 "[gra] Created flatpak release file: {:?}, {}",
351 tar_file,
352 manifest_yml.to_string_lossy()
353 );
354
355 return Ok(());
356 }
357
358 let flatpak_temp = match prepare_flatpak_temp(project_dir) {
359 Ok(f) => f,
360 Err(e) => {
361 eprintln!("Could not prepare flatpak-temp: {:?}", e);
362 std::process::exit(-1);
363 }
364 };
365
366 println!("[gra] Setup temp flatpak build dir: {:?}.", flatpak_temp);
367
368 if arguments.prepare_only {
369 return Ok(());
370 }
371
372 let flatpak_build_rel = "../flatpak-build";
374
375 println!("[gra] Running flatpak-builder --repo={0}/repo {0}/host {1}/data/{2}.dev.yml --force-clean --state-dir={0}/state {3}",
376 flatpak_build_rel,
377 gra_gen.to_str().unwrap(),
378 &project_descriptor.app.id,
379 arguments.arch.as_ref().map(|a| format!("--arch {}", a)).unwrap_or_else(|| "".into())
380 );
381
382 let mut c = Command::new("flatpak-builder");
383 c.current_dir(&flatpak_temp);
384 c.arg(format!("--repo={}/{}", flatpak_build_rel, "repo"));
385 c.arg("--force-clean");
386 c.arg(format!("--state-dir={}/{}", flatpak_build_rel, "state"));
387 if let Some(arch) = &arguments.arch.as_ref() {
388 c.arg("--arch").arg(arch);
389 }
390 if let Some(arch) = arguments.arch.as_ref() {
391 c.arg(format!("{}/{}", flatpak_build_rel, arch));
392 } else {
393 c.arg(format!("{}/{}", flatpak_build_rel, "host"));
394 }
395 c.arg(format!(
396 "../gra-gen/data/{}.dev.yml",
397 &project_descriptor.app.id
398 ));
399 let mut c = c.spawn()?;
400 c.wait()?;
401
402 println!(
403 "[gra] Bundling flatpak build-bundle {0}/repo {1}/{2}.flatpak {3}",
404 flatpak_build_rel,
405 target_dir.to_str().unwrap(),
406 &project_descriptor.package.name,
407 &project_descriptor.app.id
408 );
409
410 let flatpak_file_name = format!("{}.flatpak", &project_descriptor.package.name);
411
412 let mut c = Command::new("flatpak")
413 .current_dir(&flatpak_temp)
414 .arg("build-bundle")
415 .arg(format!("{}/repo", flatpak_build_rel))
416 .arg(format!("../{}", flatpak_file_name))
417 .arg(&project_descriptor.app.id)
418 .spawn()?;
419 c.wait()?;
420
421 if flatpak_temp.exists() {
422 if let Err(e) = remove_dir_all(&flatpak_temp) {
423 eprintln!("[gra] Could not clean flatpak-temp.");
424 return Err(e);
425 }
426 }
427
428 Ok(())
429}
430
431fn from_fs_to_io(e: fs_extra::error::Error) -> std::io::Error {
432 std::io::Error::new(
433 match &e.kind {
434 fs_extra::error::ErrorKind::NotFound => std::io::ErrorKind::NotFound,
435 fs_extra::error::ErrorKind::PermissionDenied => std::io::ErrorKind::PermissionDenied,
436 fs_extra::error::ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists,
437 fs_extra::error::ErrorKind::Interrupted => std::io::ErrorKind::Interrupted,
438 fs_extra::error::ErrorKind::InvalidFolder => std::io::ErrorKind::InvalidInput,
439 fs_extra::error::ErrorKind::InvalidFile => std::io::ErrorKind::InvalidInput,
440 fs_extra::error::ErrorKind::InvalidFileName => std::io::ErrorKind::InvalidInput,
441 fs_extra::error::ErrorKind::InvalidPath => std::io::ErrorKind::InvalidInput,
442 fs_extra::error::ErrorKind::Io(e) => e.kind(),
443 fs_extra::error::ErrorKind::StripPrefix(_) => std::io::ErrorKind::Other,
444 fs_extra::error::ErrorKind::OsString(_) => std::io::ErrorKind::Other,
445 fs_extra::error::ErrorKind::Other => std::io::ErrorKind::Other,
446 },
447 format!("{:?}", e),
448 )
449}