205

Note: This is for OS X Installer packages only, packages for submission to the Mac App Store follow different rules.

Because of Mountain Lion's Gatekeeper I finally had to take my PackageMaker build script behind the barn and shoot it. PackageMaker was already removed from Xcode and moved into "Auxiliary Tools for Xcode", so hopefully it will be soon forgotten.

The question is how do I use pkgbuild, productbuild, and pkgutil to replace it?

catlan
  • 25,100
  • 8
  • 67
  • 78
  • so I assume the issue with packagemaker is inability to properly sign pkg files for use with gatekeeper on Mountain Lion? – JasonZ Aug 12 '12 at 21:36
  • 1
    It is possible, but PackageMaker always was buggy has hell, and got deprecated with Mac OS X 10.6 Snow Leopard. It will save you time in the long run to just get familiar with the new tools. – catlan Aug 12 '12 at 23:08
  • @catlan: Do you have an official link that says packagemaker has been deprecated on 10.6? – Carl Aug 22 '12 at 02:09
  • The issue with PackageMaker is that it no longer runs at all on Mtn Lion, even with the Auxiliary Tools. A private DSObjCWrappers.framework is now missing from Mtn Lion so when you attempt to use PackageMaker I get: Dyld Error Message: Library not loaded: /System/Library/PrivateFrameworks/DSObjCWrappers.framework/Versions/A/DSObjCWrappers Referenced from: /Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker Reason: image not found – Thomson Comer Sep 20 '12 at 18:28
  • 2
    @carleeto: It was never announced as being deprecated, just removed from Xcode and eventually "disappeared" like a Burmese protester. – bug Oct 23 '12 at 19:53
  • Although PackageMaker is no longer part of the core Xcode install, neither are a load of other ancillary utilities that aren't deprecated. PackageMaker still runs just fine for me on 10.8.2, and I downloaded the July 2012 version of the Auxiliary Tools disk image. It's true that `pbxbuild` and `projectbuild` are gradually replacing it (particularly for App Store releases) and that's probably for the best, but it's not really accurate to say it's dead. ("I'm not dead yet!") – Quinn Taylor Jan 10 '13 at 17:29
  • 5
    Xcode 4.6 release notes: Deprecation of Package Maker http://adcdownload.apple.com/Developer_Tools/xcode_4.6/release_notes_xcode46gm.pdf – catlan Jan 29 '13 at 09:52

5 Answers5

377

Our example project has two build targets: HelloWorld.app and Helper.app. We make a component package for each and combine them into a product archive.

A component package contains payload to be installed by the OS X Installer. Although a component package can be installed on its own, it is typically incorporated into a product archive.

Our tools: pkgbuild, productbuild, and pkgutil

After a successful "Build and Archive" open $BUILT_PRODUCTS_DIR in the Terminal.

$ cd ~/Library/Developer/Xcode/DerivedData/.../InstallationBuildProductsLocation
$ pkgbuild --analyze --root ./HelloWorld.app HelloWorldAppComponents.plist
$ pkgbuild --analyze --root ./Helper.app HelperAppComponents.plist

This give us the component-plist, you find the value description in the "Component Property List" section. pkgbuild -root generates the component packages, if you don't need to change any of the default properties you can omit the --component-plist parameter in the following command.

productbuild --synthesize results in a Distribution Definition.

$ pkgbuild --root ./HelloWorld.app \
    --component-plist HelloWorldAppComponents.plist \
    HelloWorld.pkg
$ pkgbuild --root ./Helper.app \
    --component-plist HelperAppComponents.plist \
    Helper.pkg
$ productbuild --synthesize \
    --package HelloWorld.pkg --package Helper.pkg \
    Distribution.xml 

In the Distribution.xml you can change things like title, background, welcome, readme, license, and so on. You turn your component packages and distribution definition with this command into a product archive:

$ productbuild --distribution ./Distribution.xml \
    --package-path . \
    ./Installer.pkg

I recommend to take a look at iTunes Installers Distribution.xml to see what is possible. You can extract "Install iTunes.pkg" with:

