📜 ⬆️ ⬇️

Cross-platform application on Qt: Mac App Store

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

I will not describe the trivial and already disassembled things like getting a 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 an Application Loader, which can be downloaded as follows . The examples are the files of our organizer for students of iStodo , so it turns out less abstract and more close to reality.

So, first we need three additional files: icon * , Info.plist, Entitlements.plist

Info.plist


This file contains all the information about the application (table of recommended fields here ).
')
Here is the minimum frame:
<?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>CFBundleDevelopmentRegion</key> <string>Russian</string> <key>CFBundleDisplayName</key> <string>iStodo</string> <key>CFBundleExecutable</key> <string>iStodo</string> <key>CFBundleIconFile</key> <string>iStodo.icns</string> <key>CFBundleIdentifier</key> <string>ru.istodo.istodo</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>iStodo</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>1.1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>NSPrincipalClass</key> <string>NSApplication</string> <key>LSApplicationCategoryType</key> <string>public.app-category.productivity</string> </dict> </plist> 

In order for a custom plist to be automatically added to the bundle with each build, you need to add to the project file:
 QMAKE_INFO_PLIST= $${PWD}/Info.plist 

At the same time, immediately add a couple of lines to the .pro file for debugging:
 QMAKE_CFLAGS += -gdwarf-2 QMAKE_CXXFLAGS += -gdwarf-2 

Entitlements.plist


Programs installed via the App Store will work in the sandbox , for this you need to make some preparations. According to the rules, we, through QDesktopServices :: storageLocation (), have access to a folder like company_name / application_name (as specified in iTunes Connect), so you need to set these parameters explicitly:
 QApplication::setOrganizationName("MyCompany") QApplication::setApplicationName("MyApp") 

Entitlements, in essence, are 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, we should set the flag com.apple.security.network.client, etc. Possible keys with descriptions here .

Well, an example of the finished file:
 <?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.user-selected.read-write</key> <true/> </dict> </plist> 

Publication


So, we can highlight the following key points:


To 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 copies all the necessary plug-ins and frameworks into 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 sign the application correctly. 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.dylib they swear at using an outdated library. In order not to tempt fate, before publishing it is worth carrying unnecessary drivers, at the same time reducing the size of the package a little. Also, to reduce the size, you can remove the extra image format plug-ins, etc.

Subscribe

Starting with OS X 10.9, it is necessary to sign not only the application, but all frameworks, plugins. Despite the fact that in Technical Note 2206 it is written that it would be correct to sign not the framework folder, but the version directory, in practice this does not work, and there were no complaints during 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 all the libraries are signed, the entire application queue comes, just here 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

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 item Deliver your app , your application, specify the path to. pkg . Many are faced with a problem (hang) in the process of filling, working solution here.

Script


The result is a lot of manual labor: copy the .plist files for the frameworks, sign each library ... A python script was written that completely automates the process. It is assumed that the script is in the build directory (or at least in the same directory as 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 the work will appear - a .pkg file. To configure, edit the block with parameters - specify the application name, 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

The result is https://itunes.apple.com/ru/app/istodo/id840850188?mt=12
In conclusion, it can be said that it is not particularly difficult to prepare a Qt application for publication in the Mac App Store, but doing everything manually is a chore.

The 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/SubmittingYourApp/SubmittingYourApp.html

Source: https://habr.com/ru/post/215971/


All Articles