Deploying simple "Hello world" QT Python App to App Store

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!

Replies

I was able to execute productbuild using a different certificate, namely the 3rd party developer certificate. Also, I watched a video on Notarization, and it says the notarization is only for apps distributed outside the app store?

https://developer.apple.com/videos/play/wwdc2021/10261/

Is this true? So this means that I do not need to notarize or staple my app if I am uploading it to the App store directly?

I attempted to follow this assumption and skipped notorization altogether, opting to simply run the following productbuild command and then use Transporter:

productbuild \
  --version '0.0.1' \
  --sign "3rd Party Mac Developer Installer: Nikolay **** (Z57YJ*****)" \
  --component '/Users/nikolay/Desktop/projects/cling_wrap/dist/testapp.app' \
  /Applications testapp.pkg

I'm now just stuck on this one error:

I resolved this final issue by ensuring that I had a folder (/Users/nikolay/Desktop/projects/cling_wrap/assets/icon.iconset) which contained the following 2 files:

  • icon_512x512.png (with a resolution of 512pixels by 512pixels, obviously)
  • icon_512x512@2x.png (with a resolution of 1024pixels by 1024 pixels)

Then I used the following command to regenerate the icon.icns file:

iconutil --convert icns assets/icon.iconset

With this, Transporter accepted the app and was able to upload it: