Repository: pyinstaller/pyinstaller

Issue:
Problems with app start as LaunchAgent on macOS due to malformed bundle error (CFBundleExecutable property)
Hi, I'm developing PyQT application that mainly works from `.app` bundle in macOS. PyInstaller forms bundle with these parameters:

(i use here `<Application Name>` in brackets notation to mean that this string looks like `Application Name` and `<<Application Name>>` in double brackets for XML-based files)

```
pyinstaller -y \
            --clean \
            --windowed \
            --name="<Application Name>" \
            --icon="assets/icon/<application-name>-icon" \
            --noupx \
            --strip \
            app.py
```

Then I'm adding `info_plist={'NSHighResolutionCapable': True, 'LSUIElement': True, }` to `BUNDLE` section into `.spec` and re-build with `pyinstaller "<Application Name>.spec"`.

At the end I'm getting a bundled macOS application that works fine. This application doesn't have any icon in Dock, only system tray icon and simple one-window GUI, all made with PyQT framework.

Now I'm adding auto-load of this application on system start feature by creating a `.plist` file in `~/Library/LaunchAgents/` with following content:

```
<?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>
	<key>Label</key>
	<string><<com.organization.appname>></string>
	<key>LimitLoadToSessionType</key>
	<string>Aqua</string>
	<key>ProgramArguments</key>
	<array>
		<string>open</string>
		<string>/<<path>>/<<Application Name>>.app</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
</dict>
</plist>
```

And `launchd` trying to start an application on system boot and fails:

```
(com.apple.xpc.launchd.domain.pid.<Application Name>.1819): Could not resolve origin of domain. XPC services in this domain's bundle will not be bootstrapped: error = 107: Malformed bundle, taint = missing executable
```

I looked into `info.plist` inside `.app` bundle and it was formed like:

```
<?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>
	<key>CFBundleDisplayName</key>
	<string><<Application Name>></string>
	<key>CFBundleExecutable</key>
	<string>MacOS/<<Application Name>></string>
	<key>CFBundleIconFile</key>
	<string><<application-name>>-icon</string>
	<key>CFBundleIdentifier</key>
	<string><<Application Name>></string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string><<Application Name>></string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>0.0.0</string>
	<key>LSUIElement</key>
	<true/>
	<key>NSHighResolutionCapable</key>
	<true/>
</dict>
</plist>
```

The problem is `CFBundleExecutable` value includes `MacOS/` path which supposed not to be declared in this property. When this path is removed everything works fine on the next system boot.

There is a code in `PyInstaller/building/osx.py`
```
# Fix for #156 - 'MacOS' must be in the name - not sure why
"CFBundleExecutable": 'MacOS/%s' % os.path.basename(self.exename),
"CFBundleIconFile": os.path.basename(self.icon),
```

I looked through git history and issues history and looks like this change was provided by 6b99e01 change and #1061 ticket (looks like commit message ticket points to a wrong pull request).

Apple documentation on `CFBundleExecutable` property https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101909 does not have any details on how this property should be declared and an algorithm of binary file location inside the bundle. Also various discussions point to only binary file name as a value, for example, answers on SO like this one https://stackoverflow.com/questions/1596945/building-osx-app-bundle

Trying to figure out I looked into macOS applications installed in my system, and seems everywhere `CFBundleExecutable` contains only binary file name, that is supposed to be placed in `MacOS` folder inside the bundle, without this folder being specified explicitly. Also the same is for standard applications like `Books.app` or `Calendar.app`.

As a solution I propose to remove `MacOS/` from `CFBundleExecutable` default declaration in `PyInstaller/building/osx.py`, on the other hand I have no idea yet could this break 6b99e01/#1061 change and how. Probably this should be discussed?


Discussion:
This is weird, I am using this feature without issues for a while.

Check the [.spec](https://github.com/nuxeo/nuxeo-drive/blob/19abd68a1dea273aaa78bd4d817e29cc0ea27c6b/ndrive.spec#L103-L113) and the [launcher](https://github.com/nuxeo/nuxeo-drive/blob/19abd68a1dea273aaa78bd4d817e29cc0ea27c6b/nxdrive/osi/darwin/darwin.py#L47-L59), perhaps could it help?
I can confirm that the `CFBundleExecutable` value should only be the executable name, as per Apple's documentation, and not `MacOS/NAME`. This may otherwise cause very difficult to debug issues: macOS will search for the specified executable but won't find it and instead default to assuming that the bundle name matches the executable name (which needn't be the case).

If it cannot find the executable as specified in the plist, the app will still run but with restrictions:

1. It will be launched without XPC services and an error is logged:
  
   ```
   Could not resolve origin of domain. XPC services in this domain's bundle will not be bootstrapped: error = 107: Malformed bundle, taint = missing executable
   ```

2. When a user renames the app bundle, the executable can no longer be found. This is the reason for specifying the executable name in the plist in the first place.

I am not sure about the issue with PyQt5 dock icons but my suspicion is that providing an incorrect executable name is the wrong way to fix it. 
I am wondering if the original issue is the lack of bundle ID. If you pass the argument `--osx-bundle-identifier=local.my.app` and set the same bundle ID in the launchd agent (`local.my.app.agentlauncher`), it should work. As that value is not set, it will be filled with the app name, resulting in a bad format.
I may be wrong but if should be interesting to check.

Resolving patch:
diff --git a/PyInstaller/building/osx.py b/PyInstaller/building/osx.py
--- a/PyInstaller/building/osx.py
+++ b/PyInstaller/building/osx.py
@@ -150,8 +150,8 @@ def assemble(self):
                            # Cli option --osx-bundle-identifier sets this value.
                            "CFBundleIdentifier": self.bundle_identifier,
 
-                           # Fix for #156 - 'MacOS' must be in the name - not sure why
-                           "CFBundleExecutable": 'MacOS/%s' % os.path.basename(self.exename),
+                           "CFBundleExecutable":
+                               os.path.basename(self.exename),
                            "CFBundleIconFile": os.path.basename(self.icon),
                            "CFBundleInfoDictionaryVersion": "6.0",
                            "CFBundlePackageType": "APPL",


Test patch: