Cross-platform application on Qt: Mac App Store

  • Tutorial
After development for OS X is completed, there may remain a feeling of incompleteness - for complete happiness I would like to see my application in the catalog, especially since this is perhaps the best platform for selling desktop applications. On this subject there is an article times Qt 4.8 in the official blog, and even older in the Habré . Fortunately, there is no longer any need to rebuild Qt, however, with the advent of OS X 10.9, some bugs became critical, you have to get out.

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:
CFBundleDevelopmentRegionRussianCFBundleDisplayNameiStodoCFBundleExecutableiStodoCFBundleIconFileiStodo.icnsCFBundleIdentifierru.istodo.istodoCFBundleInfoDictionaryVersion6.0CFBundleNameiStodoCFBundlePackageTypeAPPLCFBundleShortVersionString1.1.0CFBundleSignature????NSPrincipalClassNSApplicationLSApplicationCategoryTypepublic.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-sandboxcom.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

Resulthttps://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

Also popular now: