use std::path::Path;
const LOGO_EXTS: &[&str] = &[
"png", "svg", "webp", "jpg", "jpeg", "gif", "ico", "avif", "bmp",
];
const LOGO_MAX_DIM: u32 = 96;
fn main() {
stage_blueprint_logos();
install_desktop_entry();
println!("cargo:rerun-if-changed=assets/rustploy.rc");
println!("cargo:rerun-if-changed=assets/rustploy.ico");
println!("cargo:rerun-if-changed=assets/application.manifest");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".into());
let mut parts: Vec<String> = version
.split(['.', '-', '+'])
.filter(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
.map(str::to_owned)
.collect();
while parts.len() < 4 {
parts.push("0".into());
}
let comma = parts[..4].join(",");
let macros = [
format!("RUSTPLOY_VER_COMMA={comma}"),
format!("RUSTPLOY_VER_STR=\"{version}\""),
];
embed_resource::compile("assets/rustploy.rc", ¯os);
}
fn stage_blueprint_logos() {
let manifest = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let out = std::env::var("OUT_DIR").expect("OUT_DIR");
let src = Path::new(&manifest).join("assets/blueprint-logos");
let dst = Path::new(&out).join("blueprint_logos");
println!("cargo:rerun-if-changed={}", src.display());
let _ = std::fs::remove_dir_all(&dst);
std::fs::create_dir_all(&dst).expect("criar staging de logos");
copy_images(&src, &dst);
}
fn copy_images(src: &Path, dst: &Path) {
let entries = match std::fs::read_dir(src) {
Ok(e) => e,
Err(e) => panic!("lendo {}: {e}", src.display()),
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
copy_images(&path, &dst.join(entry.file_name()));
continue;
}
let out = dst.join(entry.file_name());
if is_raster(&path) {
std::fs::create_dir_all(dst).expect("criar diretório de staging");
stage_raster(&path, &out);
} else if is_logo(&path) {
std::fs::create_dir_all(dst).expect("criar diretório de staging");
std::fs::copy(&path, &out)
.unwrap_or_else(|e| panic!("copiando {}: {e}", path.display()));
}
}
}
fn stage_raster(src: &Path, dst: &Path) {
let bytes = std::fs::read(src).unwrap_or_else(|e| panic!("lendo {}: {e}", src.display()));
let staged = downscale_png(&bytes).unwrap_or(bytes);
std::fs::write(dst, staged).unwrap_or_else(|e| panic!("gravando {}: {e}", dst.display()));
}
fn downscale_png(bytes: &[u8]) -> Option<Vec<u8>> {
let img = image::load_from_memory(bytes).ok()?;
if img.width().max(img.height()) <= LOGO_MAX_DIM {
return None;
}
let resized = img.resize(
LOGO_MAX_DIM,
LOGO_MAX_DIM,
image::imageops::FilterType::Lanczos3,
);
let mut out = Vec::new();
resized
.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png)
.ok()?;
Some(out)
}
fn ext_lower(path: &Path) -> Option<String> {
path.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase())
}
fn is_logo(path: &Path) -> bool {
ext_lower(path).is_some_and(|e| LOGO_EXTS.contains(&e.as_str()))
}
fn is_raster(path: &Path) -> bool {
matches!(
ext_lower(path).as_deref(),
Some("png" | "webp" | "jpg" | "jpeg" | "gif" | "bmp")
)
}
fn install_desktop_entry() {
println!("cargo:rerun-if-env-changed=RUSTPLOY_INSTALL_DESKTOP");
println!("cargo:rerun-if-changed=packaging/rustploy-gui.desktop");
println!("cargo:rerun-if-changed=packaging/icons");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("linux") || !cfg!(target_os = "linux")
{
return;
}
let forced = std::env::var("RUSTPLOY_INSTALL_DESKTOP").ok();
match forced.as_deref() {
Some("0") => return,
Some("1") => {}
_ if is_cargo_install() => {}
_ => return,
}
if let Err(e) = write_desktop_entry() {
println!(
"cargo:warning=rustploy-gui: não instalei o .desktop ({e}); use `rustploy-gui --install-desktop`"
);
}
}
fn is_cargo_install() -> bool {
std::env::var("OUT_DIR").is_ok_and(|out| {
Path::new(&out)
.components()
.any(|c| c.as_os_str().to_string_lossy().starts_with("cargo-install"))
})
}
fn write_desktop_entry() -> Result<(), String> {
let manifest = std::env::var("CARGO_MANIFEST_DIR").map_err(|e| e.to_string())?;
let pkg = Path::new(&manifest).join("packaging");
let home = std::env::var_os("HOME")
.filter(|h| !h.is_empty())
.ok_or("HOME não definido")?;
let home = std::path::PathBuf::from(home);
let data = match std::env::var_os("XDG_DATA_HOME").filter(|v| !v.is_empty()) {
Some(x) => std::path::PathBuf::from(x),
None => home.join(".local/share"),
};
let bin_dir = if let Some(r) = std::env::var_os("CARGO_INSTALL_ROOT").filter(|v| !v.is_empty())
{
std::path::PathBuf::from(r).join("bin")
} else if let Some(c) = std::env::var_os("CARGO_HOME").filter(|v| !v.is_empty()) {
std::path::PathBuf::from(c).join("bin")
} else {
home.join(".cargo/bin")
};
let exe = bin_dir.join("rustploy-gui");
let exe = exe.to_str().ok_or("caminho do executável não é UTF-8")?;
let desktop =
std::fs::read_to_string(pkg.join("rustploy-gui.desktop")).map_err(|e| e.to_string())?;
let apps = data.join("applications");
write_file(
&apps.join("rustploy-gui.desktop"),
exec_com(&desktop, exe).as_bytes(),
)?;
let src = pkg.join("icons/hicolor");
let dst = data.join("icons/hicolor");
copy_tree(&src, &dst)?;
let _ = std::process::Command::new("update-desktop-database")
.arg("-q")
.arg(&apps)
.status();
let _ = std::process::Command::new("gtk-update-icon-cache")
.arg("-qtf")
.arg(&dst)
.status();
Ok(())
}
fn exec_com(desktop: &str, exe: &str) -> String {
let mut quoted = String::from("\"");
for c in exe.chars() {
if matches!(c, '\\' | '"' | '`' | '$') {
quoted.push_str("\\\\");
}
quoted.push(c);
}
quoted.push('"');
desktop
.lines()
.map(|l| {
if l.starts_with("Exec=") {
format!("Exec={quoted}")
} else {
l.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n"
}
fn write_file(path: &Path, bytes: &[u8]) -> Result<(), String> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).map_err(|e| format!("criar {}: {e}", dir.display()))?;
}
std::fs::write(path, bytes).map_err(|e| format!("gravar {}: {e}", path.display()))
}
fn copy_tree(src: &Path, dst: &Path) -> Result<(), String> {
for entry in std::fs::read_dir(src)
.map_err(|e| format!("ler {}: {e}", src.display()))?
.flatten()
{
let (from, to) = (entry.path(), dst.join(entry.file_name()));
if from.is_dir() {
copy_tree(&from, &to)?;
} else {
write_file(&to, &std::fs::read(&from).map_err(|e| e.to_string())?)?;
}
}
Ok(())
}