PackageMaker and Installer

Wednesday, 2012-02-15; 03:01:07



Drag-and-drop installation is a great method of software distribution. For software that is self-contained and doesn’t require living in any specific directory to run, it allows the user to be as neat or as messy with their hard drive hierarchy as they see fit. But for certain types of software, certain things need to be in certain places, and drag-and-drop installation just doesn’t cut it. ClickToFlash, a Safari plug-in I worked on a few years ago, is one of those cases.

Really, the only two types of software distribution for Mac OS X that are at all credible in my mind are 1) drag-and-drop installation, and 2) Apple’s installer technology. Any other method of installation is dead on arrival. There are numerous criteria — remote deployment, user familiarity, flexibility, scriptability — areas in which these two methods excel across the board. All other installer technology (VISE, FileStorm, I’m looking at you) falls flat in at least one of these areas, if not more.

Despite that, however, one of the worst parts about Mac OS X software distribution that you’ll ever run into is creating an installer package for Apple’s Installer. It will frustrate the heck out of you, cause you to curse out loud, and generally cause you stress for days at a time until you solve your problem, and then you’ll never want to touch it again for fear of breakage.

In addition, if you’re deploying for previous versions of Mac OS X, you have to live with all the bugs of the previous versions of the installers in order to be able to target them. And that’s where things get really, really icky. You’ll get reports from users about your installer not working, and you will struggle to figure out just what that problem is. This article serves as (what I hope will be) the definitive guide for knowing all the pitfalls and workarounds to the atrocious bugs that exist in Apple’s older installer technology.

I’ve actually had this guide semi-published for a while now, but never got around to finishing it. I’m doing so now. It’s kind of outdated what with the Mac App Store, but if you do ever need to touch PackageMaker and/or Installer, hopefully this guide will help you through the nightmare.

This article will inform you regarding Mac OS X versions 10.4-10.6. It hasn’t been specifically checked for changes on 10.7, but as far as I know, the general situation hasn’t changed with Lion. As for older versions, Panther and previous releases of Mac OS X are dead to me, as they are to the vast majority of Mac OS X users. While there are some users for which these older releases are no doubt more than enough, I doubt it’s worth any effort anymore to target these users.

There are three sections to this guide. The first outlines specific problems that you might want to solve when creating an installer, their solutions, and their pitfalls. The second section outlines specific annoyances with PackageMaker that you need to be aware of. The third section provides a general recommendation of how you should structure your installer.

If you’re using Lion and find any inaccuracies with this guide, please let me know.

Section 1: Installer Problems and Solutions

Problem: You want to install items into the current user’s home folder. Solution: Use the new “domain” feature for 10.5 installers to target the user’s home. Pitfall: FileVault users will be unable to use your installer. OS Compatibility: 10.4: unsupported. 10.5: fatally buggy. 10.6: Works correctly, recommended.

Details: Before Leopard, there was no easy way to install items using Apple’s installer to the user’s home directory. Leopard introduced a “domain” feature that allowed you to target the user’s home directory. If you had a package that installed something to /Library/Internet Plug-ins/ , you could offer the user a choice: install for all users (which installed to /Library/Internet Plug-ins/ ), or install only for the current user (which installed to ~/Library/Internet Plug-ins/ ). This is a great feature, if it only it would work correctly and consistently in all cases.

Unfortunately, as you’ll see is typical of Apple’s installer technology, it doesn’t. When FileVault users try to install to the home directory on Leopard, three things will happen:

  1. The installer will hang for about 30 seconds when attempting to change to the “Select a Destination” screen.
  2. You’ll see this error in the Console:

    Installer[23479]: -[PFReceiptDB initWithPkgID:andName:onVolumeOrHomeDir:] some arguments are required! pkgid: ‘com.packagemaker.simx.testapp.bugDemonstration.test.pkg’ domainPath: ‘(null)’

  3. When the “Select a Destination” screen finally comes up, the user can opt to install to their home folder, but the “Continue” button will be grayed out if they do so. They will be unable to proceed with home folder installation.

Workaround: If your installer does not depend on you installing to their home folder, that’s good news. Just deselect the “home folder” checkbox in PackageMaker, and rebuild your installer.

In many cases, however, it’s better to allow the user to install to their home folder. With ClickToFlash, for example, we don’t want to force our software onto all users of a machine simply because one user installed it. We want one user to be able to use ClickToFlash and allow another to not have it installed at all.

The workaround in this case is to install the necessary files and folders to /tmp , which any user can read and write to. Then, in a postinstall, postupgrade, or postflight script, move the items from /tmp to the correct location in the user’s home folder.

