views:

9248

answers:

8

I need to create a nice installer for a Mac application. I want it to be a disk image (DMG), with a predefined size, layout and background image.

I need to do this programmatically in a script, to be integrated in an existing build system (more of a pack system really, since it only create installers. The builds are done separately).

I already have the DMG creation done using "hdiutil", what I haven't found out yet is how to make an icon layout and specify a background bitmap.

+17  A: 

Don't go there. As a long term Mac developer, I can assure you, no solution is really working well. I tried so many solutions, but they are all not too good. I think the problem is that Apple does not really document the meta data format for the necessary data.

Here's how I'm doing it for a long time, very successfully:

  1. Create a new DMG, writeable(!), big enough to hold the expected binary and extra files like readme (sparse might work).

  2. Mount the DMG and give it a layout manually in Finder or with whatever tools suits you for doing that (see FileStorm link at the bottom for a good tool). The background image is usually an image we put into a hidden folder (".something") on the DMG. Put a copy of your app there (any version, even outdated one will do). Copy other files (aliases, readme, etc.) you want there, again, outdated versions will do just fine. Make sure icons have the right sizes and positions (IOW, layout the DMG the way you want it to be).

  3. Unmount the DMG again, all settings should be stored by now.

  4. Write a create DMG script, that works as follows:

    • It copies the DMG, so the original one is never touched again.
    • It mounts the copy.
    • It replaces all files with the most up to date ones (e.g. latest app after build). You can simply use mv or ditto for that on command line. Note, when you replace a file like that, the icon will stay the same, the position will stay the same, everything but the file (or directory) content stays the same (at least with ditto, which we usually use for that task). You can of course also replace the background image with another one (just make sure it has the same dimensions).
    • After replacing the files, make the script unmount the DMG copy again.
    • Finally call hdiutil to convert the writable, to a compressed (and such not writable) DMG.

This method may not sound optimal, but trust me, it works really well in practice. You can put the original DMG (DMG template) even under version control (e.g. SVN), so if you ever accidentally change/destroy it, you can just go back to a revision where it was still okay. You can add the DMG template to your Xcode project, together with all other files that belong onto the DMG (readme, URL file, background image), all under version control and then create a target (e.g. external target named "Create DMG") and there run the DMG script of above and add your old main target as dependent target. You can access files in the Xcode tree using ${SRCROOT} in the script (is always the source root of your product) and you can access build products by using ${BUILT_PRODUCTS_DIR} (is always the directory where Xcode creates the build results).

Result: Actually Xcode can produce the DMG at the end of the build. A DMG that is ready to release. Not only you can create a relase DMG pretty easy that way, you can actually do so in an automated process (on a headless server if you like), using xcodebuild from command line (automated nightly builds for example).

Regarding the initial layout of the template, FileStorm is a good tool for doing it. It is commercial, but very powerful and easy to use. The normal version is less than $20, so it is really affordable. Maybe one can automate FileStorm to create a DMG (e.g. via AppleScript), never tried that, but once you have found the perfect template DMG, it's really easy to update it for every release.

Mecki
I've already discarded the idea of doing it this way, for several reasons. Here are two of them: the contents of the installers will vary with product, and we want to rely only on software installed on the pack machines and scripts - a single, minimal, manual routine for adding new products.
Ludvig A Norin
This is the same scenario as we have. We have more than a dozen of products; each has an entirely different DMG. Creating one template DMG per product is one time only task and takes you a couple of minutes. And what do you mean by "installer"? PKG/MPKG install packages?
Mecki
It's not the same scenario. We add products often, with short notice. The minimal manual routine means running a script giving the product a name and a few other attributes. There are reasons beyond this as well that made us make the decision not to use that kind of solution.
Ludvig A Norin
We have separated the pack process from the build process, because it is done by different people at different times. The pack process creates installers for Windows, Windows CE, Symbian, AIX, Linux and Solaris as well.
Ludvig A Norin
You are probably refering to hdiutil, not hdutil.
Ivan Vučica
@Ivan: You are right - fixing reply. Sorry about that typo
Mecki
+5  A: 

For those of you that are interested in this topic, I should mention how I create the DMG:

hdiutil create XXX.dmg -volname "YYY" -fs HFS+ -srcfolder "ZZZ"

where

XXX == disk image file name (duh!)
YYY == window title displayed when DMG is opened
ZZZ == Path to a folder containing the files that will be copied into the DMG
Ludvig A Norin
+3  A: 

