为什么要用 Python 编写 MacOS 应用程序?
Python 拥有极其丰富的库、工具和框架生态系统。它是一种干净、现代的语言,允许快速原型设计和快速开发周期。UI 不是我的应用程序的中心和焦点,所以用 Python 做它对我来说很有意义:我想我会先编写核心功能,然后再添加 UI。
打包应用程序
MacOS Catalina(2019 年发布)仍然没有默认安装 Python 3,只有 Python 2。因此,我需要一种方法将整个应用程序打包成一个应用程序包,而不是让它依赖于用户的 Python 安装。有几个工具可以帮助解决这个问题:py2app、 公文包、pyinstaller。我决定使用 PyInstaller,它成熟、灵活,并且提供比其他选项更多的自定义。
第 1 步:PyInstaller 规范文件
PyInstaller 可以单独由命令行选项驱动,但这仅适用于最简单的情况,并且打包任何非平凡的应用程序都不是其中之一。我建议使用命令行参数运行它,这会使用默认值创建规范文件,然后修改规范文件以更好地满足您的需求。例如,将捆绑版本设置为与应用程序版本相同的值,并添加一些自定义 plist 值:
from my_application import __version__ as package_version
...
...
# The final bundling step in the spec file:
# reference: https://pyinstaller.readthedocs.io/en/stable/spec-files.html#spec-file-options-for-a-mac-os-x-bundle
app = BUNDLE(coll,
name='My Application.app',
icon='my_application/resources/my_application.icns',
bundle_identifier='com.example.my_application',
info_plist={
'CFBundleName': 'My Application',
'CFBundleDisplayName': 'My Application',
'CFBundleVersion': package_version,
'CFBundleShortVersionString': package_version,
'NSRequiresAquaSystemAppearance': 'No',
'NSHighResolutionCapable': 'True',
},
)
第 2 步:构建应用程序
这很简单
pyinstaller --noconfirm my_application.spec
My Application.app
将被创建,您可以(并且应该)测试以确保它确实有效。一些 Python 包需要在 PyInstaller 规范文件中进行调整:包括包中的额外数据文件等。
第 3 步:签署应用程序
为了能够对其进行公证,您必须使用强化运行时。Hardened Runtime 不会影响大多数应用程序的运行,但它不允许某些功能。特别是对于 Python 应用程序,我们需要允许未签名的可执行内存。如果您的应用程序依赖于 Hardened Runtime 限制的任何其他功能,请添加一项权利以禁用该个人保护。
添加entitlements.plist
到项目的根目录:
<?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">
<!--
These are required for binaries built by PyInstaller.
For more info, see:
https://developer.apple.com/documentation/security/hardened_runtime
https://github.com/pyinstaller/pyinstaller/issues/4629
-->
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
现在我们准备签署:
codesign -s Developer -v --deep --timestamp --entitlements entitlements.plist -o runtime "dist/My Application.app"
请注意,为了能够对应用程序进行公证,您需要使用您的 Apple Developer ID 证书对其进行签名。如果需要,调整上面的“开发人员”以匹配您的证书名称。
Apple 建议不要“深度签名”,但在这种情况下,它实际上是必需的,因为所有捆绑的 Python 库都需要签名,而不仅仅是主二进制文件。
通过添加--timestamp
参数,我们包含一个带有代码签名的安全时间戳。这是公证的要求。
通过添加权利文件并传递-o runtime
参数,我们启用了强化运行时,这也是公证的要求。
第 3 步:对应用程序进行公证
- 应用程序中的所有二进制文件都必须链接到 macOS 10.9 或更高版本的 SDK。如果您只是使用最新的 Xcode 重新构建所有内容,则可以满足此要求。但是,如果您使用预先构建的二进制扩展打包一些 Python 依赖项,则它可能是针对较旧的 SDK 构建的。在这种情况下,您需要从源代码构建这个特定的包。
- 不包括明确禁止的权利。在撰写本文时,这只是一项
com.apple.security.get-task-allow
权利。
将 Apple 凭据存储在钥匙串中
为了对应用程序进行公证,altool
必须能够代表您访问 Apple API。要确保此访问的安全,请将您的 Apple 帐户凭据存储在钥匙串中:
xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "your-username" -p "your-password"
公证吧!
由于altool
需要 Zip 存档而不是裸.app
目录,因此请先创建一个 Zip 文件,然后对其进行公证: .
ditto -c -k --keepParent "dist/My Application.app" dist/MyApplication.zip
xcrun altool --notarize-app -t osx -f dist/MyApplication.zip \
--primary-bundle-id your.bundle.id -u your-username --password "@keychain:AC_PASSWORD"
现在您需要等待公证结果。一旦完成,您将收到来自 Apple 的电子邮件,说明成功或失败(并在这种情况下链接到错误日志)。
第 4 步:装订应用程序
在上面的公证步骤中,Apple 已经创建了一张“票”,它基本上是一个数据库记录,它与您的应用程序的签名相匹配并表示它已经过公证。您的二进制文件没有以任何方式被修改。当 MacOS 运行此应用程序时,它会联系 Apple 服务器并索要票证。如果存在这样的票证,则该应用程序被视为“经过公证”这只会发生一次,然后 MacOS 将缓存结果。
如果我们想加速这个初始应用程序的执行,或者如果我们希望能够在离线时运行它,我们需要“将这张票装订到应用程序”,它会下载票并将其附加到您的二进制文件中。这很简单:
xcrun stapler staple "dist/MyApplication.app"
此步骤是可选的,但必须在您收到 Apple 发送的说明公证成功的电子邮件后运行。
第 5 步:验证
现在是验证一切正常的好时机:
spctl --assess --type execute -vvv "dist/My Application.app"
此命令直接使用 Gatekeeper 来评估应用程序是否正确签名和公证。它应该报告:
dist/My Application.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: YourName (XXX)
结论
虽然上面概述的过程今天有效,但它肯定很麻烦并且有一些缺点:
- 开发和构建过程比使用 Xcode 的普通 MacOS 开发过程复杂得多。让新开发人员加入会很棘手。
- 很难控制被拉入应用程序的库。如果某些依赖项是使用 Homebrew 构建的,则该应用程序可能无法在比构建机器更旧的 MacOS 版本上运行。
在我看来,用 Python 编写 MacOS 应用程序对于原型设计或快速构建简单的内部工具是可接受的途径,甚至对应用程序进行公证也是完全可行的。
原文链接https://haim.dev/posts/2020-08-08-python-macos-app/