The good news is that this bug is fixed in 10.6. Hooray! If you’re targeting 10.6 only, feel free to use the “domain” feature to install to the user’s home directory.

Problem: You want all users to be able to run your installer, including non-admins. Solution: Do not require an administrator password. Pitfall: Once one user installs your piece of software, other users who attempt to install it will fail. OS Compatibility: 10.4: fatally buggy. 10.5: fatally buggy. 10.6: Works correctly.

Details: Most installers request an administrator password to be able to install your software. However, you may have non-admin users of your software that want to install it as well. If you’re trying to install to the user’s home folder using the workaround method as described above (install to /tmp and then moving in a postflight script), you will want to allow non-admin users to install your software without an administrator password.

The problem is, this doesn’t work so well. In both 10.4 and 10.5, installers leave a receipt of the package behind in ~/Library/Receipts/db/a.receiptdb, and this causes problems when another user tries to install it. Installation by the other user attempts to update this receipt, but if they are not an admin user, they are unable to do so, and installation fails with the following error message:

“installdb[2296]: Error: Could not update existing receipt using uid=503 as the prior owner is uid=501”.

Unfortunately, postflight scripts are run after the receipt is updated, so if you have critical commands in your postflight script, this is a real installer failure.

Workaround: The workaround to this problem is to tell the receipts database to immediately forget that the installer has been run before, by issuing it a forget command. To do so, you need to set up a postflight script, with the line: sudo pkgutil ‐‐forget com.packagemaker.testapp.blah.pkg that runs immediately after the user installs the software. This forgets the package was installed, and allows other users to update the receipts database because they will not need admin privs to overwrite the existing receipt.

Problem: You want all users to be able to run your installer, including non-admins. Solution: Do not require an administrator password. Pitfall: If an admin user has not yet installed the software, non-admin users who try to install it will fail. OS Compatibility: 10.4: fatally buggy. 10.5: fatally buggy. 10.6: Works correctly.

Details: Yes, you read correctly, this is the exact opposite of the above problem. Non-admin users are screwed on 10.4 and 10.5.

In this case, this issue is caused by installer packages writing their list of materials as a receipt to another location on disk, so that when the user repairs permissions, the list of materials will allow restoration of the original permissions as originally installed. These are the traditional receipts that you see in /Library/Receipts.

The problem here is that non-admin users are not allowed to write to /Library/Receipts. So if the software hasn’t been installed yet, the installer run under the non-admin user will attempt to write the receipt and will fail. As with the previous issue, postflight scripts will fail to run, and this is a real installer failure.

Workaround: Fortunately, if the Installer finds any file where the receipt should be, it just throws up its hands and doesn’t even attempt to write the receipt file. So, your job is to trick the Installer that the installation has already happened. To do so, just create a zero-length file with the correct filename. If your package is named “ClickToFlash.pkg” for example, then simply issue the following command in the Terminal: su admin_user -c ‘touch /Library/Receipts/ClickToFlash.pkg’

Unfortunately, this Terminal command still requires admin privileges, and there’s no automated way to work around this problem. You’ll have to tell your users to manually run this Terminal command. Alternatively, you could create a GUI application that runs this command for them and simply requests an administrator password in a nice password dialog using Authorization Services — this requires more work but is more user-friendly.

Problem: You want all users to be able to run your installer, including non-admins, but you’re running up against the previous issues. Solution: Detect whether an administrator password will be required, and ask for if it if it is needed. Pitfall: The installer will occasionally deadlock when you attempt to install. OS Compatibility: 10.4: fatally buggy. 10.5: fatally buggy. 10.6: Unknown.

Details: So, you’re clever and you want to only request an administrator password when it is absolutely necessary, and allow non-admins to install if they are able to do so without an administrator password.

So you set up a meta-package, which contains two installer packages: one requires an admin password for installation, and one doesn’t. They both contain the exact same set of files. You’re even more clever because you symbolic linked the contents of one of the packages to the other, so you’re not actually forcing users to download the same bytes twice in order to install your software. Then, in the requirements check, you run a script that tests for the existence of the receipt, and requests an administrator password if it isn’t there, and allows installation without a password if it already exists.

Yes. I’ve done this for ClickToFlash. And the Installer doesn’t like it. It deadlocks with the following Console messages: com.apple.launchd[1] (com.apple.installdb.system) Throttling respawn: Will start in 3 seconds. And then you’ll see this:

Installer[8601] *** -[NSLock lock]: deadlock ( ‘(null)’)
Installer[8601] *** Break on _NSLockError() to debug.

To the user, what happens is that the Installer will hang at the “Select a Destination” screen, and will show the spinning rainbow cursor of death. The Installer will need to be force quit.

