Python macOS 构建从终端运行,但在 Finder 启动时崩溃

Cir*_*lus 6 python macos py2app

当应用程序通过双击启动时,我的 py2app 构建会显示一条带有终止按钮的错误消息。但是,如果我直接在终端中打开它,那么它运行良好。以下所有终端命令都成功启动了应用程序:

# .MyApp.app/Contents/MacOS/MyApp
# open MyApp.app
# open -a MyApp.app
Run Code Online (Sandbox Code Playgroud)

我已经阅读了几篇 关于类似 py2app 错误的帖子,但我无法弄清楚这里可能是什么问题。这个问题似乎已经存在至少四年了,不是 py2app 特有的,似乎与 macOS 上 Python 的一般问题有关。pyinstaller 的用户报告了完全相同的问题,AFAIK 看不到明显的解决方案。

我的 setup.py 文件:

from setuptools import setup

APP = ['myapp.py']
DATA_FILES = [('img', ['img/myapp-logo.png']), ('data', ['data/data.yml'])]
OPTIONS = {'iconfile': 'icon.icns'}

setup(
    app=APP,
    name='My App',
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)
Run Code Online (Sandbox Code Playgroud)

macOS 控制台在 system.log 中显示以下消息: com.apple.xpc.launchd[1] (org.pythonmac.unspecified.MyApp.4952[16783]): Service exited with abnormal code: 255

嫌疑人1:文件访问?

pyinstaller GitHub 问题页面上的这个线程有多个人报告了相同的错误,但没有明确的修复。这个提议的解决方案似乎与 pyinstaller 线程中描述的工作目录问题有关,但没有解决我系统上的问题。

我的应用程序仅从其工作目录中读取单个 yml 文件,并且没有向磁盘写入任何内容。它只不过是一个简单的文件访问语句:

file = os.path.realpath('data/data.yml')
with open(file) as f:
    # etc
Run Code Online (Sandbox Code Playgroud)

在 Catalina 中,我已将该应用程序添加到“安全首选项”中允许全盘访问的应用程序列表中,但这也不能解决问题(不过,如果这样做会令人惊讶,因为 open 命令的工作原理如上所述以上)。

嫌疑人2:Tkinter?

pyinstaller GitHub 上的此线程表明该问题可能与 Tkinter 版本有关。提议的解决方案似乎已为某些用户解决了该问题。但是,在我添加 open file 语句之前,我有一个早期版本的工作应用程序包,双击时它启动得很好。

嫌疑人 1 对 2

我已经将我的代码分成两个分支,一个完全删除了对 Tkinter 的引用,另一个删除了文件访问以支持通过代码中的变量进行初始化。在第二种情况下不会发生崩溃。这似乎排除了 Tkinter 作为问题的根源,尽管奇怪的是,对 Tkinter 的黑客已经为其他用户修复了它。

macOS 版本

我已经在 Catalina 10.15.6 和 El Capitan 10.11.6 上测试了两个捆绑版本(工作和不工作),行为是相同的。

死亡哨兵的射击

以下是通过 Finder 正常启动应用程序后控制台日志的样子:

default 21:26:13.836380+0200    runningboardd   Acquiring assertion targeting executable<MyPythonApplication(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:165] with description <RBSAssertionDescriptor; frontmost:27280; ID: 391-165-34219; target: 27280> attributes = {
    <RBSDomainAttribute: 0x7f9f1a712910; domain: com.apple.launchservicesd; name: RoleUserInteractiveFocal; sourceEnvironment: 0x0>;
}
default 21:26:13.836649+0200    runningboardd   Assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) will be created as active
default 21:26:13.838258+0200    runningboardd   [executable<MyPythonApplication(501)>:27280] Ignoring jetsam update because this process is not memory-managed
default 21:26:13.839703+0200    runningboardd   [executable<MyPythonApplication(501)>:27280] Set darwin role to: UserInteractiveFocal
default 21:26:13.840634+0200    runningboardd   [executable<MyPythonApplication(501)>:27280] Ignoring GPU update because this process is not GPU managed
default 21:26:13.840912+0200    runningboardd   Finished acquiring assertion 391-165-34219 (target:executable<MyPythonApplication(501)>)
default 21:26:15.166436+0200    hidd    Connection removed: IOHIDEventSystemConnection uuid:B1D40AB3-FD55-455C-9E1B-2E4C4C6E4982 pid:27280 process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
default 21:26:15.171128+0200    runningboardd   [executable<MyPythonApplication(501)>:27280] Death sentinel fired!
default 21:26:15.174315+0200    runningboardd   Invalidating assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.176832+0200    loginwindow -[PersistentAppsSupport applicationQuit:] | for app:MyPythonApplication, _appTrackingState = 2
default 21:26:15.176856+0200    loginwindow -[PersistentAppsSupport applicationQuit:] | App: MyPythonApplication, quit, updating active tracking timer
default 21:26:15.179589+0200    runningboardd   Invalidating assertion 391-165-34209 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.281529+0200    runningboardd   Removing process: [executable<MyPythonApplication(501)>:27280]
default 21:26:15.282124+0200    runningboardd   Removing assertions for terminated process: [executable<MyPythonApplication(501)>:27280]
error   21:26:15.292603+0200    runningboardd   RBSStateCapture remove item called for untracked item 391-165-34209 (target:executable<MyPythonApplication(501)>)
error   21:26:15.292622+0200    runningboardd   RBSStateCapture remove item called for untracked item 391-165-34219 (target:executable<MyPythonApplication(501)>)
Run Code Online (Sandbox Code Playgroud)

