diff --git a/src/create-dmg/.editorconfig b/src/create-dmg/.editorconfig new file mode 100644 index 0000000000..e72f878d12 --- /dev/null +++ b/src/create-dmg/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig for create-dmg project +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +# We use tabs in our own code +[{create-dmg,*.applescript,*.sh}] +indent_style = tab +indent_size = 2 + +# But the Python code we pull in from pyhacker uses spaces +[*.py] +indent_style = space +indent_size = 4 diff --git a/src/create-dmg/.gitignore b/src/create-dmg/.gitignore new file mode 100644 index 0000000000..8ea92b5276 --- /dev/null +++ b/src/create-dmg/.gitignore @@ -0,0 +1,5 @@ +.svn +.vscode + +*.dmg +.DS_Store diff --git a/src/create-dmg/.this-is-the-create-dmg-repo b/src/create-dmg/.this-is-the-create-dmg-repo index e69de29bb2..7d4add6657 100644 --- a/src/create-dmg/.this-is-the-create-dmg-repo +++ b/src/create-dmg/.this-is-the-create-dmg-repo @@ -0,0 +1,2 @@ +This is just a dummy file so create-dmg can tell whether it's being run from +inside the Git repo or from an installed location. diff --git a/src/create-dmg/Makefile b/src/create-dmg/Makefile new file mode 100644 index 0000000000..41e8f08397 --- /dev/null +++ b/src/create-dmg/Makefile @@ -0,0 +1,33 @@ +# Makefile for Cowsay + +PACKAGE_TARNAME = create-dmg + +prefix = /usr/local +exec_prefix = ${prefix} +bindir = ${exec_prefix}/bin +datarootdir = ${prefix}/share +datadir = ${datarootdir} +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +sysconfdir = ${prefix}/etc +mandir=${datarootdir}/man +srcdir = . + +SHELL = /bin/sh +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = ${INSTALL} -m 644 + +.PHONY: install uninstall + +install: create-dmg + $(INSTALL) -d $(DESTDIR)$(prefix) + $(INSTALL) -d $(DESTDIR)$(bindir) + $(INSTALL_PROGRAM) create-dmg $(DESTDIR)$(bindir)/create-dmg + $(INSTALL) -d $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) + cp -R support $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) + cp -R examples $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) + cp -R tests $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) + +uninstall: + rm -f $(DESTDIR)$(bindir)/create-dmg + rm -rf $(DESTDIR)$(datadir)/$(PACKAGE_TARNAME) diff --git a/src/create-dmg/README b/src/create-dmg/README index 170da8de1e..bf53c3629c 100644 --- a/src/create-dmg/README +++ b/src/create-dmg/README @@ -1 +1 @@ -Content of this directory is copied from https://github.com/create-dmg/create-dmg (v1.0.8 / 0985398). +Content of this directory is copied from https://github.com/create-dmg/create-dmg (v1.1.0 / 48a4aadb768da76e7903d090f31a2db20c583942). diff --git a/src/create-dmg/README.md b/src/create-dmg/README.md index f71dae337d..01f68d5ccc 100644 --- a/src/create-dmg/README.md +++ b/src/create-dmg/README.md @@ -6,8 +6,7 @@ A shell script to build fancy DMGs. Status and contribution policy ------------------------------ -Create-dmg is maintained thanks to the contributors who send pull requests. -As of May 2020, [Andrew Janke](https://github.com/apjanke) is the primary maintainer, and (since September 2018) [@aonez](https://github.com/aonez) has helped with the maintenance. +Create-dmg is mostly maintained by [@aonez](https://github.com/aonez) and the contributors who send pull requests. The project home page is . We will merge any pull request that adds something useful and does not break existing things. @@ -15,6 +14,7 @@ We will merge any pull request that adds something useful and does not break exi If you're an active user and want to be a maintainer, or just want to chat, please ping us on Gitter at [gitter.im/create-dmg/Lobby](https://gitter.im/create-dmg/Lobby), or [email Andrew directly](floss@apjanke.net). Create-dmg was originally created by [Andrey Tarantsov](https://github.com/andreyvit). +In May 2020 [Andrew Janke](https://github.com/apjanke) helped vastly with the project. Installation ------------ @@ -63,12 +63,17 @@ All contents of source\_folder will be copied into the disk image. - **--eula \:** attach a license file to the dmg - **--rez \:** specify custom path to Rez tool used to include license file - **--no-internet-enable:** disable automatic mount© -- **--format:** specify the final image format (default is UDZO) +- **--format:** specify the final image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO) - **--add-file \ \ \ \:** add additional file or folder (can be used multiple times) - **--disk-image-size \:** set the disk image size manually to x MB - **--hdiutil-verbose:** execute hdiutil in verbose mode - **--hdiutil-quiet:** execute hdiutil in quiet mode -- **--sandbox-safe:** execute hdiutil with sandbox compatibility and do not bless +- **--bless:** bless the mount folder (deprecated, needs macOS 12.2.1 or older, [#127](https://github.com/create-dmg/create-dmg/pull/127)) +- **--codesign \:** codesign the disk image with the specified signature +- **--notarize \:** notarize the disk image (waits and staples) with the keychain stored credentials + For more information check [Apple's documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow) +- **--skip-jenkins:** skip Finder-prettifying AppleScript, useful in Sandbox and non-GUI environments, [#72](https://github.com/create-dmg/create-dmg/pull/72) +- **--sandbox-safe:** hdiutil with sandbox compatibility, do not bless and do not execute the cosmetic AppleScript - **--version:** show tool version number - **-h, --help:** display the help @@ -94,6 +99,19 @@ create-dmg \ See the `examples` folder in the source tree for more examples. +Requirements +------------ + +Nothing except a standard installation of macOS/OS X is required. + +We think this works in OS X 10.6 Snow Leopard and later. + +We'd like to keep it working in as many versions as possible, but unfortunately, we just don't have test boxes running old versions of OS X adequate to make this happen. Development and testing mostly happens in the last 3-5 years' worth of macOS releases; as of 2020, this means macOS 10.12 and later. + +But if you find a bug in an older version, go ahead and report it! We'll try to work with you to get it fixed. + +If you're running OS X 10.5 or later, you're SOL. That's just too hard to deal with in 2020. ;) + Alternatives ------------ diff --git a/src/create-dmg/create-dmg b/src/create-dmg/create-dmg index 79185a5a21..4a9715e671 100755 --- a/src/create-dmg/create-dmg +++ b/src/create-dmg/create-dmg @@ -5,7 +5,7 @@ # Bail out on any unhandled errors set -e; -CDMG_VERSION='1.0.8' +CDMG_VERSION='1.1.0' # The full path to the "support/" directory this script is using # (This will be set up by code later in the script.) @@ -23,7 +23,11 @@ ADD_FILE_TARGETS=() IMAGEKEY="" HDIUTIL_VERBOSITY="" SANDBOX_SAFE=0 +BLESS=0 SKIP_JENKINS=0 +MAXIMUM_UNMOUNTING_ATTEMPTS=3 +SIGNATURE="" +NOTARIZE="" function pure_version() { echo "$CDMG_VERSION" @@ -62,8 +66,6 @@ Options: set position of the file's icon --hide-extension hide the extension of file - --custom-icon - set position and custom icon --app-drop-link make a drop link to Applications, at location x,y --ql-drop-link @@ -73,7 +75,7 @@ Options: --no-internet-enable disable automatic mount & copy --format - specify the final image format (default is UDZO) + specify the final disk image format (UDZO|UDBZ|ULFO|ULMO) (default is UDZO) --add-file | add additional file or folder (can be used multiple times) --disk-image-size @@ -82,10 +84,14 @@ Options: execute hdiutil in verbose mode --hdiutil-quiet execute hdiutil in quiet mode + --bless + bless the mount folder (deprecated, needs macOS 12.2.1 or older) + --codesign + codesign the disk image with the specified signature + --notarize + notarize the disk image (waits and staples) with the keychain stored credentials --sandbox-safe execute hdiutil with sandbox compatibility and do not bless - --rez - use custom path to Rez tool --version show create-dmg version number -h, --help @@ -131,8 +137,6 @@ while [[ "${1:0:1}" = "-" ]]; do 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) @@ -173,12 +177,21 @@ while [[ "${1:0:1}" = "-" ]]; do shift;; --hdiutil-quiet) HDIUTIL_VERBOSITY='-quiet' - shift;; + shift;; + --codesign) + SIGNATURE="$2" + shift; shift;; + --notarize) + NOTARIZE="$2" + shift; shift;; --sandbox-safe) SANDBOX_SAFE=1 - shift;; + shift;; + --bless) + BLESS=1 + shift;; --rez) - REZ_PATH="$2" + echo "REZ is no more directly used. You can remove the --rez argument." shift; shift;; --skip-jenkins) SKIP_JENKINS=1 @@ -192,8 +205,12 @@ while [[ "${1:0:1}" = "-" ]]; do IMAGEKEY="-imagekey zlib-level=9";; UDBZ) IMAGEKEY="-imagekey bzip2-level=9";; + ULFO) + ;; + ULMO) + ;; *) - echo >&2 "Unknown format: $FORMAT" + echo >&2 "Unknown disk image format: $FORMAT" exit 1;; esac done @@ -206,6 +223,13 @@ fi DMG_PATH="$1" SRC_FOLDER="$(cd "$2" > /dev/null; pwd)" +# Argument validation checks + +if [[ "${DMG_PATH: -4}" != ".dmg" ]]; then + echo "Output file name must end with a .dmg extension. Run 'create-dmg --help' for help." + exit 1 +fi + # Main script logic SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -283,7 +307,7 @@ if [[ $SANDBOX_SAFE -eq 1 ]] && [[ ! -z "$DISK_IMAGE_SIZE_CUSTOM" ]] && [[ $DISK DISK_IMAGE_SIZE=$DISK_IMAGE_SIZE_CUSTOM fi -# Estimate the additional soruces size +# Estimate the additional sources size if [[ -n "$ADD_FILE_SOURCES" ]]; then for i in "${!ADD_FILE_SOURCES[@]}"; do SOURCE_SIZE=$(get_size "${ADD_FILE_SOURCES[$i]}") @@ -294,6 +318,12 @@ fi # Add extra space for additional resources DISK_IMAGE_SIZE=$(expr $DISK_IMAGE_SIZE + 20) +# Make sure target image size is within limits +MIN_DISK_IMAGE_SIZE=$(hdiutil resize -limits "${DMG_TEMP_NAME}" | awk 'NR=1{print int($1/2048+1)}') +if [ $MIN_DISK_IMAGE_SIZE -gt $DISK_IMAGE_SIZE ]; then + DISK_IMAGE_SIZE=$MIN_DISK_IMAGE_SIZE +fi + # Resize the image for the extra stuff hdiutil resize ${HDIUTIL_VERBOSITY} -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}" @@ -304,18 +334,18 @@ MOUNT_DIR="/Volumes/${VOLUME_NAME}" # Unmount leftover dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it) if [[ -d "${MOUNT_DIR}" ]]; then echo "Unmounting old disk image from $MOUNT_DIR..." - DEV_NAME=$(hdiutil info | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}') + DEV_NAME=$(hdiutil info | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}') hdiutil detach "${DEV_NAME}" fi echo "Mounting disk image..." echo "Mount directory: $MOUNT_DIR" -DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}') +DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | grep -E --color=never '^/dev/' | sed 1q | awk '{print $1}') echo "Device name: $DEV_NAME" if [[ -n "$BACKGROUND_FILE" ]]; then - echo "Copying background file..." + echo "Copying background file '$BACKGROUND_FILE'..." [[ -d "$MOUNT_DIR/.background" ]] || mkdir "$MOUNT_DIR/.background" cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME" fi @@ -384,7 +414,7 @@ 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 +if [[ $BLESS -eq 1 && $SANDBOX_SAFE -eq 0 ]]; then echo "Blessing started" bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}" echo "Blessing finished" @@ -397,9 +427,27 @@ if [[ -n "$VOLUME_ICON_FILE" ]]; then SetFile -a C "$MOUNT_DIR" fi +# Delete unnecessary file system events log if possible +echo "Deleting .fseventsd" +rm -rf "${MOUNT_DIR}/.fseventsd" || true + # Unmount -echo "Unmounting disk image..." -hdiutil detach "${DEV_NAME}" +unmounting_attempts=0 +until + echo "Unmounting disk image..." + (( unmounting_attempts++ )) + hdiutil detach "${DEV_NAME}" + exit_code=$? + (( exit_code == 0 )) && break # nothing goes wrong + (( exit_code != 16 )) && exit $exit_code # exit with the original exit code + # The above statement returns 1 if test failed (exit_code == 16). + # It can make the code in the {do... done} block to be executed +do + (( unmounting_attempts == MAXIMUM_UNMOUNTING_ATTEMPTS )) && exit 16 # patience exhausted, exit with code EBUSY + echo "Wait a moment..." + sleep $(( 1 * (2 ** unmounting_attempts) )) +done +unset unmounting_attempts # Compress image echo "Compressing disk image..." @@ -409,15 +457,35 @@ rm -f "${DMG_TEMP_NAME}" # Adding EULA resources if [[ -n "${EULA_RSRC}" && "${EULA_RSRC}" != "-null-" ]]; then echo "Adding EULA resources..." - REZ_ARG="" - if [[ -n "${REZ_PATH}" && "${REZ_PATH}" != "-null-" ]]; then - REZ_ARG="--rez ${REZ_PATH}" + # + # Use udifrez instead flatten/rez/unflatten + # https://github.com/create-dmg/create-dmg/issues/109 + # + # Based on a thread from dawn2dusk & peterguy + # https://developer.apple.com/forums/thread/668084 + # + EULA_RESOURCES_FILE=$(mktemp -t createdmg.tmp.XXXXXXXXXX) + EULA_FORMAT=$(file -b ${EULA_RSRC}) + if [[ ${EULA_FORMAT} == 'Rich Text Format data'* ]] ; then + EULA_FORMAT='RTF ' else - xcode_path=$(xcode-select -p) - default_rez="$xcode_path/Tools/Rez" - REZ_ARG="--rez ${default_rez}" + EULA_FORMAT='TEXT' fi - "${CDMG_SUPPORT_DIR}/licenseDMG.py" "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}" ${REZ_ARG} + # Encode the EULA to base64 + # Replace 'openssl base64' with 'base64' if Mac OS X 10.6 support is no more needed + # EULA_DATA="$(base64 -b 52 "${EULA_RSRC}" | sed s$'/^\(.*\)$/\t\t\t\\1/')" + EULA_DATA="$(openssl base64 -in "${EULA_RSRC}" | tr -d '\n' | awk '{gsub(/.{52}/,"&\n")}1' | sed s$'/^\(.*\)$/\t\t\t\\1/')" + # Fill the template with the custom EULA contents + eval "cat > \"${EULA_RESOURCES_FILE}\" <&1 >/dev/null)" + if [ $? -eq 0 ]; then + echo "The disk image is now codesigned" + else + echo "The signature seems invalid${NC}" + exit 1 + fi +fi + +if [[ -n "${NOTARIZE}" && "${NOTARIZE}" != "-null-" ]]; then + echo "Notarization started" + xcrun notarytool submit "${DMG_DIR}/${DMG_NAME}" --keychain-profile "${NOTARIZE}" --wait + echo "Stapling the notarization ticket" + staple="$(xcrun stapler staple "${DMG_DIR}/${DMG_NAME}")" + if [ $? -eq 0 ]; then + echo "The disk image is now notarized" + else + echo "$staple" + echo "The notarization failed with error $?" + exit 1 + fi +fi + # All done! echo "Disk image done" exit 0 diff --git a/src/create-dmg/support/LICENSE-licenseDMG-pyhacker b/src/create-dmg/support/LICENSE-licenseDMG-pyhacker deleted file mode 100644 index 94b7fe934e..0000000000 --- a/src/create-dmg/support/LICENSE-licenseDMG-pyhacker +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015-2019 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. \ No newline at end of file diff --git a/src/create-dmg/support/README b/src/create-dmg/support/README deleted file mode 100644 index 0ecb9eefce..0000000000 --- a/src/create-dmg/support/README +++ /dev/null @@ -1,7 +0,0 @@ -# create-dmg support files - -## licenseDMG.py - -The licenseDMG.py script is taken from Jared Hobbs' pyhacker library, at . - -See LICENSE-licenseDMG-pyhacker for licensing details. \ No newline at end of file diff --git a/src/create-dmg/support/eula-resources-template.xml b/src/create-dmg/support/eula-resources-template.xml new file mode 100644 index 0000000000..7495632b0d --- /dev/null +++ b/src/create-dmg/support/eula-resources-template.xml @@ -0,0 +1,105 @@ + + + + + LPic + + + Attributes + 0x0000 + Data + + AAAAAgAAAAAAAAAAAAQAAA== + + ID + 5000 + Name + + + + STR# + + + Attributes + 0x0000 + Data + + AAYNRW5nbGlzaCB0ZXN0MQVBZ3JlZQhEaXNhZ3JlZQVQcmludAdT + YXZlLi4ueklmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0 + aGlzIGxpY2Vuc2UsIGNsaWNrICJBZ3JlZSIgdG8gYWNjZXNzIHRo + ZSBzb2Z0d2FyZS4gIElmIHlvdSBkbyBub3QgYWdyZWUsIHByZXNz + ICJEaXNhZ3JlZS4i + + ID + 5000 + Name + English buttons + + + Attributes + 0x0000 + Data + + AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4u + e0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxp + Y2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29m + dHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlz + YWdyZWUiLg== + + ID + 5002 + Name + English + + + ${EULA_FORMAT} + + + Attributes + 0x0000 + Data + +${EULA_DATA} + + ID + 5000 + Name + English + + + TMPL + + + Attributes + 0x0000 + Data + + E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioq + TFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZz + ZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQq + KioqTFNURQ== + + ID + 128 + Name + LPic + + + styl + + + Attributes + 0x0000 + Data + + AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAA + AAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA= + + ID + 5000 + Name + English + + + + diff --git a/src/create-dmg/support/licenseDMG.py b/src/create-dmg/support/licenseDMG.py deleted file mode 100755 index f6eaf98395..0000000000 --- a/src/create-dmg/support/licenseDMG.py +++ /dev/null @@ -1,200 +0,0 @@ -#! /usr/bin/env python -""" -This script adds a license file to a DMG. Requires Xcode and a plain ascii text -license file or an RTF license file. -Obviously only runs on a Mac. - -Copyright (C) 2011-2019 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 unicode_literals -from subprocess import check_call, check_output, call, CalledProcessError -import argparse -import logging as logger -import os -import sys -import tempfile - -logger.basicConfig(format='%(message)s', level=logger.DEBUG) - - -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 escape(s): - return s.strip().replace('\\', '\\\\').replace('"', '\\"') - - -def main(options): - dmg_file = options.dmg_file - output = options.output or dmg_file - license = options.license_file - if dmg_file != output: - check_call(['cp', dmg_file, output]) - dmg_file = output - with mktemp('.') as tmp_file: - with open(tmp_file, '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_file: - kind = 'RTF ' if license.lower().endswith('.rtf') else 'TEXT' - f.write('data \'{}\' (5000, "English") {{\n'.format(kind)) - - for line in l_file: - if len(line) < 1000: - f.write(' "{}\\n"\n'.format(escape(line))) - else: - for liner in line.split('.'): - f.write(' "{}. \\n"\n'.format(escape(liner))) - 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""") - call(['hdiutil', 'unflatten', '-quiet', dmg_file]) - ret = check_call([options.rez, '-a', tmp_file, '-o', dmg_file]) - call(['hdiutil', 'flatten', '-quiet', dmg_file]) - if options.compression is not None: - tmp_dmg = '{}.temp.dmg'.format(dmg_file) - check_call(['cp', dmg_file, tmp_dmg]) - os.remove(dmg_file) - args = ['hdiutil', 'convert', tmp_dmg, '-quiet', '-format'] - if options.compression == 'bz2': - args.append('UDBZ') - elif options.compression == "gz": - args.extend(['UDZO', '-imagekey', 'zlib-devel=9']) - args.extend(['-o', dmg_file]) - check_call(args) - os.remove(tmp_dmg) - if ret == 0: - logger.info("Successfully added license to '{}'".format(dmg_file)) - else: - logger.error("Failed to add license to '{}'".format(dmg_file)) - - -if __name__ == '__main__': - try: - rez_path = check_output( - ['xcrun', '--find', 'Rez'], - ).strip().decode('utf-8') - except CalledProcessError: - rez_path = '/Library/Developer/CommandLineTools/usr/bin/Rez' - parser = argparse.ArgumentParser( - description="""\ -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_argument( - 'dmg_file', - help='the path to the dmg file which will receive the license', - ) - parser.add_argument( - 'license_file', - help='the path to the plain ascii or RTF license file; for RTF files, ' - 'the file must use a .rtf extension', - ) - parser.add_argument( - '--rez', - '-r', - action='store', - default=rez_path, - help='the path to the Rez tool; defaults to %(default)s', - ) - parser.add_argument( - '--compression', - '-c', - action='store', - choices=('bz2', 'gz'), - default=None, - help='optionally compress dmg using specified compression type; ' - 'choices are bz2 and gz', - ) - parser.add_argument( - '--output', - '-o', - action='store', - default=None, - help='specify an output DMG file; if not given, the license will be ' - 'directly applied to the input DMG file', - ) - options = parser.parse_args() - if not os.path.exists(options.rez): - logger.error('Failed to find Rez at "{}"!\n'.format(options.rez)) - parser.print_usage() - sys.exit(1) - main(options)