2

I am trying to figure out the best way to distribute a graphical Qt application to users of Mac OS X.

I have learned about pkgbuild, productbuild, and used them to create flat packages with a .pkg extension.

However, I am seeing some very strange behavior when I try to install such a flat package on Mac OS X 10.11. It looks like the Mac OS X installer for these packages searches my home directory for the application I am trying to install. If it finds the application there, then it actually steals that application, changing the ownership of those files to root, and it fails to install the application in the /Applications folder where I wanted it to be installed.

The expected behavior is that the package should always install my application into the /Applications directory, and it should not modify the ownership of any files in my home directory.

This seems really weird. What is going on and how I can fix it? The flat packages seem nice, but I want to guarantee that they always install the application in the right place and I don't want them to be modifying files in the user's home directory.

Steps to reproduce the problem

Install Homebrew in your home directory.

Run brew install qt5 to install Qt5.

Make a new directory with these three files:

Info.plist.in:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleExecutable</key>
    <string>main</string>
    <key>CFBundleIconFile</key>
    <string>app.icns</string>
    <key>CFBundleIdentifier</key>
    <string>com.example.abc.app</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>test thing</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.1.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0.0</string>
    <key>NSHumanReadableCopyright</key>
    <string>public domain</string>
</dict>
</plist>

main.cpp:

#include <stdio.h>
int main(int argc, char *argv[])
{
  (void)argc; (void)argv;
  printf("hello\n");
  return 0;
}

test.sh:

c++ -isystem ~/opt/qt5/lib/QtCore.framework/Headers \
  ~/opt/qt5/lib/QtCore.framework/QtCore \
  main.cpp -o main

rm -rf "staging"
mkdir -p "staging/abc.app/Contents/"{MacOS,Resources}
cp main "staging/abc.app/Contents/MacOS/"
cp Info.plist.in "staging/abc.app/Contents/Info.plist"
"$(brew --prefix qt5)/bin/macdeployqt" "staging/abc.app"

pkgbuild --identifier com.example.abc.pkg \
  --version 1.0.0 \
  --root staging \
  --install-location /Applications \
  app.pkg

Now run test.sh by typing ./test.sh in a Terminal. On my computer, the output looks something like this:

pkgbuild: Inferring bundle components from contents of staging
pkgbuild: Adding component at abc.app
pkgbuild: Adding component at abc.app/Contents/Frameworks/QtWidgets.framework
pkgbuild: Adding component at abc.app/Contents/Frameworks/QtGui.framework
pkgbuild: Adding component at abc.app/Contents/Frameworks/QtCore.framework
pkgbuild: Adding component at abc.app/Contents/Frameworks/QtPrintSupport.framework
pkgbuild: Wrote package to app.pkg

(Clearly, pkgbuild is detecting components inside my staging folder. I wish it would not do that. I want it to simply install all those files into the user's /Applications directory. If it treats the files specially just because they have Info.plist files, that is probably a bad thing.)

Next, install the package by running open app.pkg and following the on-screen GUI:

standard UI for Mac OS X flat package

Run ls /Applications/abc.app and you can see that the application was not installed in the expected place.

Run ls -l staging or find . -uid root and you can see that the files in staging/abc.app are now all owned by the root user, which is unexpected.

Now delete the staging directory with sudo rm -rf staging. Try installing the package again. This time, since the installer didn't find the app in my home directory, it has successfully installed it to /Applications.

Alternatives

I am not interested in just packaging the .app folder into a .dmg file because my app will actually have a GUI and a command-line component, and it would be hard to get the command-line component onto the user's path with that method. With flat packages, I can easily get the command-line component onto the path by adding a file to /etc/paths.d.

Are there better tools for authoring Mac OS X flat packages?

Is the source code of pkgbuild available so I can figure out what it is doing?

I might try the more complex procedure described in this answer to see if that helps.

Community
  • 1
  • 1
David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • Did you check if cmake support external stuff to be bundled to the pkg? cmake+Qt work very well together (I gave away the qmake stuff) – bibi Feb 16 '16 at 06:46
  • I found CMake's Mac bundle support to be annoying so I'm just making the bundle myself using a simple shell script. Anyway, my question was more about how Mac OS X packages work. CMake does work well with Qt. – David Grayson Feb 16 '16 at 08:21
  • OS X doesn't like multiple copies of the same application with the same URI and version number, as [mentioned here](http://stackoverflow.com/questions/33204293/services-menu-launches-wrong-application-bundle/33211493#33211493). Using the Console app, look at the Installer log when running the installation and it will show you what it's doing. You'll likely see that it already knows about the presence of the app on your system, so won't install into /Applications. – TheDarkKnight Feb 16 '16 at 10:18

1 Answers1

0

Here is one solution I found. I am not sure if it is optimal, but it seems to work.

First, generate an empty list of components:

pkgbuild --analyze zzz --root nocomponents.plist

(This command actually succeeds even though the zzz directory does not exist.)

Later, when you are building your package, supply this option to pkgbuild:

--components-plist nocomponents.plist
David Grayson
  • 84,103
  • 24
  • 152
  • 189