How To Deploy your C++ Qt macOS App Properly
Introduction:
After a while of searching the internet, and forums, I was unable to find a definitive guide on deploying a Qt app from A to Z on macOS. Given that Apple has a weird code signing and app notarization system. This quick “how-to” will cover how to deploy your Qt macOS, building and linking will not be covered obviously as that differs per project. Hope this helps :)
I have automated it all with a simple python script which will be posted at the bottom, but I will go through it here.
Please note, this assumes you have a valid developer account with Apple.
Signing and Packaging your Application:
First off, we need to deploy all the shared libraries, sign and package the app. Before hand, you need to tell Cmake to package your executable into a .app. To do so simply do the following:
set(BUNDLE )
if(APPLE)
set(BUNDLE MACOSX_BUNDLE)
endif()
add_executable(project_name ${BUNDLE} main.cpp)
Now that you can compile and get your .app, you need to deploy all the Qt dependencies, and other dependencies into the bundle. What’s wonderful is Qt does this for you :), using macdeployqt! Like so:
macdeployqt {app_name}.app -always-overwrite
Then we would need to code sign, using the codesign command, like so:
codesign -f --deep -v --options runtime -s 'Developer ID Application: {developer_ID}' {app_name}.app
After all of that, you need to bundle the app as a dmg for notarization, like so:
hdiutil create /tmp/tmp.dmg -ov -volname '{app_name}' -fs HFS+ -srcfolder '{app_name}.app
hdiutil convert /tmp/tmp.dmg -format UDZO -o {app_name}.dmg
Then we need to sign the dmg.
codesign -f --deep -v --options runtime -s 'Developer ID Application: {developer_ID}' {app_name}.dmg
Notarizing the Application:
After all of that code signing, and bundling the app into a dmg, we need to upload the dmg to Apple for notarization.
xcrun altool --notarize-app --primary-bundle-id '{bundle_ID}' --username '{apple_username}' --password '{apple_password}' --file {app_name}.dmg
The following command, if successful should output the following
No errors uploading 'app_name.dmg'.
RequestUUID = 664647b4-adf8-4ac6-971b-XXXXXXX
What we need to do is grab that UUID and wait for it to be approved, to check on its status, we can use this command.
xcrun altool --notarization-info {UUID} -u {apple_username} -p {apple_password}
When the application has been successfully notarized, we get the following output:
No errors getting notarization info.
Date: 2021-05-25 18:26:08 +0000
Hash: 986e0c5019XXXXXXXX
LogFileURL: https://osxapps-ssl.itunes.apple.com/......
RequestUUID: 664647b4-adf8-4ac6-971b-XXXXXXXXX
Status: success
Status Code: 0
Status Message: Package Approved
When it has been approved what we finally need to do, is staple the notarization info to the dmg, and validate it.
xcrun stapler staple {app_name}.dmg
We should get this output: The staple and validate action worked!
Automating this Process
Congrats! You now have successfully signed and notarized your Qt application. Lets automate this whole thing! Below is a simple, yet messy python script to automate this whole process. Enjoy :)
def qt_deploy_sign_notarize(app_name: str, developer_ID: str,keychain_password: str, apple_username: str,apple_password: str, bundle_ID: str) -> bool:
# Unlock keychain, incase it is locked. Needed for signing.
os.system(f"security unlock-keychain -p {keychain_password} login.keychain")
# Use macdeployqt to deploy or shared libs and dependencies into the app bundle
os.system(f"~/Qt/6.0.0/clang_64/bin/macdeployqt {app_name}.app -always-overwrite")
# Sign the app bundle using codesign
os.system(f"codesign -f --deep -v --options runtime -s 'Developer ID Application: {developer_ID}' {app_name}.app")
# Create a dmg of the app
os.system(f"hdiutil create /tmp/tmp.dmg -ov -volname '{app_name}' -fs HFS+ -srcfolder '{app_name}.app'")
os.system(f"hdiutil convert /tmp/tmp.dmg -format UDZO -o {app_name}.dmg")
# Sign the dmg
os.system(f"codesign -f --deep -v --options runtime -s 'Developer ID Application: {developer_ID}' {app_name}.dmg")
# Notrize app and get the UUID:
altool_res = subprocess.check_output(f"xcrun altool --notarize-app --primary-bundle-id '{bundle_ID}' --username '{apple_username}' --password '{apple_password}' --file {app_name}.dmg",stderr=subprocess.STDOUT,shell=True).decode("utf-8")
UUID = altool_res.split("RequestUUID = ")[1].strip()
print(altool_res)
while True:
time.sleep(10)
try:
notarization_history_res = subprocess.check_output(f"xcrun altool --notarization-info {UUID} -u {apple_username} -p {apple_password}",stderr=subprocess.STDOUT,shell=True).decode("utf-8")
if "Package Invalid" in notarization_history_res and "in progress" not in notarization_history_res:
print(notarization_history_res)
print("ERROR: Package not notarized..")
return False
elif "Package Approved" in notarization_history_res and "in progress" not in notarization_history_res:
print(notarization_history_res)
print("Package notarized!")
print("Stapling....")
time.sleep(15)
os.system(f"xcrun stapler staple {app_name}.dmg")
os.system(f"xcrun stapler validate {app_name}.dmg")
return True
except:
print("ERROR with notarization_history_res")
Leave a comment