by Trenton Schulz
The day has finally come. After spending many long hours, the features are implemented, the code is optimized, the testing is complete: The application is ready to leave your protective care and head out into the real world. This article is the first in a series of three covering deployment of Qt applications on various platforms. This one focuses on Mac OS X.
Mac OS X handles most applications as "bundles". A bundle is a directory structure that groups related files together. Bundles are used for GUI applications, frameworks, and installer packages. These are presented to the user as one file in the Finder. When set up correctly, bundles make for easy deployment. All one needs to do is to archive the application using some preferred method. Users then open the archive and drag the application to wherever they please and are ready to go.
The diagram below shows the directory structure of a bundle. Files are shown in italics; directories are shown in roman.
Deploying an application on Mac OS X does not involve any C++ programming. All you need is to build your application in release mode (the default) and to follow the procedure shown in this article. To illustrate the procedure, we will show how to deploy the demo example from the Qt distribution. The demo application is fairly simple, yet it is interesting because it can use SQL driver plugins.
To keep things simple, we will stick to command-line tools here instead of using the still rapidly evolving Xcode IDE. The first tool is otool. It gives us information about an executable. We will use it to find out what type of shared libraries a Qt application is referencing. You get this by running otool -L on the executable. For instance, here's how to obtain the information about Qt's demo example:
otool -L $QTDIR/examples/demo/demo.app/Contents/MacOS/demo
Developers familiar with Unix will notice that this is quite similar to the ldd tool.
The other tool that we will use is called install_name_tool. This tool allows us to change information about where an application looks for libraries or what these libraries are called. We will see some examples of this shortly.
Static Linking |
One solution to distributing the application is to build everything statically. The big advantage is that everything the application needs will be part of the executable and the application won't depend on an external Qt library. It will also load and execute slightly faster, since it won't use symbols resolved from a shared Qt library. Furthermore, you can be sure that if the executable is copied onto another machine running the same version of Mac OS X, it will still work.
The main drawback with static linking is that the executable itself swells to include the size of Qt. Another disadvantage is that, since Qt is linked statically, any updates in Qt or in your application require rebuilding the entire application again. Finally, statically linked applications cannot take advantage of Qt plugins.
To build an application statically, we need a static Qt library. We can either re-configure Qt to be built as a static library (by passing the -static flag to the configure script) or type the following:
cd $QTDIR/src
make staticlib
This will use any object files lying around from previous builds to generate a static library and is therefore much quicker than a full re-build.
Once we have the static library, we need to re-link the application's object file against the static library to produce a new executable. To achieve this, we need to add the following lines to the application's .pro file:
mystaticconfig { QMAKE_LIBS_QT = QMAKE_LIBS_QT_THREAD = LIBS += $(QTDIR)/lib/libqt.a -lz -framework Carbon }
This ensures that the static Qt library is chosen if both a static and a shared Qt library are available. Without the additional lines in the .pro file, most linkers would choose the shared library over the static library.
If Qt was built with multithreaded support enabled, replace libqt.a with libqt-mt.a in the LIBS line above.
After editing the .pro file, we must regenerate the makefile using the new configuration and re-link our application:
make clean
qmake "CONFIG+=mystaticconfig"
make
The application will now be built. We can verify that the application links against Qt statically by running the otool and checking that the Qt library does not appear in the output:
otool -L $QTDIR/examples/demo/demo.app/Contents/MacOS/demo
The size of the executable should also give us a clue.
Shared Libraries |
Another way of tying the Qt library to the application is to put it into the bundle as a "private framework". This makes it possible to upgrade Qt and the executable independently.
First, we must copy the library into the bundle. Libraries and frameworks belong in the "Contents/Frameworks" section of the bundle:
cd $QTDIR/examples/demo mkdir -p demo.app/Contents/Frameworks cp $QTDIR/lib/libqt.3.dylib demo.app/Contents/Frameworks
If you use a multithreaded version of Qt, replace libqt.3.dylib with libqt-mt.3.dylib in the last line above.
Now that we have copied the Qt library into the bundle, we must update both the library and the executable so that they know where they can be found. This is where the install_name_tool command-line tool comes in handy. For the Qt library:
install_name_tool \ -id @executable_path/../Frameworks/libqt.3.dylib \ demo.app/Contents/Frameworks/libqt.3.dylib
And for the executable:
install_name_tool \ -change libqt.3.dylib \ @executable_path/../Frameworks/libqt.3.dylib \ demo.app/Contents/MacOS/demo
The "@executable_path" symbol should to be typed as is; it must not be replaced by the real path to the executable on your machine. As usual, all occurrences of libqt.3.dylib should be replaced with libqt-mt.3.dylib if you enabled multithreading support in Qt.
If you run otool now, the output should now contain a reference to "@executable_path/../Frameworks/libqt.3.dylib".
By putting the Qt library in the bundle's Contents/Frameworks directory, we ensure that the application will always use that version of the library, even if a more recent Qt library is installed in "/usr/lib" or in " /lib".
One final word: The DYLD_LIBRARY_PATH variable allows developers to specify where to look for libraries first, overriding the application's preference. This can be useful for debugging.
Plugins |
The Qt documentation states that Qt looks in several places for plugins. This is useful for development, but when we are deploying applications we often want to have more control over where Qt looks for plugins since we want to include them in the bundle to keep everything in one nice package.
Plugins can be put in the Contents/PlugIns section of a bundle. An advantage of using this location is that the plugins will show up in Finder and users can enable and disable them easily. For a Qt program to take advantage of this feature, we must add this call to main():
qApp->setLibraryPath(qApp->applicationDirPath() + "/../PlugIns");
This tells the application to only look in this directory for plugins. Naturally we must copy the plugins into this directory and also tell the plugins to look for the Qt library in the Frameworks (or SharedFrameworks) directory using the install_name_tool as we did before. Once we've done this, we can be sure that the application will only use the plugins included in the bundle.
Deploying on Panther and Jaguar |
The easiest way to ensure that an application works correctly on both Jaguar (Mac OS X 10.2) and Panther (Mac OS X 10.3) is to provide two bundles: one with the Qt library built on Jaguar, the other with the Qt library built on Panther. If we do this, the application itself can be built on either version of Mac OS X.
An alternative that usually works just as well is to build the application and the Qt library on Jaguar. The application should then work on Panther as well, although we recommend that you test it to make sure.
With Qt 3.3, it is now possible to do the opposite: Build Qt on Panther and deploy it on Jaguar. Again, we recommend that you test it on both platforms to make sure that it works correctly. We are aware of issues with the third-party PostgreSQL library that will arise if you build the PostgreSQL driver into Qt or want to use it as a plugin on Jaguar. In that case, you need to consider one of the two other options instead.
Conclusion |
We've touched quite briefly on the issues that arise when making a Qt application work with a simple drag and drop installation. If your installation is more elaborate, you might want to take a look at DeveloperTools/Conceptual/SoftwareDistribution for information about using packages for deployment. You might also want to read MacOSX/Conceptual/BPBundles, which describes the structure of a bundle. For more information about otool and install_name_tool, see the man pages.
Copyright © 2004 Trolltech | Trademarks |