Repository: pyinstaller/pyinstaller
Pull request: Qt: extend OpenSSL collection for QtNetwork & implement strict Qt/QML plugin dependency validation
Part 1: extend OpenSSL collection for QtNetwork
-----------------------------------------------

The first part of this PR extends the collection of OpenSSL shared libraries for `QtNetwork`, to handle different OpenSSL versions - 1.0.2 for very old PyQt5 wheels, 1.1.x for PySide2 and PyQt5, and >= 3.x for PySide6 and PyQt6 (and sometimes for Qt5, if using custom build). We also extend the collection, which was previously done only on Windows, to Linux (where PyPI wheels for all four bindings expect the shared lib to be provided by the environment) and macOS (where `openssl` TLS backend in Qt6 is enabled if OpenSSL dylib is available in the environment, e.g. from Homebrew).

To make collected OpenSSL dylib visible to `QtNetwork` in macOS POSIX builds, we need to add `sys._MEIPASS` to `DYLD_LIBRARY_PATH`, so have the run-time hook do that. This does not seem to be necessary for .app bundles, because `Contents/Frameworks` is automatically searched by `QtNetwork`.

Extend the `QtNetwork` OpenSSL test for Qt6, where we need to check if `openssl` TLS backend is available, instead of just checking for SSL/TLS support.

Part 2: implement strict Qt/QML plugin dependency validation
------------------------------------------------------------

The second part implements strict dependency validation for Qt plugins and for QML plugins.

Inside our Qt hook helpers, we now perform binary dependency analysis of Qt plugins, and discard the plugins that have missing Qt dependencies - for example, PyPI wheels ship Qt plugins with Qt dependencies that are missing from the wheels (they are missing completely, or require installation of an optional wheel). Such plugins are non-functional, so there is no point in collecting them. Plus, the missing dependencies would raise warnings later on during general binary dependency analysis. Same applies to the QML plugins.

