diff options
154 files changed, 1774 insertions, 739 deletions
diff --git a/README.pyside6.md b/README.pyside6.md index 8c70b1c8f..9178660d5 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -88,7 +88,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 3247a550d..e6044c4a4 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -65,7 +65,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_essentials.md b/README.pyside6_essentials.md index ef0376e43..7f96c19b1 100644 --- a/README.pyside6_essentials.md +++ b/README.pyside6_essentials.md @@ -51,7 +51,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial license. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/README.pyside6_examples.md b/README.pyside6_examples.md index ffa7d83d5..15e318151 100644 --- a/README.pyside6_examples.md +++ b/README.pyside6_examples.md @@ -28,7 +28,7 @@ and [join our community](https://wiki.qt.io/Qt_for_Python#Community)! ### Licensing -PySide6 is available under both Open Source (LGPLv3/GPLv2) and commercial +PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial licenses. Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/build_scripts/main.py b/build_scripts/main.py index 9a8d4fb3f..dac476cdb 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -508,7 +508,11 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): log.info("-" * 3) if sys.platform == 'win32': log.info(f"OpenSSL dll directory: {OPTION['OPENSSL']}") - if sys.platform == 'darwin': + # for cross-compilation it is possible to use a macOS host, but + # pyside_macos_deployment_target is not relevant for the target. + # The only exception here is when we are trying to cross-compile from intel mac to m1 mac. + # This case is not supported yet. + if sys.platform == 'darwin' and not self.is_cross_compile: pyside_macos_deployment_target = (macos_pyside_min_deployment_target()) log.info(f"MACOSX_DEPLOYMENT_TARGET set to: {pyside_macos_deployment_target}") log.info("=" * 30) @@ -745,7 +749,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): cmake_cmd += platform_cmake_options() - if sys.platform == 'darwin': + # for a macOS host, cross-compilation is possible, but for the host system as such + # we only build shiboken. Hence the following code can be skipped. + if sys.platform == 'darwin' and not self.is_cross_compile: if OPTION["MACOS_ARCH"]: # also tell cmake which architecture to use cmake_cmd.append(f"-DCMAKE_OSX_ARCHITECTURES:STRING={OPTION['MACOS_ARCH']}") diff --git a/build_scripts/platforms/macos.py b/build_scripts/platforms/macos.py index dbe60d343..505573e0b 100644 --- a/build_scripts/platforms/macos.py +++ b/build_scripts/platforms/macos.py @@ -21,7 +21,7 @@ def _macos_patch_executable(name, _vars=None): macos_add_rpath(rpath, binary) -def prepare_standalone_package_macos(pyside_build, _vars): +def prepare_standalone_package_macos(pyside_build, _vars, is_android=False): built_modules = _vars['built_modules'] constrain_modules = None @@ -119,7 +119,11 @@ def prepare_standalone_package_macos(pyside_build, _vars): ignored_modules = [] if not pyside_build.is_webengine_built(built_modules): ignored_modules.extend(['libQt6WebEngine*.dylib']) + accepted_modules = ['libQt6*.6.dylib'] + if is_android: + accepted_modules = ['libQt6*.so', '*-android-dependencies.xml'] + if constrain_modules: accepted_modules = [f"libQt6{module}*.6.dylib" for module in constrain_modules] @@ -156,6 +160,8 @@ def prepare_standalone_package_macos(pyside_build, _vars): # <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins plugins_target = destination_qt_dir / "plugins" filters = ["*.dylib"] + if is_android: + filters = ["*.so"] copydir("{qt_plugins_dir}", plugins_target, _filter=filters, recursive=True, diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py index 3333f5f96..8378d42be 100644 --- a/build_scripts/platforms/unix.py +++ b/build_scripts/platforms/unix.py @@ -237,7 +237,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build(): _vars['built_modules'] = generated_config['built_modules'] if sys.platform == 'darwin': - prepare_standalone_package_macos(pyside_build, _vars) + prepare_standalone_package_macos(pyside_build, _vars, is_android=is_android) else: prepare_standalone_package_linux(pyside_build, _vars, cross_build, is_android=is_android) diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index d34ada113..2112bba9a 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -997,6 +997,8 @@ def module_QtSerialBus() -> ModuleData: def module_QtVirtualKeyboard() -> ModuleData: data = ModuleData("VirtualKeyboard") data.plugins.append("virtualkeyboard") + data.qtlib.append("libQt6VirtualKeyboardSettings") + return data diff --git a/build_scripts/wheel_override.py b/build_scripts/wheel_override.py index f3f9f17a9..790282cd4 100644 --- a/build_scripts/wheel_override.py +++ b/build_scripts/wheel_override.py @@ -47,7 +47,7 @@ class PysideBuildWheel(_bdist_wheel, CommandMixin): def finalize_options(self): CommandMixin.mixin_finalize_options(self) - if sys.platform == 'darwin': + if sys.platform == 'darwin' and not self.is_cross_compile: # Override the platform name to contain the correct # minimum deployment target. # This is used in the final wheel name. diff --git a/doc/changelogs/changes-6.7.1 b/doc/changelogs/changes-6.7.1 new file mode 100644 index 000000000..66263ed80 --- /dev/null +++ b/doc/changelogs/changes-6.7.1 @@ -0,0 +1,61 @@ +Qt for Python 6.7.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-487] A number of missing classes have been added. + - [PYSIDE-2629] Tooling: pyside6-qsb, pyside6-balsam and pyside6-balsamui + have been added. + - [PYSIDE-2644] QtAsyncio: An issue with tasks with loop not cancelling + has been fixed. + - [PYSIDE-2663] A crash when browsing https://outlook.com has been fixed. + - [PYSIDE-2665] A syntax error in the pyi-files has been fixed. + - [PYSIDE-2668] The comparison of QOperatingSystemVersion.OSType has been + fixed. + - [PYSIDE-2675] Lazy Load: An issue with polymorphic classes has been + fixed. + - [PYSIDE-2676] A crash with Python 3.12 when creating classes from + meta classes has been fixed. + - [PYSIDE-2685] An error in the pyi-files related to the import of + NoneType has been fixed. + - [PYSIDE-2686] Missing imports for types of return values + have been added to the pyi-files. + - [PYSIDE-2698] A crash when querying the size of QtQml.ListProperty + has been fixed and documentation for QtQml.ListProperty + has been added. + - [PYSIDE-2705] Warnings about failures of QObject.disconnect() can + now be suppressed. + - [PYSIDE-2709] A bug using legacy qmlRegisterType() for class hierarchies + has been fixed. + - [QTBUG-123997] Multimedia: The renaming of the namespace QAudio to + QtAudio has been undone following a revert in Qt. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2590] An attribute for global inline namespace scopes has been + added. + - [PYSIDE-2602] Generate Python override code for added virtuals + - [PYSIDE-2602] Support for virtual functions with return type + modifications has been added and the function + QWebEnginePage.javaScriptPrompt() + has been fixed accordingly. + - [PYSIDE-2675] A code snippet placeholder for the base class for + polymorphic-id-expressions has been added, fixing + a potentially undefined behavior when using the + derived classes. diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst deleted file mode 100644 index 1470001d6..000000000 --- a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst +++ /dev/null @@ -1,11 +0,0 @@ -Widget Gallery -============== - - -Widget Gallery demonstrates all three graph types and some of their special -features. The graphs have their own tabs in the application. - - -.. image:: widgetgallery.webp - :width: 400 - :alt: Widget Screenshot diff --git a/examples/graphs/3d/widgetgallery/axesinputhandler.py b/examples/graphs/3d/widgetgraphgallery/axesinputhandler.py index 4c4202974..4c4202974 100644 --- a/examples/graphs/3d/widgetgallery/axesinputhandler.py +++ b/examples/graphs/3d/widgetgraphgallery/axesinputhandler.py diff --git a/examples/graphs/3d/widgetgallery/bargraph.py b/examples/graphs/3d/widgetgraphgallery/bargraph.py index 822acb4a9..822acb4a9 100644 --- a/examples/graphs/3d/widgetgallery/bargraph.py +++ b/examples/graphs/3d/widgetgraphgallery/bargraph.py diff --git a/examples/graphs/3d/widgetgallery/custominputhandler.py b/examples/graphs/3d/widgetgraphgallery/custominputhandler.py index 15fe00e70..15fe00e70 100644 --- a/examples/graphs/3d/widgetgallery/custominputhandler.py +++ b/examples/graphs/3d/widgetgraphgallery/custominputhandler.py diff --git a/examples/graphs/3d/widgetgallery/data/layer_1.png b/examples/graphs/3d/widgetgraphgallery/data/layer_1.png Binary files differindex 9138c710a..9138c710a 100644 --- a/examples/graphs/3d/widgetgallery/data/layer_1.png +++ b/examples/graphs/3d/widgetgraphgallery/data/layer_1.png diff --git a/examples/graphs/3d/widgetgallery/data/layer_2.png b/examples/graphs/3d/widgetgraphgallery/data/layer_2.png Binary files differindex 61631ae8b..61631ae8b 100644 --- a/examples/graphs/3d/widgetgallery/data/layer_2.png +++ b/examples/graphs/3d/widgetgraphgallery/data/layer_2.png diff --git a/examples/graphs/3d/widgetgallery/data/layer_3.png b/examples/graphs/3d/widgetgraphgallery/data/layer_3.png Binary files differindex 066ffbe75..066ffbe75 100644 --- a/examples/graphs/3d/widgetgallery/data/layer_3.png +++ b/examples/graphs/3d/widgetgraphgallery/data/layer_3.png diff --git a/examples/graphs/3d/widgetgallery/data/license.txt b/examples/graphs/3d/widgetgraphgallery/data/license.txt index 749daf31f..749daf31f 100644 --- a/examples/graphs/3d/widgetgallery/data/license.txt +++ b/examples/graphs/3d/widgetgraphgallery/data/license.txt diff --git a/examples/graphs/3d/widgetgallery/data/maptexture.jpg b/examples/graphs/3d/widgetgraphgallery/data/maptexture.jpg Binary files differindex ae5d66ebe..ae5d66ebe 100644 --- a/examples/graphs/3d/widgetgallery/data/maptexture.jpg +++ b/examples/graphs/3d/widgetgraphgallery/data/maptexture.jpg diff --git a/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh b/examples/graphs/3d/widgetgraphgallery/data/narrowarrow.mesh Binary files differindex 288867b1e..288867b1e 100644 --- a/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh +++ b/examples/graphs/3d/widgetgraphgallery/data/narrowarrow.mesh diff --git a/examples/graphs/3d/widgetgallery/data/oilrig.mesh b/examples/graphs/3d/widgetgraphgallery/data/oilrig.mesh Binary files differindex 4a7baeddf..4a7baeddf 100644 --- a/examples/graphs/3d/widgetgallery/data/oilrig.mesh +++ b/examples/graphs/3d/widgetgraphgallery/data/oilrig.mesh diff --git a/examples/graphs/3d/widgetgallery/data/pipe.mesh b/examples/graphs/3d/widgetgraphgallery/data/pipe.mesh Binary files differindex 984b6d443..984b6d443 100644 --- a/examples/graphs/3d/widgetgallery/data/pipe.mesh +++ b/examples/graphs/3d/widgetgraphgallery/data/pipe.mesh diff --git a/examples/graphs/3d/widgetgallery/data/raindata.txt b/examples/graphs/3d/widgetgraphgallery/data/raindata.txt index d95589219..d95589219 100644 --- a/examples/graphs/3d/widgetgallery/data/raindata.txt +++ b/examples/graphs/3d/widgetgraphgallery/data/raindata.txt diff --git a/examples/graphs/3d/widgetgallery/data/refinery.mesh b/examples/graphs/3d/widgetgraphgallery/data/refinery.mesh Binary files differindex a7e249353..a7e249353 100644 --- a/examples/graphs/3d/widgetgallery/data/refinery.mesh +++ b/examples/graphs/3d/widgetgraphgallery/data/refinery.mesh diff --git a/examples/graphs/3d/widgetgallery/data/topography.png b/examples/graphs/3d/widgetgraphgallery/data/topography.png Binary files differindex 9349cdb31..9349cdb31 100644 --- a/examples/graphs/3d/widgetgallery/data/topography.png +++ b/examples/graphs/3d/widgetgraphgallery/data/topography.png diff --git a/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst new file mode 100644 index 000000000..33a4da15b --- /dev/null +++ b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst @@ -0,0 +1,11 @@ +Widget Graph Gallery +==================== + + +Widget Graph Gallery demonstrates all three graph types and some of their +special features. The graphs have their own tabs in the application. + + +.. image:: widgetgraphgallery.webp + :width: 400 + :alt: Widget Screenshot diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.webp Binary files differindex eb5767264..eb5767264 100644 --- a/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp +++ b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.webp diff --git a/examples/graphs/3d/widgetgallery/graphmodifier.py b/examples/graphs/3d/widgetgraphgallery/graphmodifier.py index 2eaafa792..2eaafa792 100644 --- a/examples/graphs/3d/widgetgallery/graphmodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/graphmodifier.py diff --git a/examples/graphs/3d/widgetgallery/highlightseries.py b/examples/graphs/3d/widgetgraphgallery/highlightseries.py index 8c7b91633..8c7b91633 100644 --- a/examples/graphs/3d/widgetgallery/highlightseries.py +++ b/examples/graphs/3d/widgetgraphgallery/highlightseries.py diff --git a/examples/graphs/3d/widgetgallery/main.py b/examples/graphs/3d/widgetgraphgallery/main.py index 7bb2238a7..7bb2238a7 100644 --- a/examples/graphs/3d/widgetgallery/main.py +++ b/examples/graphs/3d/widgetgraphgallery/main.py diff --git a/examples/graphs/3d/widgetgallery/rainfalldata.py b/examples/graphs/3d/widgetgraphgallery/rainfalldata.py index d74f45a8b..d74f45a8b 100644 --- a/examples/graphs/3d/widgetgallery/rainfalldata.py +++ b/examples/graphs/3d/widgetgraphgallery/rainfalldata.py diff --git a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py index 15064b412..15064b412 100644 --- a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py diff --git a/examples/graphs/3d/widgetgallery/scattergraph.py b/examples/graphs/3d/widgetgraphgallery/scattergraph.py index 79e8933eb..79e8933eb 100644 --- a/examples/graphs/3d/widgetgallery/scattergraph.py +++ b/examples/graphs/3d/widgetgraphgallery/scattergraph.py diff --git a/examples/graphs/3d/widgetgallery/surfacegraph.py b/examples/graphs/3d/widgetgraphgallery/surfacegraph.py index 4052da821..4052da821 100644 --- a/examples/graphs/3d/widgetgallery/surfacegraph.py +++ b/examples/graphs/3d/widgetgraphgallery/surfacegraph.py diff --git a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py b/examples/graphs/3d/widgetgraphgallery/surfacegraphmodifier.py index b2706c6fa..b2706c6fa 100644 --- a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/surfacegraphmodifier.py diff --git a/examples/graphs/3d/widgetgallery/topographicseries.py b/examples/graphs/3d/widgetgraphgallery/topographicseries.py index 4f286a222..4f286a222 100644 --- a/examples/graphs/3d/widgetgallery/topographicseries.py +++ b/examples/graphs/3d/widgetgraphgallery/topographicseries.py diff --git a/examples/graphs/3d/widgetgallery/variantbardatamapping.py b/examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py index 50bdefa6a..50bdefa6a 100644 --- a/examples/graphs/3d/widgetgallery/variantbardatamapping.py +++ b/examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py diff --git a/examples/graphs/3d/widgetgallery/variantbardataproxy.py b/examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py index 5ab2a2cd2..5ab2a2cd2 100644 --- a/examples/graphs/3d/widgetgallery/variantbardataproxy.py +++ b/examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py diff --git a/examples/graphs/3d/widgetgallery/variantdataset.py b/examples/graphs/3d/widgetgraphgallery/variantdataset.py index 752bc3887..752bc3887 100644 --- a/examples/graphs/3d/widgetgallery/variantdataset.py +++ b/examples/graphs/3d/widgetgraphgallery/variantdataset.py diff --git a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject b/examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject index 581b21483..581b21483 100644 --- a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject +++ b/examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject diff --git a/examples/samplebinding/doc/samplebinding.rst b/examples/samplebinding/doc/samplebinding.rst index defb55d6b..51b6b4c20 100644 --- a/examples/samplebinding/doc/samplebinding.rst +++ b/examples/samplebinding/doc/samplebinding.rst @@ -165,23 +165,27 @@ Using CMake You can build and run this example by executing the following commands (slightly adapted to your file system layout) in a terminal: -macOS/Linux: +Run CMake on macOS/Linux: .. code-block:: bash cd ~/pyside-setup/examples/samplebinding + mkdir build + cd build + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -On Windows: +Run CMake on Windows: .. code-block:: bash cd C:\pyside-setup\examples\samplebinding - -.. code-block:: bash - mkdir build cd build - cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe + +To build: + +.. code-block:: bash ninja ninja install cd .. diff --git a/examples/scriptableapplication/doc/scriptableapplication.rst b/examples/scriptableapplication/doc/scriptableapplication.rst index 146911f13..bbabb1247 100644 --- a/examples/scriptableapplication/doc/scriptableapplication.rst +++ b/examples/scriptableapplication/doc/scriptableapplication.rst @@ -72,21 +72,22 @@ macOS/Linux: .. code-block:: bash cd ~/pyside-setup/examples/scriptableapplication + mkdir build + cd build + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + ninja + ./scriptableapplication On Windows: .. code-block:: bash cd C:\pyside-setup\examples\scriptableapplication - - -.. code-block:: bash - mkdir build cd build - cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe ninja - ./scriptableapplication + .\scriptableapplication.exe Using QMake +++++++++++ diff --git a/examples/widgetbinding/doc/widgetbinding.md b/examples/widgetbinding/doc/widgetbinding.md index 6355ea311..910961b1e 100644 --- a/examples/widgetbinding/doc/widgetbinding.md +++ b/examples/widgetbinding/doc/widgetbinding.md @@ -40,20 +40,24 @@ The most important files are: Now create a `build/` directory, and from inside run `cmake` to use the provided `CMakeLists.txt`: -macOS/Linux: +Run CMake on macOS/Linux: ```bash cd ~/pyside-setup/examples/widgetbinding +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release ``` -On Windows: +Run CMake on Windows: ```bash cd C:\pyside-setup\examples\widgetbinding +mkdir build +cd build +cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe ``` +To build: ```bash -mkdir build -cd build -cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release + ninja ninja install cd .. diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py index 828982b5b..0c314c356 100644 --- a/sources/pyside-tools/deploy_lib/android/buildozer.py +++ b/sources/pyside-tools/deploy_lib/android/buildozer.py @@ -39,6 +39,13 @@ class BuildozerConfig(BaseConfig): self.set_value("app", "p4a.bootstrap", "qt") self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir)) + # add p4a branch + # by default the master branch is used + # https://github.com/kivy/python-for-android/commit/b92522fab879dbfc0028966ca3c59ef46ab7767d + # has not been merged to master yet. So, we use the develop branch for now + # TODO: remove this once the above commit is merged to master + self.set_value("app", "p4a.branch", "develop") + # add permissions permissions = self.__find_permissions(pysidedeploy_config.dependency_files) permissions = ", ".join(permissions) diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index d202db25e..ac9a83f3f 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -35,6 +35,12 @@ class Nuitka: "generic" # plugins that error with Nuitka ] + # .webp are considered to be dlls by Nuitka instead of data files causing + # the packaging to fail + # https://github.com/Nuitka/Nuitka/issues/2854 + # TODO: Remove .webp when the issue is fixed + self.files_to_ignore = [".cpp.o", ".qsb", ".webp"] + @staticmethod def icon_option(): if sys.platform == "linux": @@ -81,6 +87,14 @@ class Nuitka: dll_name = plugin.replace("Qt", f"Qt{MAJOR_VERSION}") qml_args.append(f"--noinclude-dlls={prefix}{dll_name}*") + # Exclude .qen json files from QtQuickEffectMaker + # These files are not relevant for PySide6 applications + qml_args.append("--noinclude-dlls=*/qml/QtQuickEffectMaker/*") + + # Exclude files that cannot be processed by Nuitka + for file in self.files_to_ignore: + extra_args.append(f"--noinclude-dlls=*{file}") + output_dir = source_file.parent / "deployment" if not dry_run: output_dir.mkdir(parents=True, exist_ok=True) diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index 4baa8134e..60d1846d1 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -28,8 +28,8 @@ def run(coro: typing.Optional[typing.Coroutine] = None, # subsequent removal in Python 3.15. At that point, part of the current # logic of the QAsyncioEventLoopPolicy constructor will have to be moved # here and/or to a loop factory class (to be provided as an argument to - # asyncio.run()), namely setting up the QCoreApplication and the SIGINT - # handler. + # asyncio.run()). In particular, this concerns the logic of setting up the + # QCoreApplication and the SIGINT handler. # # More details: # https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 # noqa: E501 diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index a29e480b7..86b89014a 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -26,6 +26,19 @@ __all__ = [ class QAsyncioExecutorWrapper(QObject): + """ + Executors in asyncio allow running synchronous code in a separate thread or + process without blocking the event loop or interrupting the asynchronous + program flow. Callables are scheduled for execution by calling submit() or + map() on an executor object. + + Executors require a bit of extra work for QtAsyncio, as we can't use + naked Python threads; instead, we must make sure that the thread created + by executor.submit() has an event loop. This is achieved by not submitting + the callable directly, but a small wrapper that attaches a QEventLoop to + the executor thread, and then creates a zero-delay singleshot timer to push + the actual callable for the executor into this new event loop. + """ def __init__(self, func: typing.Callable, *args: typing.Tuple) -> None: super().__init__() @@ -37,16 +50,21 @@ class QAsyncioExecutorWrapper(QObject): def _cb(self): try: + # Call the synchronous callable that we submitted with submit() or + # map(). self._result = self._func(*self._args) except BaseException as e: self._exception = e self._loop.exit() def do(self): - # This creates a new event loop and dispatcher for the thread, if not already created. + # This creates a new event loop and dispatcher for the thread, if not + # already created. self._loop = QEventLoop() asyncio.events._set_running_loop(self._loop) + QTimer.singleShot(0, self._loop, lambda: self._cb()) + self._loop.exec() if self._exception is not None: raise self._exception @@ -57,6 +75,17 @@ class QAsyncioExecutorWrapper(QObject): class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): + """ + Event loop policies are expected to be deprecated with Python 3.13, with + subsequent removal in Python 3.15. At that point, part of the current + logic of the QAsyncioEventLoopPolicy constructor will have to be moved + to QtAsyncio.run() and/or to a loop factory class (to be provided as an + argument to asyncio.run()). In particular, this concerns the logic of + setting up the QCoreApplication and the SIGINT handler. + + More details: + https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 + """ def __init__(self, application: typing.Optional[QCoreApplication] = None, quit_qapp: bool = True, @@ -68,7 +97,14 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): else: application = QCoreApplication.instance() self._application: QCoreApplication = application # type: ignore[assignment] + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None if handle_sigint: @@ -99,6 +135,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): """ class ShutDownThread(QThread): + """ + Used to shut down the default executor when calling + shutdown_default_executor(). As the executor is a ThreadPoolExecutor, + it must be shut down in a separate thread as all the threads from the + thread pool must join, which we want to do without blocking the event + loop. + """ + def __init__(self, future: futures.QAsyncioFuture, loop: "QAsyncioEventLoop") -> None: super().__init__() self._future = future @@ -123,22 +167,48 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): QObject.__init__(self) self._application: QCoreApplication = application + + # Configure whether the QCoreApplication at the core of QtAsyncio + # should be shut down when asyncio finishes. A special case where one + # would want to disable this is test suites that want to reuse a single + # QCoreApplication instance across all unit tests, which would fail if + # this instance is shut down every time. self._quit_qapp = quit_qapp + self._thread = QThread.currentThread() self._closed = False + # These two flags are used to determine whether the loop was stopped + # from inside the loop (i.e., coroutine or callback called stop()) or + # from outside the loop (i.e., the QApplication is being shut down, for + # example, by the user closing the window or by calling + # QApplication.quit()). The different cases can trigger slightly + # different behaviors (see the comments where the flags are used). + # There are two variables for this as in a third case the loop is still + # running and both flags are False. self._quit_from_inside = False self._quit_from_outside = False + # A set of all asynchronous generators that are currently running. self._asyncgens: typing.Set[collections.abc.AsyncGenerator] = set() # Starting with Python 3.11, this must be an instance of # ThreadPoolExecutor. self._default_executor = concurrent.futures.ThreadPoolExecutor() + # The exception handler, if set with set_exception_handler(). The + # exception handler is currently called in two places: One, if an + # asynchonrous generator raises an exception when closed, and two, if + # an exception is raised during the execution of a task. Currently, the + # default exception handler just prints the exception to the console. self._exception_handler: typing.Optional[typing.Callable] = self.default_exception_handler + + # The task factory, if set with set_task_factory(). Otherwise, a new + # task is created with the QAsyncioTask constructor. self._task_factory: typing.Optional[typing.Callable] = None + + # The future that is currently being awaited with run_until_complete(). self._future_to_complete: typing.Optional[futures.QAsyncioFuture] = None self._debug = bool(os.getenv("PYTHONASYNCIODEBUG", False)) @@ -148,6 +218,10 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): # Running and stopping the loop def _run_until_complete_cb(self, future: futures.QAsyncioFuture) -> None: + """ + A callback that stops the loop when the future is done, used when + running the loop with run_until_complete(). + """ if not future.cancelled(): if isinstance(future.exception(), (SystemExit, KeyboardInterrupt)): return @@ -188,7 +262,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): asyncio.events._set_running_loop(None) def _about_to_quit_cb(self): + """ A callback for the aboutToQuit signal of the QCoreApplication. """ if not self._quit_from_inside: + # If the aboutToQuit signal is emitted, the user is closing the + # application window or calling QApplication.quit(). In this case, + # we want to close the event loop, and we consider this a quit from + # outside the loop. self._quit_from_outside = True self.close() @@ -197,8 +276,15 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): if self._future_to_complete.done(): self._future_to_complete = None else: + # Do not stop the loop if there is a future still being awaited + # with run_until_complete(). return + self._quit_from_inside = True + + # The user might want to keep the QApplication running after the event + # event loop finishes, which they can control with the quit_qapp + # argument. if self._quit_qapp: self._application.quit() @@ -298,6 +384,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): callback: typing.Callable, *args: typing.Any, context: typing.Optional[contextvars.Context] = None, is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: + """ All call_at() and call_later() methods map to this method. """ if not isinstance(when, (int, float)): raise TypeError("when must be an int or float") return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe) @@ -482,6 +569,13 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): raise RuntimeError("Event loop is closed") if executor is None: executor = self._default_executor + + # Executors require a bit of extra work for QtAsyncio, as we can't use + # naked Python threads; instead, we must make sure that the thread + # created by executor.submit() has an event loop. This is achieved by + # not submitting the callable directly, but a small wrapper that + # attaches a QEventLoop to the executor thread, and then pushes the + # actual callable for the executor into this new event loop. wrapper = QAsyncioExecutorWrapper(func, *args) return asyncio.futures.wrap_future( executor.submit(wrapper.do), loop=self @@ -541,6 +635,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): class QAsyncioHandle(): + """ + The handle enqueues a callback to be executed by the event loop, and allows + for this callback to be cancelled before it is executed. This callback will + typically execute the step function for a task. This makes the handle one + of the main components of asyncio. + """ class HandleState(enum.Enum): PENDING = enum.auto() CANCELLED = enum.auto() @@ -560,18 +660,30 @@ class QAsyncioHandle(): self._state = QAsyncioHandle.HandleState.PENDING self._start() + def _start(self) -> None: + self._schedule_event(self._timeout, lambda: self._cb()) + def _schedule_event(self, timeout: int, func: typing.Callable) -> None: + # Do not schedule events from asyncio when the app is quit from outside + # the event loop, as this would cause events to be enqueued after the + # event loop was destroyed. if not self._loop.is_closed() and not self._loop._quit_from_outside: if self._is_threadsafe: + # This singleShot overload will push func into self._loop + # instead of the current thread's loop. This allows scheduling + # a callback from a different thread, which is necessary for + # thread-safety. + # https://docs.python.org/3/library/asyncio-dev.html#asyncio-multithreading QTimer.singleShot(timeout, self._loop, func) else: QTimer.singleShot(timeout, func) - def _start(self) -> None: - self._schedule_event(self._timeout, lambda: self._cb()) - @Slot() def _cb(self) -> None: + """ + A slot, enqueued into the event loop, that wraps around the actual + callback, typically the step function of a task. + """ if self._state == QAsyncioHandle.HandleState.PENDING: if self._context is not None: self._context.run(self._callback, *self._args) @@ -581,7 +693,9 @@ class QAsyncioHandle(): def cancel(self) -> None: if self._state == QAsyncioHandle.HandleState.PENDING: - # The old timer that was created in _start will still trigger but _cb won't do anything. + # The old timer that was created in _start will still trigger but + # _cb won't do anything, therefore the callback is effectively + # cancelled. self._state = QAsyncioHandle.HandleState.CANCELLED def cancelled(self) -> bool: @@ -600,9 +714,11 @@ class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle): QAsyncioHandle._start(self) - # Override this so that timer.start() is only called once at the end - # of the constructor for both QtHandle and QtTimerHandle. def _start(self) -> None: + """ + Overridden so that timer.start() is only called once at the end of the + constructor for both QtHandle and QtTimerHandle. + """ pass def when(self) -> float: diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index 611bd5634..cbb005fc9 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -36,10 +36,11 @@ class QAsyncioFuture(): self._result: typing.Any = None self._exception: typing.Optional[BaseException] = None - self._callbacks: typing.List[typing.Callable] = list() - self._cancel_message: typing.Optional[str] = None + # List of callbacks that are called when the future is done. + self._callbacks: typing.List[typing.Callable] = list() + def __await__(self): if not self.done(): self._asyncio_future_blocking = True @@ -51,6 +52,7 @@ class QAsyncioFuture(): __iter__ = __await__ def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None): + """ A future can optionally have callbacks that are called when the future is done. """ for cb in self._callbacks: self._loop.call_soon( cb, self, context=context if context else self._context) diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index c04006686..7edc15093 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -20,17 +20,22 @@ class QAsyncioTask(futures.QAsyncioFuture): context: typing.Optional[contextvars.Context] = None) -> None: super().__init__(loop=loop, context=context) - self._coro = coro + self._coro = coro # The coroutine for which this task was created. self._name = name if name else "QtTask" + # The task creates a handle for its coroutine. The handle enqueues the + # task's step function as its callback in the event loop. self._handle = self._loop.call_soon(self._step, context=self._context) - self._cancellation_requests = 0 - + # The task step function executes the coroutine until it finishes, + # raises an exception or returns a future. If a future was returned, + # the task will await its completion (or exception). self._future_to_await: typing.Optional[asyncio.Future] = None - self._cancel_message: typing.Optional[str] = None + self._cancelled = False + self._cancel_message: typing.Optional[str] = None + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._register_task(self) # type: ignore[arg-type] def __repr__(self) -> str: @@ -59,6 +64,14 @@ class QAsyncioTask(futures.QAsyncioFuture): def _step(self, exception_or_future: typing.Union[ BaseException, futures.QAsyncioFuture, None] = None) -> None: + """ + The step function is the heart of a task. It is scheduled in the event + loop repeatedly, executing the coroutine "step" by "step" (i.e., + iterating through the asynchronous generator) until it finishes with an + exception or successfully. Each step can optionally receive an + exception or a future as a result from a previous step to handle. + """ + if self.done(): return result = None @@ -72,7 +85,15 @@ class QAsyncioTask(futures.QAsyncioFuture): try: asyncio._enter_task(self._loop, self) # type: ignore[arg-type] + + # It is at this point that the coroutine is resumed for the current + # step (i.e. asynchronous generator iteration). It will now be + # executed until it yields (and potentially returns a future), + # raises an exception, is cancelled, or finishes successfully. + if isinstance(exception_or_future, BaseException): + # If the coroutine doesn't handle this exception, it propagates + # to the caller. result = self._coro.throw(exception_or_future) else: result = self._coro.send(None) @@ -87,6 +108,9 @@ class QAsyncioTask(futures.QAsyncioFuture): self._exception = e else: if asyncio.futures.isfuture(result): + # If the coroutine yields a future, the task will await its + # completion, and at that point the step function will be + # called again. result.add_done_callback( self._step, context=self._context) # type: ignore[arg-type] self._future_to_await = result @@ -100,12 +124,16 @@ class QAsyncioTask(futures.QAsyncioFuture): # very quickly. self._future_to_await.cancel(self._cancel_message) elif result is None: + # If no future was yielded, we schedule the step function again + # without any arguments. self._loop.call_soon(self._step, context=self._context) else: + # This is not supposed to happen. exception = RuntimeError(f"Bad task result: {result}") self._loop.call_soon(self._step, exception, context=self._context) finally: asyncio._leave_task(self._loop, self) # type: ignore[arg-type] + if self._exception: self._loop.call_exception_handler({ "message": (str(self._exception) if self._exception @@ -117,8 +145,11 @@ class QAsyncioTask(futures.QAsyncioFuture): if asyncio.futures.isfuture(exception_or_future) else None) }) + if self.done(): self._schedule_callbacks() + + # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._unregister_task(self) # type: ignore[arg-type] def get_stack(self, *, limit=None) -> typing.List[typing.Any]: @@ -144,6 +175,8 @@ class QAsyncioTask(futures.QAsyncioFuture): self._cancel_message = msg self._handle.cancel() if self._future_to_await is not None: + # A task that is awaiting a future must also cancel this future in + # order for the cancellation to be successful. self._future_to_await.cancel(msg) self._cancelled = True return True diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index 4266e868c..f6acf9d60 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -340,9 +340,7 @@ PyObject *invokeMetaMethodWithReturn(const InvokeMetaMethodFuncWithReturn &f, a6.toGenericArgument(), a7.toGenericArgument(), a8.toGenericArgument(), a9.toGenericArgument()); PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS - if (!callResult) { - PyErr_SetString(PyExc_RuntimeError, "QMetaMethod invocation failed."); - return nullptr; - } + if (!callResult) + return PyErr_Format(PyExc_RuntimeError, "QMetaMethod invocation failed."); return convertGenericReturnArgument(r.data(), r.metaType()); } diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 0a08928f5..110f86f25 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -1289,6 +1289,7 @@ qRegisterMetaType<QList<QPersistentModelIndex> >("QList_QPersistentModelIndex"); </inject-code> <modify-function signature="internalPointer()const"> + <modify-argument index="return" pyi-type="Any"/> <inject-code class="target" position="beginning"> <insert-template name="return_internal_pointer" /> </inject-code> @@ -1677,8 +1678,9 @@ <add-function signature="operator==(const QItemSelection&)" return-type="bool"/> <add-function signature="operator!=(const QItemSelection&)" return-type="bool"/> <!-- For some reason, the empty selection is not seen. Maybe related to the new [default] - tag in Qt6? --> - <declare-function signature="QItemSelection()" return-type="QItemSelection" /> + tag in Qt6? + PYSIDE-2756: The return-type attribute is unnecessary --> + <declare-function signature="QItemSelection()"/> <!-- The __add__ function creates a result list, instead of using the inherited type. Fixed by adding with the correct type. --> <add-function signature="operator+(QItemSelection)" return-type="QItemSelection"> @@ -1759,9 +1761,6 @@ </modify-function> <!-- End of Invalidate-after-use fix --> <modify-function signature="parent()const"> - <modify-argument index="this"> - <parent index="return" action="add"/> - </modify-argument> <modify-argument index="return"> <define-ownership class="target" owner="default"/> </modify-argument> @@ -2552,6 +2551,11 @@ </modify-argument> <inject-code file="../glue/qtcore.cpp" snippet="qtranslator-load"/> </modify-function> + <modify-function signature="translate(const char*,const char*, const char*,int)const"> + <modify-argument index="1" pyi-type="str"/> + <modify-argument index="2" pyi-type="str"/> + <modify-argument index="3" pyi-type="Optional[str]"/> + </modify-function> </object-type> <object-type name="QWaitCondition"> <configuration condition="QT_CONFIG(thread)"/> @@ -3103,6 +3107,7 @@ </object-type> <value-type name="QModelIndex"> <modify-function signature="internalPointer()const"> + <modify-argument index="return" pyi-type="Any"/> <inject-code class="target" position="beginning"> <insert-template name="return_internal_pointer" /> </inject-code> diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index 7cd7871f3..c868b6c88 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -88,6 +88,7 @@ ${QtGui_GEN_DIR}/qaccessibleactioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleeditabletextinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleobject_wrapper.cpp +${QtGui_GEN_DIR}/qaccessibleselectioninterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessiblestatechangeevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablecellinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibletablemodelchangeevent_wrapper.cpp @@ -312,6 +313,15 @@ set(QtGui_private_include_dirs ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) +if (${CMAKE_SYSTEM_NAME} STREQUAL "Android") + if (QT_FEATURE_opengles2) + # add openGL ES 2.0 + find_package(GLESv2 REQUIRED) + else() + message(FATAL_ERROR "QtGui requires OpenGL ES 2.0 on Android") + endif() +endif() + configure_file("${QtGui_SOURCE_DIR}/QtGui_global.post.h.in" "${QtGui_BINARY_DIR}/QtGui_global.post.h" @ONLY) diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index dd17952f0..feb2dc11b 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -122,6 +122,7 @@ <object-type name="QAccessibleEditableTextInterface"/> <object-type name="QAccessibleInterface"/> <object-type name="QAccessibleObject" qt-register-metatype="base"/> + <object-type name="QAccessibleSelectionInterface" since="6.7"/> <object-type name="QAccessibleTableCellInterface"/> <object-type name="QAccessibleTextInterface"/> <object-type name="QAccessibleValueInterface"/> @@ -2572,7 +2573,7 @@ </extra-includes> </primitive-type> - <object-type name="QWindow" delete-in-main-thread="true"> + <object-type name="QWindow" delete-in-main-thread="true" polymorphic-base="true"> <enum-type name="AncestorMode"/> <enum-type name="Visibility"/> <modify-function signature="raise()" rename="raise_"/> diff --git a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml index 508043626..4dc7c9b0a 100644 --- a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml +++ b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml @@ -179,6 +179,11 @@ <modify-function signature="put(const QNetworkRequest &,QIODevice*)" allow-thread="yes"/> <modify-function signature="put(const QNetworkRequest &,const QByteArray &)" allow-thread="yes"/> <modify-function signature="sendCustomRequest(const QNetworkRequest &,const QByteArray &,QIODevice*)" allow-thread="yes" since="4.7"/> + <modify-function signature="setCache(QAbstractNetworkCache*)"> + <modify-argument index="1"> + <define-ownership class="target" owner="c++"/> + </modify-argument> + </modify-function> <modify-function signature="setCookieJar(QNetworkCookieJar*)"> <modify-argument index="1"> <define-ownership class="target" owner="c++"/> diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp index 6e403ab72..ca3dfebed 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp @@ -49,7 +49,10 @@ static void QtQml_VolatileBoolObject_dealloc(PyObject *self) static PyObject * QtQml_VolatileBoolObject_get(QtQml_VolatileBoolObject *self) { - return *self->flag ? Py_True : Py_False; + if (*self->flag) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } static PyObject * @@ -63,10 +66,8 @@ QtQml_VolatileBoolObject_set(QtQml_VolatileBoolObject *self, PyObject *args) } ok = PyObject_IsTrue(value); - if (ok < 0) { - PyErr_SetString(PyExc_TypeError, "Not a boolean value."); - return nullptr; - } + if (ok < 0) + return PyErr_Format(PyExc_TypeError, "Not a boolean value."); *self->flag = ok > 0; diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index 6a739bbdf..3392ef379 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -124,9 +124,12 @@ <function signature="qmlProtectModule(const char*,int)" doc-file="qqmlengine"/> <function signature="qmlRegisterModule(const char*,int,int)" doc-file="qqmlengine"/> <function signature="qmlTypeId(const char*,int,int,const char*)" doc-file="qqmlengine"/> - <function signature="qmlRegisterType(const QUrl &,const char *,int,int,const char *)"/> - <function signature="qmlRegisterSingletonType(const QUrl &,const char *,int,int,const char *)"/> - <function signature="qmlRegisterUncreatableMetaObject(const QMetaObject&,const char*,int,int, const char*,const QString&)"/> + <function signature="qmlRegisterType(const QUrl &,const char *,int,int,const char *)" + doc-file="qqmlengine"/> + <function signature="qmlRegisterSingletonType(const QUrl &,const char *,int,int,const char *)" + doc-file="qqmlengine"/> + <function signature="qmlRegisterUncreatableMetaObject(const QMetaObject&,const char*,int,int, const char*,const QString&)" + doc-file="qqmlengine"/> <enum-type identified-by-value="QML_HAS_ATTACHED_PROPERTIES"> <extra-includes> diff --git a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml index 0a24123f4..61aed6005 100644 --- a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml +++ b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml @@ -53,7 +53,7 @@ <object-type name="QQuickImageResponse"/> <object-type name="QQuickTransform"/> - <object-type name="QQuickItem" delete-in-main-thread="true"> + <object-type name="QQuickItem" delete-in-main-thread="true" polymorphic-base="true"> <value-type name="UpdatePaintNodeData"/> <enum-type name="Flag" flags="Flags"/> <enum-type name="ItemChange"/> diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt index 5aedc1147..d87dc55a4 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt @@ -14,17 +14,26 @@ set(QtWebEngineQuick_include_dirs ${QtWebEngineQuick_SOURCE_DIR} ${QtWebEngineQuick_BINARY_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}PrintSupport_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}WebChannel_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtWidgets_GEN_DIR} ${QtNetwork_GEN_DIR} - ${QtQml_GEN_DIR}) + ${QtWebEngineCore_GEN_DIR} + ${QtPrintSupport_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtWebChannel_GEN_DIR}) set(QtWebEngineQuick_libraries pyside6 ${Qt${QT_MAJOR_VERSION}WebEngineQuick_LIBRARIES}) -set(QtWebEngineQuick_deps QtQml QtNetwork QtCore) +set(QtWebEngineQuick_deps QtQml QtWebEngineCore) create_pyside_module(NAME QtWebEngineQuick INCLUDE_DIRS QtWebEngineQuick_include_dirs diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml index 108ba5fae..2e7d22334 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml +++ b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml @@ -6,6 +6,7 @@ <typesystem package="PySide6.QtWebEngineQuick" namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE"> <load-typesystem name="QtQml/typesystem_qml.xml" generate="no"/> + <load-typesystem name="QtWebEngineCore/typesystem_webenginecore.xml" generate="no"/> <namespace-type name="QtWebEngineQuick"/> <!-- initialize() --> diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 0669c18f6..b1188a514 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -1205,7 +1205,7 @@ <modify-function signature="getColor(const QColor&,QWidget*,const QString&,QFlags<QColorDialog::ColorDialogOption>)" allow-thread="yes"/> </object-type> - <object-type name="QLayout"> + <object-type name="QLayout" polymorphic-base="true"> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qwidget-retrieveobjectname"/> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qlayout-help-functions"/> @@ -1980,7 +1980,7 @@ <modify-function signature="removeItemWidget(QListWidgetItem*)" allow-thread="yes"/> </object-type> - <object-type name="QWidget" delete-in-main-thread="true"> + <object-type name="QWidget" delete-in-main-thread="true" polymorphic-base="true"> <!-- see QWindow::nativeEvent(), QAbstractNativeEventFilter::nativeEventFilter() --> <inject-code class="native" position="beginning" file="../glue/qtwidgets.cpp" snippet="qwidget-addaction-glue"/> <inject-code class="native" position="beginning"> @@ -2032,9 +2032,6 @@ </modify-function> <modify-function signature="parentWidget()const"> - <modify-argument index="this"> - <parent index="return" action="add"/> - </modify-argument> <modify-argument index="return"> <define-ownership class="target" owner="default"/> </modify-argument> diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index bc51d26d7..0c206db72 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -84,11 +84,9 @@ static PyObject *convertToPrimitiveType(const QVariant &out, int metaTypeId) return PyFloat_FromDouble(out.toFloat()); case QMetaType::Bool: if (out.toBool()) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; default: break; } @@ -632,20 +630,16 @@ if (ret == nullptr) { // @snippet qbytearray-mgetitem if (PyIndex_Check(_key)) { const Py_ssize_t _i = PyNumber_AsSsize_t(_key, PyExc_IndexError); - if (_i < 0 || _i >= %CPPSELF.size()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (_i < 0 || _i >= %CPPSELF.size()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); char res[2] = {%CPPSELF.at(_i), '\0'}; return PyBytes_FromStringAndSize(res, 1); } -if (PySlice_Check(_key) == 0) { - PyErr_Format(PyExc_TypeError, +if (PySlice_Check(_key) == 0) + return PyErr_Format(PyExc_TypeError, "list indices must be integers or slices, not %.200s", Py_TYPE(_key)->tp_name); - return nullptr; -} Py_ssize_t start, stop, step, slicelength; if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &slicelength) < 0) @@ -1028,6 +1022,8 @@ void QSingleShotTimerFunctor::operator()() Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(0)); Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); release(); // single shot } // @snippet qtimer-singleshot-functorclass diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index 130de11bb..5c860a2bf 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -912,10 +912,8 @@ return %CPPSELF.rectCount(); // @snippet qregion-len // @snippet qregion-getitem -if (_i < 0 || _i >= %CPPSELF.rectCount()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; -} +if (_i < 0 || _i >= %CPPSELF.rectCount()) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); const QRect cppResult = *(%CPPSELF.cbegin() + _i); return %CONVERTTOPYTHON[QRect](cppResult); diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index 0f4405944..1835ed096 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -45,7 +45,7 @@ static PyObject *QUiLoadedLoadUiFromDevice(QUiLoader *self, QIODevice *dev, QWid } if (!PyErr_Occurred()) - PyErr_SetString(PyExc_RuntimeError, "Unable to open/read ui device"); + PyErr_Format(PyExc_RuntimeError, "Unable to open/read ui device"); return nullptr; } diff --git a/sources/pyside6/PySide6/glue/qtwidgets.cpp b/sources/pyside6/PySide6/glue/qtwidgets.cpp index 1b3e94016..f886106cf 100644 --- a/sources/pyside6/PySide6/glue/qtwidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwidgets.cpp @@ -792,10 +792,8 @@ const char *styleOptionType(const QStyleOption *o) // @snippet qwizardpage-registerfield auto *signalInst = reinterpret_cast<PySideSignalInstance *>(%PYARG_4); const auto data = PySide::Signal::getEmitterData(signalInst); -if (data.methodIndex == -1) { - PyErr_SetString(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); - return nullptr; -} +if (data.methodIndex == -1) + return PyErr_Format(PyExc_RuntimeError, "QWizardPage::registerField(): Unable to retrieve signal emitter."); const auto method = data.emitter->metaObject()->method(data.methodIndex); const QByteArray signature = QByteArrayLiteral("2") + method.methodSignature(); %BEGIN_ALLOW_THREADS diff --git a/sources/pyside6/PySide6/qtdatavisualization_helper.h b/sources/pyside6/PySide6/qtdatavisualization_helper.h index 6884900ee..8fee4492d 100644 --- a/sources/pyside6/PySide6/qtdatavisualization_helper.h +++ b/sources/pyside6/PySide6/qtdatavisualization_helper.h @@ -6,8 +6,8 @@ #include <sbkpython.h> -#include <QtDataVisualization/QSurfaceDataProxy> -#include <QtCore/QList> +#include <QtDataVisualization/qsurfacedataproxy.h> +#include <QtCore/qlist.h> namespace QtDataVisualizationHelper { diff --git a/sources/pyside6/PySide6/qtgraphs_helper.h b/sources/pyside6/PySide6/qtgraphs_helper.h index 726f5fb37..e488fc7d3 100644 --- a/sources/pyside6/PySide6/qtgraphs_helper.h +++ b/sources/pyside6/PySide6/qtgraphs_helper.h @@ -6,8 +6,8 @@ #include <sbkpython.h> -#include <QtGraphs/QSurfaceDataProxy> -#include <QtCore/QList> +#include <QtGraphs/qsurfacedataproxy.h> +#include <QtCore/qlist.h> namespace QtGraphsHelper { diff --git a/sources/pyside6/cmake/Macros/PySideModules.cmake b/sources/pyside6/cmake/Macros/PySideModules.cmake index 1e434f9f6..33b844f95 100644 --- a/sources/pyside6/cmake/Macros/PySideModules.cmake +++ b/sources/pyside6/cmake/Macros/PySideModules.cmake @@ -194,6 +194,35 @@ macro(create_pyside_module) --lean-headers --api-version=${SUPPORTED_QT_VERSION}) + # check if building for Android with a macOS host + # This is not needed for Linux because OpenGLES2 development binaries in + # linux can be installed by installing 'libgles2-mesa-dev' package which + # comes as a default requirement for building PySide6. As such for + # cross-compiling in linux, we use the clang compiler from the installed + # libclang itself. + if(CMAKE_ANDROID_ARCH_LLVM_TRIPLE AND CMAKE_HOST_APPLE) + message(STATUS "Building for Android with arch ${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}") + list(APPEND shiboken_command "--clang-option=--target=${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}") + + # CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX does not contain the ANDROID_PLATFORM i.e. it ends with + # the form 'aarch64-linux-android-'. Remove the last '-' and add the corresponding clang + # based on ANDROID_PLATFORM making it 'aarch64-linux-android26-clang++' + + # Get the length of the string + string(LENGTH "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" _length) + + # Subtract 1 from the length to get the characters till '-' + math(EXPR _last_index "${_length} - 1") + + # Get the substring from the start to the character before the last one + string(SUBSTRING "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" 0 "${_last_index}" + SHIBOKEN_ANDROID_COMPILER_PREFIX) + + # use the compiler from the Android NDK + list(APPEND shiboken_command + "--compiler-path=${SHIBOKEN_ANDROID_COMPILER_PREFIX}${CMAKE_ANDROID_API}-clang++") + endif() + if(CMAKE_HOST_APPLE) set(shiboken_framework_include_dir_list ${QT_FRAMEWORK_INCLUDE_DIR}) make_path(shiboken_framework_include_dirs ${shiboken_framework_include_dir_list}) diff --git a/sources/pyside6/cmake/PySideHelpers.cmake b/sources/pyside6/cmake/PySideHelpers.cmake index 23ceda6bd..01c438107 100644 --- a/sources/pyside6/cmake/PySideHelpers.cmake +++ b/sources/pyside6/cmake/PySideHelpers.cmake @@ -138,13 +138,18 @@ macro(check_os) set(ENABLE_MAC "0") set(ENABLE_WIN "0") - if(CMAKE_HOST_APPLE) - set(ENABLE_MAC "1") - elseif(CMAKE_HOST_WIN32) - set(ENABLE_WIN "1") - set(ENABLE_UNIX "0") - elseif(NOT CMAKE_HOST_UNIX) - message(FATAL_ERROR "OS not supported") + # check if Android, if so, set ENABLE_UNIX=1 + # this is needed to avoid including the wrapper specific to macOS when building for Android + # from a macOS host + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") + if(CMAKE_HOST_APPLE) + set(ENABLE_MAC "1") + elseif(CMAKE_HOST_WIN32) + set(ENABLE_WIN "1") + set(ENABLE_UNIX "0") + elseif(NOT CMAKE_HOST_UNIX) + message(FATAL_ERROR "OS not supported") + endif() endif() endmacro() diff --git a/sources/pyside6/doc/gettingstarted/index.rst b/sources/pyside6/doc/gettingstarted/index.rst index 9d36061ad..51d8bea26 100644 --- a/sources/pyside6/doc/gettingstarted/index.rst +++ b/sources/pyside6/doc/gettingstarted/index.rst @@ -18,10 +18,10 @@ you might get them with ``brew``, and on **Windows** you can download the instal website. * **Python**: 3.9+ `[official Python website] <https://www.python.org/downloads/>`_ -* **Qt:** 6.4+ `[online installer] <https://download.qt.io/official_releases/online_installers/>`_ +* **Qt:** 6.8+ `[online installer] <https://download.qt.io/official_releases/online_installers/>`_ * **CMake:** 3.18+ `[official CMake website] <https://cmake.org/download/>`_ * **Git:** 2.0+. `[official Git website] <https://git-scm.com/downloads>`_ -* **libclang:** The libclang library, recommended: version 10 for 6.0+. +* **libclang:** The libclang library, recommended: version 16+ for 6.8+. Prebuilt versions for each OS can be `downloaded here`_. * Check the `Supported Platforms of Qt`_ diff --git a/sources/pyside6/doc/gettingstarted/linux.rst b/sources/pyside6/doc/gettingstarted/linux.rst index 912105ef8..eb1b5869d 100644 --- a/sources/pyside6/doc/gettingstarted/linux.rst +++ b/sources/pyside6/doc/gettingstarted/linux.rst @@ -30,12 +30,12 @@ Setting up CLANG If you don't have libclang already in your system, you can download from the Qt servers:: - wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_140-based-linux-Rhel8.2-gcc9.2-x86_64.7z + wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.5-based-linux-Rhel8.6-gcc10.3-x86_64.7z Extract the files, and leave it on any desired path, and set the environment variable required:: - 7z x libclang-release_140-based-linux-Rhel8.2-gcc9.2-x86_64.7z + 7z x libclang-release_18.1.5-based-linux-Rhel8.6-gcc10.3-x86_64.7z export LLVM_INSTALL_DIR=$PWD/libclang Getting the source @@ -45,16 +45,20 @@ Cloning the official repository can be done by:: git clone https://code.qt.io/pyside/pyside-setup -Checking out the version that we want to build, for example 6.5:: +Checking out the version that we want to build, for example 6.8:: - cd pyside-setup && git checkout 6.5 + cd pyside-setup && git checkout 6.8 Install the general dependencies:: pip install -r requirements.txt +For building the documentation:: + + pip install -r requirements-doc.txt + .. note:: Keep in mind you need to use the same version as your Qt installation. - Additionally, :command:`git checkout -b 6.5 --track origin/6.5` could be a better option + Additionally, :command:`git checkout -b 6.8 --track origin/6.8` could be a better option in case you want to work on it. Building and Installing (setuptools) @@ -64,15 +68,15 @@ The ``setuptools`` approach uses the ``setup.py`` file to execute the build, install, and packaging steps. Check your Qt installation path, to specifically use that version of qtpaths to build PySide. -for example, :command:`/opt/Qt/6.5.0/gcc_64/bin/qtpaths`. +for example, :command:`/opt/Qt/6.8.0/gcc_64/bin/qtpaths`. Build can take a few minutes, so it is recommended to use more than one CPU core:: - python setup.py build --qtpaths=/opt/Qt/6.5.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 + python setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 To install on the current directory, just run:: - python setup.py install --qtpaths=/opt/Qt/6.5.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 + python setup.py install --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 Building and Installing (cmake) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/sources/pyside6/doc/gettingstarted/macOS.rst b/sources/pyside6/doc/gettingstarted/macOS.rst index ff457a4e2..accffbe92 100644 --- a/sources/pyside6/doc/gettingstarted/macOS.rst +++ b/sources/pyside6/doc/gettingstarted/macOS.rst @@ -34,12 +34,12 @@ Setting up CLANG If you don't have libclang already in your system, you can download from the Qt servers:: - wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_140-based-macos-universal.7z + wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.5-based-macos-universal.7z Extract the files, and leave it on any desired path, and set the environment variable required:: - 7z x libclang-release_140-based-macos-universal.7z + 7z x libclang-release_18.1.5-based-macos-universal.7z export LLVM_INSTALL_DIR=$PWD/libclang Getting PySide @@ -49,32 +49,36 @@ Cloning the official repository can be done by:: git clone https://code.qt.io/pyside/pyside-setup -Checking out the version that we want to build, for example, 6.5:: +Checking out the version that we want to build, for example, 6.8:: - cd pyside-setup && git checkout 6.5 + cd pyside-setup && git checkout 6.8 Install the general dependencies:: pip install -r requirements.txt +For building the documentation:: + + pip install -r requirements-doc.txt + .. note:: Keep in mind you need to use the same version as your Qt installation Building PySide ~~~~~~~~~~~~~~~ Check your Qt installation path, to specifically use that version of qtpaths to build PySide. -for example, ``/opt/Qt/6.5.0/gcc_64/bin/qtpaths``. +for example, ``/opt/Qt/6.8.0/gcc_64/bin/qtpaths``. Build can take a few minutes, so it is recommended to use more than one CPU core:: - python setup.py build --qtpaths=/opt/Qt/6.5.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 + python setup.py build --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 Installing PySide ~~~~~~~~~~~~~~~~~ To install on the current directory, just run:: - python setup.py install --qtpaths=/opt/Qt/6.5.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 + python setup.py install --qtpaths=/opt/Qt/6.8.0/gcc_64/bin/qtpaths --build-tests --ignore-git --parallel=8 Test installation ~~~~~~~~~~~~~~~~~ diff --git a/sources/pyside6/doc/gettingstarted/windows.rst b/sources/pyside6/doc/gettingstarted/windows.rst index f688acd90..4451bb1fd 100644 --- a/sources/pyside6/doc/gettingstarted/windows.rst +++ b/sources/pyside6/doc/gettingstarted/windows.rst @@ -7,7 +7,7 @@ selected when using the online installer. Requirements ------------ -* `MSVC2022`_ or (MSVC2019) for Python 3 on Windows, +* `MSVC2022`_ for Python 3 on Windows, * `OpenSSL`_ (optional for SSL support, Qt must have been configured using the same SSL library). * ``sphinx`` package for the documentation (optional). * Check the platform dependencies of `Qt for Windows`_. @@ -38,7 +38,7 @@ Setting up CLANG libclang can be downloaded from the `Qt servers <https://download.qt.io/development_releases/prebuilt/libclang>`_. -for example, ``libclang-release_140-based-windows-vs2019_64.7z``. +for example, ``libclang-release_18.1.5-based-windows-vs2019_64.7z``. Note that from version 12 onwards, the prebuilt Windows binaries from `LLVM <https://www.llvm.org>`_ no longer contain CMake configuration files; so @@ -57,21 +57,25 @@ Cloning the official repository can be done by:: git clone https://code.qt.io/pyside/pyside-setup -Checking out the version that we want to build, for example, 6.5:: +Checking out the version that we want to build, for example, 6.8:: - cd pyside-setup && git checkout 6.5 + cd pyside-setup && git checkout 6.8 Install the general dependencies:: pip install -r requirements.txt +For building the documentation:: + + pip install -r requirements-doc.txt + .. note:: Keep in mind you need to use the same version as your Qt installation Building PySide ~~~~~~~~~~~~~~~ Check your Qt installation path, to specifically use that version of qtpaths to build PySide. -for example, ``C:\Qt\6.5.0\msvc2019_64\bin\qtpaths.exe``. +for example, ``C:\Qt\6.8.0\msvc2019_64\bin\qtpaths.exe``. Build can take a few minutes, so it is recommended to use more than one CPU core:: diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp index 51070b4ad..2c75e39e1 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -274,9 +274,8 @@ int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args) if (setSenderDynamicProperty) m_receiver->setProperty(senderDynamicProperty, QVariant::fromValue(sender())); - const bool isShortCuit = std::strchr(slot.methodSignature(), '(') == nullptr; Shiboken::AutoDecRef callback(m_data->callback()); - SignalManager::callPythonMetaMethod(slot, args, callback, isShortCuit); + SignalManager::callPythonMetaMethod(slot, args, callback); if (setSenderDynamicProperty) m_receiver->setProperty(senderDynamicProperty, QVariant{}); diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp index d5e815a42..9e12e3cd7 100644 --- a/sources/pyside6/libpyside/pyside.cpp +++ b/sources/pyside6/libpyside/pyside.cpp @@ -43,6 +43,7 @@ #include <QtCore/QMutex> #include <QtCore/QStack> #include <QtCore/QThread> +#include <QtCore/private/qobject_p.h> #include <algorithm> #include <cstring> @@ -63,6 +64,20 @@ QT_END_NAMESPACE Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg) +static QObjectData *qt_object_private(const QObject *o) +{ + class FriendlyQObject : public QObject { + public: + using QObject::d_ptr; + }; + return static_cast<const FriendlyQObject *>(o)->d_ptr.data(); +} + +static bool hasDynamicMetaObject(const QObject *o) +{ + return qt_object_private(o)->metaObject != nullptr; +} + namespace PySide { @@ -675,6 +690,32 @@ static void invalidatePtr(any_t *object) static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; +// PYSIDE-2749: Skip over internal QML classes and classes +// with dynamic meta objects when looking for the best matching +// type to avoid unnessarily triggering the lazy load mechanism +// for classes that do not have a binding from things like eventFilter(). +static inline bool isInternalObject(const char *name) +{ + return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr; +} + +static const QMetaObject *metaObjectCandidate(const QObject *o) +{ + auto *metaObject = o->metaObject(); + // Skip QML helper types and Python objects + if (hasDynamicMetaObject(o)) { + if (auto *super = metaObject->superClass()) + metaObject = super; + } + for (auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) { + if (!isInternalObject(candidate->className())) { + metaObject = candidate; + break; + } + } + return metaObject; +} + // PYSIDE-1214, when creating new wrappers for classes inheriting QObject but // not exposed to Python, try to find the best-matching (most-derived) Qt // class by walking up the meta objects. @@ -682,19 +723,13 @@ static const char *typeName(const QObject *cppSelf) { const char *typeName = typeid(*cppSelf).name(); if (!Shiboken::Conversions::getConverter(typeName)) { - for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + auto *metaObject = metaObjectCandidate(cppSelf); + for (; metaObject != nullptr; metaObject = metaObject->superClass()) { const char *name = metaObject->className(); if (Shiboken::Conversions::getConverter(name)) { typeName = name; break; } - // PYSIDE-2404: Did not find the name. Load the lazy classes - // which have this name and try again. - Shiboken::Module::loadLazyClassesWithName(name); - if (Shiboken::Conversions::getConverter(name)) { - typeName = name; - break; - } } } return typeName; @@ -738,7 +773,7 @@ PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type) } } - pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf)); + pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf)); return pyOut; } diff --git a/sources/pyside6/libpyside/pysideclassinfo.cpp b/sources/pyside6/libpyside/pysideclassinfo.cpp index 9ab5a7ad0..698cb1c76 100644 --- a/sources/pyside6/libpyside/pysideclassinfo.cpp +++ b/sources/pyside6/libpyside/pysideclassinfo.cpp @@ -51,16 +51,12 @@ PyObject *ClassInfoPrivate::tp_call(PyObject *self, PyObject *args, PyObject * / auto *pData = DecoratorPrivate::get<ClassInfoPrivate>(self); - if (pData->m_alreadyWrapped) { - PyErr_SetString(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); - return nullptr; - } + if (pData->m_alreadyWrapped) + return PyErr_Format(PyExc_TypeError, "This instance of ClassInfo() was already used to wrap an object"); PyTypeObject *klassType = reinterpret_cast<PyTypeObject *>(klass); - if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) { - PyErr_SetString(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); - return nullptr; - } + if (!PySide::ClassInfo::setClassInfo(klassType, pData->m_data)) + return PyErr_Format(PyExc_TypeError, "This decorator can only be used on classes that are subclasses of QObject"); pData->m_alreadyWrapped = true; diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp index 457415479..3720815db 100644 --- a/sources/pyside6/libpyside/pysideproperty.cpp +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -475,6 +475,7 @@ static const char *Property_SignatureStrings[] = { "PySide6.QtCore.Property.read(self,fget:typing.Callable)->PySide6.QtCore.Property", "PySide6.QtCore.Property.setter(self,fset:typing.Callable)->PySide6.QtCore.Property", "PySide6.QtCore.Property.write(self,fset:typing.Callable)->PySide6.QtCore.Property", + "PySide6.QtCore.Property.__call__(self, func:typing.Callable)->PySide6.QtCore.Property", nullptr}; // Sentinel void init(PyObject *module) diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index 774837e5b..35a6e7ef9 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -484,14 +484,10 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject return nullptr; PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); - return nullptr; - } - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance"); + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); @@ -564,10 +560,8 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, PySide::PySideName::qtConnect())); - if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists. - PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!"); - return nullptr; - } + if (pyMethod.isNull()) // PYSIDE-79: check if pyMethod exists. + return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!"); PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); if (connection_Check(result)) return result; @@ -587,17 +581,13 @@ static int argCountInSignature(const char *signature) static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) { PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance"); // PYSIDE-2201: Check if the object has vanished meanwhile. // Tried to revive it without exception, but this gives problems. - if (source->deleted) { - PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); - return nullptr; - } + if (source->deleted) + return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted"); Shiboken::AutoDecRef pyArgs(PyList_New(0)); int numArgsGiven = PySequence_Fast_GET_SIZE(args); @@ -657,8 +647,7 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) message += '"' + data->d->signature + '"'; } - PyErr_SetString(PyExc_IndexError, message.constData()); - return nullptr; + return PyErr_Format(PyExc_IndexError, message.constData()); } static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature) @@ -675,10 +664,9 @@ static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signa static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) { auto source = reinterpret_cast<PySideSignalInstance *>(self); - if (!source->d) { - PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); - return nullptr; - } + if (!source->d) + return PyErr_Format(PyExc_RuntimeError, "cannot disconnect uninitialized SignalInstance"); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); PyObject *slot = Py_None; @@ -725,8 +713,7 @@ static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) } warnDisconnectFailed(slot, source->d->signature); - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } // PYSIDE-68: Supply the missing __get__ function @@ -761,10 +748,8 @@ static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) // The only way calling a signal can succeed (the Python equivalent of C++'s operator() ) // is when a method with the same name as the signal is attached to an object. // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h). - if (!signal->homonymousMethod) { - PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); - return nullptr; - } + if (!signal->homonymousMethod) + return PyErr_Format(PyExc_TypeError, "native Qt signal is not callable"); // Check if there exists a method with the same name as the signal, which is also a static // method in C++ land. @@ -1202,13 +1187,11 @@ EmitterData getEmitterData(PySideSignalInstance *signal) return result; } -QByteArrayList getArgsFromSignature(const char *signature, bool *isShortCircuit) +QByteArrayList getArgsFromSignature(const char *signature) { QByteArray qsignature = QByteArray(signature).trimmed(); QByteArrayList result; - if (isShortCircuit) - *isShortCircuit = !qsignature.contains(u'('); if (qsignature.contains("()") || qsignature.contains("(void)")) return result; if (qsignature.endsWith(')')) { @@ -1292,24 +1275,21 @@ QByteArray getCallbackSignature(const char *signal, QObject *receiver, functionName = Shiboken::String::toCString(slotArgs.functionName); Q_ASSERT(!functionName.isEmpty()); - bool isShortCircuit = false; - if (functionName.startsWith('<') && functionName.endsWith('>')) { // fix "<lambda>" functionName[0] = '_'; functionName[functionName.size() - 1] = '_'; } QByteArray signature = encodeName ? codeCallbackName(callback, functionName) : functionName; - QByteArrayList args = getArgsFromSignature(signal, &isShortCircuit); + QByteArrayList args = getArgsFromSignature(signal); - if (!isShortCircuit) { - signature.append(u'('); - if (numArgs == -1) - numArgs = std::numeric_limits<qsizetype>::max(); + signature.append(u'('); + if (numArgs != -1) { while (!args.isEmpty() && (args.size() > (numArgs - useSelf))) args.removeLast(); - signature.append(args.join(',')); - signature.append(')'); } + signature.append(args.join(',')); + signature.append(')'); + return signature; } diff --git a/sources/pyside6/libpyside/pysidesignal.h b/sources/pyside6/libpyside/pysidesignal.h index 589fc2c92..7493f94b5 100644 --- a/sources/pyside6/libpyside/pysidesignal.h +++ b/sources/pyside6/libpyside/pysidesignal.h @@ -142,12 +142,10 @@ PYSIDE_API QByteArray getCallbackSignature(const char *signal, QObject *receiver * This function parses the signature and then returns a list of argument types. * * @param signature The signal signature - * @param isShortCircuit If this is a shortCircuit(python<->python) signal * @return Return true if this is a Qt Signal, otherwise return false * @todo replace return type by QList<QByteArray> **/ -QByteArrayList getArgsFromSignature(const char *signature, - bool *isShortCircuit = nullptr); +QByteArrayList getArgsFromSignature(const char *signature); } // namespace PySide::Signal diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index 557f130e0..f4c2bbf43 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -43,7 +43,6 @@ using namespace Qt::StringLiterals; static PyObject *metaObjectAttr = nullptr; static PyObject *parseArguments(const QMetaMethod &method, void **args); -static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args); static bool qAppRunning = false; @@ -474,16 +473,7 @@ bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *ar signal++; int signalIndex = source->metaObject()->indexOfSignal(signal); - if (signalIndex != -1) { - // cryptic but works! - // if the signature doesn't have a '(' it's a shor circuited signal, i.e. std::find - // returned the string null terminator. - bool isShortCircuit = !*std::find(signal, signal + std::strlen(signal), '('); - return isShortCircuit - ? emitShortCircuitSignal(source, signalIndex, args) - : MetaFunction::call(source, signalIndex, args); - } - return false; + return signalIndex != -1 && MetaFunction::call(source, signalIndex, args); } // Handle errors from meta calls. Requires GIL and PyErr_Occurred() @@ -589,7 +579,7 @@ int SignalManager::SignalManagerPrivate::qtMethodMetacall(QObject *object, PyErr_Format(PyExc_AttributeError, "Slot '%s::%s' not found.", metaObject->className(), method.methodSignature().constData()); } else { - SignalManager::callPythonMetaMethod(method, args, pyMethod, false); + SignalManager::callPythonMetaMethod(method, args, pyMethod); } } // WARNING Isn't safe to call any metaObject and/or object methods beyond this point @@ -635,13 +625,12 @@ int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, return id; } -int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod, bool isShortCuit) +int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod) { Q_ASSERT(pyMethod); Shiboken::GilState gil; - PyObject *pyArguments = isShortCuit - ? reinterpret_cast<PyObject *>(args[1]) : parseArguments(method, args); + PyObject *pyArguments = parseArguments(method, args); if (pyArguments) { QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter; @@ -656,8 +645,7 @@ int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments)); - if (!isShortCuit && pyArguments) - Py_DECREF(pyArguments); + Py_DECREF(pyArguments); if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) retConverter->toCpp(retval, args[0]); @@ -824,11 +812,4 @@ static PyObject *parseArguments(const QMetaMethod &method, void **args) return preparedArgs; } -static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args) -{ - void *signalArgs[2] = {nullptr, args}; - source->qt_metacall(QMetaObject::InvokeMetaMethod, signalIndex, signalArgs); - return true; -} - #include "signalmanager.moc" diff --git a/sources/pyside6/libpyside/signalmanager.h b/sources/pyside6/libpyside/signalmanager.h index c531d6630..397700df1 100644 --- a/sources/pyside6/libpyside/signalmanager.h +++ b/sources/pyside6/libpyside/signalmanager.h @@ -81,7 +81,7 @@ public: void purgeEmptyGlobalReceivers(); // Utility function to call a python method usign args received in qt_metacall - static int callPythonMetaMethod(const QMetaMethod& method, void** args, PyObject* obj, bool isShortCuit); + static int callPythonMetaMethod(const QMetaMethod& method, void** args, PyObject* obj); static void deleteGlobalReceiver(const QObject *globalReceiver); diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index 618d621bd..4ccd459d5 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -28,6 +28,7 @@ #include <QtQml/QJSValue> #include <QtQml/QQmlListProperty> #include <private/qqmlmetatype_p.h> +#include <private/qmetaobjectbuilder_p.h> #include <memory> @@ -191,19 +192,15 @@ namespace PySide::Qml { // Modern (6.7) type registration using RegisterTypeAndRevisions // and information set to QMetaClassInfo. -static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, - const ImportData &importData) +static int qmlRegisterType(PyObject *pyObj, + const ImportData &importData, + const QMetaObject *metaObject, + const QMetaObject *classInfoMetaObject = nullptr) { - using namespace Shiboken; - PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); - if (!isQObjectDerived(pyObjType, true)) - return -1; - const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); - Q_ASSERT(metaObject); - const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj - ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + if (classInfoMetaObject == nullptr) + classInfoMetaObject = metaObject; // Register as simple QObject rather than Qt Quick item. // Incref the type object, don't worry about decref'ing it because @@ -270,18 +267,44 @@ static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, return qmlTypeId; } +static int qmlRegisterType(PyObject *pyObj, PyObject *pyClassInfoObj, + const ImportData &importData) +{ + PyTypeObject *pyObjType = reinterpret_cast<PyTypeObject *>(pyObj); + if (!isQObjectDerived(pyObjType, true)) + return -1; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(pyObjType); + Q_ASSERT(metaObject); + const QMetaObject *classInfoMetaObject = pyObj == pyClassInfoObj + ? metaObject : PySide::retrieveMetaObject(pyClassInfoObj); + return qmlRegisterType(pyObj, importData, metaObject, classInfoMetaObject); +} + // Legacy (pre 6.7) compatibility helper for the free register functions. int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, int versionMinor, const char *qmlName, const char *noCreationReason, bool creatable) { auto *type = checkTypeObject(pyObj, "qmlRegisterType()"); - if (type == nullptr || !PySide::isQObjectDerived(type, true) - || !setClassInfo(type, qmlElementKey, qmlName)) + if (type == nullptr || !PySide::isQObjectDerived(type, true)) return -1; + + const QMetaObject *metaObject = PySide::retrieveMetaObject(type); + Q_ASSERT(metaObject); + + // PYSIDE-2709: Use a separate QMetaObject for the class information + // as modifying metaObject breaks inheritance. + QMetaObjectBuilder classInfobuilder(&QObject::staticMetaObject); + classInfobuilder.addClassInfo(qmlElementKey, qmlName); if (!creatable) - setUncreatableClassInfo(type, noCreationReason); - return qmlRegisterType(pyObj, pyObj, {uri, versionMajor, versionMinor}); + setUncreatableClassInfo(&classInfobuilder, noCreationReason); + auto *classInfoMetaObject = classInfobuilder.toMetaObject(); + + const int qmlTypeId = qmlRegisterType(pyObj, {uri, versionMajor, versionMinor}, + metaObject, classInfoMetaObject); + free(classInfoMetaObject); + return qmlTypeId; } // Singleton helpers @@ -369,7 +392,7 @@ QObject *SingletonQObjectCreationBase::handleReturnValue(PyObject *retVal) using Shiboken::Conversions::isPythonToCppPointerConvertible; // Make sure the callback returns something we can convert, else the entire application will crash. if (retVal == nullptr) { - PyErr_SetString(PyExc_TypeError, "Callback returns 0 value."); + PyErr_Format(PyExc_TypeError, "Callback returns 0 value."); return nullptr; } if (isPythonToCppPointerConvertible(qObjectType(), retVal) == nullptr) { diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp index 55b15ba5b..7c0f6b8ff 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.cpp @@ -10,6 +10,7 @@ #include <sbkcppstring.h> #include <QtCore/qbytearray.h> +#include <private/qmetaobjectbuilder_p.h> using namespace Qt::StringLiterals; @@ -109,3 +110,9 @@ void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason) {"QML.Creatable"_ba, "false"_ba}, {"QML.UncreatableReason"_ba, reason} }); } + +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason) +{ + builder->addClassInfo("QML.Creatable", "false"); + builder->addClassInfo("QML.UncreatableReason", reason); +} diff --git a/sources/pyside6/libpysideqml/pysideqmluncreatable.h b/sources/pyside6/libpysideqml/pysideqmluncreatable.h index 772ad4ccb..8a8adb3c8 100644 --- a/sources/pyside6/libpysideqml/pysideqmluncreatable.h +++ b/sources/pyside6/libpysideqml/pysideqmluncreatable.h @@ -8,6 +8,8 @@ #include <QtCore/QByteArray> +QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder) + // The QmlUncreatable decorator modifies QmlElement to register an uncreatable // type. Due to the (reverse) execution order of decorators, it needs to follow // QmlElement. @@ -19,5 +21,6 @@ extern "C" void initQmlUncreatable(PyObject *module); void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason); +void setUncreatableClassInfo(QMetaObjectBuilder *builder, const QByteArray &reason); #endif // PYSIDEQMLUNCREATABLE_H diff --git a/sources/pyside6/plugins/designer/CMakeLists.txt b/sources/pyside6/plugins/designer/CMakeLists.txt index c86d328cf..717652314 100644 --- a/sources/pyside6/plugins/designer/CMakeLists.txt +++ b/sources/pyside6/plugins/designer/CMakeLists.txt @@ -19,6 +19,9 @@ target_sources(PySidePlugin PRIVATE ) # See libshiboken/CMakeLists.txt + +target_compile_definitions(PySidePlugin PRIVATE -DQT_NO_KEYWORDS=1) + if(PYTHON_LIMITED_API) target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03050000") endif() diff --git a/sources/pyside6/tests/QtCore/CMakeLists.txt b/sources/pyside6/tests/QtCore/CMakeLists.txt index f584bfde6..f0228d943 100644 --- a/sources/pyside6/tests/QtCore/CMakeLists.txt +++ b/sources/pyside6/tests/QtCore/CMakeLists.txt @@ -34,6 +34,7 @@ PYSIDE_TEST(bug_1313.py) PYSIDE_TEST(bug_PYSIDE-41.py) PYSIDE_TEST(bug_PYSIDE-42.py) PYSIDE_TEST(bug_PYSIDE-164.py) +PYSIDE_TEST(bug_PYSIDE-2745.py) PYSIDE_TEST(blocking_signals_test.py) PYSIDE_TEST(classinfo_test.py) PYSIDE_TEST(child_event_test.py) diff --git a/sources/pyside6/tests/QtCore/bug_927.py b/sources/pyside6/tests/QtCore/bug_927.py index 1ecea61b2..c15a7014b 100644 --- a/sources/pyside6/tests/QtCore/bug_927.py +++ b/sources/pyside6/tests/QtCore/bug_927.py @@ -24,7 +24,7 @@ class thread_function(): class Task(QRunnable): def run(self): - QThread.sleep(2) # Sleep 2 seconds + QThread.msleep(100) class QThreadPoolTest(unittest.TestCase): @@ -34,15 +34,15 @@ class QThreadPoolTest(unittest.TestCase): for i in range(3): task = Task() QThreadPool.globalInstance().start(task) - time.sleep(1) # Sleep 1 second + time.sleep(0.05) - QThreadPool.globalInstance().waitForDone() + self.assertTrue(QThreadPool.globalInstance().waitForDone()) def testCallable(self): global thread_function_called tp = QThreadPool.globalInstance() tp.start(thread_function) - tp.waitForDone() + self.assertTrue(tp.waitForDone()) self.assertTrue(thread_function_called) diff --git a/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py b/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py new file mode 100644 index 000000000..3d6c603b7 --- /dev/null +++ b/sources/pyside6/tests/QtCore/bug_PYSIDE-2745.py @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import QTimer + +from helper.usesqapplication import UsesQApplication + + +class TestBugPYSIDE2745(UsesQApplication): + + def setUp(self): + UsesQApplication.setUp(self) + self.counter = 0 + + def fail(self): + self.counter += 1 + raise Exception() + + def test_fail(self): + QTimer.singleShot(0, self.fail) + QTimer.singleShot(0, self.fail) + QTimer.singleShot(1, self.app.quit) + self.app.exec() + self.assertEqual(self.counter, 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtGui/qbrush_test.py b/sources/pyside6/tests/QtGui/qbrush_test.py index 800e6f072..69262328b 100644 --- a/sources/pyside6/tests/QtGui/qbrush_test.py +++ b/sources/pyside6/tests/QtGui/qbrush_test.py @@ -13,7 +13,7 @@ from init_paths import init_test_paths init_test_paths(False) from PySide6.QtCore import Qt -from PySide6.QtGui import QColor, QBrush +from PySide6.QtGui import QColor, QBrush, QConicalGradient from helper.usesqapplication import UsesQApplication @@ -30,6 +30,14 @@ class Constructor(UsesQApplication): obj = QBrush(Qt.blue) self.assertEqual(obj.color(), Qt.blue) + def testGradient(self): + """Test type discovery on class hierarchies with non-virtual + destructors by specifying a polymorphic-id-expression without + polymorphic-name-function.""" + gradient = QConicalGradient() + brush = QBrush(gradient) + self.assertEqual(type(brush.gradient()), type(gradient)) + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index 720f0ef99..30bf7e786 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -17,6 +17,7 @@ PYSIDE_TEST(bug_997.py) PYSIDE_TEST(bug_1029.py) PYSIDE_TEST(groupedproperty.py) PYSIDE_TEST(listproperty.py) +PYSIDE_TEST(qmlregistertype_test.py) PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) PYSIDE_TEST(qqmlcomponent_test.py) diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.py b/sources/pyside6/tests/QtQml/qmlregistertype_test.py new file mode 100644 index 000000000..0042d6fd3 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from helper.usesqapplication import UsesQApplication + + +from PySide6.QtCore import QCoreApplication, QObject # noqa: F401 +from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType + + +class BaseClass(QObject): + def __init__(self, p=None): + super().__init__(p) + + +class ChildClass(BaseClass): + def __init__(self, p=None): + super().__init__(p) + + +class TestQmlRegisterType(UsesQApplication): + """Test the legacy QML register functions.""" + + def test(self): + qmlRegisterType(BaseClass, 'test', 1, 0, 'BaseClass') + qmlRegisterType(ChildClass, 'test', 1, 0, 'ChildClass') + # PYSIDE-2709: qmlRegisterType() would set additional class info + # on the meta objects for registration which caused another meta + # object to be created, breaking inheritance. + child = ChildClass() + base = BaseClass() + self.assertTrue(child.metaObject().inherits(base.metaObject())) + + engine = QQmlApplicationEngine() + file = Path(__file__).resolve().parent / 'qmlregistertype_test.qml' + + engine.load(file) + rootObjects = engine.rootObjects() + self.assertTrue(rootObjects) + self.assertTrue(type(rootObjects[0]), ChildClass) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/qmlregistertype_test.qml b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml new file mode 100644 index 000000000..108bb84b1 --- /dev/null +++ b/sources/pyside6/tests/QtQml/qmlregistertype_test.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import test + +ChildClass { +} diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py index 02293c33e..db60c8c3f 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -63,6 +63,9 @@ class DeployTestBase(LongSortedOptionTest): cls.deploy_lib = importlib.import_module("deploy_lib") cls.deploy = importlib.import_module("deploy") sys.modules["deploy"] = cls.deploy + files_to_ignore = [".cpp.o", ".qsb", ".webp"] + cls.dlls_ignore_nuitka = " ".join([f"--noinclude-dlls=*{file}" + for file in files_to_ignore]) # required for comparing long strings cls.maxDiff = None @@ -107,6 +110,7 @@ class TestPySide6DeployWidgets(DeployTestBase): f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" f" --noinclude-qt-translations" f" --include-qt-plugins={plugins_nuitka}" + f" {self.dlls_ignore_nuitka}" ) if sys.platform.startswith("linux"): self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile" @@ -189,6 +193,7 @@ class TestPySide6DeployQml(DeployTestBase): self.deployment_files = self.temp_example_qml / "deployment" self.first_qml_file = "main.qml" self.second_qml_file = "MovingRectangle.qml" + # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles # some plugins by default self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines", @@ -203,6 +208,8 @@ class TestPySide6DeployQml(DeployTestBase): f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports" f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" f" --noinclude-qt-translations" + f" {self.dlls_ignore_nuitka}" + " --noinclude-dlls=*/qml/QtQuickEffectMaker/*" f" --include-qt-plugins={plugins_nuitka}" f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}=" f"./main.qml --include-data-files=" @@ -334,6 +341,8 @@ class TestPySide6DeployWebEngine(DeployTestBase): f" --noinclude-qt-translations --include-qt-plugins=all" f" {data_files_cmd}" f" --include-qt-plugins={plugins_nuitka}" + f" {self.dlls_ignore_nuitka}" + " --noinclude-dlls=*/qml/QtQuickEffectMaker/*" ) if sys.platform != "win32": diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 89d636964..fa0767a62 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -461,7 +461,7 @@ FileModelItem AbstractMetaBuilderPrivate::buildDom(QByteArrayList arguments, + clang::languageLevelOption(level)); } FileModelItem result = clang::parse(arguments, addCompilerSupportArguments, - clangFlags, builder) + level, clangFlags, builder) ? builder.dom() : FileModelItem(); const clang::BaseVisitor::Diagnostics &diagnostics = builder.diagnostics(); if (const auto diagnosticsCount = diagnostics.size()) { @@ -1574,7 +1574,8 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me &info, &baseContainerType); if (templ) { setupInheritance(templ); - inheritTemplate(metaClass, templ, info); + if (!inheritTemplate(metaClass, templ, info)) + return false; metaClass->typeEntry()->setBaseContainerType(templ->typeEntry()); return true; } @@ -3128,80 +3129,103 @@ std::optional<AbstractMetaType> AbstractMetaClassPtr AbstractMetaBuilder::inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { auto result = std::make_shared<AbstractMetaClass>(); result->setTypeDef(true); result->setTypeEntry(te); if (!AbstractMetaBuilderPrivate::inheritTemplate(result, templateClass, - templateTypes, flags)) { + templateTypes)) { return {}; } AbstractMetaBuilderPrivate::inheritTemplateFunctions(result); return result; } + +static std::optional<AbstractMetaType> + inheritTemplateParameter(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info, QString *errorMessage) +{ + QString typeName = info.qualifiedName().join("::"_L1); + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeEntryPtr t; + // Check for a non-type template integer parameter, that is, for a base + // "template <int R, int C> Matrix<R, C>" and subclass + // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of + // EnumValueTypeEntry for the integer values encountered on the fly. + if (isNumber(typeName)) { + t = typeDb->findType(typeName); + if (!t) { + auto parent = typeSystemTypeEntry(subclass->typeEntry()); + t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); + } + } else { + QStringList possibleNames; + possibleNames << subclass->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << templateClass->qualifiedCppName() + "::"_L1 + typeName; + if (subclass->enclosingClass()) + possibleNames << subclass->enclosingClass()->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << typeName; + + for (const QString &possibleName : std::as_const(possibleNames)) { + t = typeDb->findType(possibleName); + if (t) + break; + } + } + + if (!t) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "The corresponding type was not found in the typesystem."); + return std::nullopt; + } + + if (t->isContainer()) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "Template inheritance from nested containers is not supported"); + return std::nullopt; + } + AbstractMetaType result(t); + result.setConstant(info.isConstant()); + result.setReferenceType(info.referenceType()); + result.setIndirectionsV(info.indirectionsV()); + result.decideUsagePattern(); + return result; +} + bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, const TypeInfo &info) { AbstractMetaTypeList templateTypes; + QString errorMessage; for (const TypeInfo &i : info.instantiations()) { - QString typeName = i.qualifiedName().join(u"::"_s); - TypeDatabase *typeDb = TypeDatabase::instance(); - TypeEntryPtr t; - // Check for a non-type template integer parameter, that is, for a base - // "template <int R, int C> Matrix<R, C>" and subclass - // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of - // EnumValueTypeEntry for the integer values encountered on the fly. - if (isNumber(typeName)) { - t = typeDb->findType(typeName); - if (!t) { - auto parent = typeSystemTypeEntry(subclass->typeEntry()); - t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); - } + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); } else { - QStringList possibleNames; - possibleNames << subclass->qualifiedCppName() + u"::"_s + typeName; - possibleNames << templateClass->qualifiedCppName() + u"::"_s + typeName; - if (subclass->enclosingClass()) - possibleNames << subclass->enclosingClass()->qualifiedCppName() + u"::"_s + typeName; - possibleNames << typeName; - - for (const QString &possibleName : std::as_const(possibleNames)) { - t = typeDb->findType(possibleName); - if (t) - break; - } - } - - if (t) { - AbstractMetaType temporaryType(t); - temporaryType.setConstant(i.isConstant()); - temporaryType.setReferenceType(i.referenceType()); - temporaryType.setIndirectionsV(i.indirectionsV()); - temporaryType.decideUsagePattern(); - templateTypes << temporaryType; - } else { - qCWarning(lcShiboken).noquote().nospace() - << "Ignoring template parameter " << typeName << " from " - << info.toString() << ". The corresponding type was not found in the typesystem."; + errorMessage = msgInheritTemplateIssue(subclass, info, errorMessage); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); } } + if (templateTypes.isEmpty()) { + errorMessage = msgInheritTemplateIssue(subclass, info, + "No template parameters could be inherited"_L1); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return false; + } return inheritTemplate(subclass, templateClass, templateTypes); } bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { subclass->setTemplateBaseClass(templateClass); - if (flags.testFlag(InheritTemplateFlag::SetEnclosingClass)) - subclass->setEnclosingClass(templateClass->enclosingClass()); subclass->setTemplateBaseClassInstantiations(templateTypes); subclass->setBaseClass(templateClass->baseClass()); return true; diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h index cbd8c7034..20261ed3c 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h @@ -100,10 +100,9 @@ public: const AbstractMetaTypeList &templateTypes); static AbstractMetaClassPtr - inheritTemplateClass(const ComplexTypeEntryPtr &te, - const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes); /// Performs a template specialization of the member function. /// \param function Member function diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h index d7aaba5b0..e65a4f176 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h @@ -190,8 +190,7 @@ public: const TypeInfo &info); static bool inheritTemplate(const AbstractMetaClassPtr &subclass, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + const AbstractMetaTypeList &templateTypes); static AbstractMetaFunctionPtr inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index dcfc74bbb..3ec07509d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -543,6 +543,7 @@ void AbstractMetaType::decideUsagePattern() pattern = ObjectPattern; } setTypeUsagePattern(pattern); + Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty()); } bool AbstractMetaTypeData::hasTemplateChildren() const diff --git a/sources/shiboken6/ApiExtractor/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp index 83ee4437e..a4efaf2f9 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp @@ -456,11 +456,10 @@ AbstractMetaFunctionPtr AbstractMetaClassPtr ApiExtractor::inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags) + const AbstractMetaTypeList &templateTypes) { return AbstractMetaBuilder::inheritTemplateClass(te, templateClass, - templateTypes, flags); + templateTypes); } QString ApiExtractorPrivate::getSimplifiedContainerTypeName(const AbstractMetaType &type) @@ -606,7 +605,6 @@ void ApiExtractorPrivate::addInstantiatedSmartPointer(InstantiationCollectContex const auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(smp.smartPointer->typeEntry()); QString name = ste->getTargetName(smp.type); auto parentTypeEntry = ste->parent(); - InheritTemplateFlags flags; auto colonPos = name.lastIndexOf(u"::"); const bool withinNameSpace = colonPos != -1; @@ -617,19 +615,18 @@ void ApiExtractorPrivate::addInstantiatedSmartPointer(InstantiationCollectContex if (nameSpaces.isEmpty()) throw Exception(msgNamespaceNotFound(name)); parentTypeEntry = nameSpaces.constFirst(); - } else { - flags.setFlag(InheritTemplateFlag::SetEnclosingClass); } TypedefEntryPtr typedefEntry(new TypedefEntry(name, ste->name(), ste->version(), parentTypeEntry)); typedefEntry->setTargetLangPackage(ste->targetLangPackage()); auto instantiationEntry = TypeDatabase::initializeTypeDefEntry(typedefEntry, ste); + instantiationEntry->setParent(parentTypeEntry); smp.specialized = ApiExtractor::inheritTemplateClass(instantiationEntry, smp.smartPointer, - {instantiatedType}, flags); + {instantiatedType}); Q_ASSERT(smp.specialized); - if (withinNameSpace) { // move class to desired namespace + if (parentTypeEntry->type() != TypeEntry::TypeSystemType) { // move class to desired namespace const auto enclClass = AbstractMetaClass::findClass(m_builder->classes(), parentTypeEntry); Q_ASSERT(enclClass); auto specialized = std::const_pointer_cast<AbstractMetaClass>(smp.specialized); diff --git a/sources/shiboken6/ApiExtractor/apiextractor.h b/sources/shiboken6/ApiExtractor/apiextractor.h index feae9454c..7bff5c252 100644 --- a/sources/shiboken6/ApiExtractor/apiextractor.h +++ b/sources/shiboken6/ApiExtractor/apiextractor.h @@ -73,8 +73,7 @@ public: static AbstractMetaClassPtr inheritTemplateClass(const ComplexTypeEntryPtr &te, const AbstractMetaClassCPtr &templateClass, - const AbstractMetaTypeList &templateTypes, - InheritTemplateFlags flags = {}); + const AbstractMetaTypeList &templateTypes); private: ApiExtractorPrivate *d; diff --git a/sources/shiboken6/ApiExtractor/apiextractorflags.h b/sources/shiboken6/ApiExtractor/apiextractorflags.h index 4fe6ecc1a..6f69b8b77 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorflags.h +++ b/sources/shiboken6/ApiExtractor/apiextractorflags.h @@ -15,12 +15,4 @@ enum class ApiExtractorFlag Q_DECLARE_FLAGS(ApiExtractorFlags, ApiExtractorFlag) Q_DECLARE_OPERATORS_FOR_FLAGS(ApiExtractorFlags) -enum class InheritTemplateFlag -{ - SetEnclosingClass = 0x1 -}; - -Q_DECLARE_FLAGS(InheritTemplateFlags, InheritTemplateFlag) -Q_DECLARE_OPERATORS_FOR_FLAGS(InheritTemplateFlags) - #endif // APIEXTRACTORFLAGS_H diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp index 2a48a30d1..b53ffa9b6 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp @@ -50,6 +50,18 @@ const InstantiatedSmartPointers &ApiExtractorResult::instantiatedSmartPointers() return m_instantiatedSmartPointers; } +std::optional<InstantiatedSmartPointer> + ApiExtractorResult::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const +{ + for (const auto &smp : m_instantiatedSmartPointers) { + const auto &i = smp.type; + if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) + return smp; + } + return std::nullopt; +} + const QMultiHash<QString, QString> &ApiExtractorResult::typedefTargetToName() const { return m_typedefTargetToName; diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.h b/sources/shiboken6/ApiExtractor/apiextractorresult.h index 88a2093f1..b2aae88ed 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorresult.h +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.h @@ -43,6 +43,9 @@ public: const AbstractMetaTypeList &instantiatedContainers() const; const InstantiatedSmartPointers &instantiatedSmartPointers() const; + std::optional<InstantiatedSmartPointer> + findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const; const QMultiHash<QString, QString> &typedefTargetToName() const; diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp index da6930476..6c0cf3ae2 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -235,6 +235,7 @@ static QByteArray msgCreateTranslationUnit(const QByteArrayList &clangArgs, unsi static CXTranslationUnit createTranslationUnit(CXIndex index, const QByteArrayList &args, bool addCompilerSupportArguments, + LanguageLevel level, unsigned flags = 0) { // courtesy qdoc @@ -255,7 +256,7 @@ static CXTranslationUnit createTranslationUnit(CXIndex index, QByteArrayList clangArgs; if (addCompilerSupportArguments) { - clangArgs += emulatedCompilerOptions(); + clangArgs += emulatedCompilerOptions(level); clangArgs += defaultArgs; } clangArgs += detectVulkan(); @@ -280,7 +281,7 @@ static CXTranslationUnit createTranslationUnit(CXIndex index, */ bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, - unsigned clangFlags, BaseVisitor &bv) + LanguageLevel level, unsigned clangFlags, BaseVisitor &bv) { CXIndex index = clang_createIndex(0 /* excludeDeclarationsFromPCH */, 1 /* displayDiagnostics */); @@ -291,7 +292,7 @@ bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, CXTranslationUnit translationUnit = createTranslationUnit(index, clangArgs, addCompilerSupportArguments, - clangFlags); + level, clangFlags); if (!translationUnit) return false; diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.h b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h index 4a46248e4..22e0a50cd 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangparser.h +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h @@ -14,6 +14,8 @@ #include <string_view> #include <utility> +enum class LanguageLevel; + namespace clang { struct Diagnostic; @@ -79,7 +81,7 @@ private: bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, - unsigned clangFlags, BaseVisitor &ctx); + LanguageLevel level, unsigned clangFlags, BaseVisitor &ctx); } // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp index 4c13b141f..20224020b 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp @@ -106,7 +106,7 @@ static bool runProcess(const QString &program, const QStringList &arguments, QProcess process; process.start(program, arguments, QProcess::ReadWrite); if (!process.waitForStarted()) { - qWarning().noquote().nospace() << "Unable to start " + qCWarning(lcShiboken).noquote().nospace() << "Unable to start " << process.program() << ": " << process.errorString(); return false; } @@ -119,18 +119,18 @@ static bool runProcess(const QString &program, const QStringList &arguments, *stdOutIn = process.readAllStandardOutput(); if (!finished) { - qWarning().noquote().nospace() << process.program() << " timed out: " << stdErr; + qCWarning(lcShiboken).noquote().nospace() << process.program() << " timed out: " << stdErr; process.kill(); return false; } if (process.exitStatus() != QProcess::NormalExit) { - qWarning().noquote().nospace() << process.program() << " crashed: " << stdErr; + qCWarning(lcShiboken).noquote().nospace() << process.program() << " crashed: " << stdErr; return false; } if (process.exitCode() != 0) { - qWarning().noquote().nospace() << process.program() << " exited " + qCWarning(lcShiboken).noquote().nospace() << process.program() << " exited " << process.exitCode() << ": " << stdErr; return false; } @@ -263,8 +263,8 @@ static QString queryLlvmConfigDir(const QString &arg) return {}; const QString path = QFile::decodeName(stdOut.trimmed()); if (!QFileInfo::exists(path)) { - qWarning(R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", - __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); + qCWarning(lcShiboken, R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", + __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); return {}; } return path; @@ -277,7 +277,8 @@ static QString findClangLibDir() const QString path = QFile::decodeName(qgetenv(envVar)) + u"/lib"_s; if (QFileInfo::exists(path)) return path; - qWarning("%s: %s as pointed to by %s does not exist.", __FUNCTION__, qPrintable(path), envVar); + qCWarning(lcShiboken, "%s: %s as pointed to by %s does not exist.", + __FUNCTION__, qPrintable(path), envVar); } } return queryLlvmConfigDir(u"--libdir"_s); @@ -289,13 +290,23 @@ static QString findClangBuiltInIncludesDir() const QString clangPathLibDir = findClangLibDir(); if (!clangPathLibDir.isEmpty()) { QString candidate; + QString clangDirName = clangPathLibDir + u"/clang"_s; + // PYSIDE-2769: llvm-config --libdir may report /usr/lib64 on manylinux_2_28_x86_64 + // whereas the includes are under /usr/lib/clang/../include. + if (!QFileInfo::exists(clangDirName) && clangPathLibDir.endsWith("64"_L1)) { + const QString fallback = clangPathLibDir.sliced(0, clangPathLibDir.size() - 2); + clangDirName = fallback + u"/clang"_s; + qCWarning(lcShiboken, "%s: Falling back from %s to %s.", + __FUNCTION__, qPrintable(clangPathLibDir), qPrintable(fallback)); + } + QVersionNumber lastVersionNumber(1, 0, 0); - const QString clangDirName = clangPathLibDir + u"/clang"_s; QDir clangDir(clangDirName); const QFileInfoList versionDirs = clangDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); if (versionDirs.isEmpty()) - qWarning("%s: No subdirectories found in %s.", __FUNCTION__, qPrintable(clangDirName)); + qCWarning(lcShiboken, "%s: No subdirectories found in %s.", + __FUNCTION__, qPrintable(clangDirName)); for (const QFileInfo &fi : versionDirs) { const QString fileName = fi.fileName(); if (fileName.at(0).isDigit()) { @@ -365,14 +376,15 @@ static void appendClangBuiltinIncludes(HeaderPaths *p) } // Returns clang options needed for emulating the host compiler -QByteArrayList emulatedCompilerOptions() +QByteArrayList emulatedCompilerOptions(LanguageLevel level) { QByteArrayList result; HeaderPaths headerPaths; switch (compiler()) { case Compiler::Msvc: result.append("-fms-compatibility-version="_ba + msvcCompatVersion()); - result.append(QByteArrayLiteral("-fdelayed-template-parsing")); + if (level < LanguageLevel::Cpp20) + result.append("-fdelayed-template-parsing"_ba); result.append(QByteArrayLiteral("-Wno-microsoft-enum-value")); result.append("/Zc:__cplusplus"_ba); // Fix yvals_core.h: STL1000: Unexpected compiler version, expected Clang 7 or newer (MSVC2017 update) diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h index 462e8f205..f1d63b7c3 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h @@ -33,7 +33,7 @@ enum class Platform { namespace clang { QVersionNumber libClangVersion(); -QByteArrayList emulatedCompilerOptions(); +QByteArrayList emulatedCompilerOptions(LanguageLevel level); LanguageLevel emulatedCompilerLanguageLevel(); const char *languageLevelOption(LanguageLevel l); diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index f9f46f520..b1f0b240e 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -481,6 +481,21 @@ QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &s + u"\" for instantiation of \""_s +smartPointerType + u"\"."_s; } +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, + const QString &what) +{ + return "While inheriting template "_L1 + subclass->name() + + " from "_L1 + info.toString() + ": "_L1 + what; +} + +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why) +{ + return "Ignoring template parameter "_L1 + typeName + + ": "_L1 + QLatin1StringView(why); +} + QString msgInvalidSmartPointerType(const TypeInfo &i) { return u"Invalid smart pointer type \""_s +i.toString() + u"\"."_s; diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index 2899cbdfa..e3f582b49 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -126,6 +126,10 @@ QString msgUnableToTranslateType(const TypeInfo &typeInfo, QString msgCannotFindTypeEntry(const QString &t); QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType); +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, const QString &what); +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why); QString msgInvalidSmartPointerType(const TypeInfo &i); QString msgCannotFindSmartPointerInstantion(const TypeInfo &i); diff --git a/sources/shiboken6/ApiExtractor/typedatabase.cpp b/sources/shiboken6/ApiExtractor/typedatabase.cpp index 749c4baa3..61fd22418 100644 --- a/sources/shiboken6/ApiExtractor/typedatabase.cpp +++ b/sources/shiboken6/ApiExtractor/typedatabase.cpp @@ -88,7 +88,8 @@ static const PythonTypes &builtinPythonTypes() {u"PyObject"_s, u"true"_s, TypeSystem::CPythonType::Other}, // shiboken-specific {u"PyPathLike"_s, u"Shiboken::String::checkPath"_s, TypeSystem::CPythonType::Other}, - {u"PySequence"_s, u"Shiboken::String::checkIterable"_s, TypeSystem::CPythonType::Other}, + {u"PySequence"_s, u"Shiboken::String::checkIterableArgument"_s, + TypeSystem::CPythonType::Other}, {u"PyUnicode"_s, u"PyUnicode_Check"_s, TypeSystem::CPythonType::String}, {u"PyTypeObject"_s, u"PyType_Check"_s, TypeSystem::CPythonType::Other}, {u"str"_s, u"Shiboken::String::check"_s, TypeSystem::CPythonType::String}, diff --git a/sources/shiboken6/cmake/ShibokenHelpers.cmake b/sources/shiboken6/cmake/ShibokenHelpers.cmake index 8bc066102..cff6df95e 100644 --- a/sources/shiboken6/cmake/ShibokenHelpers.cmake +++ b/sources/shiboken6/cmake/ShibokenHelpers.cmake @@ -202,12 +202,6 @@ macro(get_python_extension_suffix) # Python_SOABI is only set by CMake 3.17+ # TODO: Lower this to CMake 3.16 if possible. if(SHIBOKEN_IS_CROSS_BUILD) - # For android platform armv7a FindPython module return Python_SOABI as empty because - # it is unable to set Python_CONFIG i.e. find `python3-config` script - # This workaround sets the Python_SOABI manually for this platform. - if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7-a") - set(Python_SOABI "cpython-311}") - endif() if(NOT Python_SOABI) message(FATAL_ERROR "Python_SOABI variable is empty.") endif() @@ -320,6 +314,17 @@ macro(shiboken_find_required_python) "${_shiboken_backup_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}") set(CMAKE_FIND_ROOT_PATH "${_shiboken_backup_CMAKE_FIND_ROOT_PATH}") + + # For Android platform sometimes the FindPython module returns Python_SOABI as empty in + # certain scenarios eg: armv7a target, macOS host etc. This is because + # it is unable to set Python_CONFIG i.e. `python3-config` script + # This workaround sets the Python_SOABI manually for this Android platform. + # This needs to be updated manually if the Python version for Android cross compilation + # changes. + # TODO: Find a better way to set Python_SOABI for Android platform + if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND NOT Python_SOABI) + set(Python_SOABI "cpython-311") + endif() else() find_package( Python diff --git a/sources/shiboken6/doc/shibokenmodule.rst b/sources/shiboken6/doc/shibokenmodule.rst index 2f1c6d166..3bc4fa6ba 100644 --- a/sources/shiboken6/doc/shibokenmodule.rst +++ b/sources/shiboken6/doc/shibokenmodule.rst @@ -125,6 +125,11 @@ To import the module: Dumps the map of wrappers existing in libshiboken to standard error. +.. function:: dumpConverters() + + Dumps the map of named converters existing in libshiboken to standard + error. + .. py:class:: VoidPtr(address, size = -1, writeable = 0) :param address: (PyBuffer, SbkObject, int, VoidPtr) diff --git a/sources/shiboken6/doc/typediscovery.rst b/sources/shiboken6/doc/typediscovery.rst new file mode 100644 index 000000000..76d3adf7b --- /dev/null +++ b/sources/shiboken6/doc/typediscovery.rst @@ -0,0 +1,145 @@ +.. _typediscovery: + +************** +Type Discovery +************** + +When converting objects which are part of a class hierarchy from a pointer to a +base class, it is expected to get the Python type of the actual, most derived +type, as opposed to C++ which requires a cast for this: + +.. code-block:: python + + def event(self, event): + if event.type() == QEvent.Type.MousePress: + self.do_things(event.position()) + ... + + +.. code-block:: c++ + + bool event(QEvent *event) override + { + if (event->type() == QEvent::MousePress) { + auto *mouseEvent = static_cast<QMouseEvent *>(event); + doThings(mouseEvent->position()); + ... + } + +The process of determining the type of the event is called `type discovery`. + +Shiboken generates code to automatically detect the type. First, it tries to +find a converter for the name obtained by ``typeid(*pointer).name()``. This +should normally work as this name is registered by the binding. If that fails, +it starts walking a type inheritance graph built up in libshiboken to find the +most derived class by using a cast function (``dynamic_cast<>`` by default) to +check. + +For normal class hierarchies with virtual destructors, no special handling +is required since ``typeid()`` usually detects the proper class name. + +Multiple inheritance +==================== + +In case of multiple inheritance in C++, the conversion to the derived class is +not done in case it is not a single-line direct inheritance. For example, in +Qt, the class ``QWidget`` inherits both ``QObject`` (base of the ``QObject`` +hierarchy) and ``QPaintDevice``. + +When calling a function returning a ``QPaintDevice *``, for example +``QPainter.device()``, a Python type representing ``QPaintDevice`` is returned +instead of the underlying widget type. This restriction exists because the +underlying pointer in C++ is a pointer to a ``QPaintDevice *`` and differs from +the pointer to the ``QWidget``. + +Hierarchies of classes with non-virtual destructors +=================================================== + +There are some hierarchies of value-ish C++ classes that do not have virtual +destructors. This makes type discovery based on ``typeid()`` and +``dynamic_cast<>`` impossible. + +Examples in Qt are the ``QStyleOption``-derived or the ``QGradient`` +-derived classes. + +For such classes, some attributes need to be specified on the type entries: + +Primarily, a :ref:`polymorphic-id-expression` attribute +must be specified to be used as a check replacing ``dynamic_cast<>``. + +In addition, a :ref:`polymorphic-name-function` attribute can be specified. +This replaces the type name guess obtained by ``typeid()`` and is mainly a hint +to speed things up by skipping the checks for each type in the inheritance +graph. + +A :ref:`polymorphic-base` attribute identifies the base class of a hierarchy. +It should be given in case the base class inherits from another class to +prevent the logic from going below the base class. + +Using type discovery attributes for class hierarchies with virtual destructors +============================================================================== + +It is possible to use :ref:`polymorphic-id-expression` and +:ref:`polymorphic-name-function` for normal class hierarchies with virtual +destructors as well since they basically replace ``typeid()`` and +``dynamic_cast<>``. This makes sense if expressions can be specified that are +faster than the checks on virtual tables. + +Specifying :ref:`polymorphic-base` can also make sense for generating special +cast functions in case of multiple inheritance. For example, in Qt, +``QWindow``, ``QLayout``, ``QWidget`` are base classes of hierarchies. Since +they all inherit from ``QObject``, indicating the base classes prevents +the logic from using ``QObject`` as a base class. + +.. _typediscovery-attributes: + +Type discovery attributes reference +=================================== + +The following attributes related to type discovery may be be specified on the +:ref:`object-type` or :ref:`value-type` elements: + +.. _polymorphic-id-expression: + +polymorphic-id-expression ++++++++++++++++++++++++++ + +The **polymorphic-id-expression** attribute specifies an expression checking +whether a base class pointer is of the matching type. For example, in a +``virtual eventHandler(BaseEvent *e)`` function, this is used to construct a +Python wrapper matching the derived class (for example, a ``MouseEvent`` or +similar). The attribute value may contain placeholders: + +%1 + Fully qualified class name + +%B + Fully qualified name of the base class (found by base class + search or as indicated by **polymorphic-base**). + +To check for a class inheriting ``BaseEvent``, specify: + +.. code-block:: xml + + <object-type name="MouseEvent" + polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> + +.. _polymorphic-name-function: + +polymorphic-name-function ++++++++++++++++++++++++++ + +The **polymorphic-name-function** attribute specifies the name of a function +returning the type name of a derived class on the base class type entry. +Normally, ``typeid(ptr).name()`` is used for this. + +The function is expected to return ``const char *``. + +.. _polymorphic-base: + +polymorphic-base +++++++++++++++++ + +The boolean **polymorphic-base** attribute indicates whether the class is the +base class of a class hierarchy. It is used for the *%B* placeholder in +**polymorphic-id-expression** and for cast operations in multiple inheritance. diff --git a/sources/shiboken6/doc/typesystem.rst b/sources/shiboken6/doc/typesystem.rst index e1e4fdda2..26f929801 100644 --- a/sources/shiboken6/doc/typesystem.rst +++ b/sources/shiboken6/doc/typesystem.rst @@ -65,3 +65,4 @@ Extra options and Python caveats typesystem_solving_compilation.rst typesystem_specialfunctions.rst + typediscovery.rst diff --git a/sources/shiboken6/doc/typesystem_converters.rst b/sources/shiboken6/doc/typesystem_converters.rst index 7bdabc49c..ab6fba930 100644 --- a/sources/shiboken6/doc/typesystem_converters.rst +++ b/sources/shiboken6/doc/typesystem_converters.rst @@ -233,61 +233,3 @@ Variables & Functions **%CHECKTYPE[CPPTYPE]** Replaced by a |project| type checking function for a Python variable. The C++ type is indicated by ``CPPTYPE``. - - -.. _oldconverters: - -Converting The Old Converters -============================= - -If you use |project| for your bindings, and has defined some type conversions -using the ``Shiboken::Converter`` template, then you must update your converters -to the new scheme. - -Previously your conversion rules were declared in one line, like this: - - -.. code-block:: xml - - <primitive-type name="Complex" target-lang-api-name="PyComplex"> - <include file-name="complex.h" location="global"/> - <conversion-rule file="complex_conversions.h"/> - </primitive-type> - - -And implemented in a separate C++ file, like this: - - -.. code-block:: c++ - - namespace Shiboken { - template<> struct Converter<Complex> - { - static inline bool checkType(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline bool isConvertible(PyObject* pyObj) { - return PyComplex_Check(pyObj); - } - static inline PyObject* toPython(void* cppobj) { - return toPython(*reinterpret_cast<Complex*>(cppobj)); - } - static inline PyObject* toPython(const Complex& cpx) { - return PyComplex_FromDoubles(cpx.real(), cpx.imag()); - } - static inline Complex toCpp(PyObject* pyobj) { - double real = PyComplex_RealAsDouble(pyobj); - double imag = PyComplex_ImagAsDouble(pyobj); - return Complex(real, imag); - } - }; - } - - -In this case, the parts of the implementation that will be used in the new -conversion-rule are the ones in the two last method -``static inline PyObject* toPython(const Complex& cpx)`` and -``static inline Complex toCpp(PyObject* pyobj)``. The ``isConvertible`` method -is gone, and the ``checkType`` is now an attribute of the :ref:`add-conversion <add-conversion>` -tag. Refer back to the first example in this page and you will be able to -correlate the above template with the new scheme of conversion rule definition. diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 66e68ae2b..f65b79bb4 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -536,37 +536,8 @@ type system has this attribute set, the heuristics will be applied to all classes. In shiboken 7, it will be mandatory to set the attribute. -The *optional* **polymorphic-id-expression** attribute specifies an -expression checking whether a base class pointer is of the matching -type. For example, in a ``virtual eventHandler(BaseEvent *e)`` -function, this is used to construct a Python wrapper matching -the derived class (for example, a ``MouseEvent`` or similar). -The attribute value may contain placeholders: - -%1 - Fully qualified class name - -%B - Fully qualified name of the base class (found by base class - search or as indicated by **polymorphic-base**). - -To check for a class inheriting ``BaseEvent``, specify: - -.. code-block:: xml - - <object-type name="MouseEvent" - polymorphic-id-expression="%B->type() == BaseEvent::MouseEvent"/> - -The *optional* **polymorphic-name-function** specifies the name of a -function returning the type name of a derived class on the base class -type entry. Normally, ``typeid(ptr).name()`` is used for this. -However, this fails if the type hierarchy does not have virtual functions. -In this case, a function is required which typically decides depending -on some type enumeration. - -The *optional* **polymorphic-base** attribute indicates -whether the class is the base class of a class hierarchy -(used for the *%B* placeholder in **polymorphic-id-expression**). +For the *optional* **polymorphic-id-expression**, **polymorphic-name-function** +and **polymorphic-base** attributes, see :ref:`typediscovery-attributes`. interface-type ^^^^^^^^^^^^^^ diff --git a/sources/shiboken6/generator/generator.cpp b/sources/shiboken6/generator/generator.cpp index b224858c5..a01326530 100644 --- a/sources/shiboken6/generator/generator.cpp +++ b/sources/shiboken6/generator/generator.cpp @@ -231,10 +231,9 @@ QString Generator::getFileNameBaseForSmartPointer(const AbstractMetaType &smartP const AbstractMetaType innerType = smartPointerType.getSmartPointerInnerType(); smartPointerType.typeEntry()->qualifiedCppName(); QString fileName = smartPointerType.typeEntry()->qualifiedCppName().toLower(); - fileName.replace(u"::"_s, u"_"_s); - fileName.append(u"_"_s); + fileName.append(u'_'); fileName.append(innerType.name().toLower()); - + fileName.replace(u"::"_s, u"_"_s); // std::shared_ptr<std::string> return fileName; } diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 84d3c93d3..a109cd17a 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -1337,7 +1337,8 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s, s << "if (" << PYTHON_RETURN_VAR << ".isNull()) {\n" << indent << "// An error happened in python code!\n" - << "Shiboken::Errors::storeErrorOrPrint();\n" + << "Shiboken::Errors::storePythonOverrideErrorOrPrint(\"" + << func->ownerClass()->name() << "\", funcName);\n" << returnStatement.statement << "\n" << outdent << "}\n"; @@ -1590,6 +1591,33 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta s << '\n'; } +static void writePointerToPythonConverter(TextStream &c, + const AbstractMetaClassCPtr &metaClass, + const QString &typeName, + const QString &cpythonType) +{ + c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" + << "if (pyOut) {\n" << indent + << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent + << "}\n"; + + const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction(); + if (nameFunc.isEmpty() && !metaClass->hasVirtualDestructor()) { + c << "return Shiboken::Object::newObjectWithHeuristics(" + << cpythonType << ", const_cast<void *>(cppIn), false);\n"; + return; + } + + c << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); +const char *typeName = )"; + if (nameFunc.isEmpty()) + c << "typeid(*tCppIn).name();\n"; + else + c << nameFunc << "(tCppIn);\n"; + c << "return Shiboken::Object::newObjectForPointer(" + << cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n"; +} + void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass, const GeneratorContext &classContext) const { @@ -1635,26 +1663,7 @@ void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClas c << "return PySide::getWrapperForQObject(reinterpret_cast<" << typeName << " *>(const_cast<void *>(cppIn)), " << cpythonType << ");\n"; } else { - c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" - << "if (pyOut) {\n" << indent - << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent - << "}\n" - << "bool exactType = false;\n" - << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); -const char *typeName = )"; - - const QString nameFunc = metaClass->typeEntry()->polymorphicNameFunction(); - if (nameFunc.isEmpty()) - c << "typeid(*tCppIn).name();\n"; - else - c << nameFunc << "(tCppIn);\n"; - c << R"(auto *sbkType = Shiboken::ObjectType::typeForTypeName(typeName); -if (sbkType != nullptr && Shiboken::ObjectType::hasSpecialCastFunction(sbkType)) - exactType = true; -)" - << "PyObject *result = Shiboken::Object::newObject(" << cpythonType - << R"(, const_cast<void *>(cppIn), false, exactType, typeName); -return result;)"; + writePointerToPythonConverter(c, metaClass, typeName, cpythonType); } std::swap(targetTypeName, sourceTypeName); writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName); @@ -5096,6 +5105,17 @@ QList<PyMethodDefEntry> return result; } +QString CppGenerator::pythonSignature(const AbstractMetaType &type) const +{ + if (type.isSmartPointer() && !type.instantiations().isEmpty()) { + const auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(type.typeEntry()); + const auto instantiationTe = type.instantiations().constFirst().typeEntry(); + if (auto opt = api().findSmartPointerInstantiation(ste, instantiationTe)) + return opt->specialized->typeEntry()->qualifiedTargetLangName(); + } + return type.pythonSignature(); +} + // Format the type signature of a function parameter QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const { @@ -5107,17 +5127,22 @@ QString CppGenerator::signatureParameter(const AbstractMetaArgument &arg) const metaType = *viewOn; s << arg.name() << ':'; - QStringList signatures(metaType.pythonSignature()); + QStringList signatures(pythonSignature(metaType)); // Implicit conversions (C++): Check for converting constructors // "QColor(Qt::GlobalColor)" or conversion operators const AbstractMetaFunctionCList conversions = api().implicitConversions(metaType); for (const auto &f : conversions) { - if (f->isConstructor() && !f->arguments().isEmpty()) - signatures << f->arguments().constFirst().type().pythonSignature(); - else if (f->isConversionOperator()) + if (f->isConstructor() && !f->arguments().isEmpty()) { + // PYSIDE-2712: modified types from converting constructors are not always correct + // candidates if they are modified by the type system reference + if (!f->arguments().constFirst().isTypeModified()) { + signatures << pythonSignature(f->arguments().constFirst().type()); + } + } else if (f->isConversionOperator()) { signatures << f->ownerClass()->fullName(); + } } const qsizetype size = signatures.size(); @@ -5174,7 +5199,7 @@ void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloa QString returnType = f->pyiTypeReplaced(0); // pyi or modified type if (returnType.isEmpty() && !f->isVoid()) - returnType = f->type().pythonSignature(); + returnType = pythonSignature(f->type()); if (!returnType.isEmpty()) s << "->" << returnType; @@ -5826,7 +5851,7 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, } else if (metaClass->isPolymorphic()) { const auto &ancestors = metaClass->allTypeSystemAncestors(); for (const auto &ancestor : ancestors) { - if (ancestor->baseClass()) + if (ancestor->baseClass() && !ancestor->typeEntry()->isPolymorphicBase()) continue; if (ancestor->isPolymorphic()) { s << "if (instanceType == Shiboken::SbkType< " << m_gsp @@ -6046,18 +6071,14 @@ void CppGenerator::writeInitFunc(TextStream &declStr, TextStream &callStr, } else if (hasParent) { const QString &enclosingName = enclosingEntry->name(); const auto parts = QStringView{enclosingName}.split(u"::", Qt::SkipEmptyParts); + const QString namePathPrefix = enclosingEntry->name().replace("::"_L1, "."_L1); callStr << "Shiboken::Module::AddTypeCreationFunction(" - << "module, \"" << pythonName << "\", " << functionName << ", \""; - for (qsizetype i = 0; i < parts.size(); ++i) { - if (i > 0) - callStr << "\", \""; - callStr << parts.at(i); - } - callStr << "\");\n"; + << "module, \"" << parts[0] << "\", " + << functionName << ", \"" << namePathPrefix << '.' << pythonName << "\");\n"; } else { callStr << "Shiboken::Module::AddTypeCreationFunction(" << "module, \"" << pythonName << "\", " - << "init_" << initFunctionName << ");\n"; + << functionName << ");\n"; } } @@ -6133,13 +6154,11 @@ bool CppGenerator::finishGeneration() for (const auto &smp : api().instantiatedSmartPointers()) { GeneratorContext context = contextForSmartPointer(smp.specialized, smp.type); const auto enclosingClass = context.metaClass()->enclosingClass(); - auto enclosingTypeEntry = enclosingClass - ? enclosingClass->typeEntry() - : targetLangEnclosingEntry(smp.type.typeEntry()); + auto enclosingTypeEntry = targetLangEnclosingEntry(smp.specialized->typeEntry()); writeInitFunc(s_classInitDecl, s_classPythonDefines, getInitFunctionName(context), - enclosingTypeEntry, smp.type.name()); + enclosingTypeEntry, smp.specialized->name()); includes.insert(smp.type.instantiations().constFirst().typeEntry()->include()); } @@ -6403,9 +6422,14 @@ bool CppGenerator::finishGeneration() s << "{nullptr, nullptr}\n" << outdent << "};\n" << "// The new global structure consisting of (type, name) pairs.\n" - << cppApiVariableName() << " = cppApi;\n" - << "// The backward compatible alias with upper case indexes.\n" - << cppApiVariableNameOld() << " = reinterpret_cast<PyTypeObject **>(cppApi);\n\n"; + << cppApiVariableName() << " = cppApi;\n"; + if (usePySideExtensions()) + s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n"; + s << "// The backward compatible alias with upper case indexes.\n" + << cppApiVariableNameOld() << " = reinterpret_cast<PyTypeObject **>(cppApi);\n"; + if (usePySideExtensions()) + s << "QT_WARNING_POP\n"; + s << '\n'; } s << "// Create an array of primitive type converters for the current module.\n" diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index a31c2ca14..f369ab01b 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -413,6 +413,7 @@ private: void writeSignatureInfo(TextStream &s, const OverloadData &overloads) const; QString signatureParameter(const AbstractMetaArgument &arg) const; + QString pythonSignature(const AbstractMetaType &type) const; /// Writes the implementation of all methods part of python sequence protocol void writeSequenceMethods(TextStream &s, const AbstractMetaClassCPtr &metaClass, @@ -550,9 +551,6 @@ private: static bool hasBoolCast(const AbstractMetaClassCPtr &metaClass) { return boolCast(metaClass).has_value(); } - std::optional<AbstractMetaType> - findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, - const TypeEntryCPtr &pointee) const; void clearTpFuncs(); static QString chopType(QString s); diff --git a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp index 1b893640a..a00c2ba50 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp @@ -86,18 +86,6 @@ static ComparisonOperatorList smartPointeeComparisons(const GeneratorContext &co return result; } -std::optional<AbstractMetaType> - CppGenerator::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, - const TypeEntryCPtr &pointee) const -{ - for (const auto &smp : api().instantiatedSmartPointers()) { - const auto &i = smp.type; - if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) - return i; - } - return {}; -} - static bool hasParameterPredicate(const AbstractMetaFunctionCPtr &f) { return !f->arguments().isEmpty(); @@ -252,8 +240,8 @@ void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s, for (const auto &base : baseClasses) { auto baseTe = base->typeEntry(); if (smartPointerTypeEntry->matchesInstantiation(baseTe)) { - if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { - const auto smartTargetType = opt.value(); + if (auto opt = api().findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto &smartTargetType = opt.value().type; s << "// SmartPointer derived class: " << smartTargetType.cppSignature() << "\n"; writePythonToCppConversionFunctions(s, smartPointerType, @@ -308,8 +296,8 @@ void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s, for (const auto &base : classes) { auto baseTe = base->typeEntry(); - if (auto opt = findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { - const auto smartTargetType = opt.value(); + if (auto opt = api().findSmartPointerInstantiation(smartPointerTypeEntry, baseTe)) { + const auto &smartTargetType = opt.value().type; s << "// Convert to SmartPointer derived class: [" << smartTargetType.cppSignature() << "]\n"; const QString converter = u"Shiboken::Conversions::getConverter(\""_s diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index 1f574b47c..35d0d114f 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -706,8 +706,12 @@ bool HeaderGenerator::finishGeneration() macrosStream << ti; macrosStream << "};\n\n"; + // FIXME: Remove backwards compatible variable in PySide 7. macrosStream << "// This variable stores all Python types exported by this module.\n"; macrosStream << "extern Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << ";\n\n"; + macrosStream << "// This variable stores all Python types exported by this module "; + macrosStream << "in a backwards compatible way with identical indexing.\n"; + macrosStream << "[[deprecated]] extern PyTypeObject **" << cppApiVariableNameOld() << ";\n\n"; macrosStream << "// This variable stores the Python module object exported by this module.\n"; macrosStream << "extern PyObject *" << pythonModuleObjectName() << ";\n\n"; macrosStream << "// This variable stores all type converters exported by this module.\n"; diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp index a1417e5d9..67fd9c994 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -2555,7 +2555,8 @@ void ShibokenGenerator::collectFullTypeNamesArray(QStringList &typeNames) int smartPointerCountIndex = getMaxTypeIndex(); for (const auto &smp : api().instantiatedSmartPointers()) { auto entry = smp.type.typeEntry(); - typeNames[smartPointerCountIndex] = entry->qualifiedTargetLangName(); + typeNames[smartPointerCountIndex] = + smp.specialized->typeEntry()->qualifiedTargetLangName(); ++smartPointerCountIndex; } } diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index 1ac65c00c..0ce80d0c6 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -27,7 +27,9 @@ #include "signature_p.h" #include "voidptr.h" +#include <string> #include <iostream> +#include <sstream> #if defined(__APPLE__) #include <dlfcn.h> @@ -197,31 +199,6 @@ static PyGetSetDef SbkObjectType_tp_getset[] = { static PyTypeObject *createObjectTypeType() { - // PYSIDE-2676: When using the new type extension, we need to use an - // extra meta type that provides the extra size. - // This is a hairy part of Python 3.12 . - // - // The problem here is that we use the type extension both in types - // and also in meta types. This was invisible with extender dicts. - // Please study carefully: - // https://docs.python.org/3/c-api/type.html#c.PyType_Spec.basicsize - - PyType_Slot SbkObjectTypeMeta_Type_slots[] = { - {Py_tp_base, static_cast<void *>(&PyType_Type)}, - {Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)}, - {0, nullptr} - }; - - PyType_Spec SbkObjectTypeMeta_Type_spec = { - "1:Shiboken.ObjectTypeMeta", - -long(sizeof(SbkObjectTypePrivate)), - 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, - SbkObjectTypeMeta_Type_slots, - }; - - auto specMeta = &SbkObjectTypeMeta_Type_spec; - PyType_Slot SbkObjectType_Type_slots[] = { {Py_tp_dealloc, reinterpret_cast<void *>(SbkObjectType_tp_dealloc)}, {Py_tp_getattro, reinterpret_cast<void *>(mangled_type_getattro)}, @@ -259,14 +236,9 @@ static PyTypeObject *createObjectTypeType() SbkObjectType_Type_slots, }; - if (_PepRuntimeVersion() >= 0x030C00) { - auto *meta = SbkType_FromSpec(specMeta); - auto spec = &SbkObjectType_Type_spec_312; - return SbkType_FromSpecWithMeta(spec, meta); - } - - auto spec = &SbkObjectType_Type_spec; - return SbkType_FromSpec(spec); + return SbkType_FromSpec(_PepRuntimeVersion() >= 0x030C00 ? + &SbkObjectType_Type_spec_312 : + &SbkObjectType_Type_spec); } PyTypeObject *SbkObjectType_TypeF(void) @@ -801,6 +773,19 @@ namespace Shiboken void _initMainThreadId(); // helper.cpp +static std::string msgFailedToInitializeType(const char *description) +{ + std::ostringstream stream; + stream << "[libshiboken] Failed to initialize " << description; + if (auto *error = PepErr_GetRaisedException()) { + if (auto *str = PyObject_Str(error)) + stream << ": " << Shiboken::String::toCString(str); + Py_DECREF(error); + } + stream << '.'; + return stream.str(); +} + namespace Conversions { void init(); } void init() @@ -816,11 +801,13 @@ void init() //Init private data Pep384_Init(); - if (PyType_Ready(SbkObjectType_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapperType metatype."); + auto *type = SbkObjectType_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapperType metatype").c_str()); - if (PyType_Ready(SbkObject_TypeF()) < 0) - Py_FatalError("[libshiboken] Failed to initialize Shiboken.BaseWrapper type."); + type = SbkObject_TypeF(); + if (type == nullptr || PyType_Ready(type) < 0) + Py_FatalError(msgFailedToInitializeType("Shiboken.BaseWrapper type").c_str()); VoidPtr::init(); @@ -1010,8 +997,7 @@ introduceWrapperType(PyObject *enclosingObject, PyObject *bases, unsigned wrapperFlags) { - const auto basesSize = PySequence_Fast_GET_SIZE(bases); - assert(basesSize > 0); + assert(PySequence_Fast_GET_SIZE(bases) > 0); typeSpec->slots[0].pfunc = PySequence_Fast_GET_ITEM(bases, 0); auto *type = SbkType_FromSpecBasesMeta(typeSpec, bases, SbkObjectType_TypeF()); @@ -1085,6 +1071,26 @@ bool hasSpecialCastFunction(PyTypeObject *sbkType) return d != nullptr && d->mi_specialcast != nullptr; } +// Find whether base is a direct single line base class of type +// (no multiple inheritance), that is, a C++ pointer cast can safely be done. +static bool isDirectAncestor(PyTypeObject *type, PyTypeObject *base) +{ + if (type == base) + return true; + if (PyTuple_Size(type->tp_bases) == 0) + return false; + auto *sbkObjectType = SbkObject_TypeF(); + auto *firstBase = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0)); + return firstBase != sbkObjectType + && PyType_IsSubtype(type, sbkObjectType) != 0 + && isDirectAncestor(firstBase, base); +} + +bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType) +{ + return isDirectAncestor(targetType, baseType); +} + } // namespace ObjectType @@ -1454,25 +1460,67 @@ SbkObject *findColocatedChild(SbkObject *wrapper, return nullptr; } +// Legacy, for compatibility only. PyObject *newObject(PyTypeObject *instanceType, void *cptr, bool hasOwnership, bool isExactType, const char *typeName) { - // Try to find the exact type of cptr. - if (!isExactType) { - if (PyTypeObject *exactType = ObjectType::typeForTypeName(typeName)) { - instanceType = exactType; - } else { - auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); - if (resolved.first != nullptr) { - instanceType = resolved.first; - cptr = resolved.second; - } + return isExactType + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristics(instanceType, cptr, hasOwnership, typeName); +} + +static PyObject *newObjectWithHeuristicsHelper(PyTypeObject *instanceType, + PyTypeObject *exactType, + void *cptr, + bool hasOwnership) +{ + // Try to find the exact type of cptr. For hierarchies with + // non-virtual destructors, typeid() will return the base name. + // Try type discovery in these cases. + if (exactType == nullptr || exactType == instanceType) { + auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); + if (resolved.first != nullptr + && Shiboken::ObjectType::canDowncastTo(instanceType, resolved.first)) { + exactType = resolved.first; + cptr = resolved.second; } } + return newObjectForType(exactType != nullptr ? exactType : instanceType, + cptr, hasOwnership); +} + +PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + // Try to find the exact type of cptr. + PyTypeObject *exactType = ObjectType::typeForTypeName(typeName); + // PYSIDE-868: In case of multiple inheritance, (for example, + // a function returning a QPaintDevice * from a QWidget *), + // use instance type to avoid pointer offset errors. + return exactType != nullptr && !Shiboken::ObjectType::canDowncastTo(instanceType, exactType) + ? newObjectForType(instanceType, cptr, hasOwnership) + : newObjectWithHeuristicsHelper(instanceType, exactType, cptr, hasOwnership); +} + + +PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership, + const char *typeName) +{ + return newObjectWithHeuristicsHelper(instanceType, + ObjectType::typeForTypeName(typeName), + cptr, hasOwnership); +} + +PyObject *newObjectForType(PyTypeObject *instanceType, void *cptr, bool hasOwnership) +{ bool shouldCreate = true; bool shouldRegister = true; SbkObject *self = nullptr; diff --git a/sources/shiboken6/libshiboken/basewrapper.h b/sources/shiboken6/libshiboken/basewrapper.h index 4835c4810..ec5545aea 100644 --- a/sources/shiboken6/libshiboken/basewrapper.h +++ b/sources/shiboken6/libshiboken/basewrapper.h @@ -265,6 +265,14 @@ LIBSHIBOKEN_API PyTypeObject *typeForTypeName(const char *typeName); * \since 5.12 */ LIBSHIBOKEN_API bool hasSpecialCastFunction(PyTypeObject *sbkType); + +/// Returns whether a C++ pointer of \p baseType can be safely downcast +/// to \p targetType (base is a direct, single line base class of targetType). +/// (is a direct, single-line inheritance) +/// \param baseType Python type of base class +/// \param targetType Python type of derived class +/// \since 6.8 +LIBSHIBOKEN_API bool canDowncastTo(PyTypeObject *baseType, PyTypeObject *targetType); } namespace Object { @@ -297,7 +305,8 @@ LIBSHIBOKEN_API SbkObject *findColocatedChild(SbkObject *wrapper, const PyTypeObject *instanceType); /** - * Bind a C++ object to Python. + * Bind a C++ object to Python. Forwards to + * newObjectWithHeuristics(), newObjectForType() depending on \p isExactType. * \param instanceType equivalent Python type for the C++ object. * \param hasOwnership if true, Python will try to delete the underlying C++ object when there's no more refs. * \param isExactType if false, Shiboken will use some heuristics to detect the correct Python type of this C++ @@ -311,6 +320,40 @@ LIBSHIBOKEN_API PyObject *newObject(PyTypeObject *instanceType, bool isExactType = false, const char *typeName = nullptr); +/// Bind a C++ object to Python for polymorphic pointers. Calls +/// newObjectWithHeuristics() with an additional check for multiple +/// inheritance, in which case it will fall back to instanceType. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectForPointer(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using some heuristics to detect the correct +/// Python type of this C++ object. In any case \p instanceType must be provided; +/// it'll be used as search starting point and as fallback. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying C++ object +/// C++ object when there are no more references. +/// when there's no more refs. +/// \param typeName If non-null, this will be used as helper to find the correct +/// Python type for this object (obtained by typeid().name(). +LIBSHIBOKEN_API PyObject *newObjectWithHeuristics(PyTypeObject *instanceType, + void *cptr, + bool hasOwnership = true, + const char *typeName = nullptr); + +/// Bind a C++ object to Python using the given type. +/// \param instanceType Equivalent Python type for the C++ object. +/// \param hasOwnership if true, Python will try to delete the underlying +/// C++ object when there are no more references. +LIBSHIBOKEN_API PyObject *newObjectForType(PyTypeObject *instanceType, + void *cptr, bool hasOwnership = true); + /** * Changes the valid flag of a PyObject, invalid objects will raise an exception when someone tries to access it. */ diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index 4b3759456..5310207a3 100644 --- a/sources/shiboken6/libshiboken/pep384impl.cpp +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -105,13 +105,13 @@ static PyType_Spec typeprobe_spec = { static void check_PyTypeObject_valid() { - auto *obtype = reinterpret_cast<PyObject *>(&PyType_Type); - auto *probe_tp_base = reinterpret_cast<PyTypeObject *>( - PyObject_GetAttr(obtype, Shiboken::PyMagicName::base())); + auto *typetype = &PyType_Type; + auto *obtype = reinterpret_cast<PyObject *>(typetype); + auto *probe_tp_base_obj = PyObject_GetAttr(obtype, Shiboken::PyMagicName::base()); + auto *probe_tp_base = reinterpret_cast<PyTypeObject *>(probe_tp_base_obj); auto *probe_tp_bases = PyObject_GetAttr(obtype, Shiboken::PyMagicName::bases()); - auto *check = reinterpret_cast<PyTypeObject *>( - PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases)); - auto *typetype = reinterpret_cast<PyTypeObject *>(obtype); + auto *checkObj = PyType_FromSpecWithBases(&typeprobe_spec, probe_tp_bases); + auto *check = reinterpret_cast<PyTypeObject *>(checkObj); PyObject *w = PyObject_GetAttr(obtype, Shiboken::PyMagicName::weakrefoffset()); long probe_tp_weakrefoffset = PyLong_AsLong(w); PyObject *d = PyObject_GetAttr(obtype, Shiboken::PyMagicName::dictoffset()); @@ -149,8 +149,8 @@ check_PyTypeObject_valid() || probe_tp_mro != typetype->tp_mro || Py_TPFLAGS_DEFAULT != (check->tp_flags & Py_TPFLAGS_DEFAULT)) Py_FatalError("The structure of type objects has changed!"); - Py_DECREF(check); - Py_DECREF(probe_tp_base); + Py_DECREF(checkObj); + Py_DECREF(probe_tp_base_obj); Py_DECREF(w); Py_DECREF(d); Py_DECREF(probe_tp_bases); @@ -482,6 +482,47 @@ Pep_GetVerboseFlag() } #endif // Py_LIMITED_API +// Support for pyerrors.h + +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +// Emulate PyErr_GetRaisedException() using the deprecated PyErr_Fetch()/PyErr_Store() +PyObject *PepErr_GetRaisedException() +{ + PyObject *type{}; + PyObject *value{}; + PyObject *traceback{}; + PyErr_Fetch(&type, &value, &traceback); + Py_XINCREF(value); + PyErr_Restore(type, value, traceback); + return value; +} + +struct PepException_HEAD +{ + PyObject_HEAD + PyObject *x1; // dict + PyObject *args; +}; + +// PyException_GetArgs/PyException_SetArgs were added to the stable API in 3.12 +PyObject *PepException_GetArgs(PyObject *ex) +{ + auto *h = reinterpret_cast<PepException_HEAD *>(ex); + Py_XINCREF(h->args); + return h->args; +} + +LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args) +{ + auto *h = reinterpret_cast<PepException_HEAD *>(ex); + Py_XINCREF(args); + auto *old = h->args; // Py_XSETREF() + h->args = args; + Py_XDECREF(old); + +} +#endif // Limited or < 3.12 + /***************************************************************************** * * Support for code.h @@ -722,11 +763,8 @@ PyTypeObject *PepStaticMethod_TypePtr = nullptr; static PyTypeObject * getStaticMethodType(void) { - // this works for Python 3, only - // "StaticMethodType = type(str.__dict__['maketrans'])\n"; static const char prog[] = - "from xxsubtype import spamlist\n" - "result = type(spamlist.__dict__['staticmeth'])\n"; + "result = type(str.__dict__['maketrans'])\n"; return reinterpret_cast<PyTypeObject *>(PepRun_GetResult(prog)); } @@ -1009,9 +1047,12 @@ long _PepRuntimeVersion() SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + // PYSIDE-2676: Use the meta type explicitly. + // A derived type would fail the offset calculation. + static auto *meta = SbkObjectType_TypeF(); assert(SbkObjectType_Check(type)); auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PyObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PyObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } @@ -1061,11 +1102,12 @@ static thread_local SbkObjectTypePrivate *SOTP_value{}; SbkObjectTypePrivate *PepType_SOTP(PyTypeObject *type) { + static auto *meta = SbkObjectType_TypeF(); static bool use_312 = _PepRuntimeVersion() >= 0x030C00; assert(SbkObjectType_Check(type)); if (use_312) { auto *obType = reinterpret_cast<PyObject *>(type); - void *data = PepObject_GetTypeData(obType, Py_TYPE(obType)); + void *data = PepObject_GetTypeData(obType, meta); return reinterpret_cast<SbkObjectTypePrivate *>(data); } if (type == SOTP_key) diff --git a/sources/shiboken6/libshiboken/pep384impl.h b/sources/shiboken6/libshiboken/pep384impl.h index ec58aac81..7188366e2 100644 --- a/sources/shiboken6/libshiboken/pep384impl.h +++ b/sources/shiboken6/libshiboken/pep384impl.h @@ -188,6 +188,17 @@ LIBSHIBOKEN_API int Pep_GetFlag(const char *name); LIBSHIBOKEN_API int Pep_GetVerboseFlag(void); #endif +// pyerrors.h +#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000 +LIBSHIBOKEN_API PyObject *PepErr_GetRaisedException(); +LIBSHIBOKEN_API PyObject *PepException_GetArgs(PyObject *ex); +LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args); +#else +# define PepErr_GetRaisedException PyErr_GetRaisedException +# define PepException_GetArgs PyException_GetArgs +# define PepException_SetArgs PyException_SetArgs +#endif + /***************************************************************************** * * RESOLVED: unicodeobject.h diff --git a/sources/shiboken6/libshiboken/sbkcontainer.h b/sources/shiboken6/libshiboken/sbkcontainer.h index 240c772a9..8ad5aadc6 100644 --- a/sources/shiboken6/libshiboken/sbkcontainer.h +++ b/sources/shiboken6/libshiboken/sbkcontainer.h @@ -74,10 +74,9 @@ public: static PyObject *tpNewInvalid(PyTypeObject * /* subtype */, PyObject * /* args */, PyObject * /* kwds */) { - PyErr_Format(PyExc_NotImplementedError, + return PyErr_Format(PyExc_NotImplementedError, "Opaque containers of type '%s' cannot be instantiated.", typeid(SequenceContainer).name()); - return nullptr; } static int tpInit(PyObject * /* self */, PyObject * /* args */, PyObject * /* kwds */) @@ -105,10 +104,8 @@ public: static PyObject *sqGetItem(PyObject *self, Py_ssize_t i) { auto *d = get(self); - if (i < 0 || i >= Py_ssize_t(d->m_list->size())) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return nullptr; - } + if (i < 0 || i >= Py_ssize_t(d->m_list->size())) + return PyErr_Format(PyExc_IndexError, "index out of bounds"); auto it = std::cbegin(*d->m_list); std::advance(it, i); return ShibokenContainerValueConverter<value_type>::convertValueToPython(*it); @@ -133,14 +130,10 @@ public: static PyObject *push_back(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); if (!value.has_value()) @@ -152,14 +145,10 @@ public: static PyObject *push_front(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to append."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (!ShibokenContainerValueConverter<value_type>::checkValue(pyArg)) + return PyErr_Format(PyExc_TypeError, "wrong type passed to append."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); OptionalValue value = ShibokenContainerValueConverter<value_type>::convertValueToCpp(pyArg); if (!value.has_value()) @@ -171,10 +160,8 @@ public: static PyObject *clear(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->clear(); Py_RETURN_NONE; @@ -183,10 +170,8 @@ public: static PyObject *pop_back(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_back(); Py_RETURN_NONE; @@ -195,10 +180,8 @@ public: static PyObject *pop_front(PyObject *self) { auto *d = get(self); - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); d->m_list->pop_front(); Py_RETURN_NONE; @@ -208,21 +191,16 @@ public: static PyObject *reserve(PyObject *self, PyObject *pyArg) { auto *d = get(self); - if (PyLong_Check(pyArg) == 0) { - PyErr_SetString(PyExc_TypeError, "wrong type passed to reserve()."); - return nullptr; - } - if (d->m_const) { - PyErr_SetString(PyExc_TypeError, msgModifyConstContainer); - return nullptr; - } + if (PyLong_Check(pyArg) == 0) + return PyErr_Format(PyExc_TypeError, "wrong type passed to reserve()."); + if (d->m_const) + return PyErr_Format(PyExc_TypeError, msgModifyConstContainer); if constexpr (ShibokenContainerHasReserve<SequenceContainer>::value) { const Py_ssize_t size = PyLong_AsSsize_t(pyArg); d->m_list->reserve(size); } else { - PyErr_SetString(PyExc_TypeError, "Container does not support reserve()."); - return nullptr; + return PyErr_Format(PyExc_TypeError, "Container does not support reserve()."); } Py_RETURN_NONE; diff --git a/sources/shiboken6/libshiboken/sbkconverter.cpp b/sources/shiboken6/libshiboken/sbkconverter.cpp index 358827aa8..9ab674415 100644 --- a/sources/shiboken6/libshiboken/sbkconverter.cpp +++ b/sources/shiboken6/libshiboken/sbkconverter.cpp @@ -12,7 +12,12 @@ #include "voidptr.h" #include <string> +#include <cstring> +#include <iostream> #include <unordered_map> +#include <unordered_set> +#include <map> +#include <set> static SbkConverter **PrimitiveTypeConverters; @@ -72,6 +77,103 @@ void init() initArrayConverters(); } +static void dumpPyTypeObject(std::ostream &str, PyTypeObject *t) +{ + str << "\nPython type "; + if (t == nullptr) { + str << "<None>"; + return; + } + str << '"' << t->tp_name << '"'; + if (t->tp_base != nullptr && t->tp_base != &PyBaseObject_Type) + str << '(' << t->tp_base->tp_name << ')'; +} + +static void dumpSbkConverter(std::ostream &str, const SbkConverter *c) +{ + str << "SbkConverter " << static_cast<const void *>(c) << ": "; + if (c->pointerToPython != nullptr) + str << ", C++ pointer->Python"; + if (c->copyToPython != nullptr) + str << ", copy->Python"; + if (c->toCppPointerConversion.second != nullptr) + str << ", Python->C++ pointer"; + if (!c->toCppConversions.empty()) + str << ", " << c->toCppConversions.size() << " Python->C++ conversions"; +} + +// Less than operator for a PyTypeObject for dumping the converter map +static bool pyTypeObjectLessThan(const PyTypeObject *t1, const PyTypeObject *t2) +{ + const bool isNull1 = t1 == nullptr; + const bool isNull2 = t2 == nullptr; + if (isNull1 || isNull2) + return isNull1 && !isNull2; + // Internal types (lower case) first + const bool isInternal1 = std::islower(t1->tp_name[0]); + const bool isInternal2 = std::islower(t2->tp_name[0]); + if (isInternal1 != isInternal2) + return !isInternal2; + return std::strcmp(t1->tp_name, t2->tp_name) < 0; +} + +void dumpConverters() +{ + struct PyTypeObjectLess { + + bool operator()(const PyTypeObject *t1, const PyTypeObject *t2) const { + return pyTypeObjectLessThan(t1, t2); + } + }; + + using StringSet = std::set<std::string>; + using SbkConverterNamesMap = std::unordered_map<SbkConverter *, StringSet>; + using PyTypeObjectConverterMap = std::map<PyTypeObject *, SbkConverterNamesMap, + PyTypeObjectLess>; + + auto &str = std::cerr; + + // Sort the entries by the associated PyTypeObjects and converters + PyTypeObjectConverterMap pyTypeObjectConverterMap; + for (const auto &converter : converters) { + auto *sbkConverter = converter.second; + if (sbkConverter == nullptr) { + str << "Non-existent: \"" << converter.first << "\"\n"; + continue; + } + auto *typeObject = sbkConverter->pythonType; + auto typeIt = pyTypeObjectConverterMap.find(typeObject); + if (typeIt == pyTypeObjectConverterMap.end()) + typeIt = pyTypeObjectConverterMap.insert(std::make_pair(typeObject, + SbkConverterNamesMap{})).first; + SbkConverterNamesMap &sbkConverterMap = typeIt->second; + auto convIt = sbkConverterMap.find(sbkConverter); + if (convIt == sbkConverterMap.end()) + convIt = sbkConverterMap.insert(std::make_pair(sbkConverter, + StringSet{})).first; + convIt->second.insert(converter.first); + } + + for (const auto &tc : pyTypeObjectConverterMap) { + dumpPyTypeObject(str, tc.first); + str << ", " << tc.second.size() << " converter(s):\n"; + for (const auto &cn : tc.second) { + str << " "; + dumpSbkConverter(str, cn.first); + str << ", " << cn.second.size() << " alias(es):"; + int i = 0; + for (const auto &name : cn.second) { + if ((i++ % 5) == 0) + str << "\n "; + str << " \"" << name << '"'; + } + str << '\n'; + } + } + + str << '\n'; +} + SbkConverter *createConverterObject(PyTypeObject *type, PythonToCppFunc toCppPointerConvFunc, IsConvertibleToCppFunc toCppPointerCheckFunc, @@ -422,18 +524,36 @@ void registerConverterName(SbkConverter *converter, const char *typeName) converters.insert(std::make_pair(typeName, converter)); } -static std::string getRealTypeName(const char *name) +static std::string getRealTypeName(const std::string &typeName) { - std::string typeName(name); auto size = typeName.size(); if (std::isalnum(typeName[size - 1]) == 0) return typeName.substr(0, size - 1); return typeName; } -SbkConverter *getConverter(const char *typeName) +// PYSIDE-2404: Build a negative cache of already failed lookups. +// The resulting list must be reset after each new import, +// because that can change results. Also clear the cache after +// reaching some threashold. +static std::unordered_set<std::string> nonExistingTypeNames{}; + +// Arbitrary size limit to prevent random name overflows. +static constexpr std::size_t negativeCacheLimit = 50; + +static void rememberAsNonexistent(const std::string &typeName) +{ + if (nonExistingTypeNames.size() > negativeCacheLimit) + clearNegativeLazyCache(); + converters.insert(std::make_pair(typeName, nullptr)); + nonExistingTypeNames.insert(typeName); +} + +SbkConverter *getConverter(const char *typeNameC) { + std::string typeName = typeNameC; auto it = converters.find(typeName); + // PYSIDE-2404: This can also contain explicit nullptr as a negative cache. if (it != converters.end()) return it->second; // PYSIDE-2404: Did not find the name. Load the lazy classes @@ -442,6 +562,9 @@ SbkConverter *getConverter(const char *typeName) it = converters.find(typeName); if (it != converters.end()) return it->second; + // Cache the negative result. Don't forget to clear the cache for new modules. + rememberAsNonexistent(typeName); + if (Shiboken::pyVerbose() > 0) { const std::string message = std::string("Can't find type resolver for type '") + typeName + "'."; @@ -450,6 +573,15 @@ SbkConverter *getConverter(const char *typeName) return nullptr; } +void clearNegativeLazyCache() +{ + for (const auto &typeName : nonExistingTypeNames) { + auto it = converters.find(typeName); + converters.erase(it); + } + nonExistingTypeNames.clear(); +} + SbkConverter *primitiveTypeConverter(int index) { return PrimitiveTypeConverters[index]; @@ -704,14 +836,7 @@ PyTypeObject *getPythonTypeObject(const SbkConverter *converter) PyTypeObject *getPythonTypeObject(const char *typeName) { - auto *type = getPythonTypeObject(getConverter(typeName)); - if (type == nullptr) { - // PYSIDE-2404: Did not find the name. Load the lazy classes - // which have this name and try again. - Shiboken::Module::loadLazyClassesWithName(getRealTypeName(typeName).c_str()); - type = getPythonTypeObject(getConverter(typeName)); - } - return type; + return getPythonTypeObject(getConverter(typeName)); } bool pythonTypeIsValueType(const SbkConverter *converter) diff --git a/sources/shiboken6/libshiboken/sbkconverter_p.h b/sources/shiboken6/libshiboken/sbkconverter_p.h index c886c9b9f..08fc4c8e1 100644 --- a/sources/shiboken6/libshiboken/sbkconverter_p.h +++ b/sources/shiboken6/libshiboken/sbkconverter_p.h @@ -531,6 +531,12 @@ SbkConverter *createConverterObject(PyTypeObject *type, IsConvertibleToCppFunc toCppPointerCheckFunc, CppToPythonFunc pointerToPythonFunc, CppToPythonFunc copyToPythonFunc); + +LIBSHIBOKEN_API void dumpConverters(); + +/// Interface for sbkmodule which must reset cache when new module is loaded. +LIBSHIBOKEN_API void clearNegativeLazyCache(); + } // namespace Shiboken::Conversions #endif // SBK_CONVERTER_P_H diff --git a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp index 44e900f01..7637efa70 100644 --- a/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp +++ b/sources/shiboken6/libshiboken/sbkcpptonumpy.cpp @@ -49,17 +49,17 @@ PyObject *createByteArray1(Py_ssize_t, const uint8_t *) PyObject *createDoubleArray1(Py_ssize_t, const double *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createFloatArray1(Py_ssize_t, const float *) { - return Py_None; + Py_RETURN_NONE; } PyObject *createIntArray1(Py_ssize_t, const int *) { - return Py_None; + Py_RETURN_NONE; } #endif // !HAVE_NUMPY diff --git a/sources/shiboken6/libshiboken/sbkerrors.cpp b/sources/shiboken6/libshiboken/sbkerrors.cpp index 1832624d5..84c080f8d 100644 --- a/sources/shiboken6/libshiboken/sbkerrors.cpp +++ b/sources/shiboken6/libshiboken/sbkerrors.cpp @@ -6,6 +6,11 @@ #include "helper.h" #include "gilstate.h" +#include <cstdio> +#include <string> + +using namespace std::literals::string_literals; + namespace Shiboken { @@ -93,6 +98,21 @@ void setWrongContainerType() PyErr_SetString(PyExc_TypeError, "Wrong type passed to container conversion."); } +// Prepend something to an exception message provided it is a single string +// argument. +static bool prependToExceptionMessage(PyObject *exc, const char *context) +{ + Shiboken::AutoDecRef args(PepException_GetArgs(exc)); + if (args.isNull() || PyTuple_Check(args.object()) == 0 || PyTuple_Size(args) != 1) + return false; + auto *oldMessage = PyTuple_GetItem(args, 0); + if (oldMessage == nullptr || PyUnicode_CheckExact(oldMessage) == 0) + return false; + auto *newMessage = PyUnicode_FromFormat("%s%U", context, oldMessage); + PepException_SetArgs(exc, PyTuple_Pack(1, newMessage)); + return true; +} + struct ErrorStore { PyObject *type; PyObject *exc; @@ -101,17 +121,42 @@ struct ErrorStore { static thread_local ErrorStore savedError{}; +static bool hasPythonContext() +{ + return _pythonContextStack & 1; +} + void storeErrorOrPrint() { // This error happened in a function with no way to return an error state. // Therefore, we handle the error when we are error checking, anyway. // But we do that only when we know that an error handler can pick it up. - if (_pythonContextStack & 1) + if (hasPythonContext()) PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback); else PyErr_Print(); } +// Like storeErrorOrPrint() with additional context info that is prepended +// to the exception message or printed. +static void storeErrorOrPrintWithContext(const char *context) +{ + if (hasPythonContext()) { + PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback); + prependToExceptionMessage(savedError.exc, context); + } else { + std::fputs(context, stderr); + PyErr_Print(); + } +} + +void storePythonOverrideErrorOrPrint(const char *className, const char *funcName) +{ + const std::string context = "Error calling Python override of "s + + className + "::"s + funcName + "(): "s; + storeErrorOrPrintWithContext(context.c_str()); +} + PyObject *occurred() { if (savedError.type) { diff --git a/sources/shiboken6/libshiboken/sbkerrors.h b/sources/shiboken6/libshiboken/sbkerrors.h index 6ff85f8e1..18ce701e7 100644 --- a/sources/shiboken6/libshiboken/sbkerrors.h +++ b/sources/shiboken6/libshiboken/sbkerrors.h @@ -50,6 +50,11 @@ LIBSHIBOKEN_API void setWrongContainerType(); /// This replaces `PyErr_Print`, which cannot report errors as exception. /// To be used in contexts where raising errors is impossible. LIBSHIBOKEN_API void storeErrorOrPrint(); + +/// Call storeErrorOrPrint() and print the context to report +/// errors when calling Python overrides of virtual functions. +LIBSHIBOKEN_API void storePythonOverrideErrorOrPrint(const char *className, const char *funcName); + /// Handle an error as in PyErr_Occurred(), but also check for errors which /// were captured by `storeErrorOrPrint`. /// To be used in normal error checks. diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp index 4153df27f..76087fbb5 100644 --- a/sources/shiboken6/libshiboken/sbkmodule.cpp +++ b/sources/shiboken6/libshiboken/sbkmodule.cpp @@ -7,21 +7,27 @@ #include "bindingmanager.h" #include "sbkstring.h" #include "sbkcppstring.h" +#include "sbkconverter_p.h" #include <unordered_map> #include <unordered_set> +#include <vector> #include <cstring> +/// This hash maps module objects to arrays of converters. +using ModuleConvertersMap = std::unordered_map<PyObject *, SbkConverter **> ; + /// This hash maps module objects to arrays of Python types. using ModuleTypesMap = std::unordered_map<PyObject *, Shiboken::Module::TypeInitStruct *> ; -/// This hash maps module objects to arrays of converters. -using ModuleConvertersMap = std::unordered_map<PyObject *, SbkConverter **>; +struct TypeCreationStruct +{ + Shiboken::Module::TypeCreationFunction func; + std::vector<std::string> subtypeNames; +}; -/// This hash maps type names to type creation functions. -using TypeCreationFunctionModulePair = - std::pair<Shiboken::Module::TypeCreationFunction, PyObject *>; -using NameToTypeFunctionMap = std::unordered_map<std::string, TypeCreationFunctionModulePair>; +/// This hash maps type names to type creation structs. +using NameToTypeFunctionMap = std::unordered_map<std::string, TypeCreationStruct> ; /// This hash maps module objects to maps of names to functions. using ModuleToFuncsMap = std::unordered_map<PyObject *, NameToTypeFunctionMap> ; @@ -56,8 +62,8 @@ LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) AutoDecRef modName(String::fromCppStringView(names.substr(0, dotPos))); auto *modOrType = PyDict_GetItem(sysModules, modName); if (modOrType == nullptr) { - PyErr_Format(PyExc_SystemError, "Module %s should already be in sys.modules", - PyModule_GetName(modOrType)); + PyErr_Format(PyExc_SystemError, "Module \"%U\" should already be in sys.modules", + modName.object()); return nullptr; } @@ -74,6 +80,39 @@ LIBSHIBOKEN_API PyTypeObject *get(TypeInitStruct &typeStruct) return typeStruct.type; } +static void incarnateHelper(PyObject *module, const std::string_view names, + const NameToTypeFunctionMap &nameToFunc) +{ + auto dotPos = names.find('.'); + std::string::size_type startPos = 0; + auto *modOrType{module}; + while (dotPos != std::string::npos) { + auto typeName = names.substr(startPos, dotPos - startPos); + AutoDecRef obTypeName(String::fromCppStringView(typeName)); + modOrType = PyObject_GetAttr(modOrType, obTypeName); + startPos = dotPos + 1; + dotPos = names.find('.', startPos); + } + // now we have the type to create. + auto funcIter = nameToFunc.find(std::string(names)); + // - call this function that returns a PyTypeObject + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + PyTypeObject *type = initFunc(modOrType); + auto name = names.substr(startPos); + PyObject_SetAttrString(modOrType, name.data(), reinterpret_cast<PyObject *>(type)); +} + +static void incarnateSubtypes(PyObject *module, + const std::vector<std::string> &nameList, + NameToTypeFunctionMap &nameToFunc) +{ + for (auto const & tableIter : nameList) { + std::string_view names(tableIter); + incarnateHelper(module, names, nameToFunc); + } +} + static PyTypeObject *incarnateType(PyObject *module, const char *name, NameToTypeFunctionMap &nameToFunc) { @@ -85,13 +124,15 @@ static PyTypeObject *incarnateType(PyObject *module, const char *name, return nullptr; } // - call this function that returns a PyTypeObject - auto pair = funcIter->second; - auto initFunc = pair.first; - auto *modOrType = pair.second; + auto tcStruct = funcIter->second; + auto initFunc = tcStruct.func; + auto *modOrType{module}; // PYSIDE-2404: Make sure that no switching happens during type creation. auto saveFeature = initSelectableFeature(nullptr); PyTypeObject *type = initFunc(modOrType); + if (!tcStruct.subtypeNames.empty()) + incarnateSubtypes(module, tcStruct.subtypeNames, nameToFunc); initSelectableFeature(saveFeature); // - assign this object to the name in the module @@ -164,7 +205,7 @@ static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name) // - locate the name and retrieve the generating function const char *attrNameStr = Shiboken::String::toCString(name); auto &nameToFunc = tableIter->second; - // - create the real type (incarnateType checks this) + // - create the real type and handle subtypes auto *type = incarnateType(module, attrNameStr, nameToFunc); auto *ret = reinterpret_cast<PyObject *>(type); // - if attribute does really not exist use the original @@ -172,7 +213,6 @@ static PyObject *PyModule_lazyGetAttro(PyObject *module, PyObject *name) PyErr_Clear(); return origModuleGetattro(module, name); } - return ret; } @@ -292,25 +332,12 @@ static bool shouldLazyLoad(PyObject *module) return std::strncmp(modName, "PySide6.", 8) == 0; } -void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func) +void checkIfShouldLoadImmediately(PyObject *module, const std::string &name, + const NameToTypeFunctionMap &nameToFunc) { static const char *flag = getenv("PYSIDE6_OPTION_LAZY"); static const int value = flag != nullptr ? std::atoi(flag) : 1; - // - locate the module in the moduleTofuncs mapping - auto tableIter = moduleToFuncs.find(module); - assert(tableIter != moduleToFuncs.end()); - // - Assign the name/generating function pair. - auto &nameToFunc = tableIter->second; - TypeCreationFunctionModulePair pair{func, module}; - auto nit = nameToFunc.find(name); - if (nit == nameToFunc.end()) - nameToFunc.insert(std::make_pair(name, pair)); - else - nit->second = pair; - // PYSIDE-2404: Lazy Loading // // Options: @@ -319,56 +346,56 @@ void AddTypeCreationFunction(PyObject *module, // 3 - lazy loading for any module. // // By default we lazy load all known modules (option = 1). - if (value == 0 // completely disabled || canNotLazyLoad(module) // for some reason we cannot lazy load || (value == 1 && !shouldLazyLoad(module)) // not a known module ) { - PyTypeObject *type = func(module); - PyModule_AddObject(module, name, reinterpret_cast<PyObject *>(type)); // steals reference + incarnateHelper(module, name, nameToFunc); } } void AddTypeCreationFunction(PyObject *module, const char *name, - TypeCreationFunction func, - const char *containerName) + TypeCreationFunction func) { - // This version could be delayed as well, but for the few cases - // we simply fetch the container type and insert directly. - AutoDecRef obContainerType(PyObject_GetAttrString(module, containerName)); - PyTypeObject *type = func(obContainerType); - PyObject_SetAttrString(obContainerType, name, reinterpret_cast<PyObject *>(type)); // steals reference -} + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + TypeCreationStruct tcStruct{func, {}}; + auto nit = nameToFunc.find(name); + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(name, tcStruct)); + else + nit->second = tcStruct; -void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func, - const char *outerContainerName, - const char *innerContainerName) -{ - // This version has even more indirection. It is very rare, and - // we handle it directly. - AutoDecRef obOuterType(PyObject_GetAttrString(module, outerContainerName)); - AutoDecRef obInnerType(PyObject_GetAttrString(obOuterType, innerContainerName)); - PyTypeObject *type = func(obInnerType); - PyObject_SetAttrString(obInnerType, name, reinterpret_cast<PyObject *>(type)); // steals reference + checkIfShouldLoadImmediately(module, name, nameToFunc); } void AddTypeCreationFunction(PyObject *module, - const char *name, + const char *containerName, TypeCreationFunction func, - const char *containerName3, - const char *containerName2, - const char *containerName) + const char *namePath) { - // This version has even mode indirection. It is very rare, and - // we handle it directly. - AutoDecRef obContainerType3(PyObject_GetAttrString(module, containerName3)); - AutoDecRef obContainerType2(PyObject_GetAttrString(obContainerType3, containerName2)); - AutoDecRef obContainerType(PyObject_GetAttrString(obContainerType2, containerName)); - PyTypeObject *type = func(obContainerType); - PyObject_SetAttrString(obContainerType, name, reinterpret_cast<PyObject *>(type)); // steals reference + // - locate the module in the moduleTofuncs mapping + auto tableIter = moduleToFuncs.find(module); + assert(tableIter != moduleToFuncs.end()); + // - Assign the name/generating function tcStruct. + auto &nameToFunc = tableIter->second; + auto nit = nameToFunc.find(containerName); + + // - insert namePath into the subtype vector of the main type. + nit->second.subtypeNames.push_back(namePath); + // - insert it also as its own entry. + nit = nameToFunc.find(namePath); + TypeCreationStruct tcStruct{func, {}}; + if (nit == nameToFunc.end()) + nameToFunc.insert(std::make_pair(namePath, tcStruct)); + else + nit->second = tcStruct; + + checkIfShouldLoadImmediately(module, namePath, nameToFunc); } PyObject *import(const char *moduleName) @@ -455,6 +482,8 @@ PyObject *create(const char * /* modName */, void *moduleData) // into `sys.modules`. This can cause a race condition. // Insert the module early into the module dict to prevend recursion. PyDict_SetItemString(sysModules, PyModule_GetName(module), module); + // Clear the non-existing name cache because we have a new module. + Shiboken::Conversions::clearNegativeLazyCache(); return module; } diff --git a/sources/shiboken6/libshiboken/sbkmodule.h b/sources/shiboken6/libshiboken/sbkmodule.h index 1b3de33b7..2c407e09d 100644 --- a/sources/shiboken6/libshiboken/sbkmodule.h +++ b/sources/shiboken6/libshiboken/sbkmodule.h @@ -56,18 +56,6 @@ LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, TypeCreationFunction func, const char *containerName); -LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func, - const char *outerContainerName, - const char *innerContainerName); - -LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func, - const char *containerName3, - const char *containerName2, - const char *containerName); /** * Registers the list of types created by \p module. * \param module Module where the types were created. diff --git a/sources/shiboken6/libshiboken/sbknumpy.cpp b/sources/shiboken6/libshiboken/sbknumpy.cpp index 2e1c64d73..b6422e73f 100644 --- a/sources/shiboken6/libshiboken/sbknumpy.cpp +++ b/sources/shiboken6/libshiboken/sbknumpy.cpp @@ -29,10 +29,8 @@ static void initNumPy() // Expanded from macro "import_array" in __multiarray_api.h // Make sure to read about the magic defines PY_ARRAY_UNIQUE_SYMBOL etc., // when changing this or spreading the code over several source files. - if (_import_array() < 0) { + if (_import_array() < 0) PyErr_Print(); - PyErr_Clear(); - } } #endif // HAVE_NUMPY diff --git a/sources/shiboken6/libshiboken/sbkstring.cpp b/sources/shiboken6/libshiboken/sbkstring.cpp index 1471cd7fe..b5e87ca5a 100644 --- a/sources/shiboken6/libshiboken/sbkstring.cpp +++ b/sources/shiboken6/libshiboken/sbkstring.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "sbkstring.h" +#include "sbkenum.h" #include "sbkstaticstrings_p.h" #include "autodecref.h" @@ -14,6 +15,11 @@ bool checkIterable(PyObject *obj) return PyObject_HasAttr(obj, Shiboken::PyMagicName::iter()); } +bool checkIterableArgument(PyObject *obj) +{ + return checkIterable(obj) && !Shiboken::Enum::check(obj); +} + static PyObject *initPathLike() { PyObject *PathLike{}; diff --git a/sources/shiboken6/libshiboken/sbkstring.h b/sources/shiboken6/libshiboken/sbkstring.h index f91847c11..ebc5428c7 100644 --- a/sources/shiboken6/libshiboken/sbkstring.h +++ b/sources/shiboken6/libshiboken/sbkstring.h @@ -13,6 +13,8 @@ namespace String { LIBSHIBOKEN_API bool check(PyObject *obj); LIBSHIBOKEN_API bool checkIterable(PyObject *obj); + /// Check for iterable function arguments (excluding enumerations) + LIBSHIBOKEN_API bool checkIterableArgument(PyObject *obj); LIBSHIBOKEN_API bool checkPath(PyObject *path); LIBSHIBOKEN_API bool checkType(PyTypeObject *obj); LIBSHIBOKEN_API bool checkChar(PyObject *obj); diff --git a/sources/shiboken6/libshiboken/voidptr.cpp b/sources/shiboken6/libshiboken/voidptr.cpp index 7045b08b1..8bb3f6ac8 100644 --- a/sources/shiboken6/libshiboken/voidptr.cpp +++ b/sources/shiboken6/libshiboken/voidptr.cpp @@ -156,10 +156,9 @@ PyObject *SbkVoidPtrObject_int(PyObject *v) PyObject *toBytes(PyObject *self, PyObject * /* args */) { auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(self); - if (sbkObject->size < 0) { - PyErr_SetString(PyExc_IndexError, "VoidPtr does not have a size set."); - return nullptr; - } + if (sbkObject->size < 0) + return PyErr_Format(PyExc_IndexError, "VoidPtr does not have a size set."); + PyObject *bytes = PyBytes_FromStringAndSize(reinterpret_cast<const char *>(sbkObject->cptr), sbkObject->size); Py_XINCREF(bytes); diff --git a/sources/shiboken6/shibokenmodule/shibokenmodule.cpp b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp index b3adfe78b..5c6219885 100644 --- a/sources/shiboken6/shibokenmodule/shibokenmodule.cpp +++ b/sources/shiboken6/shibokenmodule/shibokenmodule.cpp @@ -100,6 +100,10 @@ const bool ok = Shiboken::BindingManager::instance().dumpTypeGraph(%1); Shiboken::BindingManager::instance().dumpWrapperMap(); // @snippet dumpwrappermap +// @snippet dumpconverters +Shiboken::Conversions::dumpConverters(); +// @snippet dumpconverters + // @snippet init // Add __version__ and __version_info__ attributes to the module PyObject* version = PyTuple_New(5); diff --git a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml index aa08a8bbf..acb522ecc 100644 --- a/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml +++ b/sources/shiboken6/shibokenmodule/typesystem_shiboken.xml @@ -57,9 +57,14 @@ <inject-code file="shibokenmodule.cpp" snippet="dumpwrappermap"/> </add-function> + <add-function signature="dumpConverters()"> + <inject-code file="shibokenmodule.cpp" snippet="dumpconverters"/> + </add-function> + <extra-includes> <include file-name="sbkversion.h" location="local"/> <include file-name="voidptr.h" location="local"/> + <include file-name="sbkconverter_p.h" location="local"/> </extra-includes> <inject-code position="end" file="shibokenmodule.cpp" snippet="init"/> </typesystem> diff --git a/sources/shiboken6/tests/libother/othermultiplederived.h b/sources/shiboken6/tests/libother/othermultiplederived.h index a8e265388..cd9910687 100644 --- a/sources/shiboken6/tests/libother/othermultiplederived.h +++ b/sources/shiboken6/tests/libother/othermultiplederived.h @@ -10,7 +10,7 @@ class ObjectType; -class LIBOTHER_API OtherMultipleDerived : public MDerived1 +class LIBOTHER_API OtherMultipleDerived : public OtherBase, public MDerived1 { public: // this will use CppCopier from other module (bug#142) diff --git a/sources/shiboken6/tests/libsample/derived.h b/sources/shiboken6/tests/libsample/derived.h index b7736c37a..cf95cb601 100644 --- a/sources/shiboken6/tests/libsample/derived.h +++ b/sources/shiboken6/tests/libsample/derived.h @@ -26,7 +26,7 @@ public: public: void uselessMethod() {} SomeInnerClass operator+(const SomeInnerClass &other) { return other; } - bool operator==(const SomeInnerClass &) { return true; } + bool operator==(const SomeInnerClass &) const { return true; } }; explicit Derived(int id = -1) noexcept; diff --git a/sources/shiboken6/tests/libsample/point.cpp b/sources/shiboken6/tests/libsample/point.cpp index b8630eb1e..0a28e877f 100644 --- a/sources/shiboken6/tests/libsample/point.cpp +++ b/sources/shiboken6/tests/libsample/point.cpp @@ -34,7 +34,7 @@ void Point::show() const std::cout << "(x: " << m_x << ", y: " << m_y << ")"; } -bool Point::operator==(const Point &other) +bool Point::operator==(const Point &other) const { return m_x == other.m_x && m_y == other.m_y; } diff --git a/sources/shiboken6/tests/libsample/point.h b/sources/shiboken6/tests/libsample/point.h index 59e0236d5..7e5d128ab 100644 --- a/sources/shiboken6/tests/libsample/point.h +++ b/sources/shiboken6/tests/libsample/point.h @@ -38,7 +38,7 @@ public: // The != operator is not implemented for the purpose of testing // for the absense of the __ne__ method in the Python binding. - bool operator==(const Point &other); + bool operator==(const Point &other) const; Point operator+(const Point &other); Point operator-(const Point &other); diff --git a/sources/shiboken6/tests/libsample/pointf.cpp b/sources/shiboken6/tests/libsample/pointf.cpp index 6b39f73a9..736a5c6b5 100644 --- a/sources/shiboken6/tests/libsample/pointf.cpp +++ b/sources/shiboken6/tests/libsample/pointf.cpp @@ -26,7 +26,7 @@ void PointF::show() const std::cout << "(x: " << m_x << ", y: " << m_y << ")"; } -bool PointF::operator==(const PointF &other) +bool PointF::operator==(const PointF &other) const { return m_x == other.m_x && m_y == other.m_y; } diff --git a/sources/shiboken6/tests/libsample/pointf.h b/sources/shiboken6/tests/libsample/pointf.h index bb50b5c6d..49e009467 100644 --- a/sources/shiboken6/tests/libsample/pointf.h +++ b/sources/shiboken6/tests/libsample/pointf.h @@ -31,7 +31,7 @@ public: // The != operator is not implemented for the purpose of testing // for the absence of the __ne__ method in the Python binding. - bool operator==(const PointF &other); + bool operator==(const PointF &other) const; PointF operator+(const PointF &other); PointF operator-(const PointF &other); diff --git a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp index a7b73cc81..1be93db66 100644 --- a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp +++ b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp @@ -51,6 +51,26 @@ void StdSharedPtrTestBench::printInt(const std::shared_ptr<int> &p) std::cerr << '\n'; } +std::shared_ptr<std::string> StdSharedPtrTestBench::createString(const char *text) +{ + return std::make_shared<std::string>(text); +} + +std::shared_ptr<std::string> StdSharedPtrTestBench::createNullString() +{ + return {}; +} + +void StdSharedPtrTestBench::printString(const std::shared_ptr<std::string> &p) +{ + std::cerr << __FUNCTION__ << ' '; + if (p.get()) + std::cerr << '"' << *p << '"'; + else + std::cerr << "nullptr"; + std::cerr << '\n'; +} + StdSharedPtrVirtualMethodTester::StdSharedPtrVirtualMethodTester() = default; StdSharedPtrVirtualMethodTester::~StdSharedPtrVirtualMethodTester() = default; diff --git a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h index 8991cded6..04d75d5ef 100644 --- a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h +++ b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.h @@ -7,6 +7,7 @@ #include "libsmartmacros.h" #include <memory> +#include <string> class Integer; @@ -23,6 +24,10 @@ public: static std::shared_ptr<int> createInt(int v = 42); static std::shared_ptr<int> createNullInt(); static void printInt(const std::shared_ptr<int> &); + + static std::shared_ptr<std::string> createString(const char *text); + static std::shared_ptr<std::string> createNullString(); + static void printString(const std::shared_ptr<std::string> &); }; class LIB_SMART_API StdSharedPtrVirtualMethodTester diff --git a/sources/shiboken6/tests/otherbinding/typediscovery_test.py b/sources/shiboken6/tests/otherbinding/typediscovery_test.py index 791d3bdce..39dc5cf0f 100644 --- a/sources/shiboken6/tests/otherbinding/typediscovery_test.py +++ b/sources/shiboken6/tests/otherbinding/typediscovery_test.py @@ -13,7 +13,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import Abstract, Base1, Derived +from sample import (Abstract, Base1, Derived, + MDerived1, SonOfMDerived1, MDerived3) from other import OtherMultipleDerived @@ -32,14 +33,18 @@ class TypeDiscoveryTest(unittest.TestCase): def testMultipleInheritance(self): obj = OtherMultipleDerived.createObject("Base1") self.assertEqual(type(obj), Base1) - # PYSIDE-868: In case of multiple inheritance, a factory - # function will return the base class wrapper. + # PYSIDE-868: In case of single line direct inheritance, + # a factory function will return the class wrapper + # of the derived class. obj = OtherMultipleDerived.createObject("MDerived1") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), MDerived1) obj = OtherMultipleDerived.createObject("SonOfMDerived1") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), SonOfMDerived1) obj = OtherMultipleDerived.createObject("MDerived3") - self.assertEqual(type(obj), Base1) + self.assertEqual(type(obj), MDerived3) + # PYSIDE-868: OtherMultipleDerived inherits + # OtherBase, Base1. In this case, a factory + # function will return the base class wrapper. obj = OtherMultipleDerived.createObject("OtherMultipleDerived") self.assertEqual(type(obj), Base1) diff --git a/sources/shiboken6/tests/smartbinding/CMakeLists.txt b/sources/shiboken6/tests/smartbinding/CMakeLists.txt index 2e729321e..c10d7ab6b 100644 --- a/sources/shiboken6/tests/smartbinding/CMakeLists.txt +++ b/sources/shiboken6/tests/smartbinding/CMakeLists.txt @@ -20,6 +20,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrtestbench_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrvirtualmethodtester_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_integer_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_int_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_std_string_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_optional_int_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/std_optional_integer_wrapper.cpp diff --git a/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py b/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py index 2e6aea3d9..ddaab43f5 100644 --- a/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py +++ b/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py @@ -49,6 +49,15 @@ class StdSharedPtrTests(unittest.TestCase): self.assertFalse(np) p = StdSharedPtrTestBench.createInt() StdSharedPtrTestBench.printInt(p) + ip = std.StdIntPtr(42) + StdSharedPtrTestBench.printInt(ip) + + def testString(self): + np = StdSharedPtrTestBench.createNullString() + StdSharedPtrTestBench.printString(np) + self.assertFalse(np) + p = StdSharedPtrTestBench.createString("bla") + StdSharedPtrTestBench.printString(p) def testVirtuals(self): """Test whether code generating virtual function overrides is generated diff --git a/sources/shiboken6/tests/smartbinding/typesystem_smart.xml b/sources/shiboken6/tests/smartbinding/typesystem_smart.xml index 261d5f15d..e4a1ef8b7 100644 --- a/sources/shiboken6/tests/smartbinding/typesystem_smart.xml +++ b/sources/shiboken6/tests/smartbinding/typesystem_smart.xml @@ -50,7 +50,7 @@ value-check-method="operator bool" ref-count-method="use_count" reset-method="reset" - instantiations="Integer,int"> + instantiations="Integer,int=StdIntPtr,std::string"> <include file-name="memory" location="global"/> </smart-pointer-type> diff --git a/tools/cross_compile_android/android_utilities.py b/tools/cross_compile_android/android_utilities.py index 039fa9431..3d93abec2 100644 --- a/tools/cross_compile_android/android_utilities.py +++ b/tools/cross_compile_android/android_utilities.py @@ -3,6 +3,7 @@ import logging import shutil +import re import os import stat import sys @@ -17,6 +18,7 @@ from tqdm import tqdm # the tag number does not matter much since we update the sdk later DEFAULT_SDK_TAG = 6514223 ANDROID_NDK_VERSION = "26b" +ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125" def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False, @@ -83,9 +85,9 @@ class SdkManager: accept_prompts=accept_license, show_stdout=show_stdout) -def _unpack(zip_file: Path, destination: Path): +def extract_zip(file: Path, destination: Path): """ - Unpacks the zip_file into destination preserving all permissions + Unpacks the zip file into destination preserving all permissions TODO: Try to use zipfile module. Currently we cannot use zipfile module here because extractAll() does not preserve permissions. @@ -97,10 +99,26 @@ def _unpack(zip_file: Path, destination: Path): raise RuntimeError("Unable to find program unzip. Use `sudo apt-get install unzip`" "to install it") - command = [unzip, zip_file, "-d", destination] + command = [unzip, file, "-d", destination] run_command(command=command, show_stdout=True) +def extract_dmg(file: Path, destination: Path): + output = run_command(['hdiutil', 'attach', '-nobrowse', '-readonly', file], + show_stdout=True, capture_stdout=True) + + # find the mounted volume + mounted_vol_name = re.search(r'/Volumes/(.*)', output).group(1) + if not mounted_vol_name: + raise RuntimeError(f"Unable to find mounted volume for file {file}") + + # copy files + shutil.copytree(f'/Volumes/{mounted_vol_name}/', destination, dirs_exist_ok=True) + + # Detach mounted volume + run_command(['hdiutil', 'detach', f'/Volumes/{mounted_vol_name}']) + + def _download(url: str, destination: Path): """ Download url to destination @@ -110,7 +128,6 @@ def _download(url: str, destination: Path): with DownloadProgressBar(unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1]) as t: download_path, headers = request.urlretrieve(url=url, filename=destination, reporthook=t.update_to) - assert headers["Content-Type"] == "application/zip" assert Path(download_path).resolve() == destination @@ -119,22 +136,40 @@ def download_android_ndk(ndk_path: Path): Downloads the given ndk_version into ndk_path """ ndk_path = ndk_path / "android-ndk" - ndk_zip_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip" - ndk_version_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}" + ndk_extension = "dmg" if sys.platform == "darwin" else "zip" + ndk_zip_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}" + ndk_version_path = "" + if sys.platform == "linux": + ndk_version_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}" + elif sys.platform == "darwin": + ndk_version_path = (ndk_path + / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK") + else: + raise RuntimeError(f"Unsupported platform {sys.platform}") if ndk_version_path.exists(): print(f"NDK path found in {str(ndk_version_path)}") else: ndk_path.mkdir(parents=True, exist_ok=True) url = (f"https://dl.google.com/android/repository" - f"/android-ndk-r{ANDROID_NDK_VERSION}-linux.zip") + f"/android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}") print(f"Downloading Android Ndk version r{ANDROID_NDK_VERSION}") _download(url=url, destination=ndk_zip_path) print("Unpacking Android Ndk") - _unpack(zip_file=(ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip"), - destination=ndk_path) + if sys.platform == "darwin": + extract_dmg(file=(ndk_path + / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}" + ), + destination=ndk_path) + ndk_version_path = (ndk_version_path + / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK") + else: + extract_zip(file=(ndk_path + / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}" + ), + destination=ndk_path) return ndk_version_path @@ -143,10 +178,12 @@ def download_android_commandlinetools(android_sdk_dir: Path): """ Downloads Android commandline tools into cltools_path. """ + sdk_platform = sys.platform if sys.platform != "darwin" else "mac" android_sdk_dir = android_sdk_dir / "android-sdk" url = ("https://dl.google.com/android/repository/" - f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip") - cltools_zip_path = android_sdk_dir / f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip" + f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip") + cltools_zip_path = (android_sdk_dir + / f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip") cltools_path = android_sdk_dir / "tools" if cltools_path.exists(): @@ -155,11 +192,11 @@ def download_android_commandlinetools(android_sdk_dir: Path): android_sdk_dir.mkdir(parents=True, exist_ok=True) print("Download Android Command Line Tools: " - f"commandlinetools-linux-{DEFAULT_SDK_TAG}_latest.zip") + f"commandlinetools-{sys.platform}-{DEFAULT_SDK_TAG}_latest.zip") _download(url=url, destination=cltools_zip_path) print("Unpacking Android Command Line Tools") - _unpack(zip_file=cltools_zip_path, destination=android_sdk_dir) + extract_zip(file=cltools_zip_path, destination=android_sdk_dir) return android_sdk_dir @@ -204,6 +241,10 @@ def find_latest_buildtools_version(sdk_manager: SdkManager): if not available_build_tools_v: raise RuntimeError('Unable to find any build tools available for download') + # find the latest build tools version that is not a release candidate + # release candidates end has rc in the version number + available_build_tools_v = [v for v in available_build_tools_v if "rc" not in str(v)] + return max(available_build_tools_v) diff --git a/tools/cross_compile_android/main.py b/tools/cross_compile_android/main.py index bda438cca..200f494cf 100644 --- a/tools/cross_compile_android/main.py +++ b/tools/cross_compile_android/main.py @@ -18,6 +18,7 @@ from android_utilities import (run_command, download_android_commandlinetools, download_android_ndk, install_android_packages) # Note: Does not work with PyEnv. Your Host Python should contain openssl. +# also update the version in ShibokenHelpers.cmake if Python version changes. PYTHON_VERSION = "3.11" SKIP_UPDATE_HELP = ("skip the updation of SDK packages build-tools, platform-tools to" @@ -86,7 +87,8 @@ if __name__ == "__main__": parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const", dest="loglevel", const=logging.INFO) - parser.add_argument("--api-level", type=str, default="33", help="Android API level to use") + parser.add_argument("--api-level", type=str, default="26", + help="Minimum Android API level to use") parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r25c)") # sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar parser.add_argument("--sdk-path", type=str, help="Path to Android SDK") @@ -184,8 +186,8 @@ if __name__ == "__main__": platform_data = PlatformData("x86_64", api_level, "x86_64", "x86_64", "x86-64", "64") # python path is valid, if Python for android installation exists in python_path - python_path = (pyside6_deploy_cache / f"Python-{platform_data.plat_name}-linux-android" - / "_install") + python_path = (pyside6_deploy_cache + / f"Python-{platform_data.plat_name}-linux-android" / "_install") valid_python_path = python_path.exists() if Path(python_path).exists(): expected_dirs = ["lib", "include"] @@ -214,6 +216,10 @@ if __name__ == "__main__": ) if not python_ccompile_script.exists(): + host_system_config_name = run_command("./config.guess", cwd=cpython_dir, + dry_run=dry_run, show_stdout=True, + capture_stdout=True).strip() + # use jinja2 to create cross_compile.sh script template = environment.get_template("cross_compile.tmpl.sh") content = template.render( @@ -221,7 +227,10 @@ if __name__ == "__main__": ndk_path=ndk_path, api_level=platform_data.api_level, android_py_install_path_prefix=pyside6_deploy_cache, - host_python_path=sys.executable + host_python_path=sys.executable, + python_version=PYTHON_VERSION, + host_system_name=host_system_config_name, + host_platform_name=sys.platform ) logging.info(f"Writing Python cross compile script into {python_ccompile_script}") @@ -240,13 +249,6 @@ if __name__ == "__main__": run_command([f"./{python_ccompile_script.name}"], cwd=cpython_dir, dry_run=dry_run, show_stdout=True) - # run patchelf to change the SONAME of libpython from libpython3.x.so.1.0 to - # libpython3.x.so, to match with python_for_android's Python library. Otherwise, - # the Qfp binaries won't be able to link to Python - run_command(["patchelf", "--set-soname", f"libpython{PYTHON_VERSION}.so", - f"libpython{PYTHON_VERSION}.so.1.0"], cwd=Path(python_path) / "lib", - dry_run=dry_run) - logging.info( f"Cross compile Python for Android platform {platform_data.plat_name}. " f"Final installation in {python_path}" @@ -286,15 +288,22 @@ if __name__ == "__main__": # give run permission to cross compile script qfp_toolchain.chmod(qfp_toolchain.stat().st_mode | stat.S_IEXEC) + if sys.platform == "linux": + host_qt_install_suffix = "gcc_64" + elif sys.platform == "darwin": + host_qt_install_suffix = "macos" + else: + raise RuntimeError("Qt for Python cross compilation not supported on this platform") + # run the cross compile script logging.info(f"Running Qt for Python cross-compile for platform {platform_data.plat_name}") qfp_ccompile_cmd = [sys.executable, "setup.py", "bdist_wheel", "--parallel=9", "--standalone", f"--cmake-toolchain-file={str(qfp_toolchain.resolve())}", - f"--qt-host-path={qt_install_path}/gcc_64", + f"--qt-host-path={qt_install_path}/{host_qt_install_suffix}", f"--plat-name=android_{platform_data.plat_name}", f"--python-target-path={python_path}", (f"--qt-target-path={qt_install_path}/" - f"android_{platform_data.qt_plat_name}"), + f"android_{platform_data.qt_plat_name}"), "--no-qt-tools"] run_command(qfp_ccompile_cmd, cwd=pyside_setup_dir, dry_run=dry_run, show_stdout=True) diff --git a/tools/cross_compile_android/templates/cross_compile.tmpl.sh b/tools/cross_compile_android/templates/cross_compile.tmpl.sh index a68907591..784e822ca 100644 --- a/tools/cross_compile_android/templates/cross_compile.tmpl.sh +++ b/tools/cross_compile_android/templates/cross_compile.tmpl.sh @@ -3,7 +3,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only set -x -e export HOST_ARCH={{ plat_name }}-linux-android -export TOOLCHAIN={{ ndk_path }}/toolchains/llvm/prebuilt/linux-x86_64/bin +export TOOLCHAIN={{ ndk_path }}/toolchains/llvm/prebuilt/{{ host_platform_name }}-x86_64/bin export TOOL_PREFIX=$TOOLCHAIN/$HOST_ARCH export PLATFORM_API={{ api_level }} {% if plat_name == "armv7a" -%} @@ -20,10 +20,10 @@ export RANLIB=$TOOLCHAIN/llvm-ranlib export LD=$TOOLCHAIN/ld export READELF=$TOOLCHAIN/llvm-readelf export CFLAGS='-fPIC -DANDROID' -./configure --host=$HOST_ARCH --target=$HOST_ARCH --build=x86_64-pc-linux-gnu \ +./configure --host=$HOST_ARCH --target=$HOST_ARCH --build={{ host_system_name }} \ --with-build-python={{ host_python_path }} --enable-shared \ --enable-ipv6 ac_cv_file__dev_ptmx=yes ac_cv_file__dev_ptc=no --without-ensurepip \ ac_cv_little_endian_double=yes -make BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- CROSS_COMPILE_TARGET=yes -make install BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- \ -CROSS_COMPILE_TARGET=yes prefix={{ android_py_install_path_prefix }}/Python-$HOST_ARCH/_install +make BLDSHARED="$CC -shared" CROSS-COMPILE=$TOOL_PREFIX- CROSS_COMPILE_TARGET=yes \ +INSTSONAME=libpython{{ python_version }}.so +make install prefix={{ android_py_install_path_prefix }}/Python-$HOST_ARCH/_install diff --git a/tools/snippets_translate/module_classes.py b/tools/snippets_translate/module_classes.py index 4992e170b..df4c7557c 100644 --- a/tools/snippets_translate/module_classes.py +++ b/tools/snippets_translate/module_classes.py @@ -532,6 +532,7 @@ module_classes = { "QAccessibleEvent", "QAccessibleInterface", "QAccessibleObject", + "QAccessibleSelectionInterface", "QAccessibleStateChangeEvent", "QAccessibleTableCellInterface", "QAccessibleTableModelChangeEvent", |