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 c58afa2af..000000000 Binary files a/cli/tauri-bundler/src/bundle/templates/seticon and /dev/null differ diff --git a/cli/tauri.js/src/helpers/tauri-config.ts b/cli/tauri.js/src/helpers/tauri-config.ts index 63eeb5af6..60b0227f6 100644 --- a/cli/tauri.js/src/helpers/tauri-config.ts +++ b/cli/tauri.js/src/helpers/tauri-config.ts @@ -58,11 +58,34 @@ const getTauriConfig = (cfg: Partial): 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)