How-To Qt5.x Max AppStore
Contents
Overview
This is a How-To for getting your Qt based application successfully into the Mac AppStore.
If you want to include QtWebKit or QtWebKitWidgets in your AppStore application you will face several problems - if not skip the ICU section and continue with Qt 5.3.
Problems
If you use Qt as it is (without any modification) and use Application Loader to upload it to the AppStore you will probably get Apple's response:
Use of non-public APIs not permitted. Following non-public APIs are included:
<source lang=bash>
'usr/lib/libSystem.B.dylib'
+++ : bootstrap_look_up2
+++ : bootstrap_register2
'usr/lib/libicucore.A.dylib'
+++ : ubrk_getRuleStatus
+++ : ubrk_setUText
</source>
What does it mean? One or more Qt libraries uses APIs which are not declared public by Apple. In this case the QtWebKit implementation is making trouble (actually it is not a problem of Qt as Apple's own Safari App uses these APIs). It is not possible (please contact me if I am wrong) to use the WebKit2 implementation as there is currently no workaround for the bootstrap_*
issues. However Qt delivers both: WebKit1 and WebKit2 - so we can use WebKit1. The ubrk_*
issues can be fixed by compiling your own ICU libs and we let Qt link against them.
So we need to patch Qt and this How-To shows what has to be done.
ICU
First of all you need the latest ICU libs. Download them from http://site.icu-project.org/download. In this example we use version 53.1 but you can use any recent version. We also need to patch some files otherwise we will face linking problems with Qt later. Skip this section if you are not intent to use QtWebKit and/or QtWebKitWidgets.
<source lang=bash>
$ tar -zxf icu-53_1-src.tar
$ cd icu/source
</source>
By default ICU will add a version number suffix for symbols and therefore e.g. _UCNV_FROM_U_CALLBACK_ESCAPE
becomes _UCNV_FROM_U_CALLBACK_ESCAPE_53
. In WebKit those symbols are referenced without the version suffix and therefore Qt will not be able to link against the self-compiled ICU library. So we need to disable the version suffix:
<source lang=bash>
$ ./configure --prefix=/usr/local/icu53.1 --enable-renaming=no
</source>
After configuring ICU, it will prompt you with the following:
<source lang=C>
/* ICU customizations: put these lines at the top of uconfig.h */
/* -DU_DISABLE_RENAMING=1 */
- define U_DISABLE_RENAMING 1
</source>
So add #define U_DISABLE_RENAMING 1
to the file common/unicode/uconfig.h
:
<source lang=C>
/*
- Copyright (C) 2002-2014, International Business Machines
- Corporation and others. All Rights Reserved.
- file name: uconfig.h
- encoding: US-ASCII
- tab size: 8 (not used)
- indentation:4
- created on: 2002sep19
- created by: Markus W. Scherer
- /
- ifndef __UCONFIG_H__
- define __UCONFIG_H__
// Add it here:
- define U_DISABLE_RENAMING 1
[...]
</source>
Now compile ICU and install them:
<source lang=bash>
$ make -j4
[...]
$ sudo make install
</source>
The ICU libraries should now be available in /usr/local/icu53.1/libs
.
Qt 5.3
For Qt we need some more work todo. Change into the directory of Qt: <source lang=bash> $ cd qt-everywhere-enterprise-src-5.3.0 </source>
Patch
To remove the dependency from QtDeclarative patch the file qt.pro
in the top level directory (IMHO there is absolutely no reason why these libs should be dependent and link against QtDeclarative - many projects don't use QtQuick at all):
<source lang=diff>
--- qt.pro.org 2014-05-26 12:09:34.000000000 +0200
+++ qt.pro 2014-05-26 11:09:03.000000000 +0200
@@ -65,10 +65,10 @@
addModule(qtxmlpatterns, qtbase) addModule(qtdeclarative, qtbase, qtsvg qtxmlpatterns) addModule(qtquickcontrols, qtdeclarative)
-addModule(qtmultimedia, qtdeclarative) +addModule(qtmultimedia, qtbase)
addModule(qtwinextras, qtbase, qtdeclarative qtmultimedia) addModule(qtactiveqt, qtbase)
-addModule(qt3d, qtdeclarative) +addModule(qt3d, qtbase)
addModule(qtjsondb, qtdeclarative) addModule(qtsystems, qtbase, qtdeclarative) addModule(qtlocation, qtbase, qt3d qtsystems qtmultimedia)
@@ -76,7 +76,7 @@
addModule(qtconnectivity, qtbase $$ANDROID_EXTRAS, qtdeclarative) addModule(qtfeedback, qtdeclarative, qtmultimedia) addModule(qtpim, qtdeclarative, qtjsondb)
-addModule(qtwebkit, qtdeclarative, qtlocation qtmultimedia qtsensors, WebKit.pro) +addModule(qtwebkit, qtbase, qtlocation qtmultimedia qtsensors, WebKit.pro)
addModule(qttools, qtbase, qtdeclarative qtactiveqt qtwebkit) addModule(qtwebkit-examples, qtwebkit qttools) addModule(qtimageformats, qtbase)
</source>
If you don't use QtQuick rename the following directories to prevent Qt from building them:
<source lang=bash>
$ mv qtdeclarative qtdeclarative.org
$ mv qtquick1 qtquick1.org
$ mv qtquickcontrols qtquickcontrols.org
</source>
If you are not using QtWebKit and/or QtWebKitWidgets continue with Configure. For using WebKit patch file qtwebkit/Source/WTF/WTF.pri
to link against our own ICU libs:
<source lang=diff>
--- qtwebkit/Source/WTF/WTF.pri.org 2014-05-23 17:21:30.000000000 +0200
+++ qtwebkit/Source/WTF/WTF.pri 2014-05-23 17:23:43.000000000 +0200
@@ -11,8 +11,8 @@
mac { # Mac OS does ship libicu but not the associated header files. # Therefore WebKit provides adequate header files.
- INCLUDEPATH = $${ROOT_WEBKIT_DIR}/Source/WTF/icu $$INCLUDEPATH - LIBS += -licucore + INCLUDEPATH = /usr/local/icu53.1/include $$INCLUDEPATH + LIBS += -licui18n -licuuc -licudata -L/usr/local/icu53.1/lib
} else { contains(QT_CONFIG,icu) { win32: LIBS += -licuin -licuuc -licudt
</source>
Now we need to disable the WebKit2 implementation, patch file qtwebkit/Tools/qmake/mkspecs/features/configure.prf
:
<source lang=diff>
--- qtwebkit/Tools/qmake/mkspecs/features/configure.prf.org 2014-05-23 17:36:24.000000000 +0200
+++ qtwebkit/Tools/qmake/mkspecs/features/configure.prf 2014-05-23 17:37:46.000000000 +0200
@@ -49,7 +49,6 @@
WEBKIT_CONFIG += \ build_webkit1 \
- build_webkit2 \
build_tests \ $$WEBKIT_TOOLS_CONFIG
</source>
Configure
Now we can configure and build Qt (prefix it to your own needs) and omit the -commercial
and -confirm-license
switch if you are using the non-commercial version:
<source lang=bash>
$ ./configure -prefix /usr/local/qt/5.3.0 -no-icu -release -strip -commercial -confirm-license -no-rpath -nomake examples
[...]
$ make -j4
</source>
Install
After building Qt we install it in the predefined directory: <source lang=bash> $ sudo make install </source>
Post checks
Some Qt versions (mainly prior 5.3.0) do not build the path ID for all Qt frameworks correctly. In our case the path ../lib/libicudata.53.1.dylib
of QtWebKit.framework
will make trouble as it is relative. We can fix this using install_name_tool
.
To find out all relevant frameworks use otool
:
<source lang=bash>
$ cd /usr/local/qt/5.3.0/lib
$ otool -L Qt*/Qt*[^prl] | grep "\.\.\/" # for all files containing e.g. '../'
[...]
$ otool -L Qt*/Qt*[^prl] | grep "/usr/local/qt" # for all files containing absolute paths
[...]
</source>
After identifying the wrong frameworks we use install_name_tool
(this is an example fixing the ICU path but is representative for any other lib containing wrong paths):
<source lang=bash>
$ cd /usr/local/qt/5.3.0/lib/QtWebKit.framework
$ sudo install_name_tool -change ../lib/libicudata.53.1.dylib libicudata.53.dylib QtWebKit
</source>
Checking again with otool
and grep
no files should be listed anymore.
Bundle
Applications in Mac OS X are organized in a so called bundle which represents a well defined file system structure: <source lang=bash> MyApp.app/
Contents/ Info.plist # information about your app Frameworks/ # place of our Qt libs MacOS/ # application(s) PlugIns/ # plug-ins and your own libs (*dylib) Resources/ # entitlements, readme, icons, translations SharedSupport/ # optional files, documents
</source>
For creating such a bundle you can use macdeployqt
see http://qt-project.org/doc/qt-5/macosx-deployment.html. However this tool is not sufficient in many cases, so we will develop our own deployment script.
Entitlements
The AppStore requires an entitlements file containing several security options for our application. We store this file in $HOME/src/myapp.entitlements
. An example could be:
<source lang=xml>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key> <true/>
</dict> </plist> </source> This file will be used later for signing our application.
Structure
First we create the directories and copy all the files we need (this shell script is an example but you can use it and change it for your own needs): <source lang=bash>
- !/bin/bash
MYAPP="MyApp" DEST="$HOME/bundles/$MYAPP.app" # Our final App directory SRC="$HOME/src/$MYAPP.app" # Usually the App directory created by our Qt *.pro file ENTITLEMENTS="$HOME/src/$MYAPP.entitlements"
ICUDIR="/usr/local/icu53.1" ICULIBS="libicui18n.53 libicudata.53 libicuuc.53" QTDIR="/usr/local/qt/5.3.0" QTLIBS="QtCore QtNetwork QtSql QtGui QtSvg QtScript QtOpenGL QtWidgets QtWebKit QtWebKitWidgets \
QtPrintSupport QtXml QtPositioning QtSensors QtMultimedia QtMultimediaWidgets" # QtQml QtQuick
PLUGINS="sqldrivers imageformats iconengines platforms printsupport accessible \
position mediaservice" # playlistformats sensors sensorgestures bearer audio
- make clean & create pathes
rm -rf $DEST cp -Rp $SRC $DEST mkdir -p $DEST/Contents/Frameworks $DEST/Contents/PlugIns $DEST/Contents/SharedSupport cp -p $ENTITLEMENTS $DEST/Contents/Resources/
- copy Qt libs, plug-ins and ICU
for L in $QTLIBS ; do
cp -Rp $QTDIR/lib/$L.framework $DEST/$MYAPP.app/Contents/Frameworks # remove all unnecessary header files: rm -f $DEST/$MYAPP.app/Contents/Frameworks/$L.framework/Headers rm -rf $DEST/$MYAPP.app/Contents/Frameworks/$L.framework/Versions/5/Headers
done for P in $PLUGINS ; do
cp -Rp $QTDIR/plugins/$P/*.dylib $DEST/$MYAPP.app/Contents/PlugIns/$P/
done for I in $ICULIBS ; do
cp -p $ICUDIR/lib/$I.dylib $DEST/$MYAPP.app/Contents/PlugIns/icu/
done
- copy own application libs if necessary to /Contents/PlugIns/myapp/
</source>
Library paths
We need to change the library IDs and paths of all our libs for our application to create an exclusive fully self-contained bundle using again install_name_tool
- it provides the variable @executable_path
to offer a path relative to our executable in /Contents/MacOS
:
<source lang=bash>
DISTPLUGINS=`cd $DES/$MYAPP.app/Contents/PlugIns; ls -1 */*.dylib` # extract all our *.dylib libs
for I in $QTLIBS ; do
install_name_tool -id "@executable_path/../Frameworks/$I.framework/Versions/5/$I"\ "$DEST/$MYAPP.app/Contents/Frameworks/$I.framework/Versions/5/$I" install_name_tool -change $I.framework/Versions/5/$I\ @executable_path/../Frameworks/$I.framework/Versions/5/$I\ $DEST/$MYAPP.app/Contents/MacOS/$MYAPP # change references to Qt frameworks for L in $QTLIBS ; do # change all lib references in all Qt frameworks if [ $L = $I ] ; then continue; fi install_name_tool -change $I.framework/Versions/5/$I\ @executable_path/../Frameworks/$I.framework/Versions/5/$I\ $DEST/$MYAPP.app/Contents/Frameworks/$L.framework/Versions/5/$L done
done
for P in $DISTPLUGINS ; do # change ID for all *.dylib libs
install_name_tool -id "@executable_path/../PlugIns/$I" "$DEST/$MYAPP.app/Contents/PlugIns/$P" for L in $QTLIBS ; do # change any reference to Qt in our *.dylib libs install_name_tool -change $L.framework/Versions/5/$L\ @executable_path/../Frameworks/$L.framework/Versions/5/$L\ $DEST/$MYAPP.app/Contents/PlugIns/$P done
done
for L in $ICULIBS ; do
install_name_tool -id "@executable_path/../PlugIns/icu/$L.dylib"\ "$DEST/$MYAPP.app/Contents/PlugIns/icu/$L.dylib" for I in $ICULIBS ; do # change all references in ICU libs if [ $I = $L ] ; then continue; fi install_name_tool -change "$I.dylib" "@executable_path/../PlugIns/icu/$I.dylib"\ "$DEST/$MYAPP.app/Contents/PlugIns/icu/$L.dylib" done
done
- we do the same for additional own libs in /Contents/PlugIns/myapp
</source>
Check if every lib, framework and especially our executable contains the correct lib references i.e. no /usr/local/qt/...
is part of a path. Again use otool -L
.
Code sign
For uploading successfully to the Mac AppStore each of our files must be signed. By registering with Apple you get a Developer Application and a Developer Installer certificate. For signing the files we need the Developer Application certificate and for packaging the Developer Installer certificate. <source lang=bash> APPLCERT="3rd Party Mac Developer Application: <your ID>" INSTCERT="3rd Party Mac Developer Installer: <your ID>" DOMAIN="com.yourdomain" # must be the domain registered for this App
for I in $QTLIBS ; do # signing the Qt frameworks
codesign -s "$APPLCERT" -v -i "$DOMAIN.$I" \ $DEST/$MYAPP.app/Contents/Frameworks/$I.framework/Versions/5/$I
done for I in $DISTPLUGINS ; do # signing all *.dylib libs
BN=`basename $I .dylib` codesign -s "$APPLCERT" -v -i "$DOMAIN.$BN" \ $DEST/$MYAPP.app/Contents/PlugIns/$I
done </source> Finally we need to sign our executable using the entitlements file: <source lang=bash> codesign -s "$APPLCERT" -v -i "$DOMAIN.$MYAPP" --entitlements \
"$DEST/$MYAPP.app/Contents/Resources/$MYAPP.entitlements" \ "$DEST/$MYAPP.app/Contents/MacOS/$MYAPP"
</source> This step will only be successful if any library and framework is code signed.
Package
If everything went successful until now we can package our application to get it ready for the AppStore upload. Therefor we use productbuild
(take care of the $INSTCERT
variable):
<source lang=bash>
productbuild --component "$DEST/$MYAPP.app" /Applications \
--sign "$INSTCERT" "$DEST/$MYAPP.pkg"
</source> To check our created package we can use: <source lang=bash> $ sudo installer -store -pkg MyApp.pkg -target / </source>
Upload
Before you can upload your package you need to create a new version for you App with http://itunesconnect.apple.com. After that you can use the Application Loader to upload your package *.pkg file.