mod actions;
mod desktop;
mod flatpak;
mod gresources;
mod gsettings;
mod i18n;
mod makefile;
use std::{
env::current_dir,
fs::{create_dir, create_dir_all, remove_dir_all, remove_file, File},
io::Write,
path::{Path, PathBuf},
process::Command,
};
use actions::*;
use flatpak::*;
use fs_extra::{dir::CopyOptions, file::read_to_string};
use gresources::*;
use gsettings::*;
use i18n::*;
use makefile::*;
use crate::parse_project_descriptor;
pub fn target_dir() -> PathBuf {
let target_env = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".into());
PathBuf::new().join(target_env)
}
pub fn build(project_dir: Option<&std::path::Path>, output_dir: Option<&std::path::Path>) {
let project_dir = project_dir
.map(PathBuf::from)
.unwrap_or_else(|| current_dir().unwrap());
let app_toml = project_dir.join("App.toml");
let cargo_toml = project_dir.join("Cargo.toml");
if !app_toml.exists() {
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");
return;
}
let project_descriptor = parse_project_descriptor(&cargo_toml, &app_toml);
if project_descriptor.is_err() {
eprintln!(
"[gra] Could not parse App.toml: {}",
project_descriptor.unwrap_err()
);
return;
}
let project_descriptor = project_descriptor.unwrap();
println!("{:#?}", project_descriptor.app);
let gra_gen_dir = output_dir.unwrap_or_else(|| std::path::Path::new("target/gra-gen"));
std::fs::create_dir_all(gra_gen_dir).expect("Could not create out dir.");
build_actions(&project_descriptor, gra_gen_dir);
build_gschema_settings(&project_descriptor, gra_gen_dir);
build_flatpak(&project_dir, &project_descriptor, gra_gen_dir);
build_gresources(&project_descriptor, gra_gen_dir);
build_makefile(&project_descriptor, gra_gen_dir);
build_gettext(&project_dir, &project_descriptor);
}
pub fn prepare_flatpak_temp(project_dir: &Path) -> Result<PathBuf, String> {
let target_dir = project_dir.join("target");
let flatpak_temp = target_dir.join("flatpak-temp");
if flatpak_temp.exists() {
remove_dir_all(&flatpak_temp).map_err(|e| e.to_string())?;
}
println!("[gra] mkdir target/flatpak-temp");
create_dir_all(&flatpak_temp).map_err(|e| e.to_string())?;
println!("[gra] mkdir target/flatpak-temp/target");
create_dir_all(flatpak_temp.join("target")).map_err(|e| e.to_string())?;
println!("[gra] mkdir target/flatpak-temp/.cargo");
create_dir_all(flatpak_temp.join(".cargo")).map_err(|e| e.to_string())?;
let mut options = CopyOptions::new();
options.overwrite = true;
options.copy_inside = true;
println!("[gra] cp -r src target/flatpak-temp");
fs_extra::dir::copy(project_dir.join("src"), &flatpak_temp, &options)
.map_err(|e| e.to_string())?;
println!("[gra] cp -r po target/flatpak-temp");
fs_extra::dir::copy(project_dir.join("po"), &flatpak_temp, &options)
.map_err(|e| e.to_string())?;
println!("[gra] cp Cargo.toml target/flatpak-temp");
std::fs::copy(
project_dir.join("Cargo.toml"),
flatpak_temp.join("Cargo.toml"),
)
.map_err(|e| e.to_string())?;
println!("[gra] cp App.toml target/flatpak-temp");
std::fs::copy(project_dir.join("App.toml"), flatpak_temp.join("App.toml"))
.map_err(|e| e.to_string())?;
println!("[gra] cp -r target/gra-gen target/flatpak-temp/target");
fs_extra::dir::copy(
project_dir.join("target/gra-gen"),
flatpak_temp.join("target"),
&options,
)
.map_err(|e| e.to_string())?;
println!("[gra] Vendoring sources...");
let c = Command::new("cargo")
.current_dir(&flatpak_temp)
.args(["vendor", "target/vendor"])
.output()
.map_err(|e| e.to_string())?;
if let Ok(e) = String::from_utf8(c.stderr) {
if !e.trim().is_empty() {
println!("[gra] {}", e);
}
}
let mut config =
File::create(flatpak_temp.join(".cargo").join("config.toml")).map_err(|e| e.to_string())?;
config.write_all(&c.stdout).map_err(|e| e.to_string())?;
Ok(flatpak_temp)
}
pub fn generate(project_dir: &Path) {
let target_dir = project_dir.join("target");
let gra_gen = target_dir.join("gra-gen");
create_dir_all(&gra_gen).expect("Could not create target/gra-gen dir");
build(Some(project_dir), Some(gra_gen.as_path()));
}
pub fn clean(project_dir: &Path) {
let target_dir = project_dir.join("target");
let flatpak_temp = target_dir.join("flatpak-temp");
if flatpak_temp.exists() {
remove_dir_all(&flatpak_temp).expect("Could not clean flatpak-temp");
}
let flatpak_build = target_dir.join("flatpak-build");
if flatpak_build.exists() {
remove_dir_all(&flatpak_build).expect("Could not clean flatpak-build");
}
let gra_gen = target_dir.join("gra-gen");
if gra_gen.exists() {
remove_dir_all(&gra_gen).expect("Could not clean gra-gen");
}
}
const TEMPLATE: &str = include_str!("../../App.toml.template");
const PO: &str = include_str!("../../po.template");
pub fn init(project_dir: &Path) {
let app_toml = project_dir.join("App.toml");
if app_toml.exists() {
println!("[gra] App.toml already exists!");
return;
}
match File::create(app_toml) {
Ok(mut at) => {
if let Err(e) = writeln!(at, "{}", TEMPLATE) {
eprintln!("[gra] Failed to write to App.toml: {}", e);
}
}
Err(e) => {
eprintln!("Failed to create App.toml: {}", e)
}
}
let po_dir = project_dir.join("po");
if !po_dir.exists() {
if let Err(e) = create_dir("po") {
eprintln!("Failed to create po dir: {}", e);
}
}
let linguas_file = po_dir.join("LINGUAS");
if !linguas_file.exists() {
match File::create(linguas_file) {
Ok(mut f) => {
if let Err(e) = writeln!(f, "en") {
eprintln!("[gra] Failed to write to App.toml: {}", e);
}
}
Err(e) => {
eprintln!("Failed to create App.toml: {}", e)
}
}
}
let en_file = po_dir.join("en.po");
if !en_file.exists() {
match File::create(en_file) {
Ok(mut f) => {
if let Err(e) = writeln!(f, "{}", PO) {
eprintln!("[gra] Failed to write to App.toml: {}", e);
}
}
Err(e) => {
eprintln!("Failed to create App.toml: {}", e)
}
}
}
}
pub struct FlatpakArgs {
pub release: Option<String>,
pub prepare_only: bool,
pub arch: Option<String>,
}
pub fn flatpak(project_dir: &Path, arguments: FlatpakArgs) -> std::io::Result<()> {
let target_dir = project_dir.join("target");
println!("[gra] Prepare flatpak build...");
let project_descriptor = match parse_project_descriptor(
&project_dir.join("Cargo.toml"),
&project_dir.join("App.toml"),
) {
Err(e) => {
eprintln!("[gra] Could not parse App.toml for flatpak build: {}", e);
return Err(e);
}
Ok(pd) => pd,
};
let gra_gen = target_dir.join("gra-gen");
if arguments.release.is_some() {
clean(project_dir);
generate(project_dir);
let flatpak_temp = match prepare_flatpak_temp(&PathBuf::from(target_dir.parent().unwrap()))
{
Ok(f) => f,
Err(e) => {
eprintln!("Could not prepare flatpak-temp: {:?}", e);
std::process::exit(-1);
}
};
let manifest_yml = target_dir.join(format!("{}.yml", &project_descriptor.app.id));
remove_file(gra_gen.join(format!("data/{}.dev.yml", &project_descriptor.app.id)))?;
if let Err(e) = std::fs::rename(
gra_gen.join(format!("data/{}.yml", &project_descriptor.app.id)),
&manifest_yml,
) {
eprintln!(
"[gra] Could not move target/gra-gen/data/{0}.yml to target/{0}.yml: {1}",
&project_descriptor.app.id, e
);
return Err(e);
}
let tar_file = target_dir.join(format!("{}.tar.xz", &project_descriptor.package.name));
println!("[gra] Call tar -C {:?} -cJf {:?} .", flatpak_temp, tar_file);
match Command::new("tar")
.args([
"-C",
flatpak_temp.to_str().unwrap(),
"-cJf",
tar_file.to_str().unwrap(),
".",
])
.spawn()
{
Err(e) => {
eprintln!("[gra] tar flatpak-temp resulted in error: {}", e);
return Err(e);
}
Ok(mut c) => {
if let Err(e) = c.wait() {
eprintln!("[gra] tar command failed: {}", e);
return Err(e);
}
}
}
println!("[gra] Call sha256sum {:?}", &tar_file);
let sha = Command::new("sha256sum").arg(&tar_file).output();
if let Err(e) = sha {
eprintln!(
"[gra] sha256sum for {:?} resulted in error: {}",
&tar_file, e
);
return Err(e);
}
let sha = String::from_utf8_lossy(&sha.unwrap().stdout)
.to_string()
.split_once(' ')
.unwrap()
.0
.trim()
.to_string();
let manifest_template = read_to_string(&manifest_yml).map_err(from_fs_to_io)?;
let mut sources = format!(
r#" - type: archive
url: "{}"
sha256: "{}""#,
arguments.release.as_ref().unwrap(),
&sha
);
if arguments.release.as_ref().unwrap() == "local" {
sources = format!(
r#" - type: archive
path: "{}.tar.xz""#,
&project_descriptor.package.name,
);
}
let manifest = manifest_template.replace("{sources}", &sources);
let sha_file = target_dir.join(format!("{}.sha256.txt", &project_descriptor.package.name));
match File::create(&sha_file) {
Ok(mut f) => {
f.write_all(sha.as_bytes())?;
}
Err(e) => {
eprintln!("[gra] Could not create {:?}", sha_file);
return Err(e);
}
}
match File::create(&manifest_yml) {
Ok(mut f) => {
f.write_all(manifest.as_bytes())?;
}
Err(e) => {
eprintln!("[gra] Could not write to release {:?}", manifest_yml);
return Err(e);
}
}
println!(
"[gra] Created flatpak release file: {:?}, {}",
tar_file,
manifest_yml.to_string_lossy()
);
return Ok(());
}
let flatpak_temp = match prepare_flatpak_temp(project_dir) {
Ok(f) => f,
Err(e) => {
eprintln!("Could not prepare flatpak-temp: {:?}", e);
std::process::exit(-1);
}
};
println!("[gra] Setup temp flatpak build dir: {:?}.", flatpak_temp);
if arguments.prepare_only {
return Ok(());
}
let flatpak_build_rel = "../flatpak-build";
println!("[gra] Running flatpak-builder --repo={0}/repo {0}/host {1}/data/{2}.dev.yml --force-clean --state-dir={0}/state {3}",
flatpak_build_rel,
gra_gen.to_str().unwrap(),
&project_descriptor.app.id,
arguments.arch.as_ref().map(|a| format!("--arch {}", a)).unwrap_or_else(|| "".into())
);
let mut c = Command::new("flatpak-builder");
c.current_dir(&flatpak_temp);
c.arg(format!("--repo={}/{}", flatpak_build_rel, "repo"));
c.arg("--force-clean");
c.arg(format!("--state-dir={}/{}", flatpak_build_rel, "state"));
if let Some(arch) = &arguments.arch.as_ref() {
c.arg("--arch").arg(arch);
}
if let Some(arch) = arguments.arch.as_ref() {
c.arg(format!("{}/{}", flatpak_build_rel, arch));
} else {
c.arg(format!("{}/{}", flatpak_build_rel, "host"));
}
c.arg(format!(
"../gra-gen/data/{}.dev.yml",
&project_descriptor.app.id
));
let mut c = c.spawn()?;
c.wait()?;
println!(
"[gra] Bundling flatpak build-bundle {0}/repo {1}/{2}.flatpak {3}",
flatpak_build_rel,
target_dir.to_str().unwrap(),
&project_descriptor.package.name,
&project_descriptor.app.id
);
let flatpak_file_name = format!("{}.flatpak", &project_descriptor.package.name);
let mut c = Command::new("flatpak")
.current_dir(&flatpak_temp)
.arg("build-bundle")
.arg(format!("{}/repo", flatpak_build_rel))
.arg(format!("../{}", flatpak_file_name))
.arg(&project_descriptor.app.id)
.spawn()?;
c.wait()?;
if flatpak_temp.exists() {
if let Err(e) = remove_dir_all(&flatpak_temp) {
eprintln!("[gra] Could not clean flatpak-temp.");
return Err(e);
}
}
Ok(())
}
fn from_fs_to_io(e: fs_extra::error::Error) -> std::io::Error {
std::io::Error::new(
match &e.kind {
fs_extra::error::ErrorKind::NotFound => std::io::ErrorKind::NotFound,
fs_extra::error::ErrorKind::PermissionDenied => std::io::ErrorKind::PermissionDenied,
fs_extra::error::ErrorKind::AlreadyExists => std::io::ErrorKind::AlreadyExists,
fs_extra::error::ErrorKind::Interrupted => std::io::ErrorKind::Interrupted,
fs_extra::error::ErrorKind::InvalidFolder => std::io::ErrorKind::InvalidInput,
fs_extra::error::ErrorKind::InvalidFile => std::io::ErrorKind::InvalidInput,
fs_extra::error::ErrorKind::InvalidFileName => std::io::ErrorKind::InvalidInput,
fs_extra::error::ErrorKind::InvalidPath => std::io::ErrorKind::InvalidInput,
fs_extra::error::ErrorKind::Io(e) => e.kind(),
fs_extra::error::ErrorKind::StripPrefix(_) => std::io::ErrorKind::Other,
fs_extra::error::ErrorKind::OsString(_) => std::io::ErrorKind::Other,
fs_extra::error::ErrorKind::Other => std::io::ErrorKind::Other,
},
format!("{:?}", e),
)
}