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:
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.