From 2cd656f0f0f59a2d93dc2ac830f361dbb936ae7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 26 Mar 2026 19:22:06 -0400 Subject: [PATCH 01/10] added resvg dep --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ac4a82f..cac8d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ reqwest = { version = "0.13.2", features = [ "native-tls", ], default-features = false } tempfile = "3.27.0" +resvg = "0.45" serde = "1.0.228" serde_derive = "1.0.228" serde_json = "1.0.149" From 865b9fc5dc50f8a9ae2d186273112d8c960f8fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 26 Mar 2026 19:32:19 -0400 Subject: [PATCH 02/10] Svg dir icon generation --- src/bundle/linux/appimage_bundle.rs | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/bundle/linux/appimage_bundle.rs b/src/bundle/linux/appimage_bundle.rs index ab82491..e8a8989 100644 --- a/src/bundle/linux/appimage_bundle.rs +++ b/src/bundle/linux/appimage_bundle.rs @@ -8,6 +8,9 @@ use std::{ process::Command, }; +use resvg::tiny_skia::{Pixmap, Transform}; +use resvg::usvg::{Options, Tree}; + use crate::bundle::{Settings, common}; use super::common::{generate_desktop_file, generate_icon_files}; @@ -70,6 +73,47 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { Ok(vec![package_path]) } +fn generate_dir_icon(settings: &Settings, app_dir: &std::path::Path) -> crate::Result<()> { + for icon_path in settings.icon_files() { + let icon_path = icon_path?; + if icon_path.extension() == Some(OsStr::new("svg")) { + let svg_data = std::fs::read_to_string(&icon_path) + .with_context(|| format!("Failed to read SVG icon {icon_path:?}"))?; + + let opt = Options::default(); + let tree = Tree::from_data(svg_data.as_bytes(), &opt) + .with_context(|| "Failed to parse SVG for .DirIcon")?; + + let size: u32 = 256; + let mut pixmap = + Pixmap::new(size, size).with_context(|| "Failed to create pixmap for .DirIcon")?; + resvg::render( + &tree, + Transform::from_scale( + size as f32 / tree.size().width(), + size as f32 / tree.size().height(), + ), + &mut pixmap.as_mut(), + ); + + pixmap + .save_png(app_dir.join(".DirIcon")) + .with_context(|| "Failed to save .DirIcon PNG")?; + + // Also symlink the SVG into the AppDir root so tools that prefer + // the vector version can find it. + let svg_filename = format!("{}.svg", settings.binary_name()); + let svg_rel = + PathBuf::from("usr/share/icons/hicolor/scalable/apps").join(&svg_filename); + let _ = common::symlink_file(&svg_rel, &app_dir.join(&svg_filename)); + + return Ok(()); + } + } + + Ok(()) +} + fn fetch_runtime(arch: &str) -> crate::Result> { let url = format!( "https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-{arch}" From da9dd5170338ca9292a47b548ca04498b2c59c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 26 Mar 2026 19:33:34 -0400 Subject: [PATCH 03/10] generate the dir icon (png or svg) --- src/bundle/linux/appimage_bundle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bundle/linux/appimage_bundle.rs b/src/bundle/linux/appimage_bundle.rs index e8a8989..aec2aa0 100644 --- a/src/bundle/linux/appimage_bundle.rs +++ b/src/bundle/linux/appimage_bundle.rs @@ -2,6 +2,7 @@ use anyhow::Context; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ + ffi::OsStr, fs::File, io::{BufReader, BufWriter, Write}, path::PathBuf, @@ -40,9 +41,12 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { generate_icon_files(settings, &app_dir)?; generate_desktop_file(settings, &app_dir)?; - // TODO Symlinks (AppRun, .DirIcon, .desktop) common::symlink_file(&binary_dest_rel, &app_dir.join("AppRun"))?; + // Generate a .DirIcon PNG from the first SVG icon, or symlink the first + // PNG icon so AppImage tools can pick it up. + generate_dir_icon(settings, &app_dir)?; + // Download the AppImage runtime let runtime = fetch_runtime(settings.binary_arch())?; From b284e8fa5a539c663987e0675342236a58a37c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 26 Mar 2026 19:34:21 -0400 Subject: [PATCH 04/10] Full symlink support Fixes the todo --- src/bundle/linux/appimage_bundle.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bundle/linux/appimage_bundle.rs b/src/bundle/linux/appimage_bundle.rs index aec2aa0..402d0dc 100644 --- a/src/bundle/linux/appimage_bundle.rs +++ b/src/bundle/linux/appimage_bundle.rs @@ -43,6 +43,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { common::symlink_file(&binary_dest_rel, &app_dir.join("AppRun"))?; + // Symlink the .desktop file to the AppDir root. + let desktop_filename = format!("{}.desktop", settings.binary_name()); + let desktop_rel = PathBuf::from("usr/share/applications").join(&desktop_filename); + common::symlink_file(&desktop_rel, &app_dir.join(&desktop_filename))?; + // Generate a .DirIcon PNG from the first SVG icon, or symlink the first // PNG icon so AppImage tools can pick it up. generate_dir_icon(settings, &app_dir)?; From 2a0246e033deb663c76983e7fa240efe3d2d8d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 21 May 2026 12:22:43 -0400 Subject: [PATCH 05/10] creating macos icons from an svg source --- src/bundle/osx_bundle.rs | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/bundle/osx_bundle.rs b/src/bundle/osx_bundle.rs index 361bf8c..d13fd31 100644 --- a/src/bundle/osx_bundle.rs +++ b/src/bundle/osx_bundle.rs @@ -23,6 +23,8 @@ use crate::Settings; use anyhow::Context; use image::imageops::FilterType::Lanczos3; use image::{self, GenericImageView}; +use resvg::tiny_skia::{Pixmap, Transform}; +use resvg::usvg::{Options, Tree}; use std::cmp::min; use std::ffi::OsStr; use std::fs::{self, File}; @@ -506,6 +508,81 @@ fn create_icns_file( anyhow::bail!("No usable icon files found."); } +/// Renders an SVG icon at standard macOS iconset sizes, then runs `iconutil` +/// to produce an ICNS file at `dest_path`. +fn create_icns_from_svg( + svg_path: &Path, + resources_dir: &Path, + dest_path: &Path, +) -> crate::Result<()> { + let svg_data = fs::read_to_string(svg_path) + .with_context(|| format!("Failed to read SVG file {svg_path:?}"))?; + + let temp_dir = tempfile::tempdir().with_context(|| "Failed to create temp dir for iconset")?; + let iconset_dir = temp_dir.path().join("icon.iconset"); + fs::create_dir_all(&iconset_dir)?; + + let opt = Options::default(); + let tree = + Tree::from_data(svg_data.as_bytes(), &opt).with_context(|| "Failed to parse SVG data")?; + + // Standard macOS iconset sizes: (logical_size, scale_factor) + let sizes: &[(u32, u32)] = &[ + (16, 1), + (16, 2), + (32, 1), + (32, 2), + (128, 1), + (128, 2), + (256, 1), + (256, 2), + (512, 1), + (512, 2), + ]; + + for &(size, scale) in sizes { + let pixel_size = size * scale; + let filename = if scale == 1 { + format!("icon_{size}x{size}.png") + } else { + format!("icon_{size}x{size}@{scale}x.png") + }; + let output_path = iconset_dir.join(&filename); + + let mut pixmap = Pixmap::new(pixel_size, pixel_size) + .with_context(|| format!("Failed to create {pixel_size}x{pixel_size} pixmap"))?; + resvg::render( + &tree, + Transform::from_scale( + pixel_size as f32 / tree.size().width(), + pixel_size as f32 / tree.size().height(), + ), + &mut pixmap.as_mut(), + ); + pixmap + .save_png(&output_path) + .with_context(|| format!("Failed to save PNG {output_path:?}"))?; + } + + let status = std::process::Command::new("iconutil") + .current_dir(temp_dir.path()) + .arg("-c") + .arg("icns") + .arg("icon.iconset") + .status() + .with_context(|| "Failed to run iconutil (is Xcode command-line tools installed?)")?; + + if !status.success() { + anyhow::bail!("iconutil failed to create ICNS file"); + } + + fs::create_dir_all(resources_dir)?; + fs::copy(temp_dir.path().join("icon.icns"), dest_path) + .with_context(|| "Failed to copy generated icon.icns")?; + + Ok(()) +} + /// Writes `*.lproj/InfoPlist.strings` localisation files into `resources_dir` /// for every locale present in the settings' `osx_localizations` map. fn write_localizations(resources_dir: &Path, settings: &Settings) -> crate::Result<()> { From 7f8460d23a0aeb18168f9874b57e451d8b95d840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 26 Mar 2026 20:44:35 -0400 Subject: [PATCH 06/10] Integrating the svg support into the bundling process --- src/bundle/osx_bundle.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bundle/osx_bundle.rs b/src/bundle/osx_bundle.rs index d13fd31..6c04bac 100644 --- a/src/bundle/osx_bundle.rs +++ b/src/bundle/osx_bundle.rs @@ -435,6 +435,20 @@ fn create_icns_file( return Ok(None); } + // If one of the icon files is an SVG, convert it via resvg + iconutil. + for icon_path in settings.icon_files() { + let icon_path = icon_path?; + if icon_path.extension() == Some(OsStr::new("svg")) { + fs::create_dir_all(resources_dir)?; + let mut dest_path = resources_dir.clone(); + dest_path.push(settings.bundle_name()); + dest_path.set_extension("icns"); + create_icns_from_svg(&icon_path, resources_dir, &dest_path) + .with_context(|| format!("Failed to convert SVG icon {icon_path:?} to ICNS"))?; + return Ok(Some(dest_path)); + } + } + // If one of the icon files is already an ICNS file, just use that. for icon_path in settings.icon_files() { let icon_path = icon_path?; @@ -476,7 +490,7 @@ fn create_icns_file( for icon_path in settings.icon_files() { let icon_path = icon_path?; if icon_path.extension() == Some(OsStr::new("svg")) { - continue; // TODO: convert svg to appropriate format? + continue; } let icon = image::open(&icon_path)?; let density = if common::is_retina(&icon_path) { 2 } else { 1 }; From 8bf156b3959495118b56084854937ec48f06f919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 19:22:54 -0400 Subject: [PATCH 07/10] siwtched to icns crate --- src/bundle/osx_bundle.rs | 51 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/src/bundle/osx_bundle.rs b/src/bundle/osx_bundle.rs index 6c04bac..575aa2c 100644 --- a/src/bundle/osx_bundle.rs +++ b/src/bundle/osx_bundle.rs @@ -29,7 +29,7 @@ use std::cmp::min; use std::ffi::OsStr; use std::fs::{self, File}; use std::io::prelude::*; -use std::io::{self, BufWriter}; +use std::io::{self, BufWriter, Cursor}; use std::path::{Path, PathBuf}; pub fn bundle_project(settings: &Settings) -> crate::Result> { @@ -435,7 +435,7 @@ fn create_icns_file( return Ok(None); } - // If one of the icon files is an SVG, convert it via resvg + iconutil. + // If one of the icon files is an SVG, convert it via resvg + icns crate. for icon_path in settings.icon_files() { let icon_path = icon_path?; if icon_path.extension() == Some(OsStr::new("svg")) { @@ -522,8 +522,7 @@ fn create_icns_file( anyhow::bail!("No usable icon files found."); } -/// Renders an SVG icon at standard macOS iconset sizes, then runs `iconutil` -/// to produce an ICNS file at `dest_path`. +/// Renders an SVG icon at standard macOS iconset sizes and produces an ICNS file at `dest_path`. fn create_icns_from_svg( svg_path: &Path, resources_dir: &Path, @@ -532,14 +531,12 @@ fn create_icns_from_svg( let svg_data = fs::read_to_string(svg_path) .with_context(|| format!("Failed to read SVG file {svg_path:?}"))?; - let temp_dir = tempfile::tempdir().with_context(|| "Failed to create temp dir for iconset")?; - let iconset_dir = temp_dir.path().join("icon.iconset"); - fs::create_dir_all(&iconset_dir)?; - let opt = Options::default(); let tree = Tree::from_data(svg_data.as_bytes(), &opt).with_context(|| "Failed to parse SVG data")?; + let mut family = icns::IconFamily::new(); + // Standard macOS iconset sizes: (logical_size, scale_factor) let sizes: &[(u32, u32)] = &[ (16, 1), @@ -556,13 +553,6 @@ fn create_icns_from_svg( for &(size, scale) in sizes { let pixel_size = size * scale; - let filename = if scale == 1 { - format!("icon_{size}x{size}.png") - } else { - format!("icon_{size}x{size}@{scale}x.png") - }; - let output_path = iconset_dir.join(&filename); - let mut pixmap = Pixmap::new(pixel_size, pixel_size) .with_context(|| format!("Failed to create {pixel_size}x{pixel_size} pixmap"))?; resvg::render( @@ -573,26 +563,27 @@ fn create_icns_from_svg( ), &mut pixmap.as_mut(), ); - pixmap - .save_png(&output_path) - .with_context(|| format!("Failed to save PNG {output_path:?}"))?; - } - let status = std::process::Command::new("iconutil") - .current_dir(temp_dir.path()) - .arg("-c") - .arg("icns") - .arg("icon.iconset") - .status() - .with_context(|| "Failed to run iconutil (is Xcode command-line tools installed?)")?; + let png_data = pixmap + .encode_png() + .with_context(|| format!("Failed to encode {pixel_size}x{pixel_size} pixmap to PNG"))?; + + let icon_image = icns::Image::read_png(Cursor::new(&png_data)) + .with_context(|| format!("Failed to read PNG for {pixel_size}x{pixel_size} icon"))?; + + let icon_type = icns::IconType::from_pixel_size_and_density(pixel_size, pixel_size, scale) + .with_context(|| format!("No ICNS icon type for size {size} and scale {scale}"))?; - if !status.success() { - anyhow::bail!("iconutil failed to create ICNS file"); + family + .add_icon_with_type(&icon_image, icon_type) + .with_context(|| format!("Failed to add {size}x{size}@{scale}x icon to ICNS family"))?; } fs::create_dir_all(resources_dir)?; - fs::copy(temp_dir.path().join("icon.icns"), dest_path) - .with_context(|| "Failed to copy generated icon.icns")?; + let icns_file = BufWriter::new(File::create(dest_path)?); + family + .write(icns_file) + .with_context(|| format!("Failed to write ICNS file to {dest_path:?}"))?; Ok(()) } From c8483a8da78d2300dd6d76a69a0de2002d8dd438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Sat, 23 May 2026 21:09:16 -0400 Subject: [PATCH 08/10] added some nice and pleasant integration test --- Cargo.toml | 15 ++++++ examples/goodbye/icon128x128.png | Bin 0 -> 19340 bytes examples/goodbye/icon32x32.png | Bin 0 -> 2601 bytes examples/goodbye/main.rs | 3 ++ tests/common.rs | 27 ++++++++++ tests/goodbye.rs | 84 +++++++++++++++++++++++++++++++ tests/hello.rs | 84 +++++++++++++++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 examples/goodbye/icon128x128.png create mode 100644 examples/goodbye/icon32x32.png create mode 100644 examples/goodbye/main.rs create mode 100644 tests/common.rs create mode 100644 tests/goodbye.rs create mode 100644 tests/hello.rs diff --git a/Cargo.toml b/Cargo.toml index cac8d2f..b67cf9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,17 @@ description = "Wrap rust executables in OS-specific app bundles" edition = "2024" readme = "Readme.md" +[package.metadata.bundle.example.goodbye] +name = "Goodbye" +identifier = "io.github.burtonageo.cargo-bundle.goodbye" +icon = ["examples/goodbye/icon32x32.png", "examples/goodbye/icon128x128.png"] +category = "Developer Tool" +short_description = "An example CLI application showing PNG icon bundling" +long_description = """ +A simple command-line application that demonstrates bundling a Rust binary +with PNG icons using cargo-bundle. +""" + [package.metadata.bundle.example.hello] name = "hello" identifier = "io.github.burtonageo.cargo-bundle.hello" @@ -59,3 +70,7 @@ winit = "0.30.13" [[example]] name = "hello" path = "examples/hello/main.rs" + +[[example]] +name = "goodbye" +path = "examples/goodbye/main.rs" diff --git a/examples/goodbye/icon128x128.png b/examples/goodbye/icon128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..213de6360a63aaf0b6e993163cea6f7d1e750d90 GIT binary patch literal 19340 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 LNkvXXu0mjfF5cNd literal 0 HcmV?d00001 diff --git a/examples/goodbye/icon32x32.png b/examples/goodbye/icon32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f170e8089b2114feb0f93eb1738aacfe424db5b1 GIT binary patch literal 2601 zcmY*bcRU+v_m53fwMLDiLCqjmOCkv&6tx?zeeDV&A#nxuioJ@f!)nS)TQwVEwY6KV zR?SkB)-0~wVHCj|z4v#2@B4f{=XuUK&-Z-4=RAKsDK=J@_<-U-006*eW{S3Bo}gpn z=49@~h=@(*!9ujVWC(cDC$Yo~cnGGBL;!$S@Yq-Y`BYIRz!8gaAURlCz`XD{H4ksR zr;i#LM_{4>05}=Oba6f;4-gp_7(|4T5i-9JFs6T;hRA?^K}Z1z83#)nkP$w_2XsMA zTTNXC2?T*a@DOibm>t^W?{a2@kntmt2rvjFEG$edOiK+Pavh=pg+d|fnh;G*RVG4} z7#>9OAgcxuW&a}iKOVFX(JKT?AYt)ApkrPSPkbl|AtQ4v^w0XMPZHMm|CNG>e~-l+ z5ORD2(NI%|{KL&Gg&${O)*)CQrt&dAQUm@A`9Hb8b>NU=@&9V(uStJpnWG|saL7N; zh6Gx?P;dkQxUptv6vlz4{#qmfb4~2153#eZb?8^Ne%(M#a`FILT2;HpX7gY{=4bO9=CGCoJTkwU@h3W6n}! z=ji97+xnmGu$SXm^KV%9rKG>jOvqhR{Q#Fnhcf`HUr|SC8XtE0WcO#@)V4*uA8Qu# zj^zE?IIH64;83cA56UhKa;Znsk45iM(Tr`sF*7&Cy}RDC&qbRbX)*w z%J7;Pypj-P^~bFxuBNVKG#GzeF;B;0L)vx}w<3viK%K{fHUcwB!!2};y+fAsHGF0& z4#4~(q({K#D^H?0o;`X5%{B~Hxsh*oL8Qmm?ZJF+v77aYGrZKuvd{FCn2EJ^Rc@B- zo!*CzlLwPVdX6mhvCjZEZDZTVjKIbe-NWYSe zz22o{*P3%xPF`-B<5#KQgmKhN#e>=;Xt_#Eu4sZTr&H(6TBsCTO`sCw%RID22WBDH zX#b_^!*1E6V0a@KU|&M*?~$PURF3+S(}y>3UoJf4o;4T3Y@S|WI|odi<&gZ%?6Lfh zj&s~eozO;twUUi~-+n+&F=TOmfo3HLG`{qw5PvF%w=pM2y{X5vsiEy`J4#Bl-nhK; z*-T)?{#cr`a@3JdOg6er=UvkHh<;Wc$Hdlhh!1?cH_1!ql!hdaB1Pg}XU^Bv>fqm-v1UDbr-S+>Vd<-*;Rnw@ zyia%V+;V4EG)?8I`{a~23S@MS$t|Yb|60rFW~5xzd(&u4+0qmgz;M+UEQKwj+yHu< zXX5*>rqbqpMI2seD5vjDy($I+Y*XV@q{%NLrQY@wZB|{?Hk1#Id|oV)X+6%8?KbJ8 zBW8T|D8<0ev_Rj{vCSha*iB0SYqx$t?n{DPHGDIs=i@dMpIZc9^h(2SIoQbl-%VPg#;p8 z4nzor?JE`QP>_KXrwNZ%XvhKm}{klB9fdBxL{yg z7$i-94`+Os(-yQ`olpPX#3}oovV+IG6b6-U29Jr6g*q%j?QwJqSJJ28-S;(;BacTm zHaBlP6Cq2fL$|q9 z1v44g;MQkPu8Vdy%<#r$``3(N*xAn;eDf%{hnnuL7IGb)TRyA*{8hDNJ5g$bUHXiz zuLAVXELqv^+3W3gSKK~Vrc_ztm&e(zNGYgVL6R6$xY?lfB0+q8BCk*@D-i*fa%(t` zyEpOj^5R|u$M8k=FMjAp3cMXT%C^{8PDbDEJ3O{9jGug%>T@=*g^ikJirH&e z{C5#3U@ix_iW52T^yCFV9&N^RL6u3R+;VcZ<~mp@_tS z@Ju^!0!22B5)$sO3p4keRSB;9MstZEF7Uy@&D(OaV$U@szqZ14R{U&q`D&) z;@mr>uT;hCxqP3V(9dNo(ff@?U+KhpjNOelM-a)jm?r-I!rd;G4J~A&t|7a5O^%PP ze0g%}84=-CoO}iADSL{a3RTx>h!mi{dNG$b!vLgReyR`6R!iULkqTootdFkPUQe|) zj@iB#=Y&fl<7;&<66D8jUwZ->bWSj)Cu80D9tc$OEQG>m>QTxlHOX1uH714@PHDDi%Kve7I?cOGFygQYCjp zfpTj5>gpfeYca9e)?AJKEI)$zA11_ zuSTxgJRqI|H{F_F+k4q_iSsN?T+=VXq|l`HX2lnY03MOlpQoGoElj|eKQ#8Y=Nja5_3l!H%$4@-2;}Y?C9?W0Ud(G5 z3M${Y!#?)9<9C`1j+U`WeTrOQxVLsSytpMfZz$JF$&8;v1$9H$-~nN)^t)qy{2#3P zSW47|+YvO6;E?&>Tmw-?E7>?rZREI(=#v@|3qm{2=(MWo*3l#)u0-A7DgvuD8 PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +pub fn cargo_bundle_bin() -> PathBuf { + project_root().join("target/debug/cargo-bundle") +} + +/// Places a valid-enough binary at `target/debug/examples/` so that +/// cargo-bundle (running with CARGO_BUNDLE_SKIP_BUILD) has something to package. +pub fn setup_example_binary(name: &str) { + let bin_dir = project_root().join("target/debug/examples"); + fs::create_dir_all(&bin_dir).unwrap(); + let bin_path = bin_dir.join(name); + #[cfg(target_os = "macos")] + { + // macOS copies the binary into the .app; use a for realsies Mach-O so the copy runs. + fs::copy(cargo_bundle_bin(), &bin_path).unwrap(); + } + #[cfg(not(target_os = "macos"))] + { + fs::write(&bin_path, b"dummy binary content").unwrap(); + } +} diff --git a/tests/goodbye.rs b/tests/goodbye.rs new file mode 100644 index 0000000..0d813fe --- /dev/null +++ b/tests/goodbye.rs @@ -0,0 +1,84 @@ +mod common; + +use std::fs; +use std::process::Command; + +#[test] +#[cfg(target_os = "macos")] +fn osx() { + common::setup_example_binary("goodbye"); + + let root = common::project_root(); + let output = Command::new(common::cargo_bundle_bin()) + .args(["bundle", "--example", "goodbye", "--format", "osx"]) + .current_dir(&root) + .env("CARGO_BUNDLE_SKIP_BUILD", "1") + .output() + .expect("Failed to execute cargo-bundle"); + + assert!( + output.status.success(), + "cargo-bundle failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let app_path = root.join("target/debug/examples/bundle/osx/Goodbye.app"); + assert!(app_path.exists(), "App bundle not found at {:?}", app_path); + + // PNG icons should have been packed into an ICNS file. + let icns_path = app_path.join("Contents/Resources/Goodbye.icns"); + assert!(icns_path.exists(), "ICNS not found at {:?}", icns_path); + assert!( + fs::metadata(&icns_path).unwrap().len() > 0, + "ICNS file is empty" + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn deb() { + common::setup_example_binary("goodbye"); + + let root = common::project_root(); + let output = Command::new(common::cargo_bundle_bin()) + .args(["bundle", "--example", "goodbye", "--format", "deb"]) + .current_dir(&root) + .env("CARGO_BUNDLE_SKIP_BUILD", "1") + .output() + .expect("Failed to execute cargo-bundle"); + + assert!( + output.status.success(), + "cargo-bundle failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let arch = if cfg!(target_arch = "x86_64") { + "amd64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else { + "amd64" + }; + + let deb_path = root.join(format!( + "target/debug/examples/bundle/deb/goodbye_0.9.1_{arch}.deb" + )); + assert!( + deb_path.exists(), + "Debian package not found at {:?}", + deb_path + ); + + // The 128x128 PNG should have been placed in the hicolor icon tree. + let icon_path = root.join(format!( + "target/debug/examples/bundle/deb/goodbye_0.9.1_{arch}/data/usr/share/icons/hicolor/128x128/apps/goodbye.png" + )); + assert!( + icon_path.exists(), + "PNG icon not found in deb data at {:?}", + icon_path + ); +} diff --git a/tests/hello.rs b/tests/hello.rs new file mode 100644 index 0000000..90974a4 --- /dev/null +++ b/tests/hello.rs @@ -0,0 +1,84 @@ +mod common; + +use std::fs; +use std::process::Command; + +#[test] +#[cfg(target_os = "macos")] +fn osx() { + common::setup_example_binary("hello"); + + let root = common::project_root(); + let output = Command::new(common::cargo_bundle_bin()) + .args(["bundle", "--example", "hello", "--format", "osx"]) + .current_dir(&root) + .env("CARGO_BUNDLE_SKIP_BUILD", "1") + .output() + .expect("Failed to execute cargo-bundle"); + + assert!( + output.status.success(), + "cargo-bundle failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let app_path = root.join("target/debug/examples/bundle/osx/hello.app"); + assert!(app_path.exists(), "App bundle not found at {:?}", app_path); + + // SVG icon should have been converted to ICNS. + let icns_path = app_path.join("Contents/Resources/hello.icns"); + assert!(icns_path.exists(), "ICNS not found at {:?}", icns_path); + assert!( + fs::metadata(&icns_path).unwrap().len() > 0, + "ICNS file is empty" + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn deb() { + common::setup_example_binary("hello"); + + let root = common::project_root(); + let output = Command::new(common::cargo_bundle_bin()) + .args(["bundle", "--example", "hello", "--format", "deb"]) + .current_dir(&root) + .env("CARGO_BUNDLE_SKIP_BUILD", "1") + .output() + .expect("Failed to execute cargo-bundle"); + + assert!( + output.status.success(), + "cargo-bundle failed:\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + let arch = if cfg!(target_arch = "x86_64") { + "amd64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else { + "amd64" + }; + + let deb_path = root.join(format!( + "target/debug/examples/bundle/deb/hello_0.9.1_{arch}.deb" + )); + assert!( + deb_path.exists(), + "Debian package not found at {:?}", + deb_path + ); + + // SVG should be copied to the scalable hicolor directory. + let svg_path = root.join(format!( + "target/debug/examples/bundle/deb/hello_0.9.1_{arch}/data/usr/share/icons/hicolor/scalable/apps/hello.svg" + )); + assert!( + svg_path.exists(), + "SVG icon not found in deb data at {:?}", + svg_path + ); +} From 381b2753566ccdd35da05318430e4b0426c8d13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 26 May 2026 15:44:34 -0400 Subject: [PATCH 09/10] fixes for clippy lints *sighs* --- tests/goodbye.rs | 2 +- tests/hello.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/goodbye.rs b/tests/goodbye.rs index 0d813fe..792dc31 100644 --- a/tests/goodbye.rs +++ b/tests/goodbye.rs @@ -1,11 +1,11 @@ mod common; -use std::fs; use std::process::Command; #[test] #[cfg(target_os = "macos")] fn osx() { + use std::fs; common::setup_example_binary("goodbye"); let root = common::project_root(); diff --git a/tests/hello.rs b/tests/hello.rs index 90974a4..83d9e3a 100644 --- a/tests/hello.rs +++ b/tests/hello.rs @@ -1,11 +1,11 @@ mod common; -use std::fs; use std::process::Command; #[test] #[cfg(target_os = "macos")] fn osx() { + use std::fs; common::setup_example_binary("hello"); let root = common::project_root(); From eaf82e079265f9c62074e98b48aa54815ad939ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Tue, 26 May 2026 17:29:47 -0400 Subject: [PATCH 10/10] fixed tests/made more robust --- tests/common.rs | 10 ++++++++++ tests/goodbye.rs | 22 ++++++++-------------- tests/hello.rs | 23 ++++++++--------------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/tests/common.rs b/tests/common.rs index d367b75..d40bd6d 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -9,6 +9,16 @@ pub fn cargo_bundle_bin() -> PathBuf { project_root().join("target/debug/cargo-bundle") } +#[cfg(test)] +#[allow(dead_code)] // Used in tests +pub fn parse_bundle_paths(stdout: &str) -> Vec { + stdout + .lines() + .filter(|line| line.starts_with(" ")) + .map(|line| PathBuf::from(line.trim())) + .collect() +} + /// Places a valid-enough binary at `target/debug/examples/` so that /// cargo-bundle (running with CARGO_BUNDLE_SKIP_BUILD) has something to package. pub fn setup_example_binary(name: &str) { diff --git a/tests/goodbye.rs b/tests/goodbye.rs index 792dc31..b7fe12f 100644 --- a/tests/goodbye.rs +++ b/tests/goodbye.rs @@ -55,17 +55,9 @@ fn deb() { String::from_utf8_lossy(&output.stderr) ); - let arch = if cfg!(target_arch = "x86_64") { - "amd64" - } else if cfg!(target_arch = "aarch64") { - "arm64" - } else { - "amd64" - }; - - let deb_path = root.join(format!( - "target/debug/examples/bundle/deb/goodbye_0.9.1_{arch}.deb" - )); + let bundle_paths = common::parse_bundle_paths(&String::from_utf8_lossy(&output.stdout)); + assert_eq!(bundle_paths.len(), 1, "Expected exactly one bundle path"); + let deb_path = &bundle_paths[0]; assert!( deb_path.exists(), "Debian package not found at {:?}", @@ -73,9 +65,11 @@ fn deb() { ); // The 128x128 PNG should have been placed in the hicolor icon tree. - let icon_path = root.join(format!( - "target/debug/examples/bundle/deb/goodbye_0.9.1_{arch}/data/usr/share/icons/hicolor/128x128/apps/goodbye.png" - )); + let package_dir = deb_path + .parent() + .unwrap() + .join(deb_path.file_stem().unwrap()); + let icon_path = package_dir.join("data/usr/share/icons/hicolor/128x128/apps/goodbye.png"); assert!( icon_path.exists(), "PNG icon not found in deb data at {:?}", diff --git a/tests/hello.rs b/tests/hello.rs index 83d9e3a..be165e6 100644 --- a/tests/hello.rs +++ b/tests/hello.rs @@ -36,7 +36,6 @@ fn osx() { } #[test] -#[cfg(target_os = "linux")] fn deb() { common::setup_example_binary("hello"); @@ -55,17 +54,9 @@ fn deb() { String::from_utf8_lossy(&output.stderr) ); - let arch = if cfg!(target_arch = "x86_64") { - "amd64" - } else if cfg!(target_arch = "aarch64") { - "arm64" - } else { - "amd64" - }; - - let deb_path = root.join(format!( - "target/debug/examples/bundle/deb/hello_0.9.1_{arch}.deb" - )); + let bundle_paths = common::parse_bundle_paths(&String::from_utf8_lossy(&output.stdout)); + assert_eq!(bundle_paths.len(), 1, "Expected exactly one bundle path"); + let deb_path = &bundle_paths[0]; assert!( deb_path.exists(), "Debian package not found at {:?}", @@ -73,9 +64,11 @@ fn deb() { ); // SVG should be copied to the scalable hicolor directory. - let svg_path = root.join(format!( - "target/debug/examples/bundle/deb/hello_0.9.1_{arch}/data/usr/share/icons/hicolor/scalable/apps/hello.svg" - )); + let package_dir = deb_path + .parent() + .unwrap() + .join(deb_path.file_stem().unwrap()); + let svg_path = package_dir.join("data/usr/share/icons/hicolor/scalable/apps/hello.svg"); assert!( svg_path.exists(), "SVG icon not found in deb data at {:?}",