Scripting DMG builds on OS X from Linux

17 Apr 2013 Three Empty Boxes #1

bliss was first made available for Mac OS X back in late Summer 2010, and since then it's been pretty successful. I decided the best way to package it would be as an OS X 'app' packaged within a DMG file. This way, the DMG can be downloaded, opened, and then the app run either directly from the DMG or copied to somewhere sensible like /Applications with as little friction as possible.

That's the user's perspective, but what about the developers? How does the DMG get built?

The OS X application bundle itself is fairly simple. An application bundle is really a directory tree with well-known files with special formats in certain places. Once you have your configuration files written (and tools like the Jar Bundler can help with that initial task), it's simply a job of assembling the files in the correct locations in your 'app' folder.

It's the building of the DMG that caused more recent problems. The bliss build runs on Linux, using Ant. As such, the creation of the app folder is easy. The creation of the DMG was also easy, using something called hfsplus. I could simply create a HFS+ filesystem and copy the app over, unmounting and then copying to the Mac for testing.

That worked until OS X Mountain Lion was released. Mountain Lion added Gatekeeper which meant applications downloaded from the Web (such as bliss) must be signed, lest they give a nasty error message. As I want to lower friction for the adoption of my product as much as possible, I decided I would have to avoid such messages using code signing.

First steps to a Mountain Lion build

And that's where things started to break.

Adding the code signing itself wasn't too difficult, although it did mean having to ssh to a Mac to perform the signing. My initial attempt at a revised build was to:

  1. Build the app folder
  2. Build the DMG, copy the app folder into it
  3. Copy the DMG to the Mac
  4. Open the DMG
  5. Sign the app
  6. Copy the DMG back for release

This would yield an app that would run direct from the DMG with Gatekeeper enabled. Hoorah! However, trying to copy the app to, for instance, /Applications meant that the app would not run from its new location. Trying to run it would give the message:

Damaged? Incomplete? What I realised was that, when code signing, a checksum is taken of each of the files in the app. It was some of these checksums that were being found to have changed following the copying of the app bundle that lead to OS X classing the file as 'damaged'.

After trying a few things I realised I was out of my depth and it was time to cash in one of the bonuses of membership of the Apple Developer Programme. I filed a bug with Apple. After providing sample files we worked out the basic problem. In their words:

Your Linux tools produce files whose extended attributes claim a compression mode that causes the system copy operation to make modified copies of his bundle. ... Creation of compressed HFS files is not officially supported (for third party development). These files were either marked in error; or the underlying tool (HFS filesystem driver on Linux?) is attempting and failing to create properly compressed HFS files.

At that point I decided to give up with the Linux HFS drivers and move to creating the DMG on the Mac.

Creating the DMG on the Mac

Given I had to ssh to the Mac to code sign my app, I decided creating the DMG there wouldn't be such a pain. I'd rather have the whole build running on my Linux workstation, but given I tend to have the Mac on to play music and perform quick tests of bliss anyway I realised it wouldn't be a big problem.

So my new build would perform the following steps:

  1. Build the app folder
  2. Copy the app folder to the Mac
  3. Build the DMG on the Mac, using the app folder as '-srcfolder'
  4. Sign the app
  5. Detach the DMG and compress it
  6. Copy the DMG back for release

I was relieved to find this worked! Here's my Ant script for this part of the build:

<exec executable="ssh">
	<arg value="usr@mac-build-server"/>
	<arg value="hdiutil create -srcfolder ${build.mac.dmg.dir.macmini} -volname 'bliss' -fs HFS+ -fsargs '-c c=64,a=16,e=16' -format UDRW -size ${dist-size-with-padding-kb}k ~/Desktop/bliss.temp.dmg;
			hdid ~/Desktop/bliss.temp.dmg; 
			security unlock-keychain -p ****; 
			codesign -s 'Developer ID Application: D Gravell' -f /Volumes/bliss/ ; 
			hdiutil detach /Volumes/bliss/ ; 
			hdiutil convert ~/Desktop/bliss.temp.dmg -format UDZO -imagekey zlib-level=9 -o ${build.mac.dmg.macmini};
			rm ~/Desktop/bliss.temp.dmg;"/>

Line for line that's...

hdiutil create -srcfolder ${build.mac.dmg.dir.macmini} -volname 'bliss' -fs HFS+ -fsargs '-c c=64,a=16,e=16' -format UDRW -size ${dist-size-with-padding-kb}k ~/Desktop/bliss.temp.dmg;

This creates the DMG file. In my build I create a temporary DMG file and rename this to my actual DMG later. The -srcfolder points to my app folder.

hdid ~/Desktop/bliss.temp.dmg; 

This mounts the new temporary DMG file so I can code sign it.

security unlock-keychain -p ****; 
codesign -s 'Developer ID Application: Developer name' -f /Volumes/bliss/ ; 

Here, the app folder inside the DMG is signed. Provide your own password and key identity.

hdiutil detach /Volumes/bliss/ ; 
hdiutil convert ~/Desktop/bliss.temp.dmg -format UDZO -imagekey zlib-level=9 -o ${build.mac.dmg.macmini};
rm ~/Desktop/bliss.temp.dmg;

Now the DMG is unmounted and then compressed, creating the final DMG file. The temporary DMG is removed at this point.

I hope this helps anyone else with problems signing apps in Linux-built DMGs!

Thanks to z287marc for the image above.
blog comments powered by Disqus