He Loves You

What do you do after working on something for 16 hours? Take a break. A long break. For 4 hours on Friday and 12 straight hours on Saturday I was working on getting my installer package ready. The installer package in question had precious paylod: a beta version of Amarok 2. I finally got it in a state I was comfortable with and released it into the wild. It took quite a bit of work, far more than expected, to get the installer working to my satisfaction. There were some things which I required it to do, or not do, before I was comfortable with releasing it; things which I list below.

I wanted it to:

  1. Have functioning collection scanning.
  2. Show the name of the software being installed as the installer progressed.
  3. Not have the installer fail if the files being installed are already present.
  4. Automatically set up the mime-database for the user.
  5. Not force the user to log out and then back in, before being able to run Amarok.
  6. Automatically run kbuildsycoca4.

I'll go through the problems I had solving (or not solving) each of these in turn. I used PackageMaker3, which comes with XCode, to create the installer. I wish I could say it's pure win, but it's not. I ran into quite some issues which, due to my unfamiliarity with PackageMaker (only starting playing with it on Tuesday), slowed down package delivery. Hence the Turtle Power.

Working collection scanning

For some reason my amarokcollectionscanner instance doesn't get executed unless it's in the same, exact location as the Amarok executable. This means it has to be put within Amarok's .app bundle, so I moved it there. The icns resource is also installed separately (/opt/local/bin) from the rest of the bundle (/Applications/KDE4). Perhaps this is related to the case of the filename. Whatever it is I manually moved this to the proper location before packaging. This is likely a CMake issue I introduced and need to look into. Whatever, now we get a pretty app icon to click on. Yay.

Show name while installing

Unfortunately this one didn't get resolved. It's mostly an aesthetic issue so, after fighting with it for 4 hours on Friday, I decided to put it on hold for later. In PackageMaker you provide it with a path to the software you want installed. The problem is that the basename of this path is used in the "Installing ..." message. Since I'm using macports, this leaves me with destroot or the software's version number as the choices shown during installation. If a name shows up more than once, a number is added to the end of it. For destroot it doesn't look that bad.
Unfortunately that makes things a little harder when I need to debug what package caused the installer to die and I need to find out what destroot-41 is. While, far uglier, 1900 or even 44-1 are far easier to deal with. Can you guess which I went with? Now you know why those crytic numbers show up during installation.

Not having the installer fail

In my experience the installer only fails when it encounters a directory it decides it can't overwrite. This happens of course, when the software being installed is already present. Since the intended users of these beta packages are testers, it is assumed that at some point they followed (or tried to follow) the instructions in the amarok wiki for installing Amarok 2 on OS X. This means that it's very likely the installer will come into contact with a directory that's in it's way, and fail.

This was a particularly puzzling problem since I forced super user privileges during the install. We're root! We should be able to do anything! Yes, true. But the installer wasn't doing that. As it turns out, whenever the installer has a .app bundle inside it tries to do something smart. .app bundles can (and usually are) relocatable. That means a user can move it to anywhere on the computer and still launch it. By default when the packages were installed the first time the bundles were set as relocatable. For some reason when installing a second time and encountering these bundles the installer failed (with the very helpful error in the console that "package.app is a directory". ORLY?). I don't really know why you can't install over relocatable bundles.

It could either be that PackageMaker realizes the relocatableness of the bundle on the computer and says "Hmm, the user might have moved this here. If I overwrite it I might kill this version of that app. I don't think I should do that."
Alternatively it could be the relocatableness of the app being installed that causes the problem. I haven't come up with a good PackageMaker inner monologue for why this would happen yet, i.e. I think PackageMaker is looking at the bundle on the computer.
Either way, I unchecked "Allow Relocation" for all the bundles. The checkbox is in the Components Tab of the Package Reference. Installation, Reinstallation, Treinstallation etc now works.

Automatically set up the mime-database

On Linux, the major desktop environments have decided to collaborate by using common software that provides functionality that both of them require, where possible. A shared mime type database is one of these collaborative efforts (someone correct if I'm wrong here). This cross desktop collaboration brought us the shared-mime-database and dbus. These bundles of joy have also made the transition from linux, to windows and OS X with KDE.

KDE, and thus Amarok 2, uses the mimetype database to identify files and folders. If it isn't set up you don't get icons when navigating through the file system. It seems you also can't identify files, such as mp3s, as mp3s. Really bad juju for an audio player. I only know how to set up the mimetye database because I'm a nosy Linux user who likes to poke about with stuff. There are even people using Linux who don't know this. As a matter of fact, RangerRick actually told me how to do it, and who knows how he found out. The point? I'm not expecting anyone on OS X to know this. Nevertheless, a mimetype database needs to be built after the shared-mime-database package is installed.

Instead of providing a ReadMe file telling the user to run sudo update-mime-database /opt/local/share/mime I decided to just do it for them. After all, the Granny Goodnesses, apparently a significant portion of the target audience, probably can't even type in passwords properly, and we already require that action before installation. I'd like to not cause anymore pain than is entirely necessary. The solution to this was to use a postinstall script.

There are several types of scripts which can be run during a package installation. I use postinstall and postflight which are run after a package has been installed. The *install scripts are only run the first time a package is installed. The *flight scripts are run every time. In related news, I read PackageMaker likes its scripts to be named just so, with no file extensions on the end: postinstall, postupgrade, postflight, so that's what I did. And it worked.

Oh, and there's also an option to set a script directory. I think the script directory recursively searches your directories while building your package to find scripts. If your script directory happens to be your downloads directory, you'll be in for a long wait. Either that or PackageMaker froze on me while doing this. After force quitting I didn't try a second time to find out.

Avoid logout/login cycle

The last two problems enumerated are actually related so I'll deal with them both here. KDE4 uses dbus for interprocess communication. The dbus-daemon needs to be launched before we can do anything. Apps just crash if you try to launch them and it isn't running. RangerRick and others who I've forgotten (sry) added/fine-tuned launchd support. The problem is that it only gets automatically started after the user logs out. Otherwise the user has to tell launchd to start dbus-daemon. And there's the rub, the user must tell launchd.

Unfortunately the user isn't doing the package installations, root is, and if root starts the dbus-daemon the user can't use it. Since each package installs to locations outside of the user's home directory, admin authentication is necessary. Therefore, if a postinstall script is run, it will run with root's permissions. You might think that root, being the super powerful type, could just pretend to be the user; and he can. Enter sudo -u $USER launchctl load -F org.etc.plist. Too bad it doesn't work.

If invoked this way, something goes horribly wrong and launchd can't find its sockets. Quite like not being able to find your socks, but worse because it means that the user has to logout/login to get things working. Remember, we want to be gentle with Granny Goodness so she can run her orphanages with little worry. Forcing her to close her browser which she's using to shop for teaching supplies should be avoided at all costs.

The solution to this, or more accurately - workaround, was to create one final installation group with an empty directory. This installs nothing. Since it doesn't install anything it doesn't require admin authorization. Since it doesn't require admin authorization, it doesn't get it and is run with the user's permissions. Since the package's installation is run with user permissions its postinstall script is also run with the user's perms. Result: local instance of dbus-daemon starts. Now we can run kbuildsycoca4. Interestingly, running kbuildsycoca4 before any KDE programs start doesn't help startup as much as I thought it would. :-/ Oh well. I can remove that next release.

And that is how the 300-lb gorilla in the room was born. If you'll excuse me, I need to go back to listening to my gospel music on last.fm now. In Amarok 2. On OS X. Yeah, s'awesome.