Good evening,
I'm trying to deploy an extremely simple Python QT app to the app store: it's roughly ~10 lines of code that shows a window containing a"Hello, world!" message.
This seemingly trivial task of deploying a "Hello World" app to the store has been extremely difficult, perhaps because of my lack of familiarity with the Apple ecosystem.
I'd like to demonstrate all of the steps I've taken, my current understanding of deployments, and what the problems are.
Project files
Here's the code for the app (in a file located at src2/test_app/app.py
).
import os, sys
from PySide2.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setCentralWidget(QLabel("Hello, world!"))
if __name__ == '__main__':
os.environ["QT_MAC_WANTS_LAYER"] = "1"
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Furthermore, I've created an assets folder which contains an icon.icns
file and I also created a Pyinstaller config/test_app.spec
file to bundle this app into a testapp.app
package:
block_cipher = None
added_files = [
('../assets', 'assets')
]
a = Analysis(
['../src2/test_app/app.py'],
pathex=[],
binaries=[],
datas=added_files,
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False
)
pyz = PYZ(
a.pure,
a.zipped_data,
cipher=block_cipher
)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='testapp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='../assets/64.icns'
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='app'
)
app = BUNDLE(
coll,
name='testapp.app',
icon='../assets/icon.icns',
bundle_identifier='com.stormbyte.test-app.pkg',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSAppleScriptEnabled': False,
'LSBackgroundOnly': False,
'LSApplicationCategoryType': 'public.app-category.utilities',
'NSRequiresAquaSystemAppearance': 'No',
'CFBundlePackageType': 'APPL',
'CFBundleSupportedPlatforms': ['MacOSX'],
'CFBundleIdentifier': 'com.stormbyte.test-app.pkg',
'CFBundleVersion': '0.0.1',
}
)
In addition, I have an config/entitlements.plist
file, containing all of the necessary information for binaries built by pyinstaller:
<?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>
<!-- These are required for binaries built by PyInstaller -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
This entitlements file is apparently necessary for pyinstaller to run things on Mac successfully, which is why I included it.
Building the .APP
I am able to build this app with:
pyinstaller \
--noconfirm \
--log-level WARN \
--distpath '/Users/nikolay/Desktop/projects/cling_wrap/dist' \
--workpath '/Users/nikolay/Desktop/projects/cling_wrap/build' \
'./config/test_app.spec'
This creates a testapp.app
file in my dist
folder and the file icon I've designated appears just as I've intended.
Codesigning the .APP binaries
Next, I am able to use the codesign
tool to sign all of the files that are part of the testapp.app I've just created.
codesign \
-vvv \
--strict \
--deep \
--force \
--timestamp \
--options runtime \
--entitlements './config/entitlements.plist' \
--sign "Developer ID Application: Nikolay ***** (Z57YJ*****)" \
'/Users/nikolay/Desktop/projects/cling_wrap/dist/testapp.app'
This successfully executes.
Building the .PKG Installer
Next, I am able to use productbuild
to create a .PKG
file, which will allow a user to install the app:
productbuild \
--version '0.0.1' \
--sign "Developer ID Installer: Nikolay **** (Z57YJ*****)" \
--component '/Users/nikolay/Desktop/projects/cling_wrap/dist/testapp.app' \
/Applications testapp.pkg
This successfully outputs:
...
productbuild: Adding certificate "Developer ID Certification Authority"
productbuild: Adding certificate "Apple Root CA"
productbuild: Wrote product to testapp.pkg
When I attempt to run this installer, everything works.
I can even see the app installed in my Applications folder, and I can double-click and run it:
With this confirmed, I am ready to run the notarization & stapling process prior to submitting my app to the App Store.
I run notarization using the xcrun tool:
xcrun altool \
--notarize-app \
--primary-bundle-id com.stormbyte.test-app.pkg \
--username=*****@gmail.com \
--password **** \
--file '/Users/nikolay/Desktop/projects/cling_wrap/testapp.pkg'
Which outputs:
No errors uploading '/Users/nikolay/Desktop/projects/cling_wrap/testapp.pkg'.
RequestUUID = c40ebde4-dcd1-***********
I then receive an e-mail from Apple telling me that the notorization process has been successful:
Next, I run the stapler
tool:
xcrun stapler staple '/Users/nikolay/Desktop/projects/cling_wrap/testapp.pkg'
Which is also successful.
Finally, I attempt to use Transporter
to upload my app to the store but this happens:
It says that the icon failed (when it clearly exists and is recognized by Mac?!!) and that the signature is invalid?
Am I using the incorrect certificates for distribution to the App store?
Thanks!