$ pkgutil --expand "Install iTunes.pkg" "Install iTunes"

Lets put it together

I usually have a folder named Package in my project which includes things like Distribution.xml, component-plists, resources and scripts.

Add a Run Script Build Phase named "Generate Package", which is set to Run script only when installing:

VERSION=$(defaults read "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Info" CFBundleVersion)

PACKAGE_NAME=`echo "$PRODUCT_NAME" | sed "s/ /_/g"`
TMP1_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp1.pkg"
TMP2_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp2"
TMP3_ARCHIVE="${BUILT_PRODUCTS_DIR}/$PACKAGE_NAME-tmp3.pkg"
ARCHIVE_FILENAME="${BUILT_PRODUCTS_DIR}/${PACKAGE_NAME}.pkg"

pkgbuild --root "${INSTALL_ROOT}" \
    --component-plist "./Package/HelloWorldAppComponents.plist" \
    --scripts "./Package/Scripts" \
    --identifier "com.test.pkg.HelloWorld" \
    --version "$VERSION" \
    --install-location "/" \
    "${BUILT_PRODUCTS_DIR}/HelloWorld.pkg"
pkgbuild --root "${BUILT_PRODUCTS_DIR}/Helper.app" \
    --component-plist "./Package/HelperAppComponents.plist" \
    --identifier "com.test.pkg.Helper" \
    --version "$VERSION" \
    --install-location "/" \
    "${BUILT_PRODUCTS_DIR}/Helper.pkg"
productbuild --distribution "./Package/Distribution.xml"  \
    --package-path "${BUILT_PRODUCTS_DIR}" \
    --resources "./Package/Resources" \
    "${TMP1_ARCHIVE}"

pkgutil --expand "${TMP1_ARCHIVE}" "${TMP2_ARCHIVE}"
    
# Patches and Workarounds

pkgutil --flatten "${TMP2_ARCHIVE}" "${TMP3_ARCHIVE}"

productsign --sign "Developer ID Installer: John Doe" \
    "${TMP3_ARCHIVE}" "${ARCHIVE_FILENAME}"

If you don't have to change the package after it's generated with productbuild you could get rid of the pkgutil --expand and pkgutil --flatten steps. Also you could use the --sign paramenter on productbuild instead of running productsign.

Sign an OS X Installer

Packages are signed with the Developer ID Installer certificate which you can download from Developer Certificate Utility.

They signing is done with the --sign "Developer ID Installer: John Doe" parameter of pkgbuild, productbuild or productsign.

Note that if you are going to create a signed product archive using productbuild, there is no reason to sign the component packages.

Developer Certificate Utility

All the way: Copy Package into Xcode Archive

To copy something into the Xcode Archive we can't use the Run Script Build Phase. For this we need to use a Scheme Action.

Edit Scheme and expand Archive. Then click post-actions and add a New Run Script Action:

In Xcode 6:

#!/bin/bash

PACKAGES="${ARCHIVE_PATH}/Packages"
  
PACKAGE_NAME=`echo "$PRODUCT_NAME" | sed "s/ /_/g"`
ARCHIVE_FILENAME="$PACKAGE_NAME.pkg"
PKG="${OBJROOT}/../BuildProductsPath/${CONFIGURATION}/${ARCHIVE_FILENAME}"

if [ -f "${PKG}" ]; then
    mkdir "${PACKAGES}"
    cp -r "${PKG}" "${PACKAGES}"
fi

In Xcode 5, use this value for PKG instead:

PKG="${OBJROOT}/ArchiveIntermediates/${TARGET_NAME}/BuildProductsPath/${CONFIGURATION}/${ARCHIVE_FILENAME}"

In case your version control doesn't store Xcode Scheme information I suggest to add this as shell script to your project so you can simple restore the action by dragging the script from the workspace into the post-action.

Scripting

There are two different kinds of scripting: JavaScript in Distribution Definition Files and Shell Scripts.