I've found a nifty little script named buildDMG which seems to address this exact concern. Although I've just started trying to integrate it into my Xcode projects, it seems to work rather well.

@Mecki, I like your approach, but would really prefer to use a simple script like this, unless it totally wrecks things for the end user. Is there any danger in using the above script?

Nik Reiman
This perl script seems to do exactly one thing: creating a dmg from a folder. That can be done in a single command as described in this thread. The buildDMG script does not handle images and icon positioning.
Ludvig A Norin
+6  A: 

We have a little Bash script called create-dmg that builds fancy DMGs with custom backgrounds, custom icon positioning and volume name. Feel free to use it however you like.

We execute some AppleScript to resize the window and position the icons property. It works just fine. It was borrowed from Adium's build process, so it seems to work fine for them too.

I don't know how create-dmg compares to buildDMG, but buildDMG seems to have the last update released 5 years ago.

Andrey Tarantsov
Looks like a Bash script to me... ;-)
Demi
@Demi right, that was a typo :)
Andrey Tarantsov
@Andrey - edited.
Demi
@Demi thanks a lot. I did not even think about fixing the typo.
Andrey Tarantsov
+10  A: 

After lots of research, I've come up with this answer, and I'm hereby putting it here as an answer for my own question, for reference:

1 - Make sure that "Enable access for assistive devices" is checked in System Preferences>>Universal Access. It is required for the AppleScript to work. You may have to reboot after this change (it doesn't work otherwise on Mac OS X Server 10.4).

2 - Create a R/W DMG. It must be larger than the result will be. In this example, the bash variable "size" contains the size in Kb and the contents of the folder in the "source" bash variable will be copied into the DMG:

hdiutil create -srcfolder "${source}" -volname "${title}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${size}k pack.temp.dmg

3 - Mount the disk image, and store the device name (you might want to use sleep for a few seconds after this operation):

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

4 - Store the background picture (in PNG format) in a folder called ".background" in the DMG, and store its name in the "backgroundPictureName" variable.

5 - Use AppleScript to set the visual styles (name of .app must be in bash variable "applicationName", use variables for the other properties as needed):

echo '
   tell application "Finder"
     tell disk "'${title}'"
           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 72
           set background picture of theViewOptions to file ".background:'${backgroundPictureName}'"
           make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
           set position of item "'${applicationName}'" of container window to {100, 100}
           set position of item "Applications" of container window to {375, 100}
           update without registering applications
           delay 5
           eject
     end tell
   end tell
' | osascript

6 - Finialize the DMG by setting permissions properly, compressing and releasing it:

chmod -Rf go-w /Volumes/"${title}"
sync
sync
hdiutil detach ${device}
hdiutil convert "/pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "${finalDMGName}"
rm -f /pack.temp.dmg

On Snow Leopard, the above applescript will not set the icon position correctly - it seems to be a Snow Leopard bug. One workaround is to simply call close/open after setting the icons, i.e.:

..
set position of item "'${applicationName}'" of container window to {100, 100}
set position of item "Applications" of container window to {375, 100}
close
open
Ludvig A Norin
Excellent. Two questions about that: 1. You recommend `sleep` after mounding the image. How long? Isn’t it possible to deterministically wait for the completion of the process? Same for `delay 5` in your AppleScript. I’m always wary of such arbitrary wait times, having had some very bad experience with them.2. In your step 6 you call `sync` twice – why?
Konrad Rudolph
I haven't found any way to deterministically wait for the completion of the 'update without registering applications' command. I am not sure sleep is needed after "hdiutil attach", you'll have to check the documentation (man hdiutil). Sync should only be needed to be called once, I do it twice out of old habit since the good old SunOS days.
Ludvig A Norin
A: 

Excellent Ludvig, thank you for sharing. Works like a charm. I don't need the delay 5 (OS X 10.5.8), and the background image can also be a jpeg (and, probably, a gif).

Paul Sanders
A: 

.DS_Store files stores windows settings in Mac. Windows settings include the icons layout, the window background, the size of the window, etc. The .DS_Store file is needed in creating the window for mounted images to preserve the arrangement of files and the windows background.

Once you have .DS_Store file created, you can just copy it to your created installer (DMG).

hor10zs
+1  A: 

My app, DropDMG, is an easy way to create disk images with background pictures, icon layouts, custom volume icons, and software license agreements. It can be controlled from a build system via the "dropdmg" command-line tool or AppleScript. If desired, the picture and license RTF files can be stored under your version control system.

Michael Tsai
That looks very interesting.
Konrad Rudolph