显然这一行中提到了错误,以防有人可以从中做出任何事情:

hidd    Connection removed: IOHIDEventSystemConnection uuid:foo pid:bar process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
Run Code Online (Sandbox Code Playgroud)

紧随其后的是 RunningBoard 发射死亡哨兵,不管这个实体可能是什么。Howard Oakley这篇文章中描述了该系统,但它超出了我的专业水平。

Ulbow收集的更多信息通过信息丰富的消息“MacOS 错误:-67062”确认了错误:

com.apple.runningboard 4941060 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error: 
com.apple.launchservices 4940599 802 OSStatus _LSLaunch(LSContext *, FSNode *, LSLaunchFlags, void *, CFArrayRef, const AppleEvent *, const AEDescList *, CFArrayRef, CFDictionaryRef, LSBundleID, const audit_token_t *, const _LSOpen2Options *, ProcessSerialNumber *, Boolean *, NSError **): launching '<private>' result=0 
com.apple.securityd 9807 904 MacOS error: -67062 
com.apple.runningboard 4941063 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error: 
com.apple.sharedfilelist 4940599 802 -[SFLGenericList _insertItem:atIndex:error:]_block_invoke com.apple.LSSharedFileList.RecentApplications 
com.apple.securityd 4940933 530 UNIX error exception: 8 
 4941117 27642 MyPythonApplication Error 
com.apple.securityd 4941064 165 UNIX error exception: 22 
com.apple.securityd 4941064 165 MacOS error: -67062 
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error: 
com.apple.securityd 4941161 198 MacOS error: -67062 
com.apple.TCC 4941161 198 Failed to copy signing info for 27642, responsible for file:///Users/me/Files/Docs/Code/Python/MyPythonApplication/dist/MyPythonApplication.app/Contents/MacOS/Pandemic%20Deck%20Tracker: #-67062: Error Domain=NSOSStatusErrorDomain Code=-67062 "(null)" 
com.apple.securityd 4941161 198 MacOS error: -67062 
com.apple.securityd 4941161 198 MacOS error: -67062 
com.apple.launchservices 4941064 165 CLIENT: 0x7fcec00b33b0/27642 Received XPC_ERROR on connection from our client, so invalidating it and our connection. 
com.apple.appleevents 4941120 462 CONNECTION: peer=? peer-pid=27642 got event (Error "Connection invalid") 
com.apple.appleevents 4941120 462 CONNECTION: Recevied XPC_ERROR on a connection from 27642, a client of ours, so unregistering any application with that pid. 
com.apple.appleevents 4941120 462 CONNECTION: releasing app App:"MyPythonApplication"/"MyPythonApplication"/"org.pythonmac.unspecified.MyPythonApplication" 27642/0x0:0x308d08a ????1010 sess=100020 because we received error on its connection. 
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error: 
Run Code Online (Sandbox Code Playgroud)

这个 GitHub 问题页面上可以找到关于最近测试的几次交流,试图查明错误的原因,但它仍然是一个谜(也是一个非常奇怪的谜)。

苹果工程师真的应该看看这个。

Cir*_*lus 2

. 的维护者已在此 GitHub 问题页面上对此问题进行了相当彻底的调查pyinstaller。事实证明,如果 Python 脚本使用内置 Pythonos模块访问 macOS 文件系统上的资源,则应用程序包会崩溃。如果直接从终端命令运行脚本,则捆绑包不会崩溃。

建议的解决方案是检查脚本是否在 macOS 上运行,在这种情况下,使用AppKit打开文件。这需要安装pyobjc模块,但除此之外这并不是一个大麻烦。

而不是这样做:

import os

file = os.path.realpath('path/somefile.ext')
with open(file) as f:
    # ...
Run Code Online (Sandbox Code Playgroud)

做这个:

import os
import platform

def get_path(filename):
    name = os.path.splitext(filename)[0]
    ext = os.path.splitext(filename)[1]

    if platform.system() == "Darwin":
        from AppKit import NSBundle
        file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)
        return file or os.path.realpath(filename)
    else:
        return os.path.realpath(filename)

file = get_path('path/somefile.ext')
with open(file) as f:
    # ...
Run Code Online (Sandbox Code Playgroud)

我可以确认这适用于 macOS Catalina 和pyinstaller. 我还没有机会在.exeWindows 版本上对此进行测试。