The best documentation about Shell Scripts I found in WhiteBox - PackageMaker How-to, but read this with caution because it refers to the old package format.

Apple Silicon

In order for the package to run as arm64, the Distribution file has to specify in its hostArchitectures section that it supports arm64 in addition to x86_64:

<options hostArchitectures="arm64,x86_64" />

Additional Reading

Known Issues and Workarounds

Destination Select Pane

The user is presented with the destination select option with only a single choice - "Install for all users of this computer". The option appears visually selected, but the user needs to click on it in order to proceed with the installation, causing some confusion.

Example showing the installer bug

Apples Documentation recommends to use <domains enable_anywhere ... /> but this triggers the new more buggy Destination Select Pane which Apple doesn't use in any of their Packages.

Using the deprecate <options rootVolumeOnly="true" /> give you the old Destination Select Pane. Example showing old Destination Select Pane


You want to install items into the current user’s home folder.

Short answer: DO NOT TRY IT!

Long answer: REALLY; DO NOT TRY IT! Read Installer Problems and Solutions. You know what I did even after reading this? I was stupid enough to try it. Telling myself I'm sure that they fixed the issues in 10.7 or 10.8.

First of all I saw from time to time the above mentioned Destination Select Pane Bug. That should have stopped me, but I ignored it. If you don't want to spend the week after you released your software answering support e-mails that they have to click once the nice blue selection DO NOT use this.

You are now thinking that your users are smart enough to figure the panel out, aren't you? Well here is another thing about home folder installation, THEY DON'T WORK!

I tested it for two weeks on around 10 different machines with different OS versions and what not, and it never failed. So I shipped it. Within an hour of the release I heart back from users who just couldn't install it. The logs hinted to permission issues you are not gonna be able to fix.

So let's repeat it one more time: We do not use the Installer for home folder installations!


RTFD for Welcome, Read-me, License and Conclusion is not accepted by productbuild.

Installer supported since the beginning RTFD files to make pretty Welcome screens with images, but productbuild doesn't accept them.

Workarounds: Use a dummy rtf file and replace it in the package by after productbuild is done.

Note: You can also have Retina images inside the RTFD file. Use multi-image tiff files for this: tiffutil -cat Welcome.tif Welcome_2x.tif -out FinalWelcome.tif. More details.


Starting an application when the installation is done with a BundlePostInstallScriptPath script:

#!/bin/bash

LOGGED_IN_USER_ID=`id -u "${USER}"`

if [ "${COMMAND_LINE_INSTALL}" = "" ]
then
    /bin/launchctl asuser "${LOGGED_IN_USER_ID}" /usr/bin/open -g PATH_OR_BUNDLE_ID
fi

exit 0

It is important to run the app as logged in user, not as the installer user. This is done with launchctl asuser uid path. Also we only run it when it is not a command line installation, done with installer tool or Apple Remote Desktop.


