« Old svn client == bad | Main | Bumped from C4 »

September 28, 2006

DMG creation tips and tricks

Warning: This post is longer than my usual. You may want coffee. The focus of this post is how to make a DMG image with a background in an automated fashion.

About DMG

For those who are reading this who are not aware of it, DMG (Disk Image) is the format commonly used to distribute software on OS X. There are other formats, the more traditional like tar.gz/zip/.sit. For more information specifically about how to distribute software as a DMG, you'll want to read Peter Hosey's how to distribute your software (and his semi-in-depth overview of compression format usage on OS X).

Motivation/History
Quite a while back Adium and Growl both were shipped in dmgs created in a product called FileStorm. At the time, it was buggy and hard to use. It would crash a lot, and in the end was just a pain in the butt.

But we used it anyhow. For a while.

However, it's not automated. It also required that everyone have Filestorm installed and licensed, so for instance only one person could make the dmg on the Growl team (I had the Adium license file), and only one or two on the Adium team could use it.

Everyone jokes about replacing random things with a small shell script, but we pretty much did that. Except this uses Makefiles, Shell, and AppleScript.

So, before I begin writing about how to do this, I would like to state the process before and after the creation of this build system in order to release Growl.

This is the before:

  1. Change directories into the growlsourceco
  2. svn up to ensure there are no new changes
  3. Open Xcode and edit version numbers OR edit version numbers without opening Xcode
  4. Build Growl
  5. Open the GrowlTunes Project and build it
  6. Open the GrowlMail Project and build it
  7. Open the Beep-Cocoa Project and build it
  8. Open the Beep-Carbon Project and build it
  9. Open the GrowlDict Project and build it
  10. Open the growlnotify Project and build it
  11. Once Growl and the Extras are built, open Filestorm
  12. Go into Filestorm and ensure the paths are correct in the dmg project
  13. Make the dmg
  14. Test the dmg and upload

This is the process when making a dmg with the build system that was created:

  1. Change directories into the growlsourceco
  2. Open Xcode and edit version numbers OR edit version numbers without opening Xcode
  3. cd Release
  4. type make
  5. Test the dmg and upload

There's actually a lot more to the process we used when we used Filestorm, but as I felt that more bullet points would only lead to excessiveness. And besides, you get the point.

Anyhow, on to the nitty gritty. This is how to make your very own DMG Creation System. I am going to run down how the Growl system works, since it is more complicated, and then run through Adium's and Perian's systems. As each application has separate needs, I'll explain decisions behind each item along the way.

Getting Started or The Growl Release System

So with Growl we have a lot of different needs for anything that would build the dmg+projects:

Pretty early on a script or Makefile seems appropriate here, and we ended up with a Makefile+Applescript, so here's the long and short of the interesting bits. Here is a screenshot of the Growl disk image when mounted:

Growldmgscreenie

We can see from this image the requirements listed above., and more. It looks like a mess because it is, but that's more the fault of myself than anything else. For instance, let's look at the Perian DMG for the .5b4 release that I'm building now:

Perianb4Dmg

As you can see, the disk image can be made to be either randomized on disk or pretty strict. Let's move on to how to actually do all of this.

Top level Makefile

The first part you have to know about is that the top level directory of your source repo needs to have a file called Makefile in it. For a simple project you should plan ahead, and for a complex one you will appreciate this. At the very end of this blog post I will post the entire dmg making system, so if you'd like you can skip this for now. However, It will not be filled with comments, it will only have what is currently in the Adium/Growl/Perian build systems.

Basically the end result is to get a Makefile at the top level of the source tree that builds your project/targets how you like.

The release system also needs a folder called (unsurprisingly) Release, which will hold the rest of your release system items.

Here is the entire top level Makefile for the .7.4 release of Growl, with explanations along the way:

PREFIX?=
PREFERENCEPANES_DIR=$(PREFIX)/Library/PreferencePanes

Since Growl is a PrefPane, it lives in either ~/Library/PreferencePanes or /Librayr/PreferencePanes

FRAMEWORKS_DIR=$(PREFIX)/Library/Frameworks
GROWL_PREFPANE=Growl.prefPane
GROWL_FRAMEWORK=Growl.framework

This portion sets the framework directory and then the prefpane and framework names

