Python macOS builds run from Terminal but crash on Finder launch
Asked Answered
O

2

6

My py2app build displays an error message with a Terminate button when the app is launched with a double-click. However, if I open it directly in Terminal then it works perfectly well. All of the following Terminal commands launch the app successfully:

# .MyApp.app/Contents/MacOS/MyApp
# open MyApp.app
# open -a MyApp.app

I've read several posts about similar py2app errors but I can't figure out what could be the issue here. The problem seems to have been around for at least four years, is not specific to py2app and seems related to a general issue with Python on macOS. Users of pyinstaller are reporting the exact same problem with no obvious solution in sight AFAIK.

My setup.py file:

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'],
)

The macOS Console shows the following message in system.log: com.apple.xpc.launchd[1] (org.pythonmac.unspecified.MyApp.4952[16783]): Service exited with abnormal code: 255

Suspect 1: file access?

This thread on the pyinstaller GitHub issues page has multiple people reporting the same error with no clear fix. This proposed solution seems related to the working directory issues described in the pyinstaller thread but doesn't solve the problem on my system.

My app is only reading a single yml file from its working directory and doesn't write anything to disk. It is nothing more than a simple file access statement:

file = os.path.realpath('data/data.yml')
with open(file) as f:
    # etc

In Catalina, I've added the app to the list of apps allowed Full Disk Access in the Security Preferences but this doesn't solve the issue either (it would have been surprising if it did, though, since the open command works as mentioned above).

Suspect 2: Tkinter?

This thread on pyinstaller GitHub suggests the problem might be related to a Tkinter version. The proposed solution seems to have fixed the issue for some users. However, on my end I have a working app bundle from an earlier version, before adding the open file statement, which launches just fine when double clicked.

Suspect 1 vs 2

I have forked my code into two branches, one completely removing references to Tkinter, the other removing file access in favour of initialisation via variables in the code. The crash does not happen in the second case. This would seem to rule out Tkinter as the source of the problem, although weirdly enough a hack to Tkinter has fixed it for other users.

macOS versions

I've tested both bundle versions (working and not working) on both Catalina 10.15.6 and El Capitan 10.11.6 and the behaviour is identical.

The Firing of the Death Sentinel

Here's what the Console log looks like after launching the app normally through the 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)>)

Apparently the error is mentioned in this line, in case someone can make anything out of it:

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

This is immediately followed by RunningBoard firing the Death Sentinel, whatever this entity might be. The system is described in this article by Howard Oakley but it is way beyond my level of expertise.

Some more info gathered by Ulbow confirms an error with the highly informative message "MacOS error: -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: 

Several exchanges on recent tests, trying to pinpoint the cause for the bug, can be found on this GitHub issues page, but it still remains a mystery (and a very bizarre one, too).

An Apple engineer should really take a look at this.

Obovate answered 27/8, 2020 at 7:29 Comment(4)
Can you come up with a minimum working (failing) example? If you remove Tkinter, does this still happen? What if you remove file access?Frustum
I've created separate branches for two versions of the code (git be blessed). I can confirm that, on my end, removing Tkinter from the code entirely doesn't fix the problem. However, when I use the version without opening a file, the issue is fixed. Problem is, other users with the same symptoms have reported it as being a Tkinter problem (cf. links in my question above). I am updating my question to reflect the test.Obovate
@Obovate did you ever find a solution to this? I am having a very similar issue with a mac app I wrote using Flutter. My app wraps a command line tool to provide buttons for running specific commands on the command line. When I launch the app from the icon, running a command crashes the app. When I launch the app from command line, I am able to run all the commands from within my app. I get the same IOHIDEventSystemConnection message as wellAtthia
@Atthia I've added an answer that I suggest you try. It might help in your case or not, I don't know. It has in mine.Obovate
O
2

A pretty thorough investigation of this issue has been conducted on this GitHub issue page by the maintainers of pyinstaller. It turns out that if a Python script accesses resources on a macOS file system using the built-in Python os module, the app bundle crashes. The bundle does not crash if the script is run directly from a Terminal command.

The proposed solution is to check if the script is running on macOS and, in that case, use AppKit to open the file. This requires installing the pyobjc module but otherwise it is not a major hassle.

Instead of doing this:

import os

file = os.path.realpath('path/somefile.ext')
with open(file) as f:
    # ...

Do this:

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:
    # ...

I can confirm this works on macOS Catalina with pyinstaller. I haven't had the chance to test this on .exe builds for Windows.

Obovate answered 24/9, 2020 at 8:22 Comment(0)
C
-1

After hours of digging I finally found an answer! Even after every Step I count gain the right /Contents/Resources path.

First I had to get this working:

from AppKit import NSBundle
    file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)

For my Python App, finding the "AppKit" module I had to install

pip install pyobjc

But even then, the path for

NSBundle.mainBundle().pathForResource_ofType_(name, ext)

was strange. It was a path in "/private/var/" where I don't have Write Access

The final answer was to run

xattr -d com.apple.quarantine /Applications/YouPythonApp.app/

then I finally got the right path

/Applications/YouPythonApp.app/Contents/Resources/

where I also had write access and now I can save my .yaml file

with open(file, "w") as f:
        yaml=YAML()
        yaml.default_flow_style = False
        yaml.dump(config, f)
Cervantez answered 19/2, 2021 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.