catlan
  • 25,100
  • 8
  • 67
  • 78
  • 10
    This is an excellent tutorial, but assumes the existence of prefabricated bundles. If I were to, for example, install a single file into `/tmp` to be processed later in a postflight script, how do I structure the component list? All available documentation appears to assume that the developer has generated it with `--analyze`, at least initially. – bug Oct 21 '12 at 18:11
  • 1
    If you don't need to change anything in the `Component Property List` you don't need to run `--analyze`. For postprocessing files I suggest put them all into a pkg and set that pkg install location to `/tmp`. But maybe I misunderstand your question. If so post it in a more detailed version on SO. – catlan Oct 21 '12 at 19:53
  • 1
    Did you ever have any trouble at the `--expand` step? Every package I've tried to expand results in the error, "Could not open package for expansion." – bug Nov 20 '12 at 18:22
  • 2
    Please notice that it makes absolutely no sense to make packages via the command line, trying to escape all the bugs in the packaging app. Rather see my comment below on using the "Packages" application by Stéphane Sudre which solves all problems for you! – Bram de Jong Aug 14 '13 at 11:24
  • Codesign on the app runs after the build phases. This means that the installer will be signed, but the .app bundles will not be. How to solve this? – Nikolay Tsenkov May 30 '14 at 07:40
  • This tutorial looks like the one exactly what I need. Can this be explained with no Xcode or its env variables involved, as I have app that is made out of C++ and Cocoa. I'm looking for everything from command line. – J Bourne Jun 16 '14 at 14:30
  • How did you get the main text area (hot to install, select destination) to be transparent so that you can see through the background? – mateuscb May 29 '15 at 18:26
  • Hm, that's default, or was. Not sure if it is still transparent in Yosemite. – catlan May 30 '15 at 08:15
  • Thanks for this tutorial. One of the things I'm running into when Signing the installer is that I need to do it by having physical access to the machine (cannot do it over ssh). This is because when I run the pkgutil --sign command from terminal, it pops up the username/password box. Do you have any suggestions about how this can be done another way such that it is purely from command line? – shehzan Sep 28 '15 at 15:12
  • @shehzan isn't there an option to always grant access? – catlan Sep 28 '15 at 16:06
  • Wouldn't that be a security hazard though? – shehzan Sep 28 '15 at 17:43
  • 1
    When you're using `pkgbuild` to create the `.pkg` file, you'll need to use the `--identifier com.mycompany.myproduct` flag where it's your company and your product name. Otherwise, you'll get the error "No package identifier specified and not exactly one component to derive it from." – Volomike Nov 26 '15 at 05:09
  • 9
    @BramdeJong "makes no sense". I disagree. Apple maintains the command line tools. Packages is a third-party app that isn't community supported and may break in the future if Apple changes something drastic. For me, I'd rather know the command line technique so that, if Apple changes something drastic, then I can keep running. – Volomike Nov 26 '15 at 06:22
  • Is there a way to tell the installer to pull from a remote URL for an internal package or component, instead of pulling from inside the PKG file? Second, is there a way that I can change the message that says "Running package scripts..." to something like "Downloading files. Please wait..." ? – Volomike Nov 29 '15 at 02:30
  • @Volomike I remember it was possible but don't find any docs about it. Also I never saw it used by anybody ... – catlan Nov 29 '15 at 03:07
  • 7
    $ pkgbuild --root ./HelloWorld.app is wrong (assuming that .app is an actual app bundle). pkgbuild operates on a destination root :i.e.: a folder that CONTAINS a bundle generated by the xcode tool chain. So the argument to pkgbuild is the path to the containing folder of the bundle we want to package. Failing to get this right results in a package that contains just the app contents folder. It won't install as an actual app bundle. The giveaway is in the component plist. If that doesn't contain a RootRelativeBundlePath entry specifying the app bundle then you have screwed up. – Jonathan Mitchell Jul 22 '16 at 21:25
  • @JonathanMitchell Thanks for the info. To fix it, you either create one more directory over the app bundle or maybe use pkgbuild --component – Siva Prakash May 07 '19 at 11:23
  • Does anyone know if the "Destination Select Pane" problem has already been reported to Apple ? I still reproduce it on macOS Catalina (10.15.3) when enable_currentUserHome is true and the other attributes are set to no in the domain tag. – Bemipefe Jun 24 '20 at 08:18
  • is it possible to add an extra screen with a text field and ask activation key from a user? – Parag Bafna Jul 07 '21 at 11:05
  • 1
    @ParagBafna it is possible with installer plug-ins, but not recommended. You get a warning screen like seen here when using installer plug-ins https://stackoverflow.com/questions/67138560/on-macos-when-adding-an-installer-plugin-to-a-package-an-alert-appears-to-the/67313453#67313453 – catlan Jul 07 '21 at 11:21
  • A pretty big gotcha I found, if you call `productbuild` from an arbitrary location with absolute paths, it can fail to bundle the component `.pkg`s resulting in a way-too-small installer. If you change to the proper directory and use relative paths, this bug goes away. This snagged me for quite a bit, my `productbuild` installers would be only 16KB. Reference: (sorry for the obscure build system) https://github.com/qzind/tray/pull/770/commits/45d058b1f40fe91cd4bf9eccf0ad677c171b9e6f. – tresf Aug 27 '21 at 17:11
  • @BramdeJong -- I've used Packages for a decade. Does it even still work with Apple Silicon universal builds? – SMGreenfield Oct 21 '21 at 23:50
199

There is one very interesting application by Stéphane Sudre which does all of this for you, is scriptable / supports building from the command line, has a super nice GUI and is FREE. Sad thing is: it's called "Packages" which makes it impossible to find in google.

http://s.sudre.free.fr/Software/Packages/about.html

I wished I had known about it before I started handcrafting my own scripts.

Packages application screenshot

Jay Taylor
  • 13,185
  • 11
  • 60
  • 85
Bram de Jong
  • 2,063
  • 1
  • 13
  • 7
  • 12
    I cannot believe that this post doesn't have more point. That software is amazing and it supports building from the command line. – Cesar Mendoza Apr 16 '13 at 21:42
  • 1
    I added "supports building from the command line" to reflect your comment. – Bram de Jong Apr 18 '13 at 14:07
  • I read the entire tutorial above and was so relieved to see that there is an App that can do the ugly things for me – codingFriend1 Jul 07 '13 at 15:30
  • 1
    Anyone tried to sign a package with this tool? I cannot get the "Set Certificate" menu item to activate.... – GTAE86 Jan 23 '14 at 22:20
  • "Set Certificate" is only available when you choose "Flat Package". I can't submit my app packaged with this tool using Application Loader though; I get several warnings about not using PackageMaker "or other tools", warnings about javascript in the installer etc. Any ideas? – jazzwhistle Feb 11 '14 at 09:35
  • 2
    @user283182: Very late bump, surely you've already figured it out, but maybe this will help others - I think the issue you are facing is detailed in the [Mac App Store Review Guidelines]( https://developer.apple.com/app-store/review/guidelines/mac/#terms-conditions), rule 2.14: "Apps must be packaged and submitted using Apple's packaging technologies included in Xcode - no third party installers allowed." – elder elder Sep 23 '14 at 19:55
  • 4
    I wish this was a payed app and the developer is constantly updating and patching. Packages.app is awesome especially if you want to quickly deploy your app. It took me total of 3 min to install read the Overview, set up my project and create the installable package. Big kudos to Stéphane. – Nikolay Christov Sep 28 '14 at 01:14
  • I really wish this was the very first answer! – Jay Taylor Jul 06 '15 at 15:44
  • I use Packages and like it very much. The author generously helped me many times. Integration of Packages App with Xcode (using Packages command-line tools in build-step scripts) has several glitches and flaws I wish were ironed (Example, changing the version of the package in a build-step modifies the Packages project and dirties your repository. Author told me he intends to open-source it on github (or similar) for others to participate - but it didn't happen as of yet. Still it is the best tool around. – Motti Shneor Jul 25 '16 at 08:23
  • This is very useful in building MAC Desktop App, however it has some limitation , like if you do Uninstall then also label and message etc shows "Installed Successfully". Any workaround for the same ? – Pragnesh Karia Aug 22 '16 at 08:13
  • What Alternative like this app is there? (free or paid). –  Jan 14 '17 at 21:32
  • This stopped working for me from High Sierra. I'm so sad ;( – Warpzit Oct 05 '17 at 10:36
  • @MottiShneor FYI, the source code is opened on github: https://github.com/packagesdev/packages – Bill Hoo Mar 19 '18 at 21:48
  • Packages.app from Stéphane Sudre is a gem for any developer creating /pkg distribution. It allowed me to replace and enhance the completely defunct PackageMaker on Catalina. Merci beaucoup Stéphane. – Klajd Deda Apr 12 '20 at 16:22
  • Hello @everyone i am using this "PACKAGES" tool to create package, I am facing one problem in Installer UI. - Destination select - Installation Type -> "Change Install Location" button I want to hide this "Change install Location" button from Installer UI. Please let me know how to hide it permanently. Refer Image: https://i.stack.imgur.com/xr12Z.png – Akash May 08 '20 at 16:51
  • I'm not sure why it is so hard to create an installer for a macOS app these days? Btw, this tool is totally defunct now. I tried to use it on macOS v.13 and the presentation tab was blank. Here's a [hidden link](http://s.sudre.free.fr/files/Packages_1211_dev.dmg) for v.1211 of this tool that may work still. But it seems like the developer gave up on it. (The source of the link is the [github page](https://github.com/packagesdev/packages/issues/127).) – c00000fd Apr 05 '23 at 13:14
4

FYI for those that are trying to create a package installer for a bundle or plugin, it's easy:

pkgbuild --component "Color Lists.colorPicker" --install-location ~/Library/ColorPickers ColorLists.pkg
gngrwzrd
  • 5,902
  • 4
  • 43
  • 56
3

A +1 to accepted answer:

Destination Selection in Installer

If domain (a.k.a destination) selection is desired between user domain and system domain then rather than trying <domains enable_anywhere="true"> use following:

<domains enable_currentUserHome="true" enable_localSystem="true"/>

enable_currentUserHome installs application app under ~/Applications/ and enable_localSystem allows the application to be installed under /Application

I've tried this in El Capitan 10.11.6 (15G1217) and it seems to be working perfectly fine in 1 dev machine and 2 different VMs I tried.

JamesWebbTelescopeAlien
  • 3,547
  • 2
  • 30
  • 51
  • This works well, but with a GOT'CHA: If you first install per-user, then install per-machine, the install will be in the user-dir, not the machine-dir, but with sudo-rights. The opposite is not the case: you can install per-machine, and then per-user afterwards and have it in both places. – Terje Dahl Jan 10 '19 at 18:51
  • @TerjeDahl yes this is because post installation the bundle is moved to the location same bundle id was installed previously by installer (and installer knows that). This can be prevented by some settings in the manifest file that I dont remember right now. – JamesWebbTelescopeAlien Jan 10 '19 at 20:37
  • @ PnotNP Ah. If you would be so kind as to come back with those setting if you could remember, then that would be great! – Terje Dahl Jan 12 '19 at 11:32
2

Here is a build script which creates a signed installer package out of a build root.

#!/bin/bash
# TRIMCheck build script
# Copyright Doug Richardson 2015
# Usage: build.sh
#
# The result is a disk image that contains the TRIMCheck installer.
#

DSTROOT=/tmp/trimcheck.dst
SRCROOT=/tmp/trimcheck.src

INSTALLER_PATH=/tmp/trimcheck
INSTALLER_PKG="TRIMCheck.pkg"
INSTALLER="$INSTALLER_PATH/$INSTALLER_PKG"

#
# Clean out anything that doesn't belong.
#
echo Going to clean out build directories
rm -rf build $DSTROOT $SRCROOT $INSTALLER_PATH
echo Build directories cleaned out


#
# Build
#
echo ------------------
echo Installing Sources
echo ------------------
xcodebuild -project TRIMCheck.xcodeproj installsrc SRCROOT=$SRCROOT || exit 1

echo ----------------
echo Building Project
echo ----------------
pushd $SRCROOT
xcodebuild -project TRIMCheck.xcodeproj -target trimcheck -configuration Release install || exit 1
popd

echo ------------------
echo Building Installer
echo ------------------
mkdir -p "$INSTALLER_PATH" || exit 1

echo "Runing pkgbuild. Note you must be connected to Internet for this to work as it"
echo "has to contact a time server in order to generate a trusted timestamp. See"
echo "man pkgbuild for more info under SIGNED PACKAGES."
pkgbuild --identifier "com.delicioussafari.TRIMCheck" \
    --sign "Developer ID Installer: Douglas Richardson (4L84QT8KA9)" \
    --root "$DSTROOT" \
    "$INSTALLER" || exit 1


echo Successfully built TRIMCheck
open "$INSTALLER_PATH"

exit 0
Doug Richardson
  • 10,483
  • 6
  • 51
  • 77