The most frustrating thing about this bug is that it is so rare and yet completely reproducible at the same time. The bug seems to only show up once per computer, usually the very first time they run this type of installer. @rentzsch noticed this as soon as I had created this installer for ClickToFlash.

Workaround: None.

Section 2: PackageMaker Problems and Solutions

Problem: You want installed items from a package for 10.4-10.5 to have their owner be the installing user. Solution: Not obvious. OS Compatibility: 10.4: Problem exists, use workaround. 10.5: Problem exists, use workaround. 10.6: Problem exists, use workaround.

Details: In the current version of PackageMaker, you can explicitly set the permissions on all the items in your package. However, there’s no obvious way to tell PackageMaker to install using the installing user’s user and group. There is no “current user” or “current group” option in the user and group pop-up menus. If you specify your own user and group (that is, the user and group of the developer creating the installer), PackageMaker installs the contents of your package with an owner of “root” and a group of “admin”. This is bad if you’re installing to a user’s home folder.

Workaround: The best workaround is to use a command-line tool to build your installer. Not only will this assist you in automated creation of your installer packages when you update your software, but it allows you to specify no permissions, and thus the installer will install the files using the installing user as the owner.

Here’s the command we used for ClickToFlash:

“$SYSTEM_DEVELOPER_UTILITIES_DIR/PackageMaker.app/Contents/MacOS/PackageMaker” \
‐‐root “$BUILT_PRODUCTS_DIR/ClickToFlash.dst” \
‐‐no-relocate \
‐‐info Info.plist \
‐‐resources resources \
‐‐scripts scripts \
‐‐target 10.4 \
‐‐version “$PRODUCT_VERSION” \
‐‐verbose \
‐‐out “$BUILT_PKG”

The ‐‐root option specifies where intermediate products will be built. The Info.plist file supplied to the ‐‐info option are the options provided to the installer. You can create a package using PackageMaker and build the package, and then just filch the Info.plist file for this command. The ‐‐resources option specifies a folder which contains items for the installer: “background.tiff” to supply the background image for the installer, “.lproj” folders (inside which contain “ReadMe.rtf”, etc.) which specify the language resources for the installer, etc. The ‐‐script option specifies a folder for preflight, postflight, and other installer scripts. The ‐‐target option specifies the minimum system version you want your installer to work on. Provide a product version number to ‐‐version and a path to ‐‐out and PackageMaker will deposit a package at the specified path. ‐‐verbose simply tells the command to show you what it’s doing, and the ‐‐no-relocate option I’ll talk about in a bit.

Problem: You want to install only if an app is a certain version. For example, you want Safari to be version 3.0 or higher. Solution: Add a requirement that checks for that version, by specifying the path of the application. Pitfall: Installer completely fails if the user has moved that application from your specified location. OS Compatibility: 10.4: Problem exists, use workaround. 10.5: Problem exists, use workaround. 10.6: Problem exists, use workaround.

Details: This is an annoying problem. When you want to specify that a certain application is at least a certain version before installing, the only option that PackageMaker allows you to choose is to specify the application’s location by a defined file path.

Unfortunately, there’s a problem that happens if the user doesn’t have that application at the specified path: PackageMaker fails to create JavaScript code that tests for the existence of the file at the path before attempting to ascertain its version. For example, if the user has moved Safari out of the Applications folder, this will cause the installer to fail. It will put up a message saying “There was an error while evaluating JavaScript for the package.”

Workaround: Write this requirement out in red in the read me file for your installer.

You can try to fiddle with the JavaScript using the Raw Editing Mode of PackageMaker, but see the next issue for why you probably shouldn’t do that.

Problem: You want to customize the JavaScript that PackageMaker creates for requirements on installers. Solution: Use PackageMaker’s raw editing mode, of course! Pitfall: It just doesn’t work. Randomly. OS Compatibility: 10.4: Problem exists. 10.5: Problem exists. 10.6: Problem exists.

Details: Whenever I’ve used PackageMaker’s Raw Editing Mode, it simply causes the installer to fail. I was editing whitespace in the JavaScript that PackageMaker had created, and that caused the installer to put up an error message saying, “There was an error while evaluating JavaScript for the package.”

I looked at the raw distribution.dst files that PackageMaker creates for your packages, and the JavaScript looked OK, so that didn’t seem to be the problem. It seemed like the Installer was having trouble evaluating the JavaScript for some reason.

Later, Raw Editing Mode seemed to work OK. This may have been a one-off quirk, but I just can’t rely on any custom JavaScript to work.

Workaround: None known.

