diff --git a/PyInstaller/hooks/hook-PyQt6.QtSpatialAudio.py b/PyInstaller/hooks/hook-PyQt6.QtSpatialAudio.py new file mode 100644 index 0000000000..49d27d6f76 --- /dev/null +++ b/PyInstaller/hooks/hook-PyQt6.QtSpatialAudio.py @@ -0,0 +1,14 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2013-2023, PyInstaller Development Team. +# +# Distributed under the terms of the GNU General Public License (version 2 +# or later) with exception for distributing the bootloader. +# +# The full license is in the file COPYING.txt, distributed with this software. +# +# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) +#----------------------------------------------------------------------------- + +from PyInstaller.utils.hooks.qt import add_qt6_dependencies + +hiddenimports, binaries, datas = add_qt6_dependencies(__file__) diff --git a/PyInstaller/hooks/hook-PyQt6.QtTextToSpeech.py b/PyInstaller/hooks/hook-PyQt6.QtTextToSpeech.py new file mode 100644 index 0000000000..49d27d6f76 --- /dev/null +++ b/PyInstaller/hooks/hook-PyQt6.QtTextToSpeech.py @@ -0,0 +1,14 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2013-2023, PyInstaller Development Team. +# +# Distributed under the terms of the GNU General Public License (version 2 +# or later) with exception for distributing the bootloader. +# +# The full license is in the file COPYING.txt, distributed with this software. +# +# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) +#----------------------------------------------------------------------------- + +from PyInstaller.utils.hooks.qt import add_qt6_dependencies + +hiddenimports, binaries, datas = add_qt6_dependencies(__file__) diff --git a/PyInstaller/hooks/hook-PySide6.QtLocation.py b/PyInstaller/hooks/hook-PySide6.QtLocation.py new file mode 100644 index 0000000000..49d27d6f76 --- /dev/null +++ b/PyInstaller/hooks/hook-PySide6.QtLocation.py @@ -0,0 +1,14 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2013-2023, PyInstaller Development Team. +# +# Distributed under the terms of the GNU General Public License (version 2 +# or later) with exception for distributing the bootloader. +# +# The full license is in the file COPYING.txt, distributed with this software. +# +# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) +#----------------------------------------------------------------------------- + +from PyInstaller.utils.hooks.qt import add_qt6_dependencies + +hiddenimports, binaries, datas = add_qt6_dependencies(__file__) diff --git a/PyInstaller/hooks/hook-PySide6.QtSerialBus.py b/PyInstaller/hooks/hook-PySide6.QtSerialBus.py new file mode 100644 index 0000000000..49d27d6f76 --- /dev/null +++ b/PyInstaller/hooks/hook-PySide6.QtSerialBus.py @@ -0,0 +1,14 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2013-2023, PyInstaller Development Team. +# +# Distributed under the terms of the GNU General Public License (version 2 +# or later) with exception for distributing the bootloader. +# +# The full license is in the file COPYING.txt, distributed with this software. +# +# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) +#----------------------------------------------------------------------------- + +from PyInstaller.utils.hooks.qt import add_qt6_dependencies + +hiddenimports, binaries, datas = add_qt6_dependencies(__file__) diff --git a/PyInstaller/hooks/hook-PySide6.QtTextToSpeech.py b/PyInstaller/hooks/hook-PySide6.QtTextToSpeech.py new file mode 100644 index 0000000000..49d27d6f76 --- /dev/null +++ b/PyInstaller/hooks/hook-PySide6.QtTextToSpeech.py @@ -0,0 +1,14 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2013-2023, PyInstaller Development Team. +# +# Distributed under the terms of the GNU General Public License (version 2 +# or later) with exception for distributing the bootloader. +# +# The full license is in the file COPYING.txt, distributed with this software. +# +# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) +#----------------------------------------------------------------------------- + +from PyInstaller.utils.hooks.qt import add_qt6_dependencies + +hiddenimports, binaries, datas = add_qt6_dependencies(__file__) diff --git a/PyInstaller/utils/hooks/qt/__init__.py b/PyInstaller/utils/hooks/qt/__init__.py index 65aa6a4573..1cb21c19ef 100644 --- a/PyInstaller/utils/hooks/qt/__init__.py +++ b/PyInstaller/utils/hooks/qt/__init__.py @@ -195,6 +195,7 @@ def _load_qt_info(self): @isolated.decorate def _read_qt_library_info(package): import os + import sys import importlib # Import the Qt-based package @@ -204,7 +205,7 @@ def _read_qt_library_info(package): QCoreApplication = QtCore.QCoreApplication # QLibraryInfo is not always valid until a QCoreApplication is instantiated. - app = QCoreApplication([]) # noqa: F841 + app = QCoreApplication(sys.argv) # noqa: F841 # Qt6 deprecated QLibraryInfo.location() in favor of QLibraryInfo.path(), and # QLibraryInfo.LibraryLocation enum was replaced by QLibraryInfo.LibraryPath. @@ -586,6 +587,7 @@ def collect_qtnetwork_files(self): # Check if QtNetwork supports SSL @isolated.decorate def _ssl_enabled(package): + import sys import importlib # Import the Qt-based package @@ -597,7 +599,7 @@ def _ssl_enabled(package): QSslSocket = QtNetwork.QSslSocket # Instantiate QCoreApplication to suppress warnings - app = QCoreApplication([]) # noqa: F841 + app = QCoreApplication(sys.argv) # noqa: F841 return QSslSocket.supportsSsl() diff --git a/PyInstaller/utils/hooks/qt/_modules_info.py b/PyInstaller/utils/hooks/qt/_modules_info.py index 339237fecb..a4907e0533 100644 --- a/PyInstaller/utils/hooks/qt/_modules_info.py +++ b/PyInstaller/utils/hooks/qt/_modules_info.py @@ -249,20 +249,19 @@ def __init__(self, module, shared_lib=None, translation=None, plugins=None, bind _QtModuleDef("QtHttpServer", shared_lib="HttpServer", bindings=["PySide6"]), # *** qt/qtlocation *** - # Qt5-only Qt module. + # QtLocation was reintroduced in Qt6 v6.5.0. _QtModuleDef( "QtLocation", shared_lib="Location", translation="qtlocation", plugins=["geoservices"], - bindings=["PySide2", "PyQt5"] + bindings=["PySide2", "PyQt5", "PySide6"] ), _QtModuleDef( "QtPositioning", shared_lib="Positioning", translation="qtlocation", plugins=["position"], - bindings=["PySide2", "PyQt5"] ), # *** qt/qtmacextras *** @@ -291,8 +290,8 @@ def __init__(self, module, shared_lib=None, translation=None, plugins=None, bind bindings=["PySide6", "PyQt6"] ), _QtModuleDef("QtMultimediaWidgets", shared_lib="MultimediaWidgets"), - # Qt6-only Qt module; python module is available in PySide6 >= 6.4.0 - _QtModuleDef("QtSpatialAudio", shared_lib="SpatialAudio", bindings=["PySide6"]), + # Qt6-only Qt module; python module is available in PySide6 >= 6.4.0 and PyQt6 >= 6.5.0 + _QtModuleDef("QtSpatialAudio", shared_lib="SpatialAudio", bindings=["PySide6", "PyQt6"]), # *** qt/qtnetworkauth *** # QtNetworkAuth python module is available in all bindings but PySide2. @@ -350,7 +349,9 @@ def __init__(self, module, shared_lib=None, translation=None, plugins=None, bind # *** qt/qtserialbus *** # No python module; shared library -> plugins association entry. - _QtModuleDef(None, shared_lib="SerialBus", plugins=["canbus"]), + # PySide6 6.5.0 introduced python module. + _QtModuleDef(None, shared_lib="SerialBus", plugins=["canbus"], bindings=["!PySide6"]), + _QtModuleDef("QtSerialBus", shared_lib="SerialBus", plugins=["canbus"], bindings=["PySide6"]), # *** qt/qtsvg *** _QtModuleDef("QtSvg", shared_lib="Svg"), @@ -358,8 +359,7 @@ def __init__(self, module, shared_lib=None, translation=None, plugins=None, bind _QtModuleDef("QtSvgWidgets", shared_lib="SvgWidgets", bindings=["PySide6", "PyQt6"]), # *** qt/qtspeech *** - # Qt5-only Qt module. - _QtModuleDef("QtTextToSpeech", shared_lib="TextToSpeech"), + _QtModuleDef("QtTextToSpeech", shared_lib="TextToSpeech", plugins=["texttospeech"]), # *** qt/qttools *** # QtDesigner python module is available in all bindings but PySide2. diff --git a/news/7549.hooks.1.rst b/news/7549.hooks.1.rst new file mode 100644 index 0000000000..7e74b7c6b2 --- /dev/null +++ b/news/7549.hooks.1.rst @@ -0,0 +1,2 @@ +Add hook for ``PyQt6.QtTextToSpeech`` module, which was added in +``PyQt6`` 6.4 series. diff --git a/news/7549.hooks.2.rst b/news/7549.hooks.2.rst new file mode 100644 index 0000000000..fb4c7a2028 --- /dev/null +++ b/news/7549.hooks.2.rst @@ -0,0 +1,2 @@ +Add hook for ``PyQt6.QtSpatialAudio`` module, which was added in +``PyQt6`` 6.5.0. diff --git a/news/7549.hooks.rst b/news/7549.hooks.rst new file mode 100644 index 0000000000..01fa0629fe --- /dev/null +++ b/news/7549.hooks.rst @@ -0,0 +1,3 @@ +Extend ``PySide6`` hooks for ``PySide6`` 6.5.0 compatibility: add hooks +for ``QtLocation``, ``QtTextToSpeech``, and ``QtSerialBus`` modules +that were introduced in ``PySide`` 6.5.0. diff --git a/tests/functional/test_qt.py b/tests/functional/test_qt.py index bc1ed1b440..c5a80d6db8 100644 --- a/tests/functional/test_qt.py +++ b/tests/functional/test_qt.py @@ -18,7 +18,7 @@ from PyInstaller.compat import is_win, is_darwin from PyInstaller.utils.hooks import is_module_satisfies, can_import_module from PyInstaller.utils.hooks.qt import get_qt_library_info -from PyInstaller.utils.tests import importorskip, requires, xfail, skipif +from PyInstaller.utils.tests import importorskip, requires, skipif PYQT5_NEED_OPENGL = pytest.mark.skipif( is_module_satisfies('PyQt5 <= 5.10.1'), @@ -144,18 +144,28 @@ def test_Qt_QtQml(pyi_builder, QtPyLib): ) -@pytest.mark.parametrize( - 'QtPyLib', [ - qt_param('PyQt5'), - qt_param('PyQt6'), - qt_param('PySide2', marks=xfail(is_win, reason='PySide2 wheels on Windows do not include SSL DLLs.')), - qt_param('PySide6', marks=xfail(is_win, reason='PySide6 wheels on Windows do not include SSL DLLs.')), - ] -) +@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). + @isolated.decorate + def check_ssl_support(package): + import sys + import importlib + QtCore = importlib.import_module('.QtCore', package) + QtNetwork = importlib.import_module('.QtNetwork', package) + 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.') + pyi_builder.test_source( """ + import sys + from {0}.QtCore import QCoreApplication from {0}.QtNetwork import QSslSocket + app = QCoreApplication(sys.argv) assert QSslSocket.supportsSsl() """.format(QtPyLib), **USE_WINDOWED_KWARG ) @@ -165,11 +175,12 @@ def test_Qt_QtNetwork_SSL_support(pyi_builder, QtPyLib): def test_Qt_QTranslate(pyi_builder, QtPyLib): pyi_builder.test_source( """ + import sys from {0}.QtWidgets import QApplication from {0}.QtCore import QTranslator, QLocale, QLibraryInfo # Initialize Qt default translations - app = QApplication([]) + app = QApplication(sys.argv) translator = QTranslator() locale = QLocale('de_DE') if hasattr(QLibraryInfo, 'path'): @@ -209,7 +220,7 @@ def test_Qt_Ui_file(tmpdir, pyi_builder, data_dir, QtPyLib): is_qt6 = '{0}' in {{'PyQt6', 'PySide6'}} is_pyqt = '{0}' in {{'PyQt5', 'PyQt6'}} - app = QApplication([]) + app = QApplication(sys.argv) # In Qt6, QtQuick supports multiple render APIs and automatically selects one. # However, QtQuickWidgets.QQuickWidget that is used by the test UI file supports only OpenGL, @@ -303,7 +314,7 @@ def _test_Qt_QtWebEngineWidgets(pyi_builder, qt_flavor): ''' - app = QApplication([]) + app = QApplication(sys.argv) class JSResultTester: @@ -373,7 +384,7 @@ def _test_Qt_QtWebEngineQuick(pyi_builder, qt_flavor): from {0}.QtWebEngine import QtWebEngine as QtWebEngineQuick QtWebEngineQuick.initialize() - app = QGuiApplication([]) + app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() engine.loadData(b''' import QtQuick 2.0 @@ -459,11 +470,19 @@ def test_Qt_QtWebEngineQuick_PyQt6(pyi_builder): @requires('PySide6 >= 6.2.2') +@pytest.mark.skipif( + is_module_satisfies('PySide6 == 6.5.0') and is_win, + reason='PySide6 6.5.0 PyPI wheels for Windows are missing opengl32sw.dll.' +) def test_Qt_QtWebEngineWidgets_PySide6(pyi_builder): _test_Qt_QtWebEngineWidgets(pyi_builder, 'PySide6') @requires('PySide6 >= 6.2.2') +@pytest.mark.skipif( + is_module_satisfies('PySide6 == 6.5.0') and is_win, + reason='PySide6 6.5.0 PyPI wheels for Windows are missing opengl32sw.dll.' +) def test_Qt_QtWebEngineQuick_PySide6(pyi_builder): _test_Qt_QtWebEngineQuick(pyi_builder, 'PySide6') @@ -496,10 +515,11 @@ def test_Qt_QtMultimedia_player_init(pyi_builder, QtPyLib): def test_Qt_QtMultimedia_with_true_property(pyi_builder, QtPyLib): pyi_builder.test_source( """ + import sys from {0} import QtCore, QtMultimedia from __feature__ import true_property - app = QtCore.QCoreApplication() + app = QtCore.QCoreApplication(sys.argv) """.format(QtPyLib), **USE_WINDOWED_KWARG ) diff --git a/tests/requirements-libraries.txt b/tests/requirements-libraries.txt index a7804edf6c..827fb789a8 100644 --- a/tests/requirements-libraries.txt +++ b/tests/requirements-libraries.txt @@ -29,7 +29,7 @@ pygments==2.14.0 PyGObject==3.44.1; sys_platform == 'linux' # Current PySide2 wheels explicitly require python < 3.11 PySide2==5.15.2.1; python_version < '3.11' -PySide6==6.4.3 +PySide6==6.5.0 # PyQt5 and add-on packages PyQt5==5.15.9 PyQt3D==5.15.6 @@ -40,13 +40,13 @@ PyQtPurchasing==5.15.5 QScintilla==2.13.4 PyQtWebEngine==5.15.6 # PyQt6 and add-on packages -PyQt6==6.4.2 -PyQt6-3D==6.4.0 -PyQt6-Charts==6.4.0 -PyQt6-DataVisualization==6.4.0 -PyQt6-NetworkAuth==6.4.0 +PyQt6==6.5.0 +PyQt6-3D==6.5.0 +PyQt6-Charts==6.5.0 +PyQt6-DataVisualization==6.5.0 +PyQt6-NetworkAuth==6.5.0 PyQt6-QScintilla==2.13.4 -PyQt6-WebEngine==6.4.0 +PyQt6-WebEngine==6.5.0 python-dateutil==2.8.2 pytz==2023.3 requests==2.28.2