End to End GUI Development with Qt5
上QQ阅读APP看书,第一时间看更新

OS X

First, build the solution using the kit of your choice in the Release mode. You already know that if we press the Run button in Qt Creator, our app launches and all is well. However, navigate to the cm-ui.app file in Finder and try and launch it directly; with this, things aren’t quite so rosy:

The problem here is missing dependencies. We can use otool to take a look at what those dependencies are. First, copy the cm-ui.app package to a new directory—cm/installer/osx.

This isn’t strictly necessary, but I like to keep build and deployment files separate. This way, if we make a code change and rebuild the solution, we will only update the app in the binaries folder, and our deployment files remain untouched.

Next, have a poke around inside the app package and see what we’re working with. In Finder, Ctrl and click on the cm-ui.app we just copied to the installer folder and select Show Package Contents. The bit we’re interested in is the Contents/MacOS folder. In there, you will find our cm-ui application executable.

With that identified, open up a command terminal, navigate to cm/installer/osx, and run otool on the executable:

$ otool -L cm-ui.app/Contents/MacOS/cm-ui

You will see an output the same as (or similar to) the following:

cm-ui:
libcm-lib.1.dylib (compatibility version 1.0.0, current version 1.0.0)
@rpath/QtQuick.framework/Versions/5/QtQuick (compatibility version 5.9.0, current version 5.9.1)
@rpath/QtQml.framework/Versions/5/QtQml (compatibility version 5.9.0, current version 5.9.1)
@rpath/QtNetwork.framework/Versions/5/QtNetwork (compatibility version 5.9.0, current version 5.9.1)
@rpath/QtCore.framework/Versions/5/QtCore (compatibility version 5.9.0, current version 5.9.1)
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
@rpath/QtGui.framework/Versions/5/QtGui (compatibility version 5.9.0, current version 5.9.1)
@rpath/QtXml.framework/Versions/5/QtXml (compatibility version 5.9.0, current version 5.9.1)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.5.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.50.2)

Let’s remind ourselves of the dependencies we need to consider and look at how they relate to the output we’ve just seen:

  • Custom libraries we’ve written or added to our solution manually (cm-lib). This is the libcm-lib.1.dylib reference. The fact that there is no path component suggests that the tool isn’t quite sure where this file is located. Should it be in the same folder as the executable itself? Should it be in the standard /usr/lib/ folder? Fortunately, we can specify the location of this file when we package our app.
  • The parts of the Qt framework that our application links to. QtQuick, QtQml, and such are all the framework modules we directly reference in our cm-ui code. Some of them are explicitly brought in via the QT variable in our cm-ui.pro file and others are implicitly included using things like QML.
  • Any internal dependencies of the Qt framework itself. We don’t see those listed earlier, but if we were to run otool against the QtQuick module, you would see that it is dependent on QtQml, QtNetwork, QtGui, and QtCore. There are also several system level libraries required, such as OpenGL, which we haven’t explicitly coded against but are used by Qt.
  • Any libraries required by the C++ compiler we have built the application with; libc++.1.dylib stands out here.

To bundle all of our dependencies manually, we can copy them all inside the app package and then perform some reconfiguration steps to update the location metadata we saw from otool.

Let’s pick one of the framework dependencies—QtQuick—and quickly work through what we will have to do to achieve this, and then we’ll move on to the really handy tool that does all of this very unpleasant grunt work for us.

First, we will create a Frameworks directory where the system will search for the bundled dependencies:

$ mkdir cm-ui.app/Contents/Frameworks

Next, we will physically copy the referenced file to that new directory. We know where to look for the existing file on our development machine, thanks to the preceding LC_RPATH entry, in this case /Users/<Your Username>/Qt5.9.1/5.9.1/clang_64/lib:

$ cp -R /Users/<Your Username>  /Qt5.9.1 /5.9.1/clang_64 /lib/ QtQuick.framework cm-ui.app/Contents/Frameworks

We then need to change the shared library identification name for the copied library file using install_name_tool:

$ install_name_tool -id @executable_path /../Frameworks / QtQuick.framework/Versions/5/QtQuick cm-ui.app /Contents /Frameworks / QtQuick.framework/Versions/5/QtQuick

The syntax here is install_name_tool -id [New name] [Shared library file]. To get to the library file (not the framework package, which is what we copied), we drill down to Versions/5/QtQuick. We set the ID of that binary to where the executable will look to find it, which, in this case, is in the Frameworks folder a level up (../) from the executable file itself.

Next, we also need to update the executable’s list of dependencies to look in the correct place for this new file:

$ install_name_tool -change @rpath/QtQuick.framework/Versions/5/QtQuick @executable_path/../Frameworks/QtQuick.framework/Versions/5/QtQuick cm-ui.app/Contents/MacOs/cm-ui

The syntax here is install_name_tool -change [old value] [new value] [executable file]. We want to change the old @rpath entry for QtQuick to be the new Frameworks path we’ve just added. Again, we use the @executable_path variable so that the dependencies are always located in the same place relative to the executable. Now, the metadata in the executable and the shared library both match each other and relate to the Frameworks folder, which we have now added to our app package.

Remember, that’s not all, because QtQuick itself has dependencies, so we will need to copy and reconfigure all of those files too and then check their dependencies. Once we’ve exhausted the whole dependency tree for our cm-ui executable, we also need to repeat the process for our cm-lib library. As you can imagine, this gets tedious very quickly.

Fortunately, the macdeployqt Qt Mac Deployment Tool is just what we need here. It scans an executable file for Qt dependencies and copies them across to our app package for us as well as for handling the reconfiguration work. The tool is located in the bin folder of the installed kit you have built the application with, for example, /Qt/5.9.1/5.9.1/clang_64/bin.

In a command terminal, execute macdeployqt as follows (assuming that you are in the cm/installer/osx directory):

$ <Path to bin>/macdeployqt cm-ui.app -qmldir=<Qt Projects>/cm/cm-ui -libpath=<Qt Projects>/cm/binaries/osx/clang/x64/release

Remember to replace the parameters in angle brackets with the full paths on your system (or add the executable paths to your system PATH variable).

The qmldir flag tells the tool where to scan for QML imports and is set to our UI project folder. The libpath flag is used to specify where our compiled cm-lib file lives.

The output of this operation will be as follows:

File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquick2plugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2plugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2materialstyleplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2universalstyleplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libwindowplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquicktemplates2plugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2materialstyleplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2materialstyleplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2universalstyleplugin.dylib"
File exists, skip copy: "cm-ui.app/Contents/PlugIns/quick/libqtquickcontrols2universalstyleplugin.dylib"
WARNING: Plugin "libqsqlodbc.dylib" uses private API and is not Mac App store compliant.
WARNING: Plugin "libqsqlpsql.dylib" uses private API and is not Mac App store compliant.
ERROR: no file at "/opt/local/lib/mysql55/mysql/libmysqlclient.18.dylib"
ERROR: no file at "/usr/local/lib/libpq.5.dylib"

Qt is a bit quirky with the SQL module, whereby if you use one SQL driver, it will try and package them all; however, we know that we are only using SQLite and don’t need MySQL or PostgreSQL, so we can safely ignore those errors.

Once executed, you should be able to Show Package Contents  again in Finder and see all the dependencies ready and waiting for deployment, as illustrated:

What a huge timesaver! It has created the appropriate file structure and copied all the Qt modules and plugins for us, along with our cm-lib shared library. Try and execute the cm-ui.app file now, and it should successfully launch the application.