From db7d10308f2c936e0a4b40b6cf1064ac41eafebd Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 7 Apr 2020 11:18:37 -0300 Subject: [PATCH] feat(bundler) dmg bundle (#551) * feat(bundler) dmg bundle * feat(bundler) fix dmg bundle support scripts, add license option * chore(bundler) add "forked from" notice * fix(tests) remove dmg bundling from the template test --- cli/tauri-bundler/src/bundle.rs | 28 +- .../src/bundle/appimage_bundle.rs | 2 +- cli/tauri-bundler/src/bundle/dmg_bundle.rs | 111 ++--- cli/tauri-bundler/src/bundle/settings.rs | 10 + cli/tauri-bundler/src/bundle/tauri_config.rs | 3 +- .../src/bundle/templates/bundle_dmg | 144 ------- .../src/bundle/templates/dmg/bundle_dmg | 394 ++++++++++++++++++ .../src/bundle/templates/dmg/dmg-license.py | 163 ++++++++ .../bundle/templates/dmg/template.applescript | 74 ++++ .../src/bundle/templates/seticon | Bin 58892 -> 0 bytes cli/tauri.js/src/helpers/tauri-config.ts | 27 +- cli/tauri.js/src/types/config.ts | 1 + .../test/jest/__tests__/template.spec.js | 8 +- 13 files changed, 761 insertions(+), 204 deletions(-) delete mode 100644 cli/tauri-bundler/src/bundle/templates/bundle_dmg create mode 100644 cli/tauri-bundler/src/bundle/templates/dmg/bundle_dmg create mode 100644 cli/tauri-bundler/src/bundle/templates/dmg/dmg-license.py create mode 100644 cli/tauri-bundler/src/bundle/templates/dmg/template.applescript delete mode 100755 cli/tauri-bundler/src/bundle/templates/seticon diff --git a/cli/tauri-bundler/src/bundle.rs b/cli/tauri-bundler/src/bundle.rs index e3767c1be..9b783e5e4 100644 --- a/cli/tauri-bundler/src/bundle.rs +++ b/cli/tauri-bundler/src/bundle.rs @@ -22,17 +22,35 @@ use std::path::PathBuf; pub fn bundle_project(settings: Settings) -> crate::Result> { let mut paths = Vec::new(); - for package_type in settings.package_types()? { - paths.append(&mut match package_type { - PackageType::OsxBundle => osx_bundle::bundle_project(&settings)?, + let package_types = settings.package_types()?; + for package_type in &package_types { + let mut bundle_paths = match package_type { + PackageType::OsxBundle => { + if package_types.clone().iter().any(|&t| t == PackageType::Dmg) { + vec![] + } else { + osx_bundle::bundle_project(&settings)? + } + } PackageType::IosBundle => ios_bundle::bundle_project(&settings)?, #[cfg(target_os = "windows")] PackageType::WindowsMsi => msi_bundle::bundle_project(&settings)?, - PackageType::Deb => deb_bundle::bundle_project(&settings)?, + PackageType::Deb => { + if package_types + .clone() + .iter() + .any(|&t| t == PackageType::AppImage) + { + vec![] + } else { + deb_bundle::bundle_project(&settings)? + } + } PackageType::Rpm => rpm_bundle::bundle_project(&settings)?, PackageType::AppImage => appimage_bundle::bundle_project(&settings)?, PackageType::Dmg => dmg_bundle::bundle_project(&settings)?, - }); + }; + paths.append(&mut bundle_paths); } settings.copy_resources(settings.project_out_directory())?; diff --git a/cli/tauri-bundler/src/bundle/appimage_bundle.rs b/cli/tauri-bundler/src/bundle/appimage_bundle.rs index 64ff26ce2..b697f768e 100644 --- a/cli/tauri-bundler/src/bundle/appimage_bundle.rs +++ b/cli/tauri-bundler/src/bundle/appimage_bundle.rs @@ -90,5 +90,5 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { .spawn() .expect("Failed to execute shell script"); - Ok(vec![appimage_path]) + Ok(vec![package_dir, appimage_path]) } diff --git a/cli/tauri-bundler/src/bundle/dmg_bundle.rs b/cli/tauri-bundler/src/bundle/dmg_bundle.rs index ad60dc6e7..5a9946e27 100644 --- a/cli/tauri-bundler/src/bundle/dmg_bundle.rs +++ b/cli/tauri-bundler/src/bundle/dmg_bundle.rs @@ -2,82 +2,93 @@ 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, File}; -use std::io::Write; +use std::fs::{self, write}; use std::path::PathBuf; use std::process::{Command, Stdio}; -// Create handlebars template for shell scripts -lazy_static! { - static ref HANDLEBARS: Handlebars<'static> = { - let mut handlebars = Handlebars::new(); - - handlebars - .register_template_string("bundle_dmg", include_str!("templates/bundle_dmg")) - .expect("Failed to setup handlebars template"); - handlebars - }; -} +use crate::ResultExt; // 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()))?; + let app_name = settings.bundle_name(); // get the target path - let output_path = settings.project_out_directory(); + let output_path = settings.project_out_directory().join("bundle/dmg"); + let dmg_name = format!("{}.dmg", app_name.clone()); + let dmg_path = output_path.join(&dmg_name.clone()); + + let bundle_name = &format!("{}.app", app_name); + let bundle_dir = settings.project_out_directory().join("bundle/osx"); + let bundle_path = bundle_dir.join(&bundle_name.clone()); + + let support_directory_path = output_path.join("support"); + if output_path.exists() { + fs::remove_dir_all(&output_path).chain_err(|| format!("Failed to remove old {}", dmg_name))?; + } + fs::create_dir_all(&support_directory_path).chain_err(|| { + format!( + "Failed to create output directory at {:?}", + support_directory_path + ) + })?; // create paths for script - let bundle_sh = output_path.join("bundle_dmg.sh"); + let bundle_script_path = output_path.join("bundle_dmg.sh"); + let license_script_path = support_directory_path.join("dmg-license.py"); - common::print_bundling(format!("{:?}", &output_path.join(format!("{}.dmg", &upcase))).as_str())?; + common::print_bundling(format!("{:?}", &dmg_path.clone()).as_str())?; // write the scripts - write(&bundle_sh, bundle_temp).or_else(|e| Err(e.to_string()))?; - - // copy seticon binary - let seticon = include_bytes!("templates/seticon"); - let seticon_out = &output_path.join("seticon"); - let mut seticon_buffer = File::create(seticon_out).or_else(|e| Err(e.to_string()))?; - seticon_buffer - .write_all(seticon) - .or_else(|e| Err(e.to_string()))?; + write(&bundle_script_path, include_str!("templates/dmg/bundle_dmg")).or_else(|e| Err(e.to_string()))?; + write(support_directory_path.join("template.applescript"), include_str!("templates/dmg/template.applescript"))?; + write(&license_script_path, include_str!("templates/dmg/dmg-license.py"))?; // chmod script for execution - Command::new("chmod") .arg("777") - .arg(&bundle_sh) - .arg(&seticon_out) - .current_dir(output_path) + .arg(&bundle_script_path) + .arg(&license_script_path) + .current_dir(output_path.clone()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .expect("Failed to chmod script"); + let mut args = vec![ + "--volname", + &app_name, + "--volicon", + "../../../../icons/icon.icns", + "--icon", + &bundle_name, "180", "170", + "--app-drop-link", + "480", "170", + "--window-size", + "660", "400", + "--hide-extension", + &bundle_name, + ]; + + if let Some(license_path) = settings.osx_license() { + args.push("--eula"); + args.push(license_path); + } + // execute the bundle script - Command::new(&bundle_sh) - .current_dir(output_path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() + let status = Command::new(&bundle_script_path) + .current_dir(bundle_dir.clone()) + .args(args) + .args(vec![dmg_name.as_str(), bundle_name.as_str()]) + .status() .expect("Failed to execute shell script"); - Ok(vec![bundle_sh]) + if !status.success() { + Err(crate::Error::from("error running bundle_dmg.sh")) + } else { + fs::rename(bundle_dir.join(dmg_name.clone()), dmg_path.clone())?; + Ok(vec![bundle_path, dmg_path]) + } } diff --git a/cli/tauri-bundler/src/bundle/settings.rs b/cli/tauri-bundler/src/bundle/settings.rs index 3fcc1ec81..ed7ffda85 100644 --- a/cli/tauri-bundler/src/bundle/settings.rs +++ b/cli/tauri-bundler/src/bundle/settings.rs @@ -97,6 +97,7 @@ struct BundleSettings { deb_depends: Option>, osx_frameworks: Option>, osx_minimum_system_version: Option, + osx_license: Option, // Bundles for other binaries/examples: bin: Option>, example: Option>, @@ -563,6 +564,14 @@ impl Settings { .as_ref() .map(String::as_str) } + + pub fn osx_license(&self) -> Option<&str> { + self + .bundle_settings + .osx_license + .as_ref() + .map(String::as_str) + } } fn bundle_settings_from_table( @@ -634,6 +643,7 @@ fn merge_settings( config.osx.minimum_system_version, bundle_settings.osx_minimum_system_version, ), + osx_license: options_value(config.osx.license, bundle_settings.osx_license), external_bin: options_value(config.external_bin, bundle_settings.external_bin), exception_domain: options_value( config.osx.exception_domain, diff --git a/cli/tauri-bundler/src/bundle/tauri_config.rs b/cli/tauri-bundler/src/bundle/tauri_config.rs index aafcd4aaf..1f3cc65f5 100644 --- a/cli/tauri-bundler/src/bundle/tauri_config.rs +++ b/cli/tauri-bundler/src/bundle/tauri_config.rs @@ -11,11 +11,12 @@ pub struct DebConfig { } #[derive(PartialEq, Deserialize, Clone, Debug, Default)] -#[serde(tag = "deb", rename_all = "camelCase")] +#[serde(tag = "osx", rename_all = "camelCase")] pub struct OsxConfig { pub frameworks: Option>, pub minimum_system_version: Option, pub exception_domain: Option, + pub license: Option, } #[derive(PartialEq, Deserialize, Clone, Debug, Default)] diff --git a/cli/tauri-bundler/src/bundle/templates/bundle_dmg b/cli/tauri-bundler/src/bundle/templates/bundle_dmg deleted file mode 100644 index c0c8c1590..000000000 --- a/cli/tauri-bundler/src/bundle/templates/bundle_dmg +++ /dev/null @@ -1,144 +0,0 @@ -#!/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" -test -f ../../icons/bg.png && 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/cli/tauri-bundler/src/bundle/templates/dmg/bundle_dmg b/cli/tauri-bundler/src/bundle/templates/dmg/bundle_dmg new file mode 100644 index 000000000..e0c60904e --- /dev/null +++ b/cli/tauri-bundler/src/bundle/templates/dmg/bundle_dmg @@ -0,0 +1,394 @@ +#!/usr/bin/env bash + +# Create a read-only disk image of the contents of a folder +# forked from https://github.com/andreyvit/create-dmg + +set -e; + +function pure_version() { + echo '1.0.0.6' +} + +function version() { + echo "create-dmg $(pure_version)" +} + +function usage() { + version + echo "Creates a fancy DMG file." + echo "Usage: $(basename $0) [options] " + echo "All contents of source_folder will be copied into the disk image." + echo "Options:" + echo " --volname name" + echo " set volume name (displayed in the Finder sidebar and window title)" + echo " --volicon icon.icns" + echo " set volume icon" + echo " --background pic.png" + echo " set folder background image (provide png, gif, jpg)" + echo " --window-pos x y" + echo " set position the folder window" + echo " --window-size width height" + echo " set size of the folder window" + echo " --text-size text_size" + echo " set window text size (10-16)" + echo " --icon-size icon_size" + echo " set window icons size (up to 128)" + echo " --icon file_name x y" + echo " set position of the file's icon" + echo " --hide-extension file_name" + echo " hide the extension of file" + echo " --custom-icon file_name custom_icon_or_sample_file x y" + echo " set position and custom icon" + echo " --app-drop-link x y" + echo " make a drop link to Applications, at location x,y" + echo " --ql-drop-link x y" + echo " make a drop link to user QuickLook install dir, at location x,y" + echo " --eula eula_file" + echo " attach a license file to the dmg" + echo " --no-internet-enable" + echo " disable automatic mount©" + echo " --format" + echo " specify the final image format (default is UDZO)" + echo " --add-file target_name file|folder x y" + echo " add additional file or folder (can be used multiple times)" + echo " --disk-image-size x" + echo " set the disk image size manually to x MB" + echo " --hdiutil-verbose" + echo " execute hdiutil in verbose mode" + echo " --hdiutil-quiet" + echo " execute hdiutil in quiet mode" + echo " --sandbox-safe" + echo " execute hdiutil with sandbox compatibility and do not bless" + echo " --rez rez_path" + echo " use custom path to Rez tool" + echo " --version show tool version number" + echo " -h, --help display this help" + exit 0 +} + +WINX=10 +WINY=60 +WINW=500 +WINH=350 +ICON_SIZE=128 +TEXT_SIZE=16 +FORMAT="UDZO" +ADD_FILE_SOURCES=() +ADD_FILE_TARGETS=() +IMAGEKEY="" +HDIUTIL_VERBOSITY="" +SANDBOX_SAFE=0 +SKIP_JENKINS=0 + +while test "${1:0:1}" = "-"; do + case $1 in + --volname) + VOLUME_NAME="$2" + shift; shift;; + --volicon) + VOLUME_ICON_FILE="$2" + shift; shift;; + --background) + BACKGROUND_FILE="$2" + BACKGROUND_FILE_NAME="$(basename $BACKGROUND_FILE)" + BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\"" + REPOSITION_HIDDEN_FILES_CLAUSE="set position of every item to {theBottomRightX + 100, 100}" + shift; shift;; + --icon-size) + ICON_SIZE="$2" + shift; shift;; + --text-size) + TEXT_SIZE="$2" + shift; shift;; + --window-pos) + WINX=$2; WINY=$3 + shift; shift; shift;; + --window-size) + WINW=$2; WINH=$3 + shift; shift; shift;; + --icon) + POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4} + " + shift; shift; shift; shift;; + --hide-extension) + HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true + " + shift; shift;; + --custom-icon) + shift; shift; shift; shift; shift;; + -h | --help) + usage;; + --version) + version; exit 0;; + --pure-version) + pure_version; exit 0;; + --ql-drop-link) + QL_LINK=$2 + QL_CLAUSE="set position of item \"QuickLook\" to {$2, $3} + " + shift; shift; shift;; + --app-drop-link) + APPLICATION_LINK=$2 + APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3} + " + shift; shift; shift;; + --eula) + EULA_RSRC=$2 + shift; shift;; + --no-internet-enable) + NOINTERNET=1 + shift;; + --format) + FORMAT="$2" + shift; shift;; + --add-file | --add-folder) + ADD_FILE_TARGETS+=("$2") + ADD_FILE_SOURCES+=("$3") + POSITION_CLAUSE="${POSITION_CLAUSE} + set position of item \"$2\" to {$4, $5} + " + shift; shift; shift; shift; shift;; + --disk-image-size) + DISK_IMAGE_SIZE="$2" + shift; shift;; + --hdiutil-verbose) + HDIUTIL_VERBOSITY='-verbose' + shift;; + --hdiutil-quiet) + HDIUTIL_VERBOSITY='-quiet' + shift;; + --sandbox-safe) + SANDBOX_SAFE=1 + shift;; + --rez) + REZ_PATH=$2 + shift; shift;; + --skip-jenkins) + SKIP_JENKINS=1 + shift;; + -*) + echo "Unknown option $1. Run with --help for help." + exit 1;; + esac + case $FORMAT in + UDZO) + IMAGEKEY="-imagekey zlib-level=9";; + UDBZ) + IMAGEKEY="-imagekey bzip2-level=9";; + esac +done + +test -z "$2" && { + echo "Not enough arguments. Invoke with --help for help." + exit 1 +} + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DMG_PATH="$1" +DMG_DIRNAME="$(dirname "$DMG_PATH")" +DMG_DIR="$(cd "$DMG_DIRNAME" > /dev/null; pwd)" +DMG_NAME="$(basename "$DMG_PATH")" +DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}" +SRC_FOLDER="$(cd "$2" > /dev/null; pwd)" + +test -z "$VOLUME_NAME" && VOLUME_NAME="$(basename "$DMG_PATH" .dmg)" + +# brew formula will set this as 1 and embed the support scripts +BREW_INSTALL=0 + +AUX_PATH="$SCRIPT_DIR/support" + +if [ $BREW_INSTALL -eq 0 ]; then + test -d "$AUX_PATH" || { + echo "Cannot find support directory: $AUX_PATH" + exit 1 + } +fi + +if [ -f "$SRC_FOLDER/.DS_Store" ]; then + echo "Deleting any .DS_Store in source folder" + rm "$SRC_FOLDER/.DS_Store" +fi + +# Create the image +echo "Creating disk image..." +test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}" + +# Using Megabytes since hdiutil fails with very large Byte numbers +function blocks_to_megabytes() { + # Add 1 extra MB, since there's no decimal retention here + MB_SIZE=$((($1 * 512 / 1000 / 1000) + 1)) + echo $MB_SIZE +} + +function get_size() { + # Get block size in disk + bytes_size=`du -s "$1" | sed -e 's/ .*//g'` + echo `blocks_to_megabytes $bytes_size` +} + +# Create the DMG with the specified size or the hdiutil estimation +CUSTOM_SIZE='' +if ! test -z "$DISK_IMAGE_SIZE"; then + CUSTOM_SIZE="-size ${DISK_IMAGE_SIZE}m" +fi + +if [ $SANDBOX_SAFE -eq 0 ]; then + hdiutil create ${HDIUTIL_VERBOSITY} -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW ${CUSTOM_SIZE} "${DMG_TEMP_NAME}" +else + hdiutil makehybrid ${HDIUTIL_VERBOSITY} -default-volume-name "${VOLUME_NAME}" -hfs -o "${DMG_TEMP_NAME}" "$SRC_FOLDER" + hdiutil convert -format UDRW -ov -o "${DMG_TEMP_NAME}" "${DMG_TEMP_NAME}" + DISK_IMAGE_SIZE_CUSTOM=$DISK_IMAGE_SIZE +fi + +# Get the created DMG actual size +DISK_IMAGE_SIZE=`get_size "${DMG_TEMP_NAME}"` + +# Use the custom size if bigger +if [ $SANDBOX_SAFE -eq 1 ] && [ ! -z "$DISK_IMAGE_SIZE_CUSTOM" ] && [ $DISK_IMAGE_SIZE_CUSTOM -gt $DISK_IMAGE_SIZE ]; then + DISK_IMAGE_SIZE=$DISK_IMAGE_SIZE_CUSTOM +fi + +# Estimate the additional soruces size +if ! test -z "$ADD_FILE_SOURCES"; then + for i in "${!ADD_FILE_SOURCES[@]}" + do + SOURCE_SIZE=`get_size "${ADD_FILE_SOURCES[$i]}"` + DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + $SOURCE_SIZE) + done +fi + +# Add extra space for additional resources +DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + 20) + +# Resize the image for the extra stuff +hdiutil resize ${HDIUTIL_VERBOSITY} -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}" + +# mount it +echo "Mounting disk image..." +MOUNT_DIR="/Volumes/${VOLUME_NAME}" + +# try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it) +echo "Unmounting disk image..." +DEV_NAME=$(hdiutil info | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}') +test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}" + +echo "Mount directory: $MOUNT_DIR" +DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}') +echo "Device name: $DEV_NAME" + +if ! test -z "$BACKGROUND_FILE"; then + echo "Copying background file..." + test -d "$MOUNT_DIR/.background" || mkdir "$MOUNT_DIR/.background" + cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME" +fi + +if ! test -z "$APPLICATION_LINK"; then + echo "making link to Applications dir" + test -d "$MOUNT_DIR/Applications" || ln -s /Applications "$MOUNT_DIR/Applications" +fi + +if ! test -z "$QL_LINK"; then + echo "making link to QuickLook install dir" + ln -s "/Library/QuickLook" "$MOUNT_DIR/QuickLook" +fi + +if ! test -z "$VOLUME_ICON_FILE"; then + echo "Copying volume icon file '$VOLUME_ICON_FILE'..." + cp "$VOLUME_ICON_FILE" "$MOUNT_DIR/.VolumeIcon.icns" + SetFile -c icnC "$MOUNT_DIR/.VolumeIcon.icns" +fi + +if ! test -z "$ADD_FILE_SOURCES"; then + echo "Copying custom files..." + for i in "${!ADD_FILE_SOURCES[@]}" + do + echo "${ADD_FILE_SOURCES[$i]}" + cp -a "${ADD_FILE_SOURCES[$i]}" "$MOUNT_DIR/${ADD_FILE_TARGETS[$i]}" + done +fi + +# run applescript +APPLESCRIPT=$(mktemp -t createdmg.tmp.XXXXXXXXXX) + +function applescript_source() { + if [ $BREW_INSTALL -eq 0 ]; then + cat "$AUX_PATH/template.applescript" + else + cat << 'EOS' + # BREW_INLINE_APPLESCRIPT_PLACEHOLDER +EOS + fi +} + +if [ $SKIP_JENKINS -eq 0 ]; then + applescript_source | sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" -e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" -e "s/REPOSITION_HIDDEN_FILES_CLAUSE/$REPOSITION_HIDDEN_FILES_CLAUSE/g" -e "s/ICON_SIZE/$ICON_SIZE/g" -e "s/TEXT_SIZE/$TEXT_SIZE/g" | perl -pe "s/POSITION_CLAUSE/$POSITION_CLAUSE/g" | perl -pe "s/QL_CLAUSE/$QL_CLAUSE/g" | perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" | perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" >"$APPLESCRIPT" + sleep 2 # pause to workaround occasional "Can’t get disk" (-1728) issues + echo "Running Applescript: /usr/bin/osascript \"${APPLESCRIPT}\" \"${VOLUME_NAME}\"" + (/usr/bin/osascript "${APPLESCRIPT}" "${VOLUME_NAME}" || if [[ "$?" -ne 0 ]]; then echo "Failed running AppleScript"; hdiutil detach "${DEV_NAME}"; exit 64; fi) + echo "Done running the applescript..." + sleep 4 + rm "$APPLESCRIPT" +fi + +# make sure it's not world writeable +echo "Fixing permissions..." +chmod -Rf go-w "${MOUNT_DIR}" &> /dev/null || true +echo "Done fixing permissions." + +# make the top window open itself on mount: +if [ $SANDBOX_SAFE -eq 0 ]; then + echo "Blessing started" + bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}" + echo "Blessing finished" +else + echo "Skipping blessing on sandbox" +fi + +if ! test -z "$VOLUME_ICON_FILE"; then + # tell the volume that it has a special file attribute + SetFile -a C "$MOUNT_DIR" +fi + +# unmount +echo "Unmounting disk image..." +hdiutil detach "${DEV_NAME}" + +# compress image +echo "Compressing disk image..." +hdiutil convert ${HDIUTIL_VERBOSITY} "${DMG_TEMP_NAME}" -format ${FORMAT} ${IMAGEKEY} -o "${DMG_DIR}/${DMG_NAME}" +rm -f "${DMG_TEMP_NAME}" + +# adding EULA resources +if [ ! -z "${EULA_RSRC}" -a "${EULA_RSRC}" != "-null-" ]; then + echo "adding EULA resources" + + REZ_ARG="" + if [ ! -z "${REZ_PATH}" -a "${REZ_PATH}" != "-null-" ]; then + REZ_ARG="--rez ${REZ_PATH}" + fi + if [ $BREW_INSTALL -eq 0 ]; then + "${AUX_PATH}/dmg-license.py" "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}" ${REZ_ARG} + else + python - "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}" ${REZ_ARG} <<-'EOS' + # BREW_INLINE_LICENSE_PLACEHOLDER +EOS + fi +fi + +if [ ! -z "${NOINTERNET}" -a "${NOINTERNET}" == 1 ]; then + echo "not setting 'internet-enable' on the dmg" +else + # check if hdiutil supports internet-enable + # support was removed in macOS 10.15 + # https://github.com/andreyvit/create-dmg/issues/76 + if hdiutil internet-enable -help >/dev/null 2>/dev/null + then + hdiutil internet-enable -yes "${DMG_DIR}/${DMG_NAME}" + else + echo "hdiutil does not support internet-enable. Note it was removed in macOS 10.15." + fi +fi + +echo "Disk image done" +exit 0 \ No newline at end of file diff --git a/cli/tauri-bundler/src/bundle/templates/dmg/dmg-license.py b/cli/tauri-bundler/src/bundle/templates/dmg/dmg-license.py new file mode 100644 index 000000000..44fd80ca9 --- /dev/null +++ b/cli/tauri-bundler/src/bundle/templates/dmg/dmg-license.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +""" +This script adds a license file to a DMG. Requires Xcode and a plain ascii text +license file. +Obviously only runs on a Mac. + +Copyright (C) 2011-2013 Jared Hobbs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from __future__ import print_function +import os +import sys +import tempfile +import optparse + + +class Path(str): + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + os.unlink(self) + + +def mktemp(dir=None, suffix=''): + (fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix) + os.close(fd) + return Path(filename) + + +def main(options, args): + dmgFile, license = args + with mktemp('.') as tmpFile: + with open(tmpFile, 'w') as f: + f.write("""data 'TMPL' (128, "LPic") { + $"1344 6566 6175 6C74 204C 616E 6775 6167" + $"6520 4944 4457 5244 0543 6F75 6E74 4F43" + $"4E54 042A 2A2A 2A4C 5354 430B 7379 7320" + $"6C61 6E67 2049 4444 5752 441E 6C6F 6361" + $"6C20 7265 7320 4944 2028 6F66 6673 6574" + $"2066 726F 6D20 3530 3030 4457 5244 1032" + $"2D62 7974 6520 6C61 6E67 7561 6765 3F44" + $"5752 4404 2A2A 2A2A 4C53 5445" +}; + +data 'LPic' (5000) { + $"0000 0002 0000 0000 0000 0000 0004 0000" +}; + +data 'STR#' (5000, "English buttons") { + $"0006 0D45 6E67 6C69 7368 2074 6573 7431" + $"0541 6772 6565 0844 6973 6167 7265 6505" + $"5072 696E 7407 5361 7665 2E2E 2E7A 4966" + $"2079 6F75 2061 6772 6565 2077 6974 6820" + $"7468 6520 7465 726D 7320 6F66 2074 6869" + $"7320 6C69 6365 6E73 652C 2063 6C69 636B" + $"2022 4167 7265 6522 2074 6F20 6163 6365" + $"7373 2074 6865 2073 6F66 7477 6172 652E" + $"2020 4966 2079 6F75 2064 6F20 6E6F 7420" + $"6167 7265 652C 2070 7265 7373 2022 4469" + $"7361 6772 6565 2E22" +}; + +data 'STR#' (5002, "English") { + $"0006 0745 6E67 6C69 7368 0541 6772 6565" + $"0844 6973 6167 7265 6505 5072 696E 7407" + $"5361 7665 2E2E 2E7B 4966 2079 6F75 2061" + $"6772 6565 2077 6974 6820 7468 6520 7465" + $"726D 7320 6F66 2074 6869 7320 6C69 6365" + $"6E73 652C 2070 7265 7373 2022 4167 7265" + $"6522 2074 6F20 696E 7374 616C 6C20 7468" + $"6520 736F 6674 7761 7265 2E20 2049 6620" + $"796F 7520 646F 206E 6F74 2061 6772 6565" + $"2C20 7072 6573 7320 2244 6973 6167 7265" + $"6522 2E" +};\n\n""") + with open(license, 'r') as l: + kind = 'RTF ' if license.lower().endswith('.rtf') else 'TEXT' + f.write('data \'%s\' (5000, "English") {\n' % kind) + def escape(s): + return s.strip().replace('\\', '\\\\').replace('"', '\\"').replace('\0', '') + + for line in l: + line = escape(line) + for liner in [line[i:i+1000] for i in range(0, len(line), 1000)]: + f.write(' "' + liner + '"\n') + f.write(' "' + '\\n"\n') + f.write('};\n\n') + f.write("""data 'styl' (5000, "English") { + $"0003 0000 0000 000C 0009 0014 0000 0000" + $"0000 0000 0000 0000 0027 000C 0009 0014" + $"0100 0000 0000 0000 0000 0000 002A 000C" + $"0009 0014 0000 0000 0000 0000 0000" +};\n""") + os.system('hdiutil unflatten -quiet "%s"' % dmgFile) + ret = os.system('%s -a %s -o "%s"' % + (options.rez, tmpFile, dmgFile)) + os.system('hdiutil flatten -quiet "%s"' % dmgFile) + if options.compression is not None: + os.system('cp %s %s.temp.dmg' % (dmgFile, dmgFile)) + os.remove(dmgFile) + if options.compression == "bz2": + os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' % + (dmgFile, dmgFile)) + elif options.compression == "gz": + os.system('hdiutil convert %s.temp.dmg -format ' % dmgFile + + 'UDZO -imagekey zlib-devel=9 -o %s' % dmgFile) + os.remove('%s.temp.dmg' % dmgFile) + if ret == 0: + print("Successfully added license to '%s'" % dmgFile) + else: + print("Failed to add license to '%s'" % dmgFile) + +if __name__ == '__main__': + parser = optparse.OptionParser() + parser.set_usage("""%prog [OPTIONS] + This program adds a software license agreement to a DMG file. + It requires Xcode and either a plain ascii text + or a with the RTF contents. + + See --help for more details.""") + parser.add_option( + '--rez', + '-r', + action='store', + default='/Applications/Xcode.app/Contents/Developer/Tools/Rez', + help='The path to the Rez tool. Defaults to %default' + ) + parser.add_option( + '--compression', + '-c', + action='store', + choices=['bz2', 'gz'], + default=None, + help='Optionally compress dmg using specified compression type. ' + 'Choices are bz2 and gz.' + ) + options, args = parser.parse_args() + cond = len(args) != 2 + if not os.path.exists(options.rez): + print('Failed to find Rez at "%s"!\n' % options.rez) + cond = True + if cond: + parser.print_usage() + sys.exit(1) + main(options, args) \ No newline at end of file diff --git a/cli/tauri-bundler/src/bundle/templates/dmg/template.applescript b/cli/tauri-bundler/src/bundle/templates/dmg/template.applescript new file mode 100644 index 000000000..fee634d31 --- /dev/null +++ b/cli/tauri-bundler/src/bundle/templates/dmg/template.applescript @@ -0,0 +1,74 @@ +on run (volumeName) + tell application "Finder" + tell disk (volumeName as string) + open + + set theXOrigin to WINX + set theYOrigin to WINY + set theWidth to WINW + set theHeight to WINH + + set theBottomRightX to (theXOrigin + theWidth) + set theBottomRightY to (theYOrigin + theHeight) + set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\"" + + tell container window + set current view to icon view + set toolbar visible to false + set statusbar visible to false + set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} + set statusbar visible to false + REPOSITION_HIDDEN_FILES_CLAUSE + end tell + + set opts to the icon view options of container window + tell opts + set icon size to ICON_SIZE + set text size to TEXT_SIZE + set arrangement to not arranged + end tell + BACKGROUND_CLAUSE + + -- Positioning + POSITION_CLAUSE + + -- Hiding + HIDING_CLAUSE + + -- Application and QL Link Clauses + APPLICATION_CLAUSE + QL_CLAUSE + close + open + -- Force saving of the size + delay 1 + + tell container window + set statusbar visible to false + set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10} + end tell + end tell + + delay 1 + + tell disk (volumeName as string) + tell container window + set statusbar visible to false + set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} + end tell + end tell + + --give the finder some time to write the .DS_Store file + delay 3 + + set waitTime to 0 + set ejectMe to false + repeat while ejectMe is false + delay 1 + set waitTime to waitTime + 1 + + if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true + end repeat + log "waited " & waitTime & " seconds for .DS_STORE to be created." + end tell +end run diff --git a/cli/tauri-bundler/src/bundle/templates/seticon b/cli/tauri-bundler/src/bundle/templates/seticon deleted file mode 100755 index c58afa2af172b12a6425679d7a76f8f40a5a9c86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58892 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: TauriConfig => { const runningDevServer = config.build.devPath && config.build.devPath.startsWith('http') if (!runningDevServer) { config.build.devPath = appPaths.resolve.tauri(config.build.devPath) - process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.devPath) + process.env.TAURI_DIST_DIR = config.build.devPath } if (config.build.distDir) { config.build.distDir = appPaths.resolve.tauri(config.build.distDir) - process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.distDir) + process.env.TAURI_DIST_DIR = config.build.distDir + } + + // bundle configuration + if (config.tauri.bundle) { + // OSX + if (config.tauri.bundle.osx) { + const license = config.tauri.bundle.osx.license + if (typeof license === 'string') { + config.tauri.bundle.osx.license = appPaths.resolve.tauri(license) + } else if (license !== null) { + const licensePath = appPaths.resolve.app('LICENSE') + if (existsSync(licensePath)) { + config.tauri.bundle.osx.license = licensePath + } + } + } + + // targets + if (Array.isArray(config.tauri.bundle.targets)) { + if (process.platform !== 'win32') { + config.tauri.bundle.targets = config.tauri.bundle.targets.filter(t => t !== 'msi') + } + } } if (!process.env.TAURI_DIST_DIR) { diff --git a/cli/tauri.js/src/types/config.ts b/cli/tauri.js/src/types/config.ts index 8c0c5d7bb..ee6590512 100644 --- a/cli/tauri.js/src/types/config.ts +++ b/cli/tauri.js/src/types/config.ts @@ -39,6 +39,7 @@ export interface TauriConfig { osx?: { frameworks?: string[] minimumSystemVersion?: string + license?: string } exceptionDomain?: string } diff --git a/cli/tauri.js/test/jest/__tests__/template.spec.js b/cli/tauri.js/test/jest/__tests__/template.spec.js index 5253248e2..5feedad1e 100644 --- a/cli/tauri.js/test/jest/__tests__/template.spec.js +++ b/cli/tauri.js/test/jest/__tests__/template.spec.js @@ -30,7 +30,13 @@ describe('[CLI] tauri.js template', () => { } const build = require('api/build') - build().promise.then(() => { + build({ + tauri: { + bundle: { + targets: ['deb', 'osx', 'msi', 'appimage'] // we can't bundle dmg on CI so we remove it here + } + } + }).promise.then(() => { process.chdir(cwd) done() }).catch(done)