Qt plugins with Qt dependencies not shipped by PyPI wheels have an even darker side - those dependencies might be available somewhere else in the system; for example, the underlying Linux distribution, a Homebrew environment on macOS, or an arbitrary Qt build that happens to be in `PATH` on Windows (see #8087). Even if they are of compatible versions, the Qt libs shipped with PyPI wheels and the external ones might not be fully compatible (e.g., different symbol visibility on Linux), so mixing the two is never a good idea.

That's why we also ensure that resolved Qt dependencies of plugins reside in the Qt shared library directory, as declared by `QLibraryInfo.PrefixPath` / `QLibraryInfo.BinariesPath` /  `QLibraryInfo.LibrariesPath` (whichever is applicable to the OS and wheel/non-wheel), and discard plugins with Qt dependencies that are resolved outside of that directory (note: "Qt dependencies" means Qt shared libraries - `Qt*.dll` or `libQt*.{so, dylib}`, as opposed to lower-level dependencies that might need to be collected from the host system).

For QML plugins, we now need to scan the QML directory for `qmldir` files to identify individual components, and parse them for lists of corresponding plugins (usually one plugin per component, though).

Examples of the new system in action, on Windows (without "fall-back" Qt installation in `PATH`):

<details><summary>PySide2</summary><p>

On Windows, `PySide2` does not seem to include any problematic plugins.

</p></details>

<details><summary>PySide6</summary><p>

With `PySide6` (which installed both `PySide6-Essential` and `PySide6-AddOns`), we now get the following warnings:
```
7156 INFO: Loading module hook 'hook-PySide6.QtQml.py' from '[...]\\pyinstaller\\PyInstaller\\hooks'...
13303 WARNING: QtLibraryInfo(PySide6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PySide6\\qml\\QtQuick3D\\Helpers\\impl\\qtquick3dhelpersimplplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DHelpersImpl.dll'.
13303 WARNING: QtLibraryInfo(PySide6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PySide6\\qml\\QtQuick3D\\Helpers\\impl' due to invalid plugin binaries!
13767 WARNING: QtLibraryInfo(PySide6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PySide6\\qml\\QtQuick3D\\SpatialAudio\\quick3dspatialaudioplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DSpatialAudio.dll'.
13767 WARNING: QtLibraryInfo(PySide6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PySide6\\qml\\QtQuick3D\\SpatialAudio' due to invalid plugin binaries!
```

</p></details>


<details><summary>PyQt5</summary><p>

With just `PyQt5` and `PyQtWebEngine` installed, we now get the following warnings:

```
6169 INFO: Loading module hook 'hook-PyQt5.QtQml.py' from '[...]\\PyInstaller\\hooks'...
6613 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtMultimedia\\declarative_multimedia.dll'! Reason: Missing Qt dependency 'Qt5MultimediaQuick.dll'.
6613 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtMultimedia' due to invalid plugin binaries!
7021 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene2D\\qtquickscene2dplugin.dll'! Reason: Missing Qt dependency 'Qt53DQuickScene2D.dll'.
7021 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene2D' due to invalid plugin binaries!
7055 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'! Reason: Missing Qt dependency 'Qt53DLogic.dll'.
7055 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D' due to invalid plugin binaries!
```

If we install extra packages (`PyQt3D`, in particular), the warnings are reduced to:

```
6702 INFO: Loading module hook 'hook-PyQt5.QtQml.py' from '[...]\\PyInstaller\\hooks'...
8129 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtMultimedia\\declarative_multimedia.dll'! Reason: Missing Qt dependency 'Qt5MultimediaQuick.dll'.
8129 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtMultimedia' due to invalid plugin binaries!
```

</p></details>

<details><summary>PyQt6</summary><p>

With just `PyQt6` and `PyQt6-WebEngine` installed, we now get the following warnings:

Similarly, for PyQt6 (`PyQt6` and `PyQt6-WebEngine` only):

```
5681 INFO: Loading module hook 'hook-PyQt6.QtQml.py' from '[...]\\PyInstaller\\hooks'...
6907 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQml\\XmlListModel\\qmlxmllistmodelplugin.dll'! Reason: Missing Qt dependency 'Qt6QmlXmlListModel.dll'.
6907 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQml\\XmlListModel' due to invalid plugin binaries!
8052 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Effects\\effectsplugin.dll'! Reason: Missing Qt dependency 'Qt6QuickEffects.dll'.
8052 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Effects' due to invalid plugin binaries!
8159 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\LocalStorage\\qmllocalstorageplugin.dll'! Reason: Missing Qt dependency 'Qt6QmlLocalStorage.dll'.
8160 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\LocalStorage' due to invalid plugin binaries!
8423 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Scene2D\\qtquickscene2dplugin.dll'! Reason: Missing Qt dependency 'Qt63DRender.dll'.
8423 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Scene2D' due to invalid plugin binaries!
8701 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'! Reason: Missing Qt dependency 'Qt63DLogic.dll'.
8701 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Scene3D' due to invalid plugin binaries!
9197 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Helpers\\impl\\qtquick3dhelpersimplplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DHelpersImpl.dll'.
9197 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Helpers\\impl' due to invalid plugin binaries!
9319 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\ParticleEffects\\qtquick3dparticleeffectsplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DParticleEffects.dll'.
9320 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\ParticleEffects' due to invalid plugin binaries!
9428 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\Helpers\\qtquick3dphysicshelpersplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DPhysicsHelpers.dll'.
9428 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\Helpers' due to invalid plugin binaries!
9475 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\qquick3dphysicsplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DPhysics.dll'.
9475 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics' due to invalid plugin binaries!
```

Installing `PyQt6-3D` fixes some of the missing dependencies, but also brings additional plugins with missing dependencies:

```
6668 INFO: Loading module hook 'hook-PyQt6.QtQml.py' from '[...]\\PyInstaller\\hooks'...
6930 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\Qt3D\\Core\\quick3dcoreplugin.dll'! Reason: Missing Qt dependency 'Qt63DQuick.dll'.
6930 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\Qt3D\\Core' due to invalid plugin binaries!
6995 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\Qt3D\\Render\\quick3drenderplugin.dll'! Reason: Missing Qt dependency 'Qt63DQuick.dll'.
6995 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\Qt3D\\Render' due to invalid plugin binaries!
7093 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQml\\XmlListModel\\qmlxmllistmodelplugin.dll'! Reason: Missing Qt dependency 'Qt6QmlXmlListModel.dll'.
7093 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQml\\XmlListModel' due to invalid plugin binaries!
7373 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Effects\\effectsplugin.dll'! Reason: Missing Qt dependency 'Qt6QuickEffects.dll'.
7373 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\Effects' due to invalid plugin binaries!
7404 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\LocalStorage\\qmllocalstorageplugin.dll'! Reason: Missing Qt dependency 'Qt6QmlLocalStorage.dll'.
7404 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick\\LocalStorage' due to invalid plugin binaries!
7616 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Helpers\\impl\\qtquick3dhelpersimplplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DHelpersImpl.dll'.
7616 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Helpers\\impl' due to invalid plugin binaries!
7648 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\ParticleEffects\\qtquick3dparticleeffectsplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DParticleEffects.dll'.
7648 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\ParticleEffects' due to invalid plugin binaries!
7665 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\Helpers\\qtquick3dphysicshelpersplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DPhysicsHelpers.dll'.
7665 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\Helpers' due to invalid plugin binaries!
7681 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics\\qquick3dphysicsplugin.dll'! Reason: Missing Qt dependency 'Qt6Quick3DPhysics.dll'.
7681 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]\\venv\\Lib\\site-packages\\PyQt6\\Qt6\\qml\\QtQuick3D\\Physics' due to invalid plugin binaries!
```

</p></details>


On my Fedora linux (which has Qt5 and Qt6 installed as part of the system):

<details><summary>PySide6</summary><p>

```
3454 INFO: Loading module hook 'hook-PySide6.QtQml.py' from '[...]/PyInstaller/hooks'...
4535 WARNING: QtLibraryInfo(PySide6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PySide6/Qt/qml/QtQuick3D/Helpers/impl/libqtquick3dhelpersimplplugin.so'! Reason: Qt dependency 'libQt6QmlModels.so.6' ('/usr/lib64/libQt6QmlModels.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PySide6/Qt/lib').
4535 WARNING: QtLibraryInfo(PySide6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PySide6/Qt/qml/QtQuick3D/Helpers/impl' due to invalid plugin binaries!
4586 WARNING: QtLibraryInfo(PySide6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PySide6/Qt/qml/QtQuick3D/SpatialAudio/libquick3dspatialaudioplugin.so'! Reason: Missing Qt dependency 'libQt6Quick3DSpatialAudio.so.6'.
4587 WARNING: QtLibraryInfo(PySide6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PySide6/Qt/qml/QtQuick3D/SpatialAudio' due to invalid plugin binaries!
```

</p></details>


<details><summary>PyQt5</summary><p>

```
3291 INFO: Loading module hook 'hook-PyQt5.QtGui.py' from '[...]/PyInstaller/hooks'...
3359 WARNING: QtLibraryInfo(PyQt5): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt5/Qt5/plugins/egldeviceintegrations/libqeglfs-x11-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt5EglFSDeviceIntegration.so.5' ('/usr/lib64/libQt5EglFSDeviceIntegration.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
3371 WARNING: QtLibraryInfo(PyQt5): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt5/Qt5/plugins/egldeviceintegrations/libqeglfs-kms-egldevice-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt5EglFsKmsSupport.so.5' ('/usr/lib64/libQt5EglFsKmsSupport.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
3381 WARNING: QtLibraryInfo(PyQt5): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt5/Qt5/plugins/egldeviceintegrations/libqeglfs-emu-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt5EglFSDeviceIntegration.so.5' ('/usr/lib64/libQt5EglFSDeviceIntegration.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
3569 WARNING: QtLibraryInfo(PyQt5): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt5/Qt5/plugins/platforms/libqeglfs.so' ('platforms')! Reason: Qt dependency 'libQt5EglFSDeviceIntegration.so.5' ('/usr/lib64/libQt5EglFSDeviceIntegration.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
...
4351 INFO: Loading module hook 'hook-PyQt5.QtQml.py' from '[...]/PyInstaller/hooks'...
4570 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtMultimedia/libdeclarative_multimedia.so'! Reason: Qt dependency 'libQt5MultimediaQuick.so.5' ('/usr/lib64/libQt5MultimediaQuick.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
4570 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtMultimedia' due to invalid plugin binaries!
4824 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtQuick/Scene2D/libqtquickscene2dplugin.so'! Reason: Qt dependency 'libQt53DLogic.so.5' ('/usr/lib64/libQt53DLogic.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
4824 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtQuick/Scene2D' due to invalid plugin binaries!
4833 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtQuick/Scene3D/libqtquickscene3dplugin.so'! Reason: Qt dependency 'libQt53DLogic.so.5' ('/usr/lib64/libQt53DLogic.so.5.15.11') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/lib').
4833 WARNING: QtLibraryInfo(PyQt5): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt5/Qt5/qml/QtQuick/Scene3D' due to invalid plugin binaries!
```

</p></details>


<details><summary>PyQt6</summary><p>

```
3198 INFO: Loading module hook 'hook-PyQt6.QtGui.py' from '[...]/PyInstaller/hooks'...
3422 WARNING: QtLibraryInfo(PyQt6): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt6/Qt6/plugins/platforms/libqeglfs.so' ('platforms')! Reason: Qt dependency 'libQt6EglFSDeviceIntegration.so.6' ('/usr/lib64/libQt6EglFSDeviceIntegration.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
3978 WARNING: QtLibraryInfo(PyQt6): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt6/Qt6/plugins/egldeviceintegrations/libqeglfs-x11-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt6EglFSDeviceIntegration.so.6' ('/usr/lib64/libQt6EglFSDeviceIntegration.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4071 WARNING: QtLibraryInfo(PyQt6): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt6/Qt6/plugins/egldeviceintegrations/libqeglfs-kms-egldevice-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt6EglFsKmsSupport.so.6' ('/usr/lib64/libQt6EglFsKmsSupport.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4091 WARNING: QtLibraryInfo(PyQt6): excluding plugin '[...]/venv/lib64/python3.10/site-packages/PyQt6/Qt6/plugins/egldeviceintegrations/libqeglfs-emu-integration.so' ('egldeviceintegrations')! Reason: Qt dependency 'libQt6EglFSDeviceIntegration.so.6' ('/usr/lib64/libQt6EglFSDeviceIntegration.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
...
4349 INFO: Loading module hook 'hook-PyQt6.QtQml.py' from '[...]/PyInstaller/hooks'...
4514 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQml/XmlListModel/libqmlxmllistmodelplugin.so'! Reason: Qt dependency 'libQt6QmlXmlListModel.so.6' ('/usr/lib64/libQt6QmlXmlListModel.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4514 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQml/XmlListModel' due to invalid plugin binaries!
4772 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Effects/libeffectsplugin.so'! Reason: Qt dependency 'libQt6QuickEffects.so.6' ('/usr/lib64/libQt6QuickEffects.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4772 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Effects' due to invalid plugin binaries!
4803 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/LocalStorage/libqmllocalstorageplugin.so'! Reason: Qt dependency 'libQt6QmlLocalStorage.so.6' ('/usr/lib64/libQt6QmlLocalStorage.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4803 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/LocalStorage' due to invalid plugin binaries!
4880 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Scene2D/libqtquickscene2dplugin.so'! Reason: Qt dependency 'libQt63DQuickScene2D.so.6' ('/usr/lib64/libQt63DQuickScene2D.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4880 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Scene2D' due to invalid plugin binaries!
4906 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Scene3D/libqtquickscene3dplugin.so'! Reason: Qt dependency 'libQt63DAnimation.so.6' ('/usr/lib64/libQt63DAnimation.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
4906 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick/Scene3D' due to invalid plugin binaries!
5025 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Helpers/impl/libqtquick3dhelpersimplplugin.so'! Reason: Qt dependency 'libQt6OpenGL.so.6' ('/usr/lib64/libQt6OpenGL.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
5025 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Helpers/impl' due to invalid plugin binaries!
5057 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/ParticleEffects/libqtquick3dparticleeffectsplugin.so'! Reason: Qt dependency 'libQt6Quick3DParticleEffects.so.6' ('/usr/lib64/libQt6Quick3DParticleEffects.so.6.6.0') has been resolved outside of the Qt shared library directory ('[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/lib').
5057 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/ParticleEffects' due to invalid plugin binaries!
5085 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Physics/Helpers/libqtquick3dphysicshelpersplugin.so'! Reason: Missing Qt dependency 'libQt6Quick3DPhysicsHelpers.so.6'.
5085 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Physics/Helpers' due to invalid plugin binaries!
5100 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin binary '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Physics/libqquick3dphysicsplugin.so'! Reason: Missing Qt dependency 'libQt6Quick3DPhysics.so.6'.
5100 WARNING: QtLibraryInfo(PyQt6): excluding QML plugin directory '[...]/venv/lib/python3.10/site-packages/PyQt6/Qt6/qml/QtQuick3D/Physics' due to invalid plugin binaries!
```
</p></details>



Problem statement:
`--contents-director=.` will make QtChart import failed
<!--
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 discussions forum.
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

### Context information (for bug reports)

* Output of `pyinstaller --version`: ```6.1.0```
* Version of Python: ```3.11.6```
* Platform: ```Windows10```
* How you installed Python: python.org
* Did you also try this on another platform? Does it work there? not yet

### A minimal example program which shows the error

versions:
PyQt5==5.15.9
PyQt5-Qt5==5.15.2
PyQt5-sip==12.13.0
PyQtChart==5.15.6
PyQtChart-Qt5==5.15.2
PyQtWebEngine==5.15.6
PyQtWebEngine-Qt5==5.15.2

hello.py
``` python
import sys
import PyQt5.QtChart
import PyQt5.QtWebEngine
from PyQt5.QtWidgets import QApplication, QWidget

if __name__ == "__main__":
    app = 0
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(250, 250)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()
    sys.exit(app.exec_())
```

try to pack the program and run it
``` shell
pyinstaller -c -y -w --noupx --contents-director=. hello.py
./dist/hello/hello
```

### Stacktrace / full error message
```
Traceback (most recent call last):
  File "hello.py", line 2, in <module>
ImportError: DLL load failed while importing QtChart: The specified module could not be found.
```

pack with `-d=all`
```
Traceback (most recent call last):
  File "hello.py", line 2, in <module>
    import PyQt5.QtChart
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 676, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 573, in module_from_spec
  File "<frozen importlib._bootstrap_external>", line 1233, in create_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
ImportError: DLL load failed while importing QtChart: The specified module could not be found.

```

Hints from issue discussion:
I cannot reproduce this - both default contents directory and `.` work if I freeze that example.

Can you archive the `build` and `dist` directories of that failing build and upload them somewhere so I can take a look at them? (The build *without*  `-d=all`).
```shell
pyinstaller -c -y -w --noupx --contents-director=. hello.py
```
The archive file is too big to upload to github issue.

https://drive.google.com/file/d/13wGBjzaRemqzjnNCDonvm9dU2qVbMl64/view?usp=sharing
The underlying problem here seems to be that you have another Qt5 installation in your search paths (`PATH`), and that messes up the DLL resolution during the build. So you end up with two copies of those DLLs; one in `PyQt5\Qt5\bin` (from PyQt5 package, which is OK), and in top-level application directory (from external Qt5 installation, which should not be there).

I.e., if you look at `build\hello\Analysis-00.toc`, you will see entries like these:
``` 
  ('PyQt5\\Qt5\\bin\\Qt5Core.dll',
   'D:\\repo\\qt-pyinstaller-test\\venv\\Lib\\site-packages\\PyQt5\\Qt5\\bin\\Qt5Core.dll',
   'BINARY'),
```

```
  ('Qt5Core.dll', 'C:\\QT\\5.15.2\\msvc2015_64\\bin\\Qt5Core.dll', 'BINARY'),
```

If you remove the `Qt5*.dll` files in top-level application directory, the application starts normally.

Cleaning up `PATH` before running PyInstaller (i.e., removing `C:\QT\5.15.2\msvc2015_64\bin` from `PATH`) should also avoid this issue.

---

But now I'm curious as to why we are not preferring PyQt5-provided DLLs in the first place.

1. Can you try re-running pyinstaller with added `--clean --log-level=DEBUG`, and attach or upload the full build log
so we can see where DLLs from `C:\QT\5.15.2\msvc2015_64\bin` are referenced?

2. If you run the following script in your python environment, what is the output?

```python
# display_qt_paths.py
import pprint
from PyQt5.QtCore import QCoreApplication, QLibraryInfo

path_names = [x for x in dir(QLibraryInfo) if x.endswith('Path')]
locations = {x: QLibraryInfo.location(getattr(QLibraryInfo, x)) for x in path_names}
pprint.pprint(locations)
```

Does any of the paths point to `C:\QT\5.15.2\msvc2015_64\bin`?
Actually, nevermind, I've reproduced the issue by placing a custom Qt5 installation in `PATH`, so I can investigate what is going on.
Looks like the problem is not that we prefer external Qt libraries, but that we fall back to them due to PyQt5 shipping QML plugins with missing dependencies (and an external Qt installation being (in)conveniently in `PATH˙)..

These are the warnings when building without external Qt5 in `PATH`:

```
19135 WARNING: Library not found: could not resolve 'Qt53DLogic.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'.
19135 WARNING: Library not found: could not resolve 'Qt53DAnimation.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'.
19148 WARNING: Library not found: could not resolve 'Qt53DCore.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'.
19148 WARNING: Library not found: could not resolve 'Qt53DInput.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'.
19148 WARNING: Library not found: could not resolve 'Qt53DRender.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene3D\\qtquickscene3dplugin.dll'.
19163 WARNING: Library not found: could not resolve 'Qt53DQuickScene2D.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene2D\\qtquickscene2dplugin.dll'.
19179 WARNING: Library not found: could not resolve 'Qt53DCore.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene2D\\qtquickscene2dplugin.dll'.
19179 WARNING: Library not found: could not resolve 'Qt53DRender.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtQuick\\Scene2D\\qtquickscene2dplugin.dll'.
19179 WARNING: Library not found: could not resolve 'Qt5MultimediaQuick.dll', dependency of '[...]\\Lib\\site-packages\\PyQt5\\Qt5\\qml\\QtMultimedia\\declarative_multimedia.dll'
```

So there are two problematic plugins, `PyQt5\Qt5\qml\QtQuick\Scene3D\qtquickscene3dplugin.dll` and `PyQt5\Qt5\qml\QtMultimedia\declarative_multimedia.dll`.

The Qt53D DLLs can be installed as part of `PyQt3D` (and `PyQt3D-Qt5`), but `Qt5MultimediaQuick.dll` seems to be completely missing from PyQt5 PyPI wheels.

Normally, the plugins with missing dependencies do not cause any issues except for that warning during binary dependency analysis (and the fact that non-functional plugin is collected). But when you have a full Qt5 installation in `PATH`, the missing DLL is found there, and collected from there, along with its dependencies. And because the other location is not part of a PyPI wheel, binary dependency analysis collects them into top-level directory. So you end up with two conflicting sets of Qt DLLs, and then it likely boils down to whether they are next to the executable (--content-directory=.) or in a sub-directory (--content-directory=_internal) how PyQt5 (and/or Qt itself) initializes its search paths.

So as stated before, the immediate work-around for this is not to have that second Qt installation in `PATH` when using PyInstaller.

But I suppose we could perform initial binary dependency analysis for plugins (both regular and QML) and automatically exclude the ones that are missing Qt dependencies and the ones whose Qt dependencies are resolved outside of the expected DLL directory (in case of PyPI wheels). I suspect similar problem would occur with PyQt5 PyPI wheels on macOS with Qt5 also installed in Homebrew... And as a bonus, early elimination of problematic plugins should also eliminate subsequent warnings during binary dependency analysis.
Thank you so much, I removed the second Qt installation from `PATH`.  And it actually worked.

Commit message: rthooks: PyQt6/PySide6: add sys._MEIPASS to DYLD_LIBRARY_PATH

Have PyQt6 and PySide6 run-time hooks add sys._MEIPASS to
DYLD_LIBRARY_PATH when not running as a macOS app bundle. This
is required for QtNetwork in Qt6 to be able to pick up OpenSSL
dynamic libraries for its `openssl` TLS backend; this both ensures
that the backend is functional if OpenSSL libs were collected, and
also prevents fallback to external locations, such as Homebrew.

Applicable only to Qt6 and its bindings; macOS Qt5 builds seem to
use the OS-provided SecureTransport API instead of OpenSSL.

Patch to review:
diff --git a/PyInstaller/hooks/rthooks/pyi_rth_pyqt6.py b/PyInstaller/hooks/rthooks/pyi_rth_pyqt6.py
index ffb2e027e12..306f692f23a 100644
--- a/PyInstaller/hooks/rthooks/pyi_rth_pyqt6.py
+++ b/PyInstaller/hooks/rthooks/pyi_rth_pyqt6.py
@@ -49,6 +49,18 @@ def _pyi_rthook():
     if sys.platform.startswith('win') and 'PATH' in os.environ:
         os.environ['PATH'] = sys._MEIPASS + os.pathsep + os.environ['PATH']
 
+    # For macOS POSIX builds, we need to add `sys._MEIPASS` to `DYLD_LIBRARY_PATH` so that QtNetwork can discover
+    # OpenSSL dynamic libraries for its `openssl` TLS backend. This also prevents fallback to external locations, such
+    # as Homebrew. For .app bundles, this is unnecessary because QtNetwork explicitly searches `Contents/Frameworks`.
+    if sys.platform == 'darwin' and not is_macos_app_bundle:
+        search_paths = os.environ.get('DYLD_LIBRARY_PATH')
+        if search_paths:
+            if sys._MEIPASS not in search_paths:
+                search_paths = os.pathsep.join([sys._MEIPASS, *search_paths.split(os.pathsep)])
+        else:
+            search_paths = sys._MEIPASS
+        os.environ['DYLD_LIBRARY_PATH'] = search_paths
+
 
 _pyi_rthook()
 del _pyi_rthook
diff --git a/PyInstaller/hooks/rthooks/pyi_rth_pyside6.py b/PyInstaller/hooks/rthooks/pyi_rth_pyside6.py
index 394b20b1273..2fa0ad770e5 100644
--- a/PyInstaller/hooks/rthooks/pyi_rth_pyside6.py
+++ b/PyInstaller/hooks/rthooks/pyi_rth_pyside6.py
@@ -48,6 +48,18 @@ def _pyi_rthook():
     if sys.platform.startswith('win') and 'PATH' in os.environ:
         os.environ['PATH'] = sys._MEIPASS + os.pathsep + os.environ['PATH']
 
+    # For macOS POSIX builds, we need to add `sys._MEIPASS` to `DYLD_LIBRARY_PATH` so that QtNetwork can discover
+    # OpenSSL dynamic libraries for its `openssl` TLS backend. This also prevents fallback to external locations, such
+    # as Homebrew. For .app bundles, this is unnecessary because QtNetwork explicitly searches `Contents/Frameworks`.
+    if sys.platform == 'darwin' and not is_macos_app_bundle:
+        search_paths = os.environ.get('DYLD_LIBRARY_PATH')
+        if search_paths:
+            if sys._MEIPASS not in search_paths:
+                search_paths = os.pathsep.join([sys._MEIPASS, *search_paths.split(os.pathsep)])
+        else:
+            search_paths = sys._MEIPASS
+        os.environ['DYLD_LIBRARY_PATH'] = search_paths
+
 
 _pyi_rthook()
 del _pyi_rthook
diff --git a/PyInstaller/utils/hooks/qt/__init__.py b/PyInstaller/utils/hooks/qt/__init__.py
index d473e74235c..ed89b8961c9 100644
--- a/PyInstaller/utils/hooks/qt/__init__.py
+++ b/PyInstaller/utils/hooks/qt/__init__.py
@@ -233,11 +233,35 @@ def _read_qt_library_info(package):
             qt_info = _read_qt_library_info(self.namespace)
         except Exception as e:
             logger.warning("%s: failed to obtain Qt library info: %s", self, e)
-            qt_info = {}
+            return
 
         for k, v in qt_info.items():
             setattr(self, k, v)
 
+        # Turn package_location into pathlib.Path(), and fully resolve it.
+        self.package_location = pathlib.Path(self.package_location).resolve()
+
+        # Determine if the Qt is bundled with python package itself; this usually means we are dealing with with PyPI
+        # wheels.
+        resolved_qt_prefix_path = pathlib.Path(self.location['PrefixPath']).resolve()
+        self.qt_inside_package = (
+            self.package_location == resolved_qt_prefix_path or  # PySide2 and PySide6 Windows PyPI wheels
+            self.package_location in resolved_qt_prefix_path.parents
+        )
+
+        # Determine directory that contains Qt shared libraries. On non-Windows, this is typically location given by
+        # `LibrariesPath`. On Windows, it is usually `BinariesPath`, except for PySide PyPI wheels, where DLLs are
+        # placed in top-level `PrefixPath`.
+        if compat.is_win:
+            if self.qt_inside_package and not self.is_pyqt:
+                # Windows PyPI wheel
+                qt_lib_dir = self.location['PrefixPath']
+            else:
+                qt_lib_dir = self.location['BinariesPath']
+        else:
+            qt_lib_dir = self.location['LibrariesPath']
+        self.qt_lib_dir = pathlib.Path(qt_lib_dir).resolve()
+
     # Module information list loading/processing
     def _load_module_info(self):
         """
@@ -504,14 +528,14 @@ def _collect_all_or_none(self, mandatory_dll_patterns, optional_dll_patterns=Non
         """
         optional_dll_patterns = optional_dll_patterns or []
 
-        # Resolve path to the the corresponding python package (actually, its parent directory). Used to preserve the
-        # directory structure when DLLs are collected from the python package (e.g., PyPI wheels).
-        package_parent_path = pathlib.Path(self.package_location).resolve().parent
+        # Package parent path; used to preserve the directory structure when DLLs are collected from the python
+        # package (e.g., PyPI wheels).
+        package_parent_path = self.package_location.parent
 
-        # In PyQt5 and PyQt6, the DLLs we are looking for are located in location['BinariesPath'], whereas in PySide2
-        # and PySide6, they are located in location['PrefixPath'].
-        dll_path = self.location['BinariesPath' if self.is_pyqt else 'PrefixPath']
-        dll_path = pathlib.Path(dll_path).resolve()
+        # On Windows, DLLs are typically placed in `location['BinariesPath']`, except for PySide PyPI wheels, where
+        # `location['PrefixPath']` is used. This difference is already handled by `qt_lib_dir`, which is also fully
+        # resolved.
+        dll_path = self.qt_lib_dir
 
         # Helper for processing single DLL pattern
         def _process_dll_pattern(dll_pattern):
@@ -561,30 +585,26 @@ def collect_extra_binaries(self):
         binaries += self._collect_all_or_none(['opengl32sw.dll'])
 
         # Include ICU files, if they exist.
-        # See the "Deployment approach" section in ``PyInstaller/utils/hooks/qt.py``.
+        # See the "Deployment approach" section at the top of this file.
         binaries += self._collect_all_or_none(['icudt??.dll', 'icuin??.dll', 'icuuc??.dll'])
 
         return binaries
 
     # Collect additional shared libraries required for SSL support in QtNetwork, if they are available.
-    # Applicable only to Windows. See issue #3520, #4048.
+    # Primarily applicable to Windows (see issue #3520, #4048).
     def collect_qtnetwork_files(self):
         """
-        Collect extra binaries/DLLs required by the QtNetwork module. These include OpenSSL DLLs. Applicable only
-        on Windows (on other OSes, empty list is returned).
+        Collect extra binaries/shared libraries required by the QtNetwork module, such as OpenSSL shared libraries.
         """
 
         # No-op if requested Qt-based package is not available.
         if self.version is None:
             return []
 
-        # Applicable only to Windows.
-        if not compat.is_win:
-            return []
-
-        # Check if QtNetwork supports SSL
+        # Check if QtNetwork supports SSL and has OpenSSL backend available (Qt >= 6.1).
+        # Also query the run-time OpenSSL version, so we know what dynamic libraries we need to search for.
         @isolated.decorate
-        def _ssl_enabled(package):
+        def _check_if_openssl_enabled(package):
             import sys
             import importlib
 
@@ -592,6 +612,7 @@ def _ssl_enabled(package):
             # equivalent to: from package.QtCore import QCoreApplication
             QtCore = importlib.import_module('.QtCore', package)
             QCoreApplication = QtCore.QCoreApplication
+            QLibraryInfo = QtCore.QLibraryInfo
             # equivalent to: from package.QtNetwork import QSslSocket
             QtNetwork = importlib.import_module('.QtNetwork', package)
             QSslSocket = QtNetwork.QSslSocket
@@ -599,34 +620,193 @@ def _ssl_enabled(package):
             # Instantiate QCoreApplication to suppress warnings
             app = QCoreApplication(sys.argv)  # noqa: F841
 
-            return QSslSocket.supportsSsl()
+            if not QSslSocket.supportsSsl():
+                return False, None
+
+            # Query the run-time OpenSSL version
+            openssl_version = QSslSocket.sslLibraryVersionNumber()
+
+            # For Qt >= 6.1, check if `openssl` TLS backend is available
+            try:
+                qt_version = QLibraryInfo.version().segments()
+            except AttributeError:
+                qt_version = []  # Qt <= 5.8
+
+            if qt_version < [6, 1]:
+                return True, openssl_version  # TLS backends not implemented yet
+
+            return ('openssl' in QSslSocket.availableBackends(), openssl_version)
+
+        openssl_enabled, openssl_version = _check_if_openssl_enabled(self.namespace)
+        if not openssl_enabled or openssl_version == 0:
+            logger.debug("%s: QtNetwork: does not support SSL or does not use OpenSSL.", self)
+            return []
 
-        if not _ssl_enabled(self.namespace):
+        # The actual search is handled in OS-specific ways.
+        if compat.is_win:
+            return self._collect_qtnetwork_openssl_windows(openssl_version)
+        elif compat.is_darwin:
+            return self._collect_qtnetwork_openssl_macos(openssl_version)
+        else:
             return []
 
-        # Resolve path to the the corresponding python package (actually, its parent directory). Used to preserve the
-        # directory structure when DLLs are collected from the python package (e.g., PyPI wheels).
-        package_parent_path = pathlib.Path(self.package_location).resolve().parent
+    def _collect_qtnetwork_openssl_windows(self, openssl_version):
+        """
+        Windows-specific collection of OpenSSL DLLs required by QtNetwork module.
+        """
+
+        # Package parent path; used to preserve the directory structure when DLLs are collected from the python
+        # package (e.g., PyPI wheels).
+        package_parent_path = self.package_location.parent
+
+        # The OpenSSL DLLs might be shipped with PyPI wheel (PyQt5), might be available in the environment (msys2,
+        # anaconda), or might be expected to be available in the environment (PySide2, PySide6, PyQt6 PyPI wheels).
+        #
+        # The OpenSSL DLL naming scheme depends on the version:
+        #  - OpenSSL 1.0.x: libeay32.dll, ssleay32.dll
+        #  - OpenSSL 1.1.x 32-bit: libssl-1_1.dll, libcrypto-1_1.dll
+        #  - OpenSSL 1.1.x 64-bit: libssl-1_1-x64.dll, libcrypto-1_1-x64.dll
+        #  - OpenSSL 3.0.x 32-bit: libssl-1.dll, libcrypto-3.dll
+        #  - OpenSSL 3.0.x 64-bit: libssl-3-x64.dll, libcrypto-3-x64.dll
+        #
+        # The official Qt builds (which are used by PySide and PyQt PyPI wheels) seem to be build against:
+        #  - OpenSSL 1.1x starting with Qt5 5.14.2:
+        #    https://www.qt.io/blog/2019/06/17/qt-5-12-4-released-support-openssl-1-1-1
+        #  - OpenSSL 3.x starting with Qt6 6.5.0:
+        #    https://www.qt.io/blog/moving-to-openssl-3-in-binary-builds-starting-from-qt-6.5-beta-2
+        #
+        # However, a package can build Qt against OpenSSL version of their own choice. For example, at the time of
+        # writing, both mingw-w64-x86_64-qt5-base 5.15.11+kde+r138-1 and mingw-w64-x86_64-qt6-base 6.6.0-2 packages
+        # depend on mingw-w64-x86_64-openssl 3.1.4-1 (so OpenSSL 3).
+        #
+        # Luckily, we can query the run-time version of OpenSSL by calling `QSslSocket.sslLibraryVersionNumber()`,
+        # and narrow down the search for specific version.
+        if openssl_version >= 0x10000000 and openssl_version < 0x10100000:
+            # OpenSSL 1.0.x - used by old Qt5 builds
+            dll_names = (
+                'libeay32.dll',
+                'ssleay32.dll',
+            )
+            logger.debug("%s: QtNetwork: looking for OpenSSL 1.0.x DLLs: %r", self, dll_names)
+        elif openssl_version >= 0x10100000 and openssl_version < 0x30000000:
+            # OpenSSL 1.1.x
+            dll_names = (
+                'libssl-1_1-x64.dll' if compat.is_64bits else 'libssl-1_1.dll',
+                'libcrypto-1_1-x64.dll' if compat.is_64bits else 'libcrypto-1_1.dll',
+            )
+            logger.debug("%s: QtNetwork: looking for OpenSSL 1.1.x DLLs: %r", self, dll_names)
+        elif openssl_version >= 0x30000000 and openssl_version < 0x30100000:
+            # OpenSSL 3.0.x
+            dll_names = (
+                'libssl-3-x64.dll' if compat.is_64bits else 'libssl-3.dll',
+                'libcrypto-3-x64.dll' if compat.is_64bits else 'libcrypto-3.dll',
+            )
+            logger.debug("%s: QtNetwork: looking for OpenSSL 3.0.x DLLs: %r", self, dll_names)
+        else:
+            dll_names = []  # Nothing to search for
+            logger.warning("%s: QtNetwork: unsupported OpenSSL version: %X", self, openssl_version)
+
+        binaries = []
+        found_in_package = False
+        for dll in dll_names:
+            # Attempt to resolve the DLL path
+            dll_file_path = bindepend.resolve_library_path(dll, search_paths=[self.qt_lib_dir])
+            if dll_file_path is None:
+                continue
+            dll_file_path = pathlib.Path(dll_file_path).resolve()
+            if package_parent_path in dll_file_path.parents:
+                # The DLL is located within python package; preserve the layout
+                dst_dll_path = dll_file_path.parent.relative_to(package_parent_path)
+                found_in_package = True
+            else:
+                # The DLL is not located within python package; collect into top-level directory
+                dst_dll_path = '.'
+            binaries.append((str(dll_file_path), str(dst_dll_path)))
+
+        # If we found at least one OpenSSL DLL in the bindings' python package directory, discard all external
+        # OpenSSL DLLs.
+        if found_in_package:
+            binaries = [(dll_src_path, dll_dest_path) for dll_src_path, dll_dest_path in binaries
+                        if package_parent_path in pathlib.Path(dll_src_path).parents]
+
+        return binaries
+
+    def _collect_qtnetwork_openssl_macos(self, openssl_version):
+        """
+        macOS-specific collection of OpenSSL dylibs required by QtNetwork module.
+        """
+
+        # The official Qt5 builds on macOS (shipped by PyPI wheels) appear to be built with Apple's SecureTransport API
+        # instead of OpenSSL; for example, `QSslSocket.sslLibraryVersionNumber` returns 0, while
+        # `sslLibraryVersionString()` returns "Secure Transport, macOS 12.6". So with PySide2 and PyQt5, we do not need
+        # to worry about collection of OpenSSL shared libraries.
+        #
+        # Support for OpenSSL was introduced in Qt 6.1 with `openssl` TLS backend; the official Qt6 builds prior to 6.5
+        # seem to be built with OpenSSL 1.1.x, and later versions with 3.0.x. However, PySide6 and PyQt6 PyPI wheels do
+        # not ship OpenSSL dynamic libraries at all , so whether `openssl` TLS backend is used or not depends on the
+        # presence of externally provided OpenSSL dynamic libraries (for example, provided by Homebrew). It is worth
+        # noting that python.org python installers *do* provide OpenSSL shared libraries (1.1.x for python <= 3.10,
+        # 3.0.x for python >= 3.12, and both for python 3.11) for its `_ssl` extension - however, these are NOT visible
+        # to Qt and its QtNetwork module.
+        #
+        # When the frozen application is built and we collect python's `_ssl` extension, we also collect the OpenSSL
+        # shared libraries shipped by python. So at least in theory, those should be available to QtNetwork module as
+        # well (assuming they are of compatible version). However, this is not exactly the case - QtNetwork looks for
+        # the libraries in locations given by `DYLD_LIBRARY_PATH` environment variable and in .app/Contents/Frameworks
+        # (if the program is an .app bundle):
+        #
+        # https://github.com/qt/qtbase/blob/6.6.0/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp#L590-L599
+        #
+        # So it works out-of-the box for our .app bundles, because starting with PyInstaller 6.0, `sys._MEIPASS` is in
+        # .app/Contents/Frameworks. But it does not with POSIX builds, because bootloader does not modify the
+        # `DYLD_LIBRARY_PATH` environment variable to include `sys._MEIPASS` (since we usually do not need that;
+        # regular linked library resolution in our macOS builds is done via path rewriting and rpaths). So either we
+        # need a run-time hook to add `sys._MEIPASS` to `DYLD_LIBRARY_PATH`, or modify the bootloader to always do that.
+        #
+        # Collecting the OpenSSL library and making it discoverable by adding `sys._MEIPASS` to `DYLD_LIBRARY_PATH`
+        # should also prevent QtNetwork from "accidentally" pulling in Homebrew version at run-time (if Homebrew is
+        # installed on the target system and provides compatible OpenSSL version).
+        #
+        # Therefore, try to resolve OpenSSL library via the version indicated by `QSslSocket.sslLibraryVersionNumber`;
+        # however, we first explicitly search only {sys.base_prefix}/lib (which is where python.org builds put their
+        # dynamic libs), and only if that fails, perform regular dylib path resolution. This way we ensure that if the
+        # OpenSSL dylibs are provided by python itself, we always prefer those over the Homebrew version (since we are
+        # very likely going to collect them for python's `_ssl` extension anyway).
+
+        # As per above text, we need to worry only about Qt6, and thus OpenSSL 1.1.x or 3.0.x
+        if openssl_version >= 0x10100000 and openssl_version < 0x30000000:
+            # OpenSSL 1.1.x
+            dylib_names = (
+                'libcrypto.1.1.dylib',
+                'libssl.1.1.dylib',
+            )
+            logger.debug("%s: QtNetwork: looking for OpenSSL 1.1.x dylibs: %r", self, dylib_names)
+        elif openssl_version >= 0x30000000 and openssl_version < 0x30100000:
+            # OpenSSL 3.0.x
+            dylib_names = (
+                'libcrypto.3.dylib',
+                'libssl.3.dylib',
+            )
+            logger.debug("%s: QtNetwork: looking for OpenSSL 3.0.x dylibs: %r", self, dylib_names)
+        else:
+            dylib_names = []  # Nothing to search for
+            logger.warning("%s: QtNetwork: unsupported OpenSSL version: %X", self, openssl_version)
 
-        # PyPI version of PySide2 requires user to manually install SSL libraries into the PrefixPath. Other versions
-        # (e.g., the one provided by Conda) put the libraries into the BinariesPath. PyQt5 also uses BinariesPath.
-        # Accommodate both options by searching both locations...
-        locations = (self.location['BinariesPath'], self.location['PrefixPath'])
-        dll_names = ('libeay32.dll', 'ssleay32.dll', 'libssl-1_1-x64.dll', 'libcrypto-1_1-x64.dll')
+        # Compared to Windows, we do not have to worry about dylib's path preservation, as these are never part of
+        # the package, and are therefore always collected to the top-level application directory.
         binaries = []
-        for location in locations:
-            location = pathlib.Path(location).resolve()
-            for dll in dll_names:
-                dll_file_path = location / dll
-                if not dll_file_path.exists():
-                    continue
-                if package_parent_path in dll_file_path.parents:
-                    # The DLL is located within python package; preserve the layout
-                    dst_dll_path = dll_file_path.parent.relative_to(package_parent_path)
-                else:
-                    # The DLL is not located within python package; collect into top-level directory
-                    dst_dll_path = '.'
-                binaries.append((str(dll_file_path), str(dst_dll_path)))
+        base_prefix_lib_dir = os.path.join(compat.base_prefix, 'lib')
+        for dylib in dylib_names:
+            # First, attempt to resolve using only {sys.base_prefix}/lib - `bindepend.resolve_library_path` uses
+            # standard dyld search semantics and uses the given search paths as fallback (and would therefore
+            # favor Homebrew-provided version of the library).
+            dylib_path = bindepend._resolve_library_path_in_search_paths(dylib, search_paths=[base_prefix_lib_dir])
+            if dylib_path is None:
+                dylib_path = bindepend.resolve_library_path(dylib, search_paths=[base_prefix_lib_dir, self.qt_lib_dir])
+            if dylib_path is None:
+                continue
+            binaries.append((str(dylib_path), '.'))
+
         return binaries
 
     def collect_qtqml_files(self):
diff --git a/news/8226.hooks.1.rst b/news/8226.hooks.1.rst
new file mode 100644
index 00000000000..f5c6bcd7f0e
--- /dev/null
+++ b/news/8226.hooks.1.rst
@@ -0,0 +1,4 @@
+(macOS) Have ``PySide6`` and ``PyQt6`` run-time hooks prepend
+``sys._MEIPASS`` to ``DYLD_LIBRARY_PATH`` in POSIX builds, in order
+to ensure that ``QtNetwork`` discovers the bundled copy of the OpenSSL
+shared library.
diff --git a/tests/functional/test_qt.py b/tests/functional/test_qt.py
index 502676a4d47..91dd111f5c4 100644
--- a/tests/functional/test_qt.py
+++ b/tests/functional/test_qt.py
@@ -147,26 +147,68 @@ def test_Qt_QtQml(pyi_builder, QtPyLib):
 @QtPyLibs
 def test_Qt_QtNetwork_SSL_support(pyi_builder, QtPyLib):
     # Skip the test if QtNetwork does not support SSL (e.g., due to lack of compatible OpenSSL shared library on the
-    # test system).
+    # test system). Starting with Qt 6.1, different backends provide TLS functionality, so explicitly check if
+    # 'openssl' backend is available.
     @isolated.decorate
-    def check_ssl_support(package):
+    def check_openssl_support(package):
         import sys
         import importlib
+
         QtCore = importlib.import_module('.QtCore', package)
         QtNetwork = importlib.import_module('.QtNetwork', package)
+
+        # We must initialize QCoreApplication before using QtNetwork
         app = QtCore.QCoreApplication(sys.argv)  # noqa: F841
-        return QtNetwork.QSslSocket.supportsSsl()
 
-    if not check_ssl_support(QtPyLib):
-        pytest.skip('QtNetwork does not support SSL on this platform.')
+        if not QtNetwork.QSslSocket.supportsSsl():
+            return False
+
+        # For Qt >= 6.1, check if `openssl` TLS backend is available
+        try:
+            qt_version = QtCore.QLibraryInfo.version().segments()
+        except AttributeError:
+            qt_version = []  # Qt <= 5.8
+
+        if qt_version < [6, 1]:
+            return True  # TLS backends not implemented yet
+
+        return 'openssl' in QtNetwork.QSslSocket.availableBackends()
+
+    if not check_openssl_support(QtPyLib):
+        pytest.skip('QtNetwork does not use OpenSSL.')
 
     pyi_builder.test_source(
         """
         import sys
-        from {0}.QtCore import QCoreApplication
+        from {0}.QtCore import QCoreApplication, QLibraryInfo
         from {0}.QtNetwork import QSslSocket
+
         app = QCoreApplication(sys.argv)
-        assert QSslSocket.supportsSsl()
+
+        # Make sure SSL is supported
+        assert QSslSocket.supportsSsl(), "SSL not supported!"
+
+        # Display OpenSSL info
+        print(
+            f"OpenSSL build version: {{QSslSocket.sslLibraryBuildVersionNumber():X}} "
+            f"({{QSslSocket.sslLibraryBuildVersionString()}})"
+        )
+        print(
+            f"OpenSSL run-time version: {{QSslSocket.sslLibraryVersionNumber():X}} "
+            f"({{QSslSocket.sslLibraryVersionString()}})"
+        )
+
+        # Obtain Qt version
+        try:
+            qt_version = QLibraryInfo.version().segments()
+        except AttributeError:
+            qt_version = []  # Qt <= 5.8
+
+        # If Qt supports TLS backends (>= 6.1), make sure OpenSSL backend is available.
+        if qt_version >= [6, 1]:
+            print(f"Active TLS backend: {{QSslSocket.activeBackend()}}")
+            print(f"Available TLS backends: {{QSslSocket.availableBackends()}}")
+            assert 'openssl' in QSslSocket.availableBackends(), "OpenSSL TLS backend not available!"
         """.format(QtPyLib), **USE_WINDOWED_KWARG
     )
 


Code review comments:
diff_hunk: @@ -525,7 +525,46 @@ def collect_plugins(self, plugin_type):
         logger.debug("%s: found plugin files for plugin type %r: %r", self, plugin_type, plugin_files)
 
         plugin_dst_dir = os.path.join(self.qt_rel_dir, 'plugins', plugin_type)
-        return [(plugin_file, plugin_dst_dir) for plugin_file in plugin_files]
+
+        # Exclude plugins with invalid Qt dependencies.
+        binaries = []
+        for plugin_file in plugin_files:
+            valid, reason = self._validate_plugin_dependencies(plugin_file)
+            if valid:
+                binaries.append((plugin_file, plugin_dst_dir))
+            else:
+                logger.warning("%s: excluding plugin %r (%r)! Reason: %s", self, plugin_file, plugin_type, reason)
+        return binaries
+
+    def _validate_plugin_dependencies(self, plugin_file):
+        """
+        Validate Qt dependencies of the given Qt plugin file. For the plugin to pass validation, all its Qt dependencies
+        must be available (resolvable), and must be resolvable from the default Qt shared library directory (to avoid
+        pulling in libraries from unrelated Qt installations that happen to be in search path).
+        """
+
+        imported_libraries = bindepend.get_imports(plugin_file, search_paths=[self.qt_lib_dir])
+        while imported_libraries:
+            imported_lib_name, imported_lib_path = imported_libraries.pop()  # (name, fullpath) tuple
path: PyInstaller/utils/hooks/qt/__init__.py
text: @user1:
Why can't this just be a generic 
```python
for (imported_lib_name, imported_lib_path) in imported_libraries:

@author:
It can, and it should.