Repository: pyinstaller/pyinstaller

Issue:
Unsuccessfully built when using conda-forge
<!--
Welcome to the PyInstaller issue tracker! Before creating an issue, please heed the following:

1. This tracker should only be used to report bugs and request features / enhancements to PyInstaller
    - For questions and general support, use the mailing list, see
	  <http://www.pyinstaller.org/support.html#mailing-list>
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
   the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for
   maintainers to reproduce, the faster it'll be fixed.
-->

<!-- +++ ONLY TEXT +++ DO NOT POST IMAGES +++ -->

## Description of the issue
I am not sure if it's related to my incorrect configuration or something else, as I am new to Python. So I just post and hope to see if I may get some help. 
When using conda-forge to install package **pyqtads**, PyInstaller will report 

```
Traceback (most recent call last):
  File "app.py", line 1, in <module>
ModuleNotFoundError: No module named 'PyQt5.QtGui'
[44990] Failed to execute script 'app' due to unhandled exception!
```
or when import specific class from PyQt5.QtGui like QPixmap or QImage, it will have errors like 
```
qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Abort trap: 6
logout
```

Thanks to any help provided! 

### Context information (for bug reports)

* Output of `pyinstaller --version`: ```4.5.1```
* Version of Python: 3.7.11
* Platform: macOS Mojave version 10.14.6 
* How you installed Python: pip 
* Did you also try this on another platform? Does it work there?
Tried on macOS and Windows 10, both not working correctly. 

* try the latest development version, using the following command:

```shell
pip install https://github.com/pyinstaller/pyinstaller/archive/develop.zip
```

* follow *all* the instructions in our "If Things Go Wrong" Guide
  (https://github.com/pyinstaller/pyinstaller/wiki/If-Things-Go-Wrong) and

### Make sure [everything is packaged correctly](https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs#make-sure-everything-is-packaged-correctly)

  * [x] start with clean installation
  * [x] use the latest development version
  * [x] Run your frozen program **from a command window (shell)** — instead of double-clicking on it
  * [x] Package your program in **--onedir mode**
  * [x] Package **without UPX**, say: use the option `--noupx` or set `upx=False` in your .spec-file
  * [x] Repackage you application in **verbose/debug mode**. For this, pass the option `--debug` to `pyi-makespec` or `pyinstaller` or use `EXE(..., debug=1, ...)` in your .spec file.


### A minimal example program which shows the error
using following to create environment
```
conda create -y --name test python=3.7
conda activate test
conda install -y -c conda-forge pyqtads
```
```
from PyQt5.QtWidgets import QWidget,QMainWindow,QApplication
from PyQtAds import QtAds
import sys

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.m_DockManager = QtAds.CDockManager()

        self.qtadsWidget = QtAds.CDockWidget('qtads')
        self.qtadsWidget.setWidget(QWidget())
        
        self.m_DockManager.addDockWidget(QtAds.TopDockWidgetArea,self.qtadsWidget)
        
        self.setCentralWidget(self.m_DockManager)
        self.show()

if __name__ == '__main__':
    app = QApplication([])

    w=MainWindow()
    w.show()
    sys.exit(app.exec())
```

### Stacktrace / full error message
```
Traceback (most recent call last):
  File "app.py", line 1, in <module>
ModuleNotFoundError: No module named 'PyQt5.QtGui'
[44990] Failed to execute script 'app' due to unhandled exception!
```
or when import specific class from PyQt5.QtGui like QPixmap or QImage, it will have errors like 
```
qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Abort trap: 6
logout
```

Please also see <https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs>
for more about what would use to solve the issue.



Discussion:
> Tried on macOS and Windows 10, both not working correctly.

It seems to work correctly on Windows 10.

But on macOS, we indeed do not handle Qt library names when installed from Anaconda correctly, so our Qt dependency scanner is failing to pick up dependent Qt modules (so you end up with missing modules, their plugins, etc.).

Resolving patch:
diff --git a/PyInstaller/utils/hooks/qt.py b/PyInstaller/utils/hooks/qt.py
--- a/PyInstaller/utils/hooks/qt.py
+++ b/PyInstaller/utils/hooks/qt.py
@@ -464,9 +464,19 @@ def add_qt_dependencies(hook_file):
             lib_name = os.path.splitext(lib_name)[0]
         if lib_name.startswith('lib'):
             lib_name = lib_name[3:]
-        # Mac: rename from ``qt`` to ``qt5`` or ``qt6`` to match names in Windows/Linux.
-        if compat.is_darwin and lib_name.startswith('qt'):
-            lib_name = 'qt' + str(qt_info.qt_major) + lib_name[2:]
+        # Mac OS: handle different naming schemes. PyPI wheels ship framework-enabled Qt builds, where shared libraries
+        # are part of .framework bundles (e.g., ``PyQt5/Qt5/lib/QtCore.framework/Versions/5/QtCore``). In Anaconda
+        # (Py)Qt installations, the shared libraries are installed in environment's library directory, and contain
+        # versioned extensions, e.g., ``libQt5Core.5.dylib``.
+        if compat.is_darwin:
+            if lib_name.startswith('qt') and not lib_name.startswith('qt' + str(qt_info.qt_major)):
+                # Qt library from a framework bundle (e.g., ``QtCore``); change prefix from ``qt`` to ``qt5`` or ``qt6``
+                # to match names in Windows/Linux.
+                lib_name = 'qt' + str(qt_info.qt_major) + lib_name[2:]
+            if lib_name.endswith('.' + str(qt_info.qt_major)):
+                # Qt library from Anaconda, which originally had versioned extension, e.g., ``libfoo.5.dynlib``.
+                # The above processing turned it into ``foo.5``, so we need to remove the last two characters.
+                lib_name = lib_name[:-2]
 
         # Match libs with QT_LIBINFIX set to '_conda', i.e. conda-forge builds.
         if lib_name.endswith('_conda'):


Test patch: