Pages

Monday, June 2, 2014

OS X: Creating Packages from the Command Line - Tutorial and a Makefile - Part I

Over the years Apple has been removing useful tools from its flagship IDE, XCode, and PackageMaker was one of them. It didn't take me by surprise, though. But while on the one hand I understand that Apple would like most users to download software from the Mac App Store (for which plenty of functionality has been baked into XCode), on the other hand removing PackageMaker means leaving developers without a useful and simple graphical tool to build their packages. And even though you can still download it separately at the Mac Dev Center (search for a package called XCode Auxiliary Tools), PackageMaker is clearly an unsupported tool of the past, and you'd better rely on the current ones: pkgbuild and productbuild.

Both pkgbuild and productbuild are command-line tools and they provide developers all the functionality they need to (man page citation):
  • Build OS X installer component packages.
  • Build a product archive for the OS X installer.
  • Build a product archive for the Mac App Store.
The only issue with them is they do not have a GUI and XCode does not publish their functionality through its UI.

In this blog post I will give a brief overview of how this tools can be invoked to build your own packages. You will be able to:
  • Create your own product archives containing multiple component packages.
  • Create a DMG disk image for your product archive.

In the next part of this tutorial we will see how we can write a Makefile to perform all the commands required to package your product and streamline your workflow, maybe executing it in a custom XCode target of yours.

Using pkgbuild

pkgbuild is the tool used to create component packages, the building blocks of product archives, the final products users install. Although component packages are packages themselves and you could distribute them and install them, building a product archive is in my opinion always preferable. Besides, component packages cannot be submitted to the Mac App Store.

pkgbuild main mode of operation is driven by a property list file which specifies the configuration of the component package to create. Developers do not need to write this file from scratch though: pkgbuild provides an analyse mode of operation that analyses the contents found in the specified root path and outputs a template component property list file that can be used as a starting point.

Assuming we want to build a component package for an OS X application, we first invoke pkgbuild in analyse mode on a release installation directory to create an initial component property list file:

$ pkgbuild --analyze \
  --root /tmp/Name.dst \
  NameComponent.plist

The root path for an application project is an XCode project property (Project/Build Settings/Deployment/Installation Build Products Location) defaulting to /tmp/$(PROJECT_NAME) that can be specified when performing the xcodebuild install target by setting the DSTROOT variable.

When run, pkgbuild will create a file called NameComponent.plist containing default configuration values such as the following (output may vary):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
  <dict>
    <key>BundleHasStrictIdentifier</key>
    <true/>
    <key>BundleIsRelocatable</key>
    <true/>
    <key>BundleIsVersionChecked</key>
    <true/>
    <key>BundleOverwriteAction</key>
    <string>upgrade</string>
    <key>RootRelativeBundlePath</key>
    <string>Applications/Name.app</string>
  </dict>
</array>
</plist>

Tweak your component property file according to your needs (man pkgbuild for detailed information about the available configuration property) and when the configuration is ready pkgbuild can be run in its main mode to create the component package (split lines for better readability):

$ pkgbuild --root /tmp/Name.dst \
  --component-plist NameComponent.plist \
  NameComponent.pkg

Here, we use NameComponent.pkg as the name of the component package to distinguish from the name of the product archive package.

Sign Packages with pkgbuild

pkgbuild lets you sign packages using an "identity" (a certificate and the corresponding private key) that must be installed into one of your keychains. To sign a component package, invoke pkgbuild with the --sign identityName option where identityName is the common name (CN) of the desired certificate.

If you are creating a signed product, for example if you want to submit it to the Mac App Store, signing all its component packages is not required: you should only sign the product archive using productbuild.

Using productbuild

Let's now suppose you have created two component packages for your product:
  • NameComponent.pkg, created in the previous section.
  • MyFramework.pkg, a dependency of NameComponent.pkg (created as seen in the previous section).

productbuild is the tool used to create product archives, that is: packages to be installed by users or to be submitted to the Mac App Store. A product archive is made up of one or more component packages and in this tutorial we will create one containing the two aforementioned components.

Similarly to pkgbuild, productbuild runs in multiple modes (currently the available modes are 5, see the man page for detailed information), two of them being:
  • Analysis mode, to create an initial distribution file.
  • Create a product archive from a distribution file.
A distribution file is to the product archive what the component property list is for the component package: it includes all the configuration of the product archive, including (see the man page for detailed information):
  • A product license.
  • A product README file.
  • The list of component packages.
  • Constraints (such as minimum OS version).

To create the initial distribution file for our product archive, we run productbuild in analysis mode specifying the list of component packages to include. In this case we specify the --synthesize option to run productbuild in analysis mode, two component packages with multiple --package options, a requirement file (described in the next section) and the name of the resulting distribution file distribution.plist (split lines for better readability):

$ productbuild --synthesize \
  --product requirements.plist \
  --package MyFrameworkComponent.pkg \
  --package NameComponent.pkg \
  distribution.plist

Once productbuild creates the initial distribution file, you can manually modify it to satisfy your needs.

Adding a License

If you want to add a license to your product archive, prepare a license file in one of the supported formats (including rtf, html and plain txt) and add its reference to the distribution file using a <license/> element to the distribution file root element <installer-gui-script/>:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="2">
  <title>Name</title>
  ...
  <license file="LICENSE.html"/>
  ...
</installer-gui-script>

Adding a README

A README file can be added using a procedure similar to what we have used to add a license file, but in this case the name of the element is <readme/>:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="2">
  <title>Name</title>
  ...
  <license file="LICENSE.html"/>
  <readme file="README.html"/>
  ...
</installer-gui-script>

Creating a Requirements File

Requirements can be specified in a requirement property list file (see man page for detailed information). A requirement property list file is a property list whose element is a dictionary in which may constraints can be expressed using a series of keys, including (but not limited to):
  • os: Minimum allowable OS version.
  • arch: Supported architectures.
  • ram: Minimum required RAM.

To specify a dependency on OS X 10.9, we can create an empty property list file whose first child is a dictionary in which we add an os key with the constraint we need:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>os</key>
  <array>
    <string>10.9</string>
  </array>
</dict>
</plist>

Creating the Product Archive

Once the distribution file is ready, productbuild can be run to finally create the product archive. In this case we have to specify the distribution file with the --distribution option, the resource path where resource files are found (such as license and README, even if they are in the current working directory) the package path where component packages can be found using the --package-path options (in case they are not in the current working directory) and the name of the product package:

$ productbuild \
  --distribution distribution.plist
  --resources .
  --package-path path/to/MyFrameworkComponent.pkg
  --package-path path/to/NameComponent.pkg
  Name.pkg

Signing the Product Archive

As explained in the case of pkgbuild, productbuild lets you sign the product archive using the identity (a certificate common name) specified with the --sign option. The specified certificate must be available in an accessible keychain, otherwise the path of a specific keychain must be specified using the --keychain option.

A product archive can even be signed after it has been created using the productsign command (see the man page for further information).

Creating a DMG Disk Image

OS X installers are often distributed in a DMG disk image file. If you want to create a DMG file containing your product archive (and optionally other files) you have to:
  • Put all the files your want to include in a folder (the source folder in hdiutil jargon).
  • Use the hdiutil command-line utility to create a DMG disk image from the source folder.

Assuming we have created the ./dist source folder where we have copied the Name.pkg product archive, we can use the following command to create a Name.dmg DMG file (split lines for better readability) named My Product:

$ hdiutil create \
  -volname "My Product" \
  -srcfolder ./dist \
  -ov \
  Name.dmg

The optional -ov option is used to overwrite an existing DMG file.

A Makefile to Put It All Together

In the next part of this tutorial we will examine an example Makefile that can be used as a starting point to perform all the operations described in this post.

4 comments:

  1. Just what I was looking for, excellent!
    There is information on this subject, but this is the most organized and clear that I have found.
    Thanks

    ReplyDelete
  2. Excellent.
    This is the most useful information about pkgbuild and productbuild

    ReplyDelete
  3. How can we execute sub-installers using pkgbuild?
    Suppose I have total 4 sub packages which are build with pkgbuild. Now needs to create a main package which will call all the above 4 sub packages silently. How can we make it using pkgbuild?

    ReplyDelete