Problem: You want to include a helper application in your installer, one that is contained within your larger app’s bundle. Solution: Seems obvious, just include it as one of the files to be installed. Pitfall: It gets “relocated”. That is, your helper application gets installed on the same level as the larger app. It does not stay within the larger app’s bundle, even though the contents clearly show it within the bundle in PackageMaker. OS Compatibility: 10.4: OK. 10.5: OK. 10.6: Problem exists, use workaround.

Details: Your helper app gets installed on the same level as your larger app. This is annoying, because it shouldn’t be there, and it causes your larger app not to know where the helper app is.

Workaround: Create your installer using a command-line tool, and include the ‐‐no-relocate option when creating your package. See the first issue in Section 2 for a sample command that will do this.

Hat Tip: Peter Hosey pointed this option out to me.

Problem: You want to include multiple items to install while using PackageMaker. Solution: Drag and drop them onto the list of contents at the left side of the PackageMaker window. Pitfall: This creates a single installer package for every single item on that list.

Details: This is one of those weird PackageMaker UI quirks. When you drag items to the list of contents in the PackageMaker window, PackageMaker assumes that you want a separate choice for each of those items.

A choice appears when you do a custom install. It allows the user to pick and choose which things he wants to install (and in some cases, where). Usually, though, you just want one choice for your entire installation, and so you want just one package.

Workaround: Create a new choice using the [Project —> Add Choice] menu item. Then drag files you want to install to that choice. This is better than creating a single choice for each item.

Problem: You want to install items to global folders while constructing your installer using PackageMaker. Solution: Change the destination for each item in your choice to the correct install path. Pitfall: Installation fails to install items to the correct locations. Randomly. Installer does not inform the user that the install failed.

Details: This is just annoying. You specify a destination path for your files, and they just don’t get installed.

Workaround: Create a folder for all the global paths, and then install to /. For example, if you wanted to install to the global applications folder, you’d create a folder named “Applications”, add that to your list of contents, and then specify the install location as /. Or, if you want to install to the global internet plug-ins folder, you’d create a folder called “Library”, inside of which you’d put a folder named “Internet Plug-ins”, inside of which you’d put the plug-ins you want to install. Then you’d drag the “Library” folder to your list of contents, and specify the installation destination as /.

There are two very important options that you need to be aware of when using this method. First, you do not want to overwrite the permissions on the global folders. It would be bad if you set the owner of the /Applications/ or /Library/ folders to something other than their defaults.

To make sure this doesn’t happen, double-click on one of the contents in the list, or select one of the contents and choose [Package —> Package Flags…]. Make sure that “Overwrite Directory Permissions” is not checked!

Now, you need to make sure that the installer knows that it’s installing your items inside those global folders. To do so, click on one of the packages in the list of contents, and then click on the “Contents” tab. Here, make sure the “Include root in package” box is checked. In our examples above, this causes the package to “install” a /Applications/ folder, but since it already exists, it doesn’t touch the permissions and installs the files just where you want them.

Problem: You want to specify the destination of a package to be an absolute path or relative to the project. Solution: Select the correct option in the gear menu to the right of the Destination text field in PackageMaker. Pitfall: A UI bug makes it unclear what is actually happening.

Details: Intially, when you create a new package, neither “Absolute” nor “Relative to Project” are checked in the gear menu for the Destination field.

However, if you try to choose either of them, both of them end up being checked.

Here’s what you need to know: when neither option is checked, you’re using an absolute path. When both options are checked, you are using a relative path.

Section 3: General Recommendations for Installers

Given all these problems and issues with the Installer and PackageMaker, it’s worth noting the best way to get around these issues. Here are my recommendations:

  1. Target 10.6 and 10.7 only. If at all possible, TOTALLY DO THIS. You will save yourself so many headaches and problems, trust me.

  2. If you must support versions of Mac OS X below 10.6, I highly recommend creating two different installers: one for 10.6 and above, and one for 10.5 and below. Because of the FileVault installer bug (the first issue in Section 1), I just cannot recommend using the “domain” feature at all on 10.5. You should stick with 10.4 installation mechanisms on 10.5. But you can still reap the benefits of 10.6’s newly (mostly) bug-free installation this way.

  3. For your combined 10.4/10.5 installer (or for combined 10.4-10.6 installers, too), require an administrator password. Just do this, it saves you so many headaches, too. You’ll still be able to install to their home folder if necessary (by installing to /tmp and then moving with a postflight script), but it’ll still require an administrator password. Unfortunately, this is the reality. (For your 10.6 installer, by all means, use the “domain” feature and don’t require an administrator password for installation to the home folder.)


Technological Supernova   Tips   Older   Newer   Post a Comment