From 7c98155591f2112c6ec58ab671b15bf42ba10d9e 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:50:09 -0400 Subject: [PATCH 01/14] osx localizaiton helper func --- src/bundle/settings.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bundle/settings.rs b/src/bundle/settings.rs index c838256..bf5e9d5 100644 --- a/src/bundle/settings.rs +++ b/src/bundle/settings.rs @@ -115,6 +115,7 @@ struct BundleSettings { osx_minimum_system_version: Option, osx_url_schemes: Option>, osx_info_plist_exts: Option>, + osx_localizations: Option>>, // Bundles for other binaries/examples: bin: Option>, example: Option>, @@ -561,6 +562,14 @@ impl Settings { None => ResourcePaths::new(&[], false), } } + + /// Returns a map of locale codes to key-value localisation strings for + /// macOS `*.lproj/InfoPlist.strings` files. The outer key is a locale + /// code such as `"fr"` or `"de"` and the inner map contains plist string + /// keys such as `"CFBundleDisplayName"` mapped to their translated value. + pub fn osx_localizations(&self) -> Option<&HashMap>> { + self.bundle_settings.osx_localizations.as_ref() + } } fn bundle_settings_from_table( From af3c60df85066eac3225ae1f4b58267df9c41ae7 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:40:33 -0400 Subject: [PATCH 02/14] writing localizations on macos --- src/bundle/osx_bundle.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/bundle/osx_bundle.rs b/src/bundle/osx_bundle.rs index 979c646..a21e94f 100644 --- a/src/bundle/osx_bundle.rs +++ b/src/bundle/osx_bundle.rs @@ -500,6 +500,31 @@ fn create_icns_file( anyhow::bail!("No usable icon files found."); } +/// 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<()> { + let Some(localizations) = settings.osx_localizations() else { + return Ok(()); + }; + + for (locale, strings) in localizations { + let lproj_dir = resources_dir.join(locale).with_extension("lproj"); + fs::create_dir_all(&lproj_dir) + .with_context(|| format!("Failed to create {lproj_dir:?}"))?; + + let strings_path = lproj_dir.join("InfoPlist.strings"); + let file = &mut common::create_file(&strings_path)?; + for (key, value) in strings { + // Escape embedded double-quotes in the value. + let escaped = value.replace('\\', "\\\\").replace('"', "\\\""); + writeln!(file, "{key} = \"{escaped}\";")?; + } + file.flush()?; + } + + Ok(()) +} + /// Converts an image::DynamicImage into an icns::Image. fn make_icns_image(img: image::DynamicImage) -> io::Result { let pixel_format = match img.color() { From 78bfc6cfefef0030f710425f7fbae65342c4dd1a 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:43:51 -0400 Subject: [PATCH 03/14] simplified the bundle writing process to handle both it and dmg --- src/bundle/osx_bundle.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/bundle/osx_bundle.rs b/src/bundle/osx_bundle.rs index a21e94f..361bf8c 100644 --- a/src/bundle/osx_bundle.rs +++ b/src/bundle/osx_bundle.rs @@ -31,12 +31,15 @@ use std::io::{self, BufWriter}; use std::path::{Path, PathBuf}; pub fn bundle_project(settings: &Settings) -> crate::Result> { + let base_dir = settings.project_out_directory().join("bundle/osx"); + let path = bundle_project_at(settings, &base_dir)?; + Ok(vec![path]) +} + +pub fn bundle_project_at(settings: &Settings, output_dir: &Path) -> crate::Result { let app_bundle_name = format!("{}.app", settings.bundle_name()); common::print_bundling(&app_bundle_name)?; - let app_bundle_path = settings - .project_out_directory() - .join("bundle/osx") - .join(&app_bundle_name); + let app_bundle_path = output_dir.join(&app_bundle_name); if app_bundle_path.exists() { fs::remove_dir_all(&app_bundle_path) .with_context(|| format!("Failed to remove old {app_bundle_name}"))?; @@ -70,11 +73,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { copy_binary_to_bundle(&bundle_directory, settings) .with_context(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?; + write_localizations(&resources_dir, settings) + .with_context(|| "Failed to write localisation files")?; + if copied > 0 { add_rpath(&bundle_directory, settings)?; } - Ok(vec![app_bundle_path]) + Ok(app_bundle_path) } #[allow(dead_code)] From f82737eaa60f643c6bdd2d811dabc0647e3f54a6 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:11:49 -0400 Subject: [PATCH 04/14] Move tempfile to main dependencies for DMG bundling --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d73c1f8..ac4a82f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ a title bar. It serves as an example of an application that can be bundled with cargo-bundle, as well as a test-case for cargo-bundle's support for bundling crate examples. """ - [dependencies] anyhow = "1.0.102" ar = "0.9.0" @@ -41,6 +40,7 @@ reqwest = { version = "0.13.2", features = [ "blocking", "native-tls", ], default-features = false } +tempfile = "3.27.0" serde = "1.0.228" serde_derive = "1.0.228" serde_json = "1.0.149" @@ -53,7 +53,6 @@ uuid = { version = "1.22.0", features = ["v5"] } walkdir = "2.5.0" [dev-dependencies] -tempfile = "3.27.0" winit = "0.30.13" [[example]] From 1a15d64c9387d2a1dfa0f7a030b48933d157a2cc 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:12:38 -0400 Subject: [PATCH 05/14] Add OsxDmg package type and osx_localizations setting --- src/bundle/settings.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bundle/settings.rs b/src/bundle/settings.rs index bf5e9d5..04faf45 100644 --- a/src/bundle/settings.rs +++ b/src/bundle/settings.rs @@ -12,6 +12,7 @@ use target_build_utils::TargetInfo; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PackageType { OsxBundle, + OsxDmg, IosBundle, WindowsMsi, WxsMsi, @@ -63,6 +64,7 @@ impl PackageType { "msi" => Some(PackageType::WindowsMsi), "wxsmsi" => Some(PackageType::WxsMsi), "osx" => Some(PackageType::OsxBundle), + "dmg" => Some(PackageType::OsxDmg), "rpm" => Some(PackageType::Rpm), "appimage" => Some(PackageType::AppImage), _ => None, @@ -76,13 +78,14 @@ impl PackageType { PackageType::WindowsMsi => "msi", PackageType::WxsMsi => "wxsmsi", PackageType::OsxBundle => "osx", + PackageType::OsxDmg => "dmg", PackageType::Rpm => "rpm", PackageType::AppImage => "appimage", } } pub const fn all() -> &'static [&'static str] { - &["deb", "ios", "msi", "wxsmsi", "osx", "rpm", "appimage"] + &["deb", "ios", "msi", "wxsmsi", "osx", "dmg", "rpm", "appimage"] } } @@ -358,7 +361,7 @@ impl Settings { std::env::consts::OS }; match target_os { - "macos" => Ok(vec![PackageType::OsxBundle]), + "macos" => Ok(vec![PackageType::OsxBundle, PackageType::OsxDmg]), "ios" => Ok(vec![PackageType::IosBundle]), "linux" => Ok(vec![PackageType::Deb, PackageType::AppImage]), // TODO: Do Rpm too, once it's implemented. "windows" => Ok(vec![PackageType::WindowsMsi]), From a25b96cae06023535233011ff3ef4d1a70384c9c 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:47:26 -0400 Subject: [PATCH 06/14] dmg_bundle header text --- src/bundle/dmg_bundle.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/bundle/dmg_bundle.rs diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs new file mode 100644 index 0000000..3f9c077 --- /dev/null +++ b/src/bundle/dmg_bundle.rs @@ -0,0 +1,13 @@ +// A macOS DMG (disk image) bundle is a compressed disk image that contains the +// application bundle and a symlink to /Applications so the user can simply +// drag-and-drop to install. +// +// The layout inside the mounted volume is: +// +// .dmg (read-only compressed UDZO image) +// .app # the application bundle +// Applications -> /Applications # drag-and-drop install target +// +// Building requires macOS because the `hdiutil` command is used to create and +// convert the disk image. + From 03d03d2fcd792a7c6e4dbba241406a9116375534 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:48:27 -0400 Subject: [PATCH 07/14] helper functions --- src/bundle/dmg_bundle.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs index 3f9c077..d022cfe 100644 --- a/src/bundle/dmg_bundle.rs +++ b/src/bundle/dmg_bundle.rs @@ -11,3 +11,20 @@ // Building requires macOS because the `hdiutil` command is used to create and // convert the disk image. +/// Walk a directory and sum the sizes of all contained files. +fn dir_size(dir: &std::path::Path) -> crate::Result { + let mut total: u64 = 0; + for entry in walkdir::WalkDir::new(dir) { + let entry = entry?; + if entry.file_type().is_file() { + total += entry.metadata()?.len(); + } + } + Ok(total) +} + +/// Extract the mount point path from `hdiutil attach` stdout. +fn parse_mount_point(stdout: &[u8]) -> crate::Result { + parse_mount_point_impl(stdout) +} + From bfa8d84ecf0cb1428d59d087735cccd50c84dffe 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:49:32 -0400 Subject: [PATCH 08/14] DMG BUNDLING PIPELINE --- src/bundle/dmg_bundle.rs | 142 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs index d022cfe..4bfa30a 100644 --- a/src/bundle/dmg_bundle.rs +++ b/src/bundle/dmg_bundle.rs @@ -11,6 +11,127 @@ // Building requires macOS because the `hdiutil` command is used to create and // convert the disk image. +use super::common; +use crate::Settings; +use crate::bundle::osx_bundle; +use anyhow::Context; +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +pub fn bundle_project(settings: &Settings) -> crate::Result> { + let dmg_name = format!("{}.dmg", settings.bundle_name()); + common::print_bundling(&dmg_name)?; + + let base_dir = settings.project_out_directory().join("bundle/dmg"); + fs::create_dir_all(&base_dir) + .with_context(|| format!("Failed to create bundle directory {base_dir:?}"))?; + + // Build the .app bundle into the DMG staging directory. + let app_bundle_name = format!("{}.app", settings.bundle_name()); + let app_bundle_path = base_dir.join(&app_bundle_name); + if app_bundle_path.exists() { + fs::remove_dir_all(&app_bundle_path) + .with_context(|| format!("Failed to remove existing {app_bundle_name}"))?; + } + + osx_bundle::bundle_project_at(settings, &base_dir) + .with_context(|| "Failed to create app bundle for DMG")?; + + let dmg_path = base_dir.join(&dmg_name); + if dmg_path.exists() { + fs::remove_file(&dmg_path) + .with_context(|| format!("Failed to remove existing {dmg_name}"))?; + } + + // Determine the size of the app bundle and add a generous overhead. + let bundle_size = dir_size(&app_bundle_path)?; + let image_size_bytes = (bundle_size + 52_428_800).max(52_428_800); // at least 50 MiB + + let temp_dir = tempfile::tempdir() + .with_context(|| "Failed to create temporary directory for DMG staging")?; + + let staging_dmg = temp_dir.path().join("staging.dmg"); + + // Create a writable HFS+ disk image large enough for the bundle. + let status = Command::new("hdiutil") + .args([ + "create", + staging_dmg.to_str().unwrap(), + "-ov", + "-fs", + "HFS+", + "-size", + &image_size_bytes.to_string(), + "-volname", + settings.bundle_name(), + ]) + .status() + .with_context(|| "Failed to run hdiutil create (macOS only)")?; + + if !status.success() { + anyhow::bail!("hdiutil create failed"); + } + + // Mount the writable image. + let output = Command::new("hdiutil") + .args([ + "attach", + staging_dmg.to_str().unwrap(), + "-nobrowse", + "-noverify", + "-noautoopen", + "-noautofsck", + ]) + .output() + .with_context(|| "Failed to mount staging DMG")?; + + if !output.status.success() { + anyhow::bail!("hdiutil attach failed"); + } + + let mount_point = parse_mount_point(&output.stdout) + .with_context(|| "Could not determine DMG mount point from hdiutil output")?; + + // Copy the app bundle and create the /Applications symlink inside the image. + let copy_result = (|| -> crate::Result<()> { + common::copy_dir(&app_bundle_path, &mount_point.join(&app_bundle_name))?; + #[cfg(unix)] + std::os::unix::fs::symlink("/Applications", mount_point.join("Applications")) + .with_context(|| "Failed to create /Applications symlink")?; + Ok(()) + })(); + + // Always unmount, even if copying failed. + let _ = Command::new("hdiutil") + .args(["detach", mount_point.to_str().unwrap()]) + .status(); + + copy_result?; + + // Convert the writable image to a read-only compressed UDZO image. + let status = Command::new("hdiutil") + .args([ + "convert", + staging_dmg.to_str().unwrap(), + "-ov", + "-format", + "UDZO", + "-imagekey", + "zlib-level=9", + "-o", + dmg_path.to_str().unwrap(), + ]) + .status() + .with_context(|| "Failed to run hdiutil convert")?; + + if !status.success() { + anyhow::bail!("hdiutil convert failed"); + } + + Ok(vec![dmg_path]) +} + /// Walk a directory and sum the sizes of all contained files. fn dir_size(dir: &std::path::Path) -> crate::Result { let mut total: u64 = 0; @@ -28,3 +149,24 @@ fn parse_mount_point(stdout: &[u8]) -> crate::Result { parse_mount_point_impl(stdout) } +/// Public re-export used only by tests in sibling modules. +#[cfg(test)] +pub fn parse_mount_point_pub(stdout: &[u8]) -> crate::Result { + parse_mount_point_impl(stdout) +} + +fn parse_mount_point_impl(stdout: &[u8]) -> crate::Result { + let text = std::str::from_utf8(stdout)?; + // hdiutil attach prints a tab-separated line whose last field is the mount + // point, e.g.: /dev/disk2s1 Apple_HFS /Volumes/MyApp + for line in text.lines().rev() { + let parts: Vec<&str> = line.split('\t').collect(); + if let Some(path) = parts.last() { + let path = path.trim(); + if path.starts_with("/Volumes/") { + return Ok(PathBuf::from(path)); + } + } + } + anyhow::bail!("Could not find a /Volumes/… mount point in hdiutil output") +} From 1946193693185a7ba7af214a14cb449abad62c1e 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:13:12 -0400 Subject: [PATCH 09/14] Integrate DMG bundle into mod.rs dispatch --- src/bundle/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bundle/mod.rs b/src/bundle/mod.rs index 001d09a..c1e14d5 100644 --- a/src/bundle/mod.rs +++ b/src/bundle/mod.rs @@ -1,5 +1,6 @@ mod category; mod common; +mod dmg_bundle; mod ios_bundle; mod linux; mod msi_bundle; @@ -18,6 +19,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { for package_type in settings.package_types()? { paths.append(&mut match package_type { PackageType::OsxBundle => osx_bundle::bundle_project(&settings)?, + PackageType::OsxDmg => dmg_bundle::bundle_project(&settings)?, PackageType::IosBundle => ios_bundle::bundle_project(&settings)?, PackageType::WindowsMsi => msi_bundle::bundle_project(&settings)?, PackageType::WxsMsi => wxsmsi_bundle::bundle_project(&settings)?, From 43f1c32efe96515e48dbcd0f5b08e604f875b57d 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:13:33 -0400 Subject: [PATCH 10/14] Add DMG and localization round-trip tests --- src/bundle/settings.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/bundle/settings.rs b/src/bundle/settings.rs index 04faf45..3fbddca 100644 --- a/src/bundle/settings.rs +++ b/src/bundle/settings.rs @@ -721,4 +721,41 @@ mod tests { let baz: &BundleSettings = examples.get("baz").unwrap(); assert_eq!(baz.name, Some("Baz Example".to_string())); } + + #[test] + fn dmg_round_trip() { + use super::PackageType; + + // Each new short name should parse back to the correct variant. + assert_eq!( + PackageType::from_short_name("dmg"), + Some(PackageType::OsxDmg) + ); + + // And Display / short_name should survive the round-trip. + assert_eq!(PackageType::OsxDmg.short_name(), "dmg"); + assert_eq!(PackageType::OsxDmg.to_string(), "dmg"); + } + + #[test] + fn all_package_types_are_listed() { + use super::PackageType; + let all = PackageType::all(); + assert!(all.contains(&"dmg"), "dmg missing from PackageType::all()"); + } + + #[test] + fn osx_localizations_parses_from_toml() { + let toml_str = r#" + [osx_localizations.fr] + CFBundleDisplayName = "Mon Application" + + [osx_localizations.de] + CFBundleDisplayName = "Meine Anwendung" + "#; + let bundle: BundleSettings = toml::from_str(toml_str).unwrap(); + let locs = bundle.osx_localizations.unwrap(); + assert_eq!(locs["fr"]["CFBundleDisplayName"], "Mon Application"); + assert_eq!(locs["de"]["CFBundleDisplayName"], "Meine Anwendung"); + } } From 4039b086acdabc73b2205b87bc05b42f77ae9cd0 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:13:41 -0400 Subject: [PATCH 11/14] Apply cargo fmt formatting --- src/bundle/settings.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bundle/settings.rs b/src/bundle/settings.rs index 3fbddca..85d4988 100644 --- a/src/bundle/settings.rs +++ b/src/bundle/settings.rs @@ -85,7 +85,9 @@ impl PackageType { } pub const fn all() -> &'static [&'static str] { - &["deb", "ios", "msi", "wxsmsi", "osx", "dmg", "rpm", "appimage"] + &[ + "deb", "ios", "msi", "wxsmsi", "osx", "dmg", "rpm", "appimage", + ] } } From 40aca6efcd166334454cf54b53cfce523d6fa13c 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: Fri, 22 May 2026 14:45:38 -0400 Subject: [PATCH 12/14] clippy fixes --- src/bundle/dmg_bundle.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs index 4bfa30a..65d0739 100644 --- a/src/bundle/dmg_bundle.rs +++ b/src/bundle/dmg_bundle.rs @@ -149,12 +149,6 @@ fn parse_mount_point(stdout: &[u8]) -> crate::Result { parse_mount_point_impl(stdout) } -/// Public re-export used only by tests in sibling modules. -#[cfg(test)] -pub fn parse_mount_point_pub(stdout: &[u8]) -> crate::Result { - parse_mount_point_impl(stdout) -} - fn parse_mount_point_impl(stdout: &[u8]) -> crate::Result { let text = std::str::from_utf8(stdout)?; // hdiutil attach prints a tab-separated line whose last field is the mount From 1ca38040ba645955592ba3fdc2e17b9ef108a017 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:06:51 -0400 Subject: [PATCH 13/14] removed indirection --- src/bundle/dmg_bundle.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs index 65d0739..33b9732 100644 --- a/src/bundle/dmg_bundle.rs +++ b/src/bundle/dmg_bundle.rs @@ -144,13 +144,9 @@ fn dir_size(dir: &std::path::Path) -> crate::Result { Ok(total) } -/// Extract the mount point path from `hdiutil attach` stdout. fn parse_mount_point(stdout: &[u8]) -> crate::Result { - parse_mount_point_impl(stdout) -} - -fn parse_mount_point_impl(stdout: &[u8]) -> crate::Result { let text = std::str::from_utf8(stdout)?; + // hdiutil attach prints a tab-separated line whose last field is the mount // point, e.g.: /dev/disk2s1 Apple_HFS /Volumes/MyApp for line in text.lines().rev() { @@ -162,5 +158,6 @@ fn parse_mount_point_impl(stdout: &[u8]) -> crate::Result { } } } + anyhow::bail!("Could not find a /Volumes/… mount point in hdiutil output") } From bdeb334ca06da026a3f83ffdae609508a126eb22 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:06:59 -0400 Subject: [PATCH 14/14] cleanup of bundle min logic --- src/bundle/dmg_bundle.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bundle/dmg_bundle.rs b/src/bundle/dmg_bundle.rs index 33b9732..d729c7f 100644 --- a/src/bundle/dmg_bundle.rs +++ b/src/bundle/dmg_bundle.rs @@ -20,6 +20,8 @@ use std::path::PathBuf; use std::process::Command; pub fn bundle_project(settings: &Settings) -> crate::Result> { + const BUNDLE_MINIMUM: u64 = 52_428_800; + let dmg_name = format!("{}.dmg", settings.bundle_name()); common::print_bundling(&dmg_name)?; @@ -46,7 +48,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { // Determine the size of the app bundle and add a generous overhead. let bundle_size = dir_size(&app_bundle_path)?; - let image_size_bytes = (bundle_size + 52_428_800).max(52_428_800); // at least 50 MiB + let image_size_bytes = bundle_size + BUNDLE_MINIMUM; // at least 50 MiB let temp_dir = tempfile::tempdir() .with_context(|| "Failed to create temporary directory for DMG staging")?;