Creating a beautiful .dmg image for a program in [Mac] OS X

  • Tutorial
Greetings to the honorable inhabitants of Habr!

Today I will tell you how to beautifully present the installer of my program to the user. Surely everyone who uses not only programs from the AppStore has come across beautiful .dmg disk images, like Adium, for example. Such an image is, so to speak, an interactive installer, in which a clear hint is given: drag the icon here. Everything is extremely clear and simple.

Of course, for a seasoned Mac OS player, a zip archive will do, but everyone loves it to be beautiful and convenient. So, we, dear readers, will take care today of creating such a beautiful disk image for our (well, or someone else's) program.

You can, of course, do it all manually, but this is not_our_method ™, so we will write a shell script to automate this process. Automation is also useful to us in the case of commissioning a build server, then this server will not only collect the program from the source, but also make a disk image for distribution.

If you don’t need to know how such a script works, but only need a tool , at the end there is a link to a very universal script, ready for use.

Action Plan in Brief:

  • Copy the bundle to a temporary directory
  • Create a disk image from a temporary directory that is readable and writable
  • Mount the resulting image
  • Customize the look of the image using AppleScript: create a symlink for / Applications, set the background and location of elements
  • Set icon for disk image
  • Unmount image
  • Convert image to compressed readonley
  • Done! You can upload it to the site or in Sparkle castes.

IMPORTANT! The script (more precisely, its part involving AppleScript) will not work properly if TotalFinder (or a similar thing) is installed , and this can lead to unpredictable consequences! Seriously.

Now, in order on all counts. Link to the finished script at the end of the article.

First, we need to determine what we will pack and what image name we will get at the end. To do this, we write in the script like this:

TMP_DIR="./tmp"
APP_BUNDLE_NAME="MyGreatApplication.app"
APP_VERSION=`/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "${APP_BUNDLE_NAME}/Contents/Info.plist"`
APP_BUILD_VERSION=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${APP_BUNDLE_NAME}/Contents/Info.plist"`
DMG_NAME_BASE=${APP_BUNDLE_NAME%.*}
DMG_NAME_SUFFIX=" ${APP_VERSION}.${APP_BUILD_VERSION}"
DMG_NAME="${DMG_NAME_BASE}${DMG_NAME_SUFFIX}.dmg"
VOL_NAME=${APP_BUNDLE_NAME%.*}

Here we set the name of the bundle to be packaged, and read the version and build of the program from it using the PlistBuddy utility and set them as a suffix for the file name of the future disk image. When mounting, we specify the name of the image simply as the name of the bundle without an extension.

To create a temporary disk image, use the hdiutil program, calling it with the following parameters

hdiutil create -ov -srcfolder ${TMP_DIR} -format UDRW -volname "${VOL_NAME}" "${DMG_NAME_TMP}"

Here, the parameters TMP_DIR, VOL_NAMEand DMG_NAME_TMPare the temporary directory, the name of the image (which will be displayed when mounting) and the name of the temporary .dmg file, respectively. The parameter -format UDRWindicates the type of image: UDIF image for reading and writing. We need the ability to write to disk to customize the appearance of the mounted image. The -ov parameter instructs the utility to overwrite the image if we did not delete it last time.

Now mount the resulting image (and save the device name for future use):

device=$(hdiutil attach -readwrite -noverify -noautoopen ${DMG_NAME_TMP} | egrep '^/dev/' | sed 1q | awk '{print $1}')

Everything is quite simple here: we attach the read and write permissions, we do not open the finder window automatically, and then we get the type name /dev/disk2using egrep, sed and awk hdiutil output.

Now copy the background image and icon to the image, which, for example, are in the resources of our bundle (although, of course, they can be taken from anywhere else):

BG_FOLDER="/Volumes/${VOL_NAME}/.background"
mkdir "${BG_FOLDER}"
cp "${APP_BUNDLE_NAME}/Contents/Resources/${BG_IMG_NAME}" "${BG_FOLDER}/"
ICON_FOLDER="/Volumes/${VOL_NAME}"
cp "${APP_BUNDLE_NAME}/Contents/Resources/${VOL_ICON_NAME}" "${ICON_FOLDER}/.VolumeIcon.icns"

Next, we need to customize the mounted image, we can do this through the Finder manually, but we will do it more cunningly: make Finder do everything automatically through AppleScript. To call such scripts from the shell, there is an osascript utility , which we will submit such a workpiece to the input:

APPLESCRIPT="
tell application \"Finder\"
	tell disk \"${VOL_NAME}\"
		open
		-- Setting view options
		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 {${WINDOW_LEFT}, ${WINDOW_TOP}, ${WINDOW_RIGHT}, ${WINDOW_BOTTOM}}
		set theViewOptions to the icon view options of container window
		set arrangement of theViewOptions to not arranged
		set icon size of theViewOptions to 72
		-- Settings background
		set background picture of theViewOptions to file \".background:${BG_IMG_NAME}\"
		-- Adding symlink to /Applications
		make new alias file at container window to POSIX file \"/Applications\" with properties {name:\"Applications\"}
		-- Reopening
		close
		open
		-- Rearranging
		set the position of item \"Applications\" to {${APPS_X}, ${APPS_Y}}
		set the position of item \"${APP_BUNDLE_NAME}\" to {${BUNDLE_X}, ${BUNDLE_Y}}
		-- Updating and sleeping for 5 secs
		update without registering applications
		delay 5
	end tell
end tell
"
echo "$APPLESCRIPT" | osascript

Of course, instead of VOL_NAMEand other things, our shell-script will substitute pre-prepared lines. This AppleScript tells the fader to open our mounted drive, set the necessary display options for it: remove the address and status bar, set the appearance of the “icon”, set the window size, icon size. All these parameters will be saved in the .DS_Store file . Next, we set the background image copied earlier. Then - create a symlink (alias in AS terms) for / Applications, re-open the window for applying the changes. Now we set the position of the program icons and the created symlink, update and sleep 5 seconds for reliability.

Now set the icon for the image through the SetFile utility:

SetFile -c icnC "${ICON_FOLDER}/.VolumeIcon.icns"
SetFile -a C "${ICON_FOLDER}"

Next, set the necessary rights for the disk image, synchronize (twice for reliability) and extract the image:

chmod -Rf go-w /Volumes/"${VOL_NAME}"
sync
sync
hdiutil detach ${device}

That's it, now you can make the final disk image!

hdiutil convert "${DMG_NAME_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_NAME}"

Here we use hdiutil to convert a temporary image to UDZO format (UDIF compressed) with compression level 9 (best). And at the output we get a disk image for distribution that has an attractive appearance!

The full script is included in the github repository . I note that in the full version of the script there are a lot of possible input parameters (parsing of which greatly increases its size), there is no hardcode at all (well, except for the default parameters). Well, the picture at the beginning of the post is obtained by the following call to my script:

$ make_dmg.sh -V -b habr_logo_big.png -i habr_icon.icns -s "800:500" -c 535:345:253:345 "Hello Habr.app"
Enabling version info in resulting dmg
Setting background to habr_logo_big.png
Setting icon to habr_icon.icns
Setting window size to 800:500
Setting coordinates to 535:345:253:345
Bundle name set to Hello Habr.app
Defaulting dmg volume name to Hello Habr
*** Copying Hello Habr.app to the temporary dir... done!
*** Creating temporary dmg disk image.........
created: /Users/silvansky/Projects/habr_demo_dmg/Hello Habr_tmp.dmg
*** Mounting temporary image... done! (device /dev/disk1)
*** Sleeping for 5 seconds... done!
*** Setting style for temporary dmg image...
    * Copying background image... done!
    * Copying volume icon... done!
    * Setting volume icon... done!
    * Executing applescript for further customization... done!
*** Converting tempoprary dmg image in compressed readonly final image... 
    * Changing mode and syncing...
chmod: /Volumes/Hello Habr/.Trashes: Permission denied
    * Detaching /dev/disk1...
"disk1" unmounted.
"disk1" ejected.
    * Converting...
Готовлюсь к созданию образа…
Читаю Driver Descriptor Map (DDM : 0)…
   (CRC32 $AF5ACFAD: Driver Descriptor Map (DDM : 0))
Читаю Apple (Apple_partition_map : 1)…
   (CRC32 $92261EDC: Apple (Apple_partition_map : 1))
Читаю disk image (Apple_HFS : 2)…
...................................................................................................................................................................................
   (CRC32 $F59F12B2: disk image (Apple_HFS : 2))
Читаю  (Apple_Free : 3)…
....................................................................................................................................................................................
   (CRC32 $00000000:  (Apple_Free : 3))
Добавляю ресурсы…
....................................................................................................................................................................................
Прошло: 804.502ms
Размер файла: 4132028 байт, сумма: CRC32 $AFE83FC5
Обработано секторов: 13243, 10041 сжато
Скорость: 6.1 Mб/с
Сохранений: 39.1 %
created: /Users/silvansky/Projects/habr_demo_dmg/Hello Habr 1.0.1.dmg
done!
*** Removing temporary image... done!
*** Cleaning up temp folder... done!
*** Everything done. DMG disk image is ready for distribution.

Well, now you can write your own script for such purposes or use mine, quite universal. Successful projects!

Also popular now: