Qt for macOS - Deployment

This document describes how to create a macOS bundle and make sure that the application finds the resources it needs at run-time. We demonstrate the procedures in terms of deploying the Plug & Paint example application that comes with the Qt installation package.

The Qt installers for macOS include a deployment tool that automates the procedures described here.

The Bundle

On macOS, a GUI application must be built and run from a bundle, which is a directory structure that appears as a single entity when viewed in the Finder. A bundle for an application typically contains the executable and all the resources it needs. Here is the snapshot of an application bundle structure:

Image non disponible

The bundle provides many advantages to the user:

  • It is easily installable as it is identified as a single entity.

  • Information about a bundle is accessible from code.

This is specific to macOS and beyond the scope of this document. For more information about bundles, see Apple's Developer Website.

qmake automatically generates a bundle for your application. To disable this, add the following statement to your application's project file (.pro):

 
Sélectionnez
CONFIG-=app_bundle

Static Linking

If you want to keep things simple and have a few files to deploy, you must build your application with statically linked libraries.

Building Qt Statically

Start by installing a static version of the Qt library. Remember that you cannot use plugins and that you must build the dependent libraries such as image formats, SQL drivers, and so on with static linking.

 
Sélectionnez
cd /path/to/Qt
./configure -static <other parameters>
make sub-src

You can check the various options that are available by running configure -help.

Linking the Application to the Static Version of Qt

Once Qt is built statically, the next step is to regenerate the makefile and rebuild the application. First, we must go into the directory that contains the application:

 
Sélectionnez
cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

Now run qmake to create a new makefile for the application, and do a clean build to create the statically linked executable:

 
Sélectionnez
make clean
qmake -config release
make

You probably want to link against the release libraries, and you can specify this when invoking qmake. If you have Xcode Tools 1.5 or higher installed, you may want to take advantage of "dead code stripping" to reduce the size of your binary even more. You can do this by passing LIBS+= -dead_strip to qmake in addition to the -config release parameter.

Now, provided that everything compiled and linked without any errors, we should have a plugandpaint.app bundle ready for deployment. Try installing the bundle on a machine running macOS that does not have Qt or any Qt applications installed.

You can check what other libraries your application links to using the otool:

 
Sélectionnez
otool -L plugandpaint.app/Contents/MacOs/plugandpaint

Here is what the output looks like for the statically linked Plug & Paint:

 
Sélectionnez
plugandpaint.app/Contents/MacOS/plugandpaint:
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
        (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
        (compatibility version 1.0.0, current version 10.0.0)
/usr/lib/libz.1.dylib
        (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
        (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
        (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
        (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
        (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
        (compatibility version 1.0.0, current version 88.0.0)

If you see Qt libraries in the output, it probably means that you have both dynamic and static Qt libraries installed on your machine. The linker always chooses dynamic linking over static. If you want to use only static libraries, you can either:

  • move your Qt dynamic libraries (.dylibs) away to another directory while you link the application and then move them back,

  • or edit the Makefile and replace link lines for the Qt libraries with the absolute path to the static libraries.

For example, replace the following:

 
Sélectionnez
-lQtGui

with this:

 
Sélectionnez
/where/static/qt/lib/is/libQtGui.a

The Plug & Paint example consists of several components: The core application (Plug & Paint), and the Basic Tools and Extra Filters plugins. As we cannot deploy plugins using the static linking approach, the bundle we have prepared so far is incomplete. The application will run, but the functionality will be disabled due to the missing plugins. To deploy plugin-based applications we should use the framework approach, which is specific to macOS.

Frameworks

In this approach, ensure that the Qt runtime is redistributed correctly with the application bundle, and that the plugins are installed in the correct location so that the application finds them.

There are two ways to distribute Qt with your application in the frameworks approach:

  • Private framework within your application bundle.

  • Standard framework (alternatively use the Qt frameworks in the installed binary).

The former is good if you have Qt built in a special way, or want to make sure the framework is there. It just comes down to where you place the Qt frameworks.

The latter option is good if you have many Qt applications and you want them use a single Qt framework rather than multiple versions of it.

Building Qt as Frameworks

We assume that you already have installed Qt as frameworks, which is the default when installing Qt, in the /path/to/Qt directory. For more information on how to build Qt without Frameworks, visit the Qt for macOS - Specific Issues documentation.

When installing, the identification name of the frameworks is set. This name is used by the dynamic linker (dyld) to find the libraries for your application.

Linking the Application to Qt as Frameworks

After building Qt as frameworks, we can build the Plug & Paint application. First, we must go to the directory that contains the application:

 
Sélectionnez
cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

Run qmake to create a new makefile for the application, and do a clean build to create the dynamically linked executable:

 
Sélectionnez
make clean
qmake -config release
make

This builds the core application. Use the following to build the plugins:

 
Sélectionnez
cd ../plugandpaint/plugins
make clean
qmake -config release
make

Now run the otool for the Qt frameworks, for example Qt Gui:

 
Sélectionnez
otool -L QtGui.framework/QtGui

You would get the following output:

 
Sélectionnez
QtGui.framework/QtGui:
/path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
        (compatibility version 4.0.0, current version 4.0.1)
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
        (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
        (compatibility version 1.0.0, current version 10.0.0)
/path/to/Qt/QtCore.framework/Versions/4.0/QtCore
        (compatibility version 4.0.0, current version 4.0.1)
/usr/lib/libz.1.dylib
        (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
        (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
        (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
        (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
        (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
        (compatibility version 1.0.0, current version 88.0.0)

For the Qt frameworks, the first line (i.e. path/to/Qt/lib/QtGui.framework/Versions/4/QtGui (compatibility version 4.0.0, current version 4.0.1)) becomes the framework's identification name which is used by the dynamic linker (dyld).

But when you are deploying the application, your users may not have the Qt frameworks installed in the specified location. For that reason, you must either provide the frameworks in an agreed location, or store the frameworks in the bundle. Regardless of which solution you choose, you must make sure that the frameworks return the proper identification name for themselves, and that the application looks for these names. Luckily we can control this with the install_name_tool command-line tool.

The install_name_tool works in two modes, -id and -change. The -id mode is for libraries and frameworks, and allows us to specify a new identification name. We use the -change mode to change the paths in the application.

Let's test this out by copying the Qt frameworks into the Plug & Paint bundle. Looking at otool's output for the bundle, we can see that we must copy both the QtCore and QtGui frameworks into the bundle. We will assume that we are in the directory where we built the bundle.

 
Sélectionnez
mkdir plugandpaint.app/Contents/Frameworks
cp -R /path/to/Qt/lib/QtCore.framework
        plugandpaint.app/Contents/Frameworks
cp -R /path/to/Qt/lib/QtGui.framework
       plugandpaint.app/Contents/Frameworks

First we create a Frameworks directory inside the bundle. This follows the macOS application convention. We then copy the frameworks into the new directory. As frameworks contain symbolic links, we use the -R option.

 
Sélectionnez
install_name_tool -id @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
       plugandpaint.app/Contents/Frameworks/QtCore.framework/Versions/4.0/QtCore
install_name_tool -id @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
       plugandpaint.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui

Then we run install_name_tool to set the identification names for the frameworks. The first argument after -id is the new name, and the second argument is the framework that we want to rename. The text @executable_path is a special dyld variable telling dyld to start looking where the executable is located. The new names specifies that these frameworks are located in the directory directly under the Frameworks directory.

 
Sélectionnez
install_name_tool -change path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/MacOs/plugandpaint
install_name_tool -change path/to/qt/lib/QtGui.framework/Versions/4.0/QtGui
        @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
        plugandpaint.app/Contents/MacOs/plugandpaint

Now, the dynamic linker knows where to look for QtCore and QtGui. We must ensure that the application also knows where to find the library, using install_name_tool's -change mode. This basically comes down to string replacement, to match the identification names that we set earlier to the frameworks.

Finally, the QtGui framework depends on QtCore, so we must remember to change the reference for QtGui:

 
Sélectionnez
install_name_tool -change path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui

After this, we run otool again and see that the application can find the libraries.

The plugins for the Plug & Paint example makes it interesting. The basic steps we need to follow with plugins are:

  • put the plugins inside the bundle,

  • run the install_name_tool to check whether the plugins are using the correct library,

  • and ensure that the application knows where to look for the plugins.

We can put the plugins anywhere we want in the bundle, but the best location is to put them under Contents/Plugins. When we built the Plug & Paint plugins, based on the DESTDIR variable in their .pro file, the plugins' .dylib files are in the plugins subdirectory under the plugandpaint directory. We just have to move this directory to the correct location.

 
Sélectionnez
mv plugins plugandpaint.app/Contents

For example, If we run otool on the Basic Tools plugin's .dylib file, we get the following information.

 
Sélectionnez
libpnp_basictools.dylib:
libpnp_basictools.dylib
       (compatibility version 0.0.0, current version 0.0.0)
/path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
       (compatibility version 4.0.0, current version 4.0.1)
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
       (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
       (compatibility version 1.0.0, current version 10.0.0)
/path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
       (compatibility version 4.0.0, current version 4.0.1)
/usr/lib/libz.1.dylib
       (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
       (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
       (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
       (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
       (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
       (compatibility version 1.0.0, current version 88.0.0)

Then we can see that the plugin links to the Qt frameworks it was built against. As we want the plugins to use the framework in the application bundle, we change them the same way as we did for the application. For example for the Basic Tools plugin:

 
Sélectionnez
install_name_tool -change /path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/plugins/libpnp_basictools.dylib
install_name_tool -change /path/to/Qt