BUILD_DIR=build
GROWL_HELPER_APP=$(PREFERENCEPANES_DIR)/$(GROWL_PREFPANE)/Contents/Resources/GrowlHelperApp.app
HEADERDOC_DIR=Docs/HeaderDoc

Setting some more stuff for use later in the script.

#DEFAULT_BUILDSTYLE=Deployment
DEFAULT_BUILDSTYLE=Development
BUILDSTYLE?=$(DEFAULT_BUILDSTYLE)

Here's where the fun stuff starts to happen. For those who don't follow this, basically this will allow you to change the build style by uncommenting one line and commenting another. If you happen to have 5 build styles, you could list 4 commented out and one uncommented, and easily switch between the whole lot of them.

CP=ditto --rsrc
RM=rm

This bit should be obvious based on the previous stuff.

.PHONY : all growl growlhelperapp growlapplicationbridge growlapplicationbridge-withinstaller frameworks clean install

all: frameworks
xcodebuild -alltargets -buildstyle $(BUILDSTYLE) build

growl:
xcodebuild -target Growl -buildstyle $(BUILDSTYLE) build

growlhelperapp:
xcodebuild -target GrowlHelperApp -buildstyle $(BUILDSTYLE) build

frameworks: growlapplicationbridge growlapplicationbridge-withinstaller

growlapplicationbridge:
xcodebuild -target Growl.framework -buildstyle $(BUILDSTYLE) build

growlapplicationbridge-withinstaller:
xcodebuild -target Growl-WithInstaller.framework -buildstyle $(BUILDSTYLE) build

clean:
xcodebuild -alltargets clean

All of this builds Growl and the frameworks or cleans it. Note the use of $(BUILDSTYLE) here.

install:
killall GrowlHelperApp || true
-$(RM) -rf $(PREFERENCEPANES_DIR)/$(GROWL_PREFPANE) $(FRAMEWORKS_DIR)/$(GROWL_FRAMEWORK)
$(CP) $(BUILD_DIR)/$(GROWL_PREFPANE) $(PREFERENCEPANES_DIR)/$(GROWL_PREFPANE)
open $(GROWL_HELPER_APP)

install-growl:
killall GrowlHelperApp || true
-$(RM) -rf $(PREFERENCEPANES_DIR)/$(GROWL_PREFPANE)
$(CP) $(BUILD_DIR)/$(GROWL_PREFPANE) $(PREFERENCEPANES_DIR)/$(GROWL_PREFPANE)
open $(GROWL_HELPER_APP)

headerdoc:
rm -rf $(HEADERDOC_DIR)
headerdoc2html -C -o $(HEADERDOC_DIR) Common/Source/GrowlDefines.h Common/Source/GrowlDefinesInternal.h Framework/Source/*.h Common/Source/GrowlPathUtil.h Display\ Plugins/GrowlDisplayProtocol.h Framework/Source/Growl.hdoc
gatherheaderdoc $(HEADERDOC_DIR)

uninstall:
killall GrowlHelperApp || true
@if [ -d "/Library/PreferencePanes/Growl.prefPane" ]; then \
echo mv "/Library/PreferencePanes/Growl.prefPane" "$(HOME)/.Trash"; \
mv "/Library/PreferencePanes/Growl.prefPane" "$(HOME)/.Trash"; \
elif [ -d "$(HOME)/Library/PreferencePanes/Growl.prefPane" ]; then \
echo mv "$(HOME)/Library/PreferencePanes/Growl.prefPane" "$(HOME)/.Trash"; \
mv "$(HOME)/Library/PreferencePanes/Growl.prefPane" "$(HOME)/.Trash"; \
fi

@if [ -d "/Library/Frameworks/GrowlAppBridge.framework" ]; then \
echo mv "/Library/Frameworks/GrowlAppBridge.framework" "$(HOME)/.Trash"; \
mv "/Library/Frameworks/GrowlAppBridge.framework" "$(HOME)/.Trash"; \
elif [ -d "$(HOME)/Library/Frameworks/GrowlAppBridge.framework" ]; then \
echo mv "$(HOME)/Library/Frameworks/GrowlAppBridge.framework" "$(HOME)/.Trash"; \
mv "$(HOME)/Library/Frameworks/GrowlAppBridge.framework" "$(HOME)/.Trash"; \
fi

The rest of this should be decently self explanatory. But, basically, the rest covers how to install and uninstall Growl. It's more convenience than anything else for developers, and is not required by any means to make this work.

This Makefile should be different for every project, but overall it should be pretty easy to modify this one for your needs. Besides, the fun bit is inside the Release directory anyhow. Let's move on to that.

The Release Directory, of Doom

Heh, DOOM. So ya, the release directory is what controls everything else here. Let's take a look inside:

growl-0.7/Release chris$ ls -1
Artwork
Get more styles.webloc
Growl Developer Documentation.webloc
Growl Documentation.webloc
Growl version history for developers.webloc
Growl version history.webloc
GrowlMail
GrowlSafari
Makefile
Uninstall Growl.app
dmg_growl.scpt
dmg_sdk.scpt
make-diskimage.sh

Specifically, what's required here to build a dmg with artwork is:

Artwork
Makefile
dmg_growl.scpt
make-diskimage.sh

This system only requires these 4 items and the top level to work. So let's get to the main Makefile, and then cover the other bits after that.

Release Makefile

SRC_DIR=..
BUILD_DIR=build
GROWL_DIR=$(BUILD_DIR)/Growl
SDK_DIR=$(BUILD_DIR)/SDK
RELEASE_NAME=Growl-0.7.5
RELEASE_DIRNAME=$(RELEASE_NAME)
RELEASE_DIR=$(BUILD_DIR)/$(RELEASE_DIRNAME)
CONFIGURATION=Deployment
BUILDFLAGS="CONFIGURATION=$(CONFIGURATION)"
PRODUCT_DIR=$(shell defaults read com.apple.Xcode PBXProductDirectory 2> /dev/null)

This bit here is where you start changing things. Specially, only 2 items, the CONFIGURATION and RELEASE_NAME. There's not much more to it here, as with before we have some settings that tell the script what things are. Some stuff is dynamically set, etc.

ifeq ($(strip $(PRODUCT_DIR)),)
GROWL_BUILD_DIR=$(SRC_DIR)/build/$(CONFIGURATION)
GROWLCTL_BUILD_DIR=$(SRC_DIR)/Extras/growlctl/build/$(CONFIGURATION)
GROWLDICT_BUILD_DIR=$(SRC_DIR)/Extras/GrowlDict/build/$(CONFIGURATION)
GROWLNOTIFY_BUILD_DIR=$(SRC_DIR)/Extras/growlnotify/build/$(CONFIGURATION)
GROWLTUNES_BUILD_DIR=$(SRC_DIR)/Extras/GrowlTunes/build/$(CONFIGURATION)
HARDWAREGROWLER_BUILD_DIR=$(SRC_DIR)/Extras/HardwareGrowler/build/$(CONFIGURATION)
GROWLMAIL_BUILD_DIR=$(SRC_DIR)/Extras/GrowlMail/build/$(CONFIGURATION)
GROWLSAFARI_BUILD_DIR=$(SRC_DIR)/Extras/GrowlSafari/build/$(CONFIGURATION)
RAWRJOUR_BUILD_DIR=$(SRC_DIR)/Extras/Rawr-jour/build/$(BUILDSTYLE)
else
TARGET_BUILD_DIR=$(PRODUCT_DIR)/$(CONFIGURATION)
GROWL_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLCTL_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLDICT_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLNOTIFY_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLTUNES_BUILD_DIR=$(TARGET_BUILD_DIR)
HARDWAREGROWLER_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLMAIL_BUILD_DIR=$(TARGET_BUILD_DIR)
RAWRJOUR_BUILD_DIR=$(TARGET_BUILD_DIR)
GROWLSAFARI_BUILD_DIR=$(TARGET_BUILD_DIR)
endif

Growl has a whole list of Extras that we ship (growlctl, GrowlDict, growlnotify, GrowlTunes, HardwareGrowler, GrowlMail, RawrJour, GrowlSafari) along with Growl, so we have to take that into consideration. The build system packages all of them up as well. So here we're telling Xcode (gcc and company) how to build it based on the style setup in Xcode.

# What to do before running this script:
# - Set version number in GHA. You can do this in GrowlController.m. Look for the "static struct Version version" line.
# - Set RELEASE_NAME
# - Edit the following two plist keys
# - Core/Resources/Info.plist
# - Core/Resources/GrowlHelperApp-Info.plist

Just some comments about what to do really.

.PHONY: all compile clean release source

all: compile release source

compile:
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/GrowlMail
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/GrowlSafari
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/GrowlDict
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/growlnotify
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/HardwareGrowler
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/growlctl
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/GrowlWidget
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/GrowlTunes
$(MAKE) $(BUILDFLAGS) -C $(SRC_DIR)/Extras/Rawr-jour

It rubs the lotion on the skin.

clean:
rm -rf $(BUILD_DIR)

Or else it gets the hose again.

source:
rm -rf $(RELEASE_DIR)
mkdir -p $(RELEASE_DIR)
cp -R $(SRC_DIR)/Bindings $(RELEASE_DIR)
cp -R $(SRC_DIR)/Common $(RELEASE_DIR)
cp -R $(SRC_DIR)/Core $(RELEASE_DIR)
cp -R $(SRC_DIR)/Display\ Plugins $(RELEASE_DIR)
cp -R $(SRC_DIR)/Docs $(RELEASE_DIR)
cp -R $(SRC_DIR)/Examples $(RELEASE_DIR)
cp -R $(SRC_DIR)/Extras $(RELEASE_DIR)
cp -R $(SRC_DIR)/Framework $(RELEASE_DIR)
cp -R $(SRC_DIR)/Growl\ Readme.rtfd $(RELEASE_DIR)
cp -R $(SRC_DIR)/Growl.xcodeproj $(RELEASE_DIR)
cp -R $(SRC_DIR)/Prefpane\ TestHarness $(RELEASE_DIR)
cp -R $(SRC_DIR)/Scripts $(RELEASE_DIR)
cp -R $(SRC_DIR)/images $(RELEASE_DIR)
cp $(SRC_DIR)/HACKING.txt $(RELEASE_DIR)
cp $(SRC_DIR)/License.txt $(RELEASE_DIR)
cp $(SRC_DIR)/Makefile $(RELEASE_DIR)
cp $(SRC_DIR)/README.rtf $(RELEASE_DIR)
cp $(SRC_DIR)/build.sh $(RELEASE_DIR)
cp $(SRC_DIR)/changes.txt $(RELEASE_DIR)
cp $(SRC_DIR)/generateSVNRevision.sh $(RELEASE_DIR)
cp $(SRC_DIR)/headerDoc2HTML.config $(RELEASE_DIR)

We really do copy all of this into the 2 dmgs that are produced, the SDK dmg and the Growl.dmg.

find $(RELEASE_DIR) \( -name build -or -name .svn \) -type d -exec rm -rf {} \; -prune
find $(RELEASE_DIR) \( -name "*~" -or -name .DS_Store \) -type f -delete
tar -cj -C $(BUILD_DIR)/ -f $(RELEASE_DIR)-src.tar.bz2 $(RELEASE_DIRNAME)

This is the bit I keep think I'll remove. It doesn't work right. But it's supposed to make a source bzball. The rest is decently commented, so if there's something I think is confusing just email me ( the_tick at this domain).

release:
@# clean build directory
rm -rf $(BUILD_DIR)
mkdir $(BUILD_DIR)
mkdir $(GROWL_DIR)
@#
@# copy uninstaller
@#
cp -R "Uninstall Growl.app" $(GROWL_DIR)
/Developer/Tools/SetFile -a E $(GROWL_DIR)/Uninstall\ Growl.app
@#
@# copy webloc files
@#
cp "Growl Documentation.webloc" "Growl version history.webloc" "Get more styles.webloc" $(GROWL_DIR)
@#
@# hide extensions of webloc files
@#
/Developer/Tools/SetFile -a E $(GROWL_DIR)/*.webloc
@#
@# copy the prefpane
@#
cp -R $(GROWL_BUILD_DIR)/Growl.prefPane $(GROWL_DIR)
@#
@# copy the extras
@#

These bits were all pretty well documented.

mkdir $(GROWL_DIR)/Extras
mkdir $(GROWL_DIR)/Extras/growlctl
cp $(GROWLCTL_BUILD_DIR)/growlctl $(GROWL_DIR)/Extras/growlctl
cp $(SRC_DIR)/Extras/growlctl/growlctl.1 $(GROWL_DIR)/Extras/growlctl
cp $(SRC_DIR)/Extras/growlctl/install.sh $(GROWL_DIR)/Extras/growlctl

Copy in growlctl

mkdir $(GROWL_DIR)/Extras/GrowlDict
cp -R $(GROWLDICT_BUILD_DIR)/GrowlDict.app $(GROWL_DIR)/Extras/GrowlDict
cp $(SRC_DIR)/Extras/GrowlDict/README.txt $(GROWL_DIR)/Extras/GrowlDict

Copy in GrowlDict

mkdir $(GROWL_DIR)/Extras/growlnotify
cp $(GROWLNOTIFY_BUILD_DIR)/growlnotify $(GROWL_DIR)/Extras/growlnotify
cp $(SRC_DIR)/Extras/growlnotify/growlnotify.1 $(GROWL_DIR)/Extras/growlnotify
cp $(SRC_DIR)/Extras/growlnotify/install.sh $(GROWL_DIR)/Extras/growlnotify
cp $(SRC_DIR)/Extras/growlnotify/README.txt $(GROWL_DIR)/Extras/growlnotify

Copy in growlnotify, the cli notification util for Growl.

mkdir $(GROWL_DIR)/Extras/GrowlTunes
cp -R $(GROWLTUNES_BUILD_DIR)/GrowlTunes.app $(GROWL_DIR)/Extras/GrowlTunes
cp -R $(SRC_DIR)/Extras/GrowlTunes/ReadMe.rtfd $(GROWL_DIR)/Extras/GrowlTunes
mkdir $(GROWL_DIR)/Extras/HardwareGrowler
cp -R $(HARDWAREGROWLER_BUILD_DIR)/HardwareGrowler.app $(GROWL_DIR)/Extras/HardwareGrowler
cp $(SRC_DIR)/Extras/HardwareGrowler/readme.txt $(GROWL_DIR)/Extras/HardwareGrowler
mkdir $(GROWL_DIR)/Extras/Rawr-jour
cp -R $(RAWRJOUR_BUILD_DIR)/Rawr-jour.app $(GROWL_DIR)/Extras/Rawr-jour
cp $(SRC_DIR)/Extras/Rawr-jour/Icon\ and\ Readme/Readme.rtf $(GROWL_DIR)/Extras/Rawr-jour

Rawr-Jour, GrowlTunes and HardwareGrowler all get copied in.

@#
@# build GrowlMail package
@#
mkdir $(GROWL_DIR)/Extras/GrowlMail
mkdir $(BUILD_DIR)/GrowlMail
mkdir $(BUILD_DIR)/GrowlMail-Resources
cp -R $(GROWLMAIL_BUILD_DIR)/GrowlMail.mailbundle $(BUILD_DIR)/GrowlMail
cp GrowlMail/InstallationCheck $(BUILD_DIR)/GrowlMail-Resources
cp GrowlMail/postflight $(BUILD_DIR)/GrowlMail-Resources
cp -R GrowlMail/English.lproj $(BUILD_DIR)/GrowlMail-Resources
cp -R GrowlMail/German.lproj $(BUILD_DIR)/GrowlMail-Resources

Copy in GrowlMail stuff to prepare it to be built into a PKG.

say enter your password
sudo chown -Rh root:admin $(BUILD_DIR)/GrowlMail
sudo chmod -R g+w $(BUILD_DIR)/GrowlMail

With PKG's, you have to set some permissions.

/Developer/Tools/packagemaker -build -p $(GROWL_DIR)/Extras/GrowlMail/GrowlMail.pkg -f $(BUILD_DIR)/GrowlMail -ds -v -i GrowlMail/Info.plist -d GrowlMail/Description.plist -r $(BUILD_DIR)/GrowlMail-Resources

Here we build the PKG.

sudo rm -rf $(BUILD_DIR)/GrowlMail
rm -rf $(BUILD_DIR)/GrowlMail-Resources

Some cleanup

cp $(SRC_DIR)/Extras/GrowlMail/GrowlMail\ Installation.rtf $(GROWL_DIR)/Extras/GrowlMail

And we copy in the final bit for GrowlMail, the Installation rtf.

@#
@# build GrowlSafari package
@#
mkdir $(GROWL_DIR)/Extras/GrowlSafari
mkdir $(BUILD_DIR)/GrowlSafari
mkdir $(BUILD_DIR)/GrowlSafari-Resources
cp -R $(GROWLSAFARI_BUILD_DIR)/GrowlSafari $(BUILD_DIR)/GrowlSafari
cp GrowlSafari/postupgrade $(BUILD_DIR)/GrowlSafari-Resources
sudo chown -Rh root:admin $(BUILD_DIR)/GrowlSafari
sudo chmod -R g+w $(BUILD_DIR)/GrowlSafari
/Developer/Tools/packagemaker -build -p $(GROWL_DIR)/Extras/GrowlSafari/GrowlSafari.pkg -f $(BUILD_DIR)/GrowlSafari -ds -v -i GrowlSafari/Info.plist -d GrowlSafari/Description.plist -r $(BUILD_DIR)/GrowlSafari-Resources
sudo rm -rf $(BUILD_DIR)/GrowlSafari
rm -rf $(BUILD_DIR)/GrowlSafari-Resources
cp -R $(SRC_DIR)/Extras/GrowlSafari/README.txt $(GROWL_DIR)/Extras/GrowlSafari

GrowlSafari is also a PKG installer, and is built the same way that GrowlMail is built. The next bit is for building the SDK that contains the Growl.framework, Growl-Withinstaller.framework, and Bindings. It's commented, so I won't go over it. But here it is:

@#
@# build the SDK
@#
mkdir $(SDK_DIR)
@#
@# copy the webloc files
@#
cp "Growl Developer Documentation.webloc" "Growl version history for developers.webloc" $(SDK_DIR)
@#
@# hide extensions of webloc files
@#
/Developer/Tools/SetFile -a E $(SDK_DIR)/*.webloc
@#
@# copy the scripts
@#
cp -R $(SRC_DIR)/Scripts $(GROWL_DIR)
@#
@# copy the frameworks
@#
mkdir $(SDK_DIR)/Frameworks
cp -R $(GROWL_BUILD_DIR)/Growl.framework $(GROWL_BUILD_DIR)/Growl-WithInstaller.framework $(SDK_DIR)/Frameworks
@#
@# copy the bindings
@#
cp -R $(SRC_DIR)/Bindings $(SDK_DIR)
@#
@# remove the AppleScript binding
@#
rm -rf $(SDK_DIR)/Bindings/applescript

Note here, we build the Applescript Bindings right into Growl, so there's no need to ship them on the SDK DMG.

@#
@# remove some symlinks
@#
rm $(SDK_DIR)/Bindings/realbasic/GrowlDefines.h
rm $(SDK_DIR)/Bindings/tcl/GrowlDefines.h
rm $(SDK_DIR)/Bindings/tcl/GrowlApplicationBridge.h
rm $(SDK_DIR)/Bindings/tcl/GrowlApplicationBridge.m

Wheeee, symlinks bad!

@#
@# delete svn and backup files
@#
find $(BUILD_DIR) -name ".svn" -type d -exec rm -rf {} \; -prune
find $(BUILD_DIR) \( -name "*~" -or -name .DS_Store -or -name classes.nib -or -name info.nib \) -type f -delete

This bit is always fun. Removing these should be part of your build system no matter what you end up doing. Or at least I would remove them :)

@#
@# make Growl disk image
@#
mkdir $(GROWL_DIR)/.background
cp $(SRC_DIR)/images/dmg/growl075DMGBackground.png $(GROWL_DIR)/.background
./make-diskimage.sh $(BUILD_DIR)/$(RELEASE_NAME).dmg $(GROWL_DIR) Growl dmg_growl.scpt

Here's the important bits. Basically in order for this to work you have to make a folder called .background and copy the image you want into it. Then you run the disk image shell script and it's done.

@#
@# make SDK disk image
@#
mkdir $(SDK_DIR)/.background
cp $(SRC_DIR)/images/dmg/growlSDK.png $(SDK_DIR)/.background
./make-diskimage.sh $(BUILD_DIR)/$(RELEASE_NAME)-SDK.dmg $(SDK_DIR) Growl-SDK dmg_sdk.scpt
@echo Build finished

The AppleScript

That's it for the Makefile. but what about the AppleScript and Shell script? I'll be including those in the zip file at the end of this post, but would like to review the AppleScript here. The shell script will not be gone through, it's pretty self explanatory.

tell application "Finder"
tell disk "Growl" open
tell container window
set current view to icon view set toolbar visible to false
set statusbar visible to false set the bounds to {30, 50, 485, 350}
end tell

This portion tells the finder how big to make the dmg. You're specifically interested in the second 2 numbers. This basically sets the width and height of the window.

close set opts to the icon view options of container window
tell opts
set icon size to 56

Here we can set the icon size of all of the items on the dmg.

set arrangement to not arranged
end tell
set background picture of opts to file ".background:growl075DMGBackground.png"

This is where you enter the name of the background artwork you are going to be using.

set position of item "Growl.prefPane" to {45, 41}
set position of item "Extras" to {162, 33}
set position of item "Scripts" to {36, 153}
set position of item "Growl Documentation.webloc" to {128, 123}
set position of item "Growl version history.webloc" to {265, 41}
set position of item "Get more styles.webloc" to {383, 41}
set position of item "Uninstall Growl.app" to {383, 123}

This sets the location of each of the icons on the DMG.

update without registering applications
tell container window
set the bounds to {31, 50, 480, 350}
set the bounds to {30, 50, 480, 350}
end tell

I believe this portion also tells the finder how to set the size of the window when mounted, albeit I'm not 100% on that. You'll need to play around with it.

update without registering applications
end tell
--
give the finder some time to write the .DS_Store file
delay 5

This bit is important. The .DS_Store stores a lot of this info, so we

end
tell

That's really all there is to this. The dmg shell script builds the rest and then the process is complete. No more need for Filestorm or other applications, plus this makes life a ton easier.

Auto Versioning

You can even automate versioning as well. The Adium MakeFile has this little tidbit:

PLIST_DIR=`pwd`/../Plists
ADIUM_PLIST=$(PLIST_DIR)/Adium_2
CRASH_REPORTER_PLIST=$(PLIST_DIR)/Adium\ Crash\ Reporter-Info


version:
@# update the plists
defaults write $(ADIUM_PLIST) CFBundleGetInfoString '$(VERSION), Copyright 2001-2007 The Adium Team'
defaults write $(ADIUM_PLIST) CFBundleVersion '$(VERSION)'
defaults write $(ADIUM_PLIST) CFBundleShortVersionString '$(VERSION)'
defaults write $(CRASH_REPORTER_PLIST) CFBundleGetInfoString '$(VERSION), Copyright 2001-2007 The Adium Team'
defaults write $(CRASH_REPORTER_PLIST) CFBundleVersion '$(VERSION)'
defaults write $(CRASH_REPORTER_PLIST) CFBundleShortVersionString '$(VERSION)'
plutil -convert xml1 $(ADIUM_PLIST).plist
plutil -convert xml1 $(CRASH_REPORTER_PLIST).plist

This writes the version out to to 2 plists, along with copyright information. So you'd set the version in the Makefile, hit Make, and this handles the rest.

openUp

Last and not least, before I go, I need to talk about a utility called openUp. The openUp utility won't exactly be required for this to all work, but it'll help (i.e. you want it). The scripts assume it is installed into /usr/local/bin, but you can modify that. I've included it in the Utilities folder on the zip file.

For what it does, I'll just copy and paste the comments from the .c file:

/*
* Shantonu Sen <<EMAIL REMOVED>>
* openUp.c - program to set the "first-open-window" field of a volume
*
* Get the directory ID for the first argument, and set it as word 2
* of the Finder Info fields for the volume it lives on
*
* cc -o openUp openUp.c
* Usage: openUp /Volumes/Foo/OpenMe/
*
*/

The end

Well, that's it. I hope this has been informative. Oh, here's the zip (2.4 mb).

Also, this is my first attempt at posting with the newer versions of Ecto. I rather like this, and will probably purchase it.

Posted by Chris Forsythe at September 28, 2006 02:27 PM

Comments

I just went through the same process though I have the additional headache of creating an installer with PackageMaker.

A couple of questions/comments:

There's this bit in AppleScript "update without registering applications". Look like this is to get the Finder to actually write the info to .DS_Store? If so much nicer than the "trick" I found which was to zoom the window twice via assitive devices. Bleh.

The other thing. I was using openUp before I stumbled across a mention of bless (man bless for details). I'm using "bless --openfolder" to auto open the dmg upon mounting.

Anyway, good info thanks for sharing it.

Posted by: Casey Fleser at September 29, 2006 10:21 AM

Post a comment




Remember Me?