diff --git a/tools/rust/cargo-tauri-bundle/Readme.md b/tools/rust/cargo-tauri-bundle/Readme.md index 742b31516..739b6aed0 100644 --- a/tools/rust/cargo-tauri-bundle/Readme.md +++ b/tools/rust/cargo-tauri-bundle/Readme.md @@ -134,3 +134,7 @@ stability. This program is licensed either under the terms of the [Apache Software License](http://www.apache.org/licenses/LICENSE-2.0), or the [MIT License](https://opensource.org/licenses/MIT). + +-> note, for bundle_dmg we have included a BSD 3 licenced binary `seticon`. +https://github.com/sveinbjornt/osxiconutils/blob/master/seticon.m +`tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon` diff --git a/tools/rust/cargo-tauri-bundle/src/bundle.rs b/tools/rust/cargo-tauri-bundle/src/bundle.rs index 5d47989ff..b58387904 100644 --- a/tools/rust/cargo-tauri-bundle/src/bundle.rs +++ b/tools/rust/cargo-tauri-bundle/src/bundle.rs @@ -1,6 +1,7 @@ mod category; mod common; mod deb_bundle; +mod dmg_bundle; mod ios_bundle; mod msi_bundle; mod osx_bundle; @@ -18,9 +19,12 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { paths.append(&mut match package_type { PackageType::OsxBundle => osx_bundle::bundle_project(&settings)?, PackageType::IosBundle => ios_bundle::bundle_project(&settings)?, + // use dmg bundler + // PackageType::OsxBundle => dmg_bundle::bundle_project(&settings)?, PackageType::WindowsMsi => msi_bundle::bundle_project(&settings)?, PackageType::Deb => deb_bundle::bundle_project(&settings)?, PackageType::Rpm => rpm_bundle::bundle_project(&settings)?, + PackageType::Dmg => dmg_bundle::bundle_project(&settings)?, }); } Ok(paths) diff --git a/tools/rust/cargo-tauri-bundle/src/bundle/dmg_bundle.rs b/tools/rust/cargo-tauri-bundle/src/bundle/dmg_bundle.rs new file mode 100644 index 000000000..8ad7c8154 --- /dev/null +++ b/tools/rust/cargo-tauri-bundle/src/bundle/dmg_bundle.rs @@ -0,0 +1,73 @@ +use super::common; +use super::osx_bundle; +use crate::Settings; + +use handlebars::Handlebars; +use lazy_static::lazy_static; + +use std::collections::BTreeMap; +use std::fs::write; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +// Create handlebars template for shell scripts +lazy_static! { + static ref HANDLEBARS: Handlebars = { + let mut handlebars = Handlebars::new(); + + handlebars + .register_template_string("bundle_dmg", include_str!("templates/bundle_dmg")) + .unwrap(); + handlebars + }; +} + +// create script files to bundle project and execute bundle_script. +pub fn bundle_project(settings: &Settings) -> crate::Result> { + // generate the app.app folder + osx_bundle::bundle_project(settings)?; + + // get uppercase string of app name + let upcase = settings.binary_name().to_uppercase(); + + // generate BTreeMap for templates + let mut sh_map = BTreeMap::new(); + sh_map.insert("app_name", settings.binary_name()); + sh_map.insert("app_name_upcase", &upcase); + + let bundle_temp = HANDLEBARS + .render("bundle_dmg", &sh_map) + .or_else(|e| Err(e.to_string()))?; + + // get the target path + let output_path = settings.project_out_directory(); + + // create paths for script + let bundle_sh = output_path.join("bundle_dmg.sh"); + + common::print_bundling(format!("{:?}", &output_path.join(format!("{}.dmg", &upcase))).as_str())?; + + // write the scripts + write(&bundle_sh, bundle_temp).or_else(|e| Err(e.to_string()))?; + + // chmod script for execution + + Command::new("chmod") + .arg("777") + .arg(&bundle_sh) + .current_dir(output_path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to chmod script"); + + // execute the bundle script + Command::new(&bundle_sh) + .current_dir(output_path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute shell script"); + + Ok(vec![bundle_sh]) +} diff --git a/tools/rust/cargo-tauri-bundle/src/bundle/settings.rs b/tools/rust/cargo-tauri-bundle/src/bundle/settings.rs index 5d643f28e..91e45711c 100644 --- a/tools/rust/cargo-tauri-bundle/src/bundle/settings.rs +++ b/tools/rust/cargo-tauri-bundle/src/bundle/settings.rs @@ -17,6 +17,7 @@ pub enum PackageType { WindowsMsi, Deb, Rpm, + Dmg, } impl PackageType { @@ -28,6 +29,7 @@ impl PackageType { "msi" => Some(PackageType::WindowsMsi), "osx" => Some(PackageType::OsxBundle), "rpm" => Some(PackageType::Rpm), + "dmg" => Some(PackageType::Dmg), _ => None, } } @@ -39,6 +41,7 @@ impl PackageType { PackageType::WindowsMsi => "msi", PackageType::OsxBundle => "osx", PackageType::Rpm => "rpm", + PackageType::Dmg => "dmg", } } diff --git a/tools/rust/cargo-tauri-bundle/src/bundle/templates/bundle_dmg b/tools/rust/cargo-tauri-bundle/src/bundle/templates/bundle_dmg new file mode 100644 index 000000000..f2f6371c1 --- /dev/null +++ b/tools/rust/cargo-tauri-bundle/src/bundle/templates/bundle_dmg @@ -0,0 +1,144 @@ +#!/usr/bin/env sh +# using sh because modern MacOS ships with zsh not bash +# some patterns "lifted" from: +# - https://github.com/andreyvit/create-dmg/blob/master/create-dmg +# - https://stackoverflow.com/a/97025 +# - https://github.com/sindresorhus/create-dmg/blob/master/cli.js + +set -e +MACOS_APP_NAME="{{app_name_upcase}}" +MACOS_APP_DIR="bundle/osx/{{app_name}}.app" +ICNS_FILE="app.app/Contents/Resources/icon.icns" +DMG_TEMP_NAME=/tmp/temp.dmg +MOUNT_DIR="/Volumes/${MACOS_APP_NAME}" + +# Create the image +echo "Creating disk image..." +test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}" +test -f "${MACOS_APP_NAME}.dmg" && rm -f "${MACOS_APP_NAME}.dmg" + +hdiutil create ${DMG_TEMP_NAME} -srcfolder bundle/osx -volname "${MACOS_APP_NAME}" -ov -format UDRW -size 10000k -fs HFS+ -fsargs "-c c=64,a=16,e=16" -anyowners -nospotlight + +# mount it +echo "Mounting disk image..." +# try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it) +DEV_NAME=$(hdiutil info | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}') +test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}" + +device=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | \ + egrep '^/dev/' | sed 1q | awk '{print $1}') + +sleep 5 + + +bless --folder "${MOUNT_DIR}" -label "${MACOS_APP_NAME}" + +test -d "${MOUNT_DIR}/.background" || mkdir "${MOUNT_DIR}/.background" +cp ../../icons/bg.png "${MOUNT_DIR}"/.background/bg.png + +# I couldn't get DeRez and Rez to work. Leaving it here in case its helpful in the future. +### DeRez -only icns "${MOUNT_DIR}/.VolumeIcon.icns" > "${MOUNT_DIR}/.VolumeIcon.rsrc" +### Rez -append "${MOUNT_DIR}/.VolumeIcon.rsrc" -o my_app.dmg + +# This Works +cp '../../icons/icon.icns' "${MOUNT_DIR}/.VolumeIcon.icns" +VERSION=$(sw_vers -productVersion | cut -d'.' -f2) + +if [ "$VERSION" -gt 12 ]; then + echo "Using SIPS v10.13+" + sips --i "${MOUNT_DIR}/.VolumeIcon.icns" # 10.13+ +else + echo "Using SIPS v10.12-" + sips --addIcon "${MOUNT_DIR}/.VolumeIcon.icns" +fi + +SetFile -c icnC "${MOUNT_DIR}/.VolumeIcon.icns" +SetFile -a C "${MOUNT_DIR}" + +# Doesn't stick after the renaming +### ./seticon "${MOUNT_DIR}/${ICNS_FILE}" "${DMG_TEMP_NAME}" + +# This does not work +### echo "read 'icns' (-16455) \"${MOUNT_DIR}/.VolumeIcon.icns\";" >> Icon.rsrc +### Rez -a Icon.rsrc -o FileName.ext +### DeRez -only icns "${MOUNT_DIR}/.VolumeIcon.icns" > "${MOUNT_DIR}/.Icon.rsrc" +### Rez -a Icon.rsrc -o "${MOUNT_DIR}/Icon$'\r'" +### SetFile -c icnC "${MOUNT_DIR}/.Icon.rsrc" +### SetFile -a C "${MOUNT_DIR}" +### SetFile -a "${MOUNT_DIR}/Icon$'\r'" + + +# this is from +# https://stackoverflow.com/questions/8371790/how-to-set-icon-on-file-or-directory-using-cli-on-os-x#8375093 +### iconSource='../../icons/icon.icns' +### iconDestination="${MOUNT_DIR}" +### icon=/tmp/$(basename $iconSource) +### rsrc=/tmp/icon.rsrc + +# Create icon from the iconSource +### cp $iconSource $icon + +# Add icon to image file, meaning use itself as the icon +### sips -i $icon + +# Take that icon and put it into a rsrc file +### DeRez -only icns $icon > $rsrc + +# Apply the rsrc file to +### SetFile -a C "${iconDestination}" + +# Create the magical Icon\r file +### touch "${iconDestination}/Icon$'\r'" +### Rez -append $rsrc -o "${iconDestination}/Icon?" +### SetFile -a V "${iconDestination}/Icon$'\r'" + +echo ' + tell application "Finder" + tell disk "'${MACOS_APP_NAME}'" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 885, 430} + set theViewOptions to the icon view options of container window + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 110 + set background picture of theViewOptions to file ".background:'bg.png'" + make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} + set position of item "'app.app'" of container window to {100, 100} + set position of item "Applications" of container window to {375, 100} + update without registering applications + delay 5 + close + end tell + end tell +' | osascript +chmod -Rf go-w /Volumes/"${MACOS_APP_NAME}" + +sync +sync + +hdiutil detach ${device} + +# Some variations on format: +## UDZO – Compressed image (default) +## UDRO – Read-only image +## UDBZ – Better compressed image +## UDRW – Read/Write image + +hdiutil convert "${DMG_TEMP_NAME}" -format UDBZ -imagekey zlib-level=9 -o "${MACOS_APP_NAME}" + +./seticon ../../icons/icon.icns "${MACOS_APP_NAME}.dmg" + +rm -f ${DMG_TEMP_NAME} + +# This seems not to work as well at maintaining the icons :( +zip -r "${MACOS_APP_NAME}.dmg.zip" "${MACOS_APP_NAME}.dmg" + + +# FUTURE WORK +# SIGNER=$(security find-identity -v -p codesigning) +# codesign --sign 'Mac Developer' "${DMG_TEMP_NAME}" + +# Add a license with Rez +# http://www.owsiak.org/adding-license-to-a-dmg-file-in-5-minutes-or-less/ diff --git a/tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon b/tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon new file mode 100755 index 000000000..c58afa2af Binary files /dev/null and b/tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon differ