
Cross-platform application on Qt: Mac App Store
- Tutorial

I will not describe trivial and long-considered things like getting developer status, creating a provision profile, registering a new application in iTunes Connect. We assume that all this is already configured, the program is ready, and is just waiting in the wings. I did not want to use Xcode, because of the additional software we need Application Loader, which can be downloaded like this . The files of our organizer for iStodo students are given as examples , so it turns out less abstractly and more closer to reality.
So, for starters, you need three additional files: the * icon , Info.plist, Entitlements.plist
Info.plist
This file contains all the information about the application (table of recommended fields here ).
Here is a minimal wireframe:
CFBundleDevelopmentRegion Russian CFBundleDisplayName iStodo CFBundleExecutable iStodo CFBundleIconFile iStodo.icns CFBundleIdentifier ru.istodo.istodo CFBundleInfoDictionaryVersion 6.0 CFBundleName iStodo CFBundlePackageType APPL CFBundleShortVersionString 1.1.0 CFBundleSignature ???? NSPrincipalClass NSApplication LSApplicationCategoryType public.app-category.productivity
In order for a non-standard plist to be automatically added to the bundle during each assembly, you need to add it to the project file:
QMAKE_INFO_PLIST= $${PWD}/Info.plist
At the same time, we immediately add a couple of lines to the .pro file for debugging:
QMAKE_CFLAGS += -gdwarf-2
QMAKE_CXXFLAGS += -gdwarf-2
Entitlements.plist
Programs installed through the App Store will work in the sandbox , for this you need to make some preparations. According to the rules, we, through QDesktopServices :: storageLocation (), available folder type imya_kompanii / application name (as specified in iTunes Connect), because you need to set these parameters explicitly:
QApplication::setOrganizationName("MyCompany")
QApplication::setApplicationName("MyApp")
Entitlements, in fact, is a permission file, and those functions that are not specified in it will be blocked. For example, if we want to do something with the network, set the flag com.apple.security.network.client, etc. Possible keys with descriptions here .
Well, an example of a finished file:
com.apple.security.app-sandbox com.apple.security.files.user-selected.read-write
Publication
So, we can highlight the following key points:
- Copy Qt frameworks and plugins inside the application folder
- Subscribe
- Pack in .pkg format
- Pour in iTunes Connect
Copy
In order for the program to run not only on systems with the Qt SDK installed, you need to run a specialized utility - macdeployqt, which will copy all the necessary plugins and frameworks to the application bundle. Unfortunately, due to an error in this utility, Info.plist files for frameworks are not copied, which is not a problem for work, but without them it will not be possible to correctly sign the application. It should be noted that if the program uses the QtSql module, all available drivers will be copied. Everything would be fine, but because of libqsqlodbc.dylib the application is rejected with the wording “For using private methods”, and because of libqsqlpsql.dylibswearing at using an outdated library. In order not to tempt fate, before publishing it is necessary to demolish unnecessary drivers, at the same time slightly reduce the package size. Also, to reduce the size, you can remove unnecessary image format plugins, etc.
Subscribe
Starting with OS X 10.9, you must sign not only the application, but all the frameworks, plugins. Despite the fact that in Technical Note 2206 it is written that it will be correct to sign not the framework folder, but the version directory, in practice this cannot be done, and there were no complaints about the review.
Examples of commands:
codesign -s "3rd Party Mac Developer Application: Developer Name" myApp.app/Contents/Frameworks/QtSql.framework/
codesign -s "3rd Party Mac Developer Application: Developer Name" myApp.app/Contents/PlugIns/platforms/libqcocoa.dylib
After signing all the libraries, the entire application queue comes in, and this is where the Entitlements file is used:
codesign --entitlements myAppEntitlements.plist -s "3rd Party Mac Developer Application: Developer Name" myApp.app
Check if everything went smoothly:
codesign --display --verbose=4 myApp.app
To wrap up
Here everything is done by one team, which, however, has changed since the writing of the official manual:
productbuild --component "myApp.app" /Applications --sign "3rd Party Mac Developer Installer: Developer Name" --product "myApp.app/Contents/Info.plist" myApp.pkg
For the sample, you can immediately run the resulting package:
sudo installer -store -pkg myApp.pkg -target /
Pour in iTunes Connect

Again, nothing complicated, select the Deliver your app item , your application, specify the path to. pkg . Many people face a problem (freezing) during the pouring process, a working solution is here.
Script
The result is a lot of manual work: copying .plist files for frameworks, signing each library ... A python script was written that completely automates the process. It is assumed that the script lies in the assembly directory (or at least in the same directory with the program bundle). A separate folder of the form myApp_1.2 will be created , in which, if everything goes as it should, the result of work will appear - the .pkg file. To configure, you should edit the block with the parameters - indicate the name of the application, version, location of Qt, the name of the file with permissions, information about the developer:
version = "1.2"
appName = "myApp"
devName = "Developer Name"
pathToQt = "/Users/_USER_NAME_/Qt5.2.0/5.2.0/clang_64/"
entitlements = "myAppEntitlements.plist"
Script
# -*- coding: utf-8 -*-
import os
import glob
import shutil
from subprocess import call
# Setup app info (Don't forget to change the version in the Info.plist)
version = "1.2"
appName = "myApp"
devName = "Developer Name"
pathToQt = "/Users/_USER_/Qt5.2.0/5.2.0/clang_64/"
entitlements = "myAppEntitlements.plist"
fullApp = appName +".app"
dirName = appName + "_" + version
# if we need only libqsqlite.dylib
sqliteOnly = True
sqldriversDir = fullApp+"/Contents/PlugIns/sqldrivers/"
frameworksDir = fullApp+"/Contents/Frameworks/"
pluginsDir = fullApp+"/Contents/PlugIns/"
print("Prepearing to deploy...")
# Check files and paths
if not os.path.exists(pathToQt) or not os.path.isdir(pathToQt):
print("Incorrect path to Qt")
exit()
if not os.path.exists(fullApp) or not os.path.isdir(fullApp):
print("App bundle not found")
exit()
if not os.path.exists(entitlements) or os.path.isdir(entitlements):
print("Entitlements file not found")
exit()
#remove old build
if os.path.exists(dirName):
shutil.rmtree(dirName)
os.makedirs(dirName)
# Copy all necessary files to new folder
shutil.copy(entitlements, dirName)
shutil.copytree(fullApp, dirName+"/"+fullApp)
# Copy Qt libs for create independent app
os.chdir(os.getcwd()+"/"+dirName)
print("\nDeploying Qt to .app bundle...")
call([pathToQt+"bin/macdeployqt", fullApp])
print("...done\n")
# Other libs in Qt 5.2(at least) will be rejected from Mac App Store anyway
if sqliteOnly and os.path.exists(sqldriversDir):
sqllibs = glob.glob(sqldriversDir+"*.dylib")
for lib in sqllibs:
if os.path.basename(lib) != "libqsqlite.dylib":
os.remove(lib)
# Copy plists for frameworks (it's fix macdeployqt bug)
frameworks = os.listdir(frameworksDir)
for framework in frameworks:
shutil.copy(pathToQt+"lib/"+framework+"/Contents/Info.plist", frameworksDir+framework+"/Resources/")
print("\nSigning frameworks, dylibs, and binary...")
# Sign frameworks (it's strange, but we can't sign "Versions" folder)
os.system('codesign -s "3rd Party Mac Developer Application: '+devName+'" '+frameworksDir+"*")
# Sign plugins
pluginGroups = os.listdir(pluginsDir)
for group in pluginGroups:
os.system('codesign -s "3rd Party Mac Developer Application: '+devName+'" '+pluginsDir+group+"/*")
# Sign app
os.system('codesign --entitlements '+entitlements+' -s "3rd Party Mac Developer Application: '+devName+'" '+fullApp)
print("\nCheck signing:")
os.system("codesign --display --verbose=4 "+fullApp)
# - - -
print("\nBuilding package...")
os.system('productbuild --component "'+fullApp+'" /Applications --sign "3rd Party Mac Developer Installer: '+devName+'" --product "'+fullApp+'/Contents/Info.plist" '+appName+'.pkg')
print("...done\n")
print('\nFor test install, run follow command: sudo installer -store -pkg '+dirName+'/'+appName+'.pkg -target /')
Bitbucket
Result - https://itunes.apple.com/en/app/istodo/id840850188?mt=12
In conclusion, we can say that preparing a Qt application for publication in the Mac App Store is not particularly difficult, but doing it manually is tedious.
Contents of the series of articles
Official guides:
developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/Introduction/Introduction.html
developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourAtingAppAppmitYourAtingAourmittingourourtingtingourourting .html