diff options
155 files changed, 1737 insertions, 747 deletions
diff --git a/build_scripts/config.py b/build_scripts/config.py index 0a6eebf78..4b1d755a1 100644 --- a/build_scripts/config.py +++ b/build_scripts/config.py @@ -65,6 +65,7 @@ class Config(object): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ] self.setup_script_dir = None 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/coin/module_config.yaml b/coin/module_config.yaml index f97971ca9..e5c2fdc8c 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -25,6 +25,9 @@ accept_configuration: - condition: property # Windows on Arm property: target.arch not_equals_value: ARM64 + - condition: property # Windows on Arm host build + property: target.arch + not_equals_value: AARCH64 - condition: property property: features not_contains_value: DebianPackaging @@ -62,6 +65,21 @@ accept_configuration: - condition: property property: features contains_value: Packaging + - condition: and # Restore LoA config + conditions: + - condition: property + property: host.osVersion + equals_value: Debian_11_6 + - condition: property + property: host.arch + equals_value: AARCH64 + - condition: property + property: features + not_contains_value: DebianPackaging + - condition: property + property: features + contains_value: Packaging + machine_type: Build: diff --git a/doc/changelogs/changes-6.7.2 b/doc/changelogs/changes-6.7.2 new file mode 100644 index 000000000..44015f2c5 --- /dev/null +++ b/doc/changelogs/changes-6.7.2 @@ -0,0 +1,50 @@ +Qt for Python 6.7.2 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-1612] Android Deployment now used the development branch of p4a. + - [PYSIDE-2712] Type hints: Modified types are no longer considered + as eligible implicit conversions. + - [PYSIDE-2745] Exceptions occurring in a slot connected to a + 0-delay singleShot timer signal are no longer suppressed. + - [PYSIDE-2747] Running on Fedora 40 with Python 3.12.3-2 has been fixed. + - [PYSIDE-2748] Type hints: QTranslator.translate() has been fixed. + - [PYSIDE-2749] Lazy Load: A performance regression showing in Qt event + filters has been fixed. + - [PYSIDE-2750] Qt6VirtualKeyboardSettings have been added to the wheel. + - [PYSIDE-2756] Type hints: The return type of QItemSelection.__init__() + has been fixed. + - [PYSIDE-2758] QQuickWebEngineProfile.setUrlRequestInterceptor() + has been added. + - [PYSIDE-2759] The ownership of cache object passed to + QNetworkAccessManager.setCache() has been fixed. + - [PYSIDE-2762] Type hints: The return type of + QModelIndex.internalPointer() has been fixed. + - [PYSIDE-2767] Type hints: An error checking the property decorator + has been fixed. + - [PYSIDE-2768] Type hints: smart pointer signatures have been fixed. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2764] The missing declaration of the deprecated variable + cppApiVariableNameOld has been added to the generated + headers. + - [PYSIDE-2769] Finding the clang include directories on + manylinux_2_28_x86_64 has been fixed. 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.py b/sources/pyside-tools/deploy.py index aa03d13d0..bab5aa0de 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -49,10 +49,20 @@ TOOL_DESCRIPTION = dedent(f""" Linux = .bin """) +HELP_MODE = dedent(""" + The mode in which the application is deployed. The options are: onefile, + standalone. The default value is onefile. + + This options translates to the mode Nuitka uses to create the executable. + + macOS by default uses the --standalone option. + """) + def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, - force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None): + force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None, + mode: bool = False): logging.basicConfig(level=loglevel) if config_file and not config_file.exists() and not main_file.exists(): @@ -91,7 +101,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe, dry_run=dry_run, existing_config_file=config_file_exists, - extra_ignore_dirs=extra_ignore_dirs) + extra_ignore_dirs=extra_ignore_dirs, mode=mode) # set application name if name: @@ -135,7 +145,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini excluded_qml_plugins=config.excluded_qml_plugins, icon=config.icon, dry_run=dry_run, - permissions=config.permissions) + permissions=config.permissions, + mode=config.mode) except Exception: print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}") finally: @@ -182,7 +193,11 @@ if __name__ == "__main__": parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) + parser.add_argument("--mode", choices=["onefile", "standalone"], default="desktop", + help=HELP_MODE) + args = parser.parse_args() main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run, - args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules) + args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules, + args.mode) 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/config.py b/sources/pyside-tools/deploy_lib/config.py index d59dd92ad..5d5070bef 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -8,6 +8,7 @@ import warnings from configparser import ConfigParser from typing import List from pathlib import Path +from enum import Enum from project import ProjectData from . import (DEFAULT_APP_ICON, find_pyside_modules, find_permission_categories, @@ -375,8 +376,13 @@ class Config(BaseConfig): class DesktopConfig(Config): """Wrapper class around pysidedeploy.spec, but specific to Desktop deployment """ + class NuitkaMode(Enum): + ONEFILE = "onefile" + STANDALONE = "standalone" + def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): + existing_config_file: bool = False, extra_ignore_dirs: List[str] = None, + mode: str = "onefile"): super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file, extra_ignore_dirs) self.dependency_reader = QtDependencyReader(dry_run=self.dry_run) @@ -402,6 +408,12 @@ class DesktopConfig(Config): else: self._find_and_set_permissions() + self._mode = self.NuitkaMode.ONEFILE + if self.get_value("nuitka", "mode") == self.NuitkaMode.STANDALONE.value: + self._mode = self.NuitkaMode.STANDALONE + elif mode == self.NuitkaMode.STANDALONE.value: + self.mode = self.NuitkaMode.STANDALONE + @property def qt_plugins(self): return self._qt_plugins @@ -420,6 +432,15 @@ class DesktopConfig(Config): self._permissions = permissions self.set_value("nuitka", "macos.permissions", ",".join(permissions)) + @property + def mode(self): + return self._mode + + @mode.setter + def mode(self, mode: NuitkaMode): + self._mode = mode + self.set_value("nuitka", "mode", mode.value) + def _find_dependent_qt_modules(self): """ Given pysidedeploy_config.modules, find all the other dependent Qt modules. diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index 0a729d585..6ba68e44d 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -27,7 +27,7 @@ python_path = # python packages to install # ordered-set: increase compile time performance of nuitka packaging # zstandard: provides final executable size optimization -packages = Nuitka==2.1 +packages = Nuitka==2.3.2 # buildozer: for deploying Android application android_packages = buildozer==1.5.0,cython==0.29.33 @@ -65,6 +65,9 @@ plugins = # eg: NSCameraUsageDescription:CameraAccess macos.permissions = +# mode of using Nuitka. Accepts standalone or onefile. Default is onefile. +mode = onefile + # (str) specify any extra nuitka arguments # eg: extra_args = --show-modules --follow-stdlib extra_args = --quiet --noinclude-qt-translations diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py index e8b05e990..1e0e2712a 100644 --- a/sources/pyside-tools/deploy_lib/deploy_util.py +++ b/sources/pyside-tools/deploy_lib/deploy_util.py @@ -7,7 +7,7 @@ import sys from pathlib import Path from . import EXE_FORMAT -from .config import Config +from .config import Config, DesktopConfig def config_option_exists(): @@ -61,17 +61,21 @@ def create_config_file(dry_run: bool = False, config_file: Path = None, main_fil return config_file -def finalize(config: Config): +def finalize(config: DesktopConfig): """ Copy the executable into the final location For Android deployment, this is done through buildozer """ - generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT) + dist_format = EXE_FORMAT + if config.mode == DesktopConfig.NuitkaMode.STANDALONE and sys.platform != "darwin": + dist_format = ".dist" + + generated_exec_path = config.generated_files_path / (config.source_file.stem + dist_format) if generated_exec_path.exists() and config.exe_dir: - if sys.platform == "darwin": - shutil.copytree(generated_exec_path, config.exe_dir / (config.title + EXE_FORMAT), + if sys.platform == "darwin" or config.mode == DesktopConfig.NuitkaMode.STANDALONE: + shutil.copytree(generated_exec_path, config.exe_dir / (config.title + dist_format), dirs_exist_ok=True) else: shutil.copy(generated_exec_path, config.exe_dir) print("[DEPLOY] Executed file created in " - f"{str(config.exe_dir / (config.source_file.stem + EXE_FORMAT))}") + f"{str(config.exe_dir / (config.source_file.stem + dist_format))}") diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index d202db25e..5d0e9032f 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -1,6 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# enables to use typehints for classes that has not been defined yet or imported +# used for resolving circular imports +from __future__ import annotations import logging import os import sys @@ -8,6 +11,7 @@ from pathlib import Path from typing import List from . import MAJOR_VERSION, run_command +from .config import DesktopConfig class Nuitka: @@ -35,6 +39,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": @@ -46,10 +56,12 @@ class Nuitka: def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path], qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str, - dry_run: bool, permissions: List[str]): + dry_run: bool, permissions: List[str], + mode: DesktopConfig.NuitkaMode): qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore] extra_args = extra_args.split() + # macOS uses the --standalone option by default to create an app bundle if sys.platform == "darwin": # create an app bundle extra_args.extend(["--standalone", "--macos-create-app-bundle"]) @@ -57,7 +69,7 @@ class Nuitka: for permission in permissions: extra_args.append(permission_pattern.format(permission=permission)) else: - extra_args.append("--onefile") + extra_args.append(f"--{mode.value}") qml_args = [] if qml_files: @@ -81,6 +93,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 3eee63ba4..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"/> 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/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 774f9dd64..b1188a514 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -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/deployment/deployment-pyside6-deploy.rst b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst index 980fe2dd1..f348bc4f8 100644 --- a/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst +++ b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst @@ -14,6 +14,9 @@ macOS. not add the virtual environment to the application directory you are trying to deploy. ``pyside6-deploy`` will try to package this venv folder and will eventually fail. +.. note:: The default version of Nuitka used with the tool is version ``2.3.2``. This can be + updated to a newer version by updating your ``pysidedeploy.spec`` file. + .. _how_pysidedeploy: How to use it? @@ -144,6 +147,12 @@ The relevant parameters for ``pyside6-deploy`` are: NSCameraUsageDescription:CameraAccess + * ``mode``: Accepts one of the options: ``onefile`` or ``standalone``. The default is ``onefile``. + This option corresponds to the mode in which Nuitka is run. The onefile mode creates a single + executable file, while the standalone mode creates a directory with the executable and all the + necessary files. The standalone mode is useful when you want to distribute the application as a + directory with dependencies and other files required by the app. + * ``extra_args``: Any extra Nuitka arguments specified. It is specified as space-separated command line arguments i.e. just like how you would specify it when you use Nuitka through the command line. By default, it contains the following arguments:: 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 cff74c260..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; 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..ed0cc5d0a 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,15 +647,17 @@ 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) { if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set. + PyObject *exc{}, *inst{}, *tb{}; + PyErr_Fetch(&exc, &inst, &tb); PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".", Py_TYPE(aSlot)->tp_name, signature.constData()); + PyErr_Restore(exc, inst, tb); } else { PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".", aSlot, signature.constData()); @@ -675,10 +667,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 +716,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 +751,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 +1190,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 +1278,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 223c6eaa3..4ccd459d5 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -392,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/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/pysidetest/enum_test.py b/sources/pyside6/tests/pysidetest/enum_test.py index 832834530..7afc5b948 100644 --- a/sources/pyside6/tests/pysidetest/enum_test.py +++ b/sources/pyside6/tests/pysidetest/enum_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 The Qt Company Ltd. +# 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 @@ -50,6 +50,8 @@ class ListConnectionTest(unittest.TestCase): # PYSIDE-1735: We are testing that opcodes do what they are supposed to do. # This is needed in the PyEnum forgiveness mode where we need # to introspect the code if an Enum was called with no args. + +# flake8: noqa class InvestigateOpcodesTest(unittest.TestCase): def probe_function1(self): @@ -162,25 +164,40 @@ class InvestigateOpcodesTest(unittest.TestCase): self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_3) self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_3) - if sys.version_info[:2] >= (3, 12): + if sys.version_info[:2] == (3, 12): - result_1 = [('RESUME', 151, 0), - ('LOAD_GLOBAL', 116, 0), - ('LOAD_ATTR', 106, 2), - ('STORE_FAST', 125, 1), + result_1 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 0), + ('LOAD_ATTR', 106, 2), + ('STORE_FAST', 125, 1), ('RETURN_CONST', 121, 0)] - result_2 = [('RESUME', 151, 0), - ('LOAD_GLOBAL', 116, 1), - ('LOAD_ATTR', 106, 2), - ('CALL', 171, 0), - ('STORE_FAST', 125, 1), + result_2 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 1), + ('LOAD_ATTR', 106, 2), + ('CALL', 171, 0), + ('STORE_FAST', 125, 1), ('RETURN_CONST', 121, 0)] + if sys.version_info[:2] >= (3, 13): + + result_1 = [('RESUME', 149, 0), + ('LOAD_GLOBAL', 91, 0), + ('LOAD_ATTR', 82, 2), + ('STORE_FAST', 110, 1), + ('RETURN_CONST', 103, 0)] + + result_2 = [('RESUME', 149, 0), + ('LOAD_GLOBAL', 91, 0), + ('LOAD_ATTR', 82, 2), + ('PUSH_NULL', 34, None), + ('CALL', 53, 0), + ('STORE_FAST', 110, 1), + ('RETURN_CONST', 103, 0)] + self.assertEqual(self.read_code(self.probe_function1), result_1) self.assertEqual(self.read_code(self.probe_function2), result_2) if __name__ == '__main__': unittest.main() - 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..46b644584 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" @@ -147,7 +151,7 @@ class TestPySide6DeployWidgets(DeployTestBase): self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") self.assertEqual(config_obj.get_value("python", "packages"), - "Nuitka==2.1") + "Nuitka==2.3.2") self.assertEqual(config_obj.get_value("qt", "qml_files"), "") equ_base = "--quiet --noinclude-qt-translations" equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base @@ -170,6 +174,16 @@ class TestPySide6DeployWidgets(DeployTestBase): self.deploy.main(main_file=fake_main_file, config_file=self.config_file) self.assertTrue("Directory does not contain main.py file." in str(context.exception)) + def testStandaloneMode(self, mock_plugins): + mock_plugins.return_value = self.all_plugins + # remove --onefile from self.expected_run_cmd and replace it with --standalone + self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone") + # test standalone mode + original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True, + force=True) + + self.assertEqual(original_output, self.expected_run_cmd) + @unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11, "Test only works on macOS version 12+") @@ -189,6 +203,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 +218,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=" @@ -250,7 +267,7 @@ class TestPySide6DeployQml(DeployTestBase): self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") self.assertEqual(config_obj.get_value("python", "packages"), - "Nuitka==2.1") + "Nuitka==2.3.2") self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml") equ_base = "--quiet --noinclude-qt-translations" equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base @@ -334,6 +351,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..f07fb96c6 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -81,7 +81,7 @@ QTextStream &operator<<(QTextStream &str, const RejectEntry &re) return str; } -static void applyCachedFunctionModifications(AbstractMetaFunction *metaFunction, +static void applyCachedFunctionModifications(const AbstractMetaFunctionPtr &metaFunction, const FunctionModificationList &functionMods) { for (const FunctionModification &mod : functionMods) { @@ -317,7 +317,7 @@ void AbstractMetaBuilderPrivate::traverseOperatorFunction(const FunctionModelIte return; } - AbstractMetaFunction *metaFunction = traverseFunction(item, baseoperandClass); + auto metaFunction = traverseFunction(item, baseoperandClass); if (metaFunction == nullptr) return; @@ -350,7 +350,7 @@ void AbstractMetaBuilderPrivate::traverseOperatorFunction(const FunctionModelIte } metaFunction->setFlags(flags); metaFunction->setAccess(Access::Public); - AbstractMetaClass::addFunction(baseoperandClass, AbstractMetaFunctionCPtr(metaFunction)); + AbstractMetaClass::addFunction(baseoperandClass, metaFunction); if (!metaFunction->arguments().isEmpty()) { const auto include = metaFunction->arguments().constFirst().type().typeEntry()->include(); baseoperandClass->typeEntry()->addArgumentInclude(include); @@ -371,7 +371,7 @@ bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem if (streamedClass == nullptr) return false; - AbstractMetaFunction *streamFunction = traverseFunction(item, streamedClass); + auto streamFunction = traverseFunction(item, streamedClass); if (!streamFunction) return false; @@ -401,7 +401,7 @@ bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem funcClass = streamClass; } - AbstractMetaClass::addFunction(funcClass, AbstractMetaFunctionCPtr(streamFunction)); + AbstractMetaClass::addFunction(funcClass, streamFunction); auto funcTe = funcClass->typeEntry(); if (funcClass == streamClass) funcTe->addArgumentInclude(streamedClass->typeEntry()->include()); @@ -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()) { @@ -555,17 +555,16 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom, if (!funcEntry || !funcEntry->generateCode()) continue; - AbstractMetaFunction *metaFunc = traverseFunction(func, nullptr); - if (!metaFunc) + auto metaFuncPtr = traverseFunction(func, nullptr); + if (!metaFuncPtr) continue; - AbstractMetaFunctionCPtr metaFuncPtr(metaFunc); - if (!funcEntry->hasSignature(metaFunc->minimalSignature())) + if (!funcEntry->hasSignature(metaFuncPtr->minimalSignature())) continue; - metaFunc->setTypeEntry(funcEntry); - applyFunctionModifications(metaFunc); - metaFunc->applyTypeModifications(); + metaFuncPtr->setTypeEntry(funcEntry); + applyFunctionModifications(metaFuncPtr); + metaFuncPtr->applyTypeModifications(); setInclude(funcEntry, func->fileName()); @@ -1348,7 +1347,7 @@ void AbstractMetaBuilderPrivate::traverseFields(const ScopeModelItem &scope_item } } -void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction) +void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(const AbstractMetaFunctionPtr &metaFunction) { if (!metaFunction->isConversionOperator()) return; @@ -1372,13 +1371,13 @@ void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaF metaFunction->setType(metaType); } -AbstractMetaFunctionRawPtrList +AbstractMetaFunctionList AbstractMetaBuilderPrivate::classFunctionList(const ScopeModelItem &scopeItem, AbstractMetaClass::Attributes *constructorAttributes, const AbstractMetaClassPtr ¤tClass) { *constructorAttributes = {}; - AbstractMetaFunctionRawPtrList result; + AbstractMetaFunctionList result; const FunctionList &scopeFunctionList = scopeItem->functions(); result.reserve(scopeFunctionList.size()); const bool isNamespace = currentClass->isNamespace(); @@ -1388,7 +1387,7 @@ AbstractMetaFunctionRawPtrList } else if (function->isSpaceshipOperator() && !function->isDeleted()) { if (currentClass) AbstractMetaClass::addSynthesizedComparisonOperators(currentClass); - } else if (auto *metaFunction = traverseFunction(function, currentClass)) { + } else if (auto metaFunction = traverseFunction(function, currentClass)) { result.append(metaFunction); } else if (!function->isDeleted() && function->functionType() == CodeModel::Constructor) { auto arguments = function->arguments(); @@ -1404,11 +1403,11 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt const AbstractMetaClassPtr &metaClass) { AbstractMetaClass::Attributes constructorAttributes; - const AbstractMetaFunctionRawPtrList functions = + const AbstractMetaFunctionList functions = classFunctionList(scopeItem, &constructorAttributes, metaClass); metaClass->setAttributes(metaClass->attributes() | constructorAttributes); - for (AbstractMetaFunction *metaFunction : functions) { + for (const auto &metaFunction : functions) { if (metaClass->isNamespace()) metaFunction->setCppAttribute(FunctionAttribute::Static); @@ -1461,23 +1460,20 @@ void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeIt if (!metaFunction->isDestructor() && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) { - if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction)) - qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass, metaFunction))); + if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction.get())) + qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass, + metaFunction.get()))); if (metaFunction->isConversionOperator()) fixReturnTypeOfConversionOperator(metaFunction); - AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction)); + AbstractMetaClass::addFunction(metaClass, metaFunction); applyFunctionModifications(metaFunction); } else if (metaFunction->isDestructor()) { metaClass->setHasPrivateDestructor(metaFunction->isPrivate()); metaClass->setHasProtectedDestructor(metaFunction->isProtected()); metaClass->setHasVirtualDestructor(metaFunction->isVirtual()); } - if (!metaFunction->ownerClass()) { - delete metaFunction; - metaFunction = nullptr; - } } fillAddedFunctions(metaClass); @@ -1539,7 +1535,7 @@ QStringList AbstractMetaBuilder::definitionNames(const QString &name, return result; } -void AbstractMetaBuilderPrivate::applyFunctionModifications(AbstractMetaFunction *func) +void AbstractMetaBuilderPrivate::applyFunctionModifications(const AbstractMetaFunctionPtr &func) { AbstractMetaFunction& funcRef = *func; for (const FunctionModification &mod : func->modifications(func->implementingClass())) { @@ -1574,7 +1570,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; } @@ -1676,15 +1673,14 @@ static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &); bool AbstractMetaBuilderPrivate::traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc, QString *errorMessage) { - AbstractMetaFunction *metaFunction = - traverseAddedFunctionHelper(addedFunc, nullptr, errorMessage); + auto metaFunction = traverseAddedFunctionHelper(addedFunc, nullptr, errorMessage); if (metaFunction == nullptr) return false; - m_globalFunctions << AbstractMetaFunctionCPtr(metaFunction); + m_globalFunctions << metaFunction; return true; } -AbstractMetaFunction * +AbstractMetaFunctionPtr AbstractMetaBuilderPrivate::traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, const AbstractMetaClassPtr &metaClass /* = {} */, QString *errorMessage) @@ -1695,10 +1691,10 @@ AbstractMetaFunction * msgAddedFunctionInvalidReturnType(addedFunc->name(), addedFunc->returnType().qualifiedName(), *errorMessage, metaClass); - return nullptr; + return {}; } - auto *metaFunction = new AbstractMetaFunction(addedFunc); + auto metaFunction = std::make_shared<AbstractMetaFunction>(addedFunc); metaFunction->setType(returnType.value()); metaFunction->setFunctionType(functionTypeFromName(addedFunc->name())); @@ -1716,8 +1712,7 @@ AbstractMetaFunction * msgAddedFunctionInvalidArgType(addedFunc->name(), arg.typeInfo.qualifiedName(), i + 1, *errorMessage, metaClass); - delete metaFunction; - return nullptr; + return {}; } type->decideUsagePattern(); @@ -1774,8 +1769,7 @@ bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunction const AbstractMetaClassPtr &metaClass, QString *errorMessage) { - AbstractMetaFunction *metaFunction = - traverseAddedFunctionHelper(addedFunc, metaClass, errorMessage); + auto metaFunction = traverseAddedFunctionHelper(addedFunc, metaClass, errorMessage); if (metaFunction == nullptr) return false; @@ -1795,12 +1789,13 @@ bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunction metaFunction->setDeclaringClass(metaClass); metaFunction->setImplementingClass(metaClass); - AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction)); + AbstractMetaClass::addFunction(metaClass, metaFunction); metaClass->setHasNonPrivateConstructor(true); return true; } -void AbstractMetaBuilderPrivate::fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods) +void AbstractMetaBuilderPrivate::fixArgumentNames(const AbstractMetaFunctionPtr &func, + const FunctionModificationList &mods) { AbstractMetaArgumentList &arguments = func->arguments(); @@ -1919,7 +1914,7 @@ static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &na // Apply the <array> modifications of the arguments static bool applyArrayArgumentModifications(const FunctionModificationList &functionMods, - AbstractMetaFunction *func, + const AbstractMetaFunctionPtr &func, QString *errorMessage) { for (const FunctionModification &mod : functionMods) { @@ -1980,13 +1975,14 @@ void AbstractMetaBuilderPrivate::rejectFunction(const FunctionModelItem &functio m_rejectedFunctions.insert({reason, signatureWithType, sortKey, rejectReason}); } -AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const FunctionModelItem &functionItem, - const AbstractMetaClassPtr ¤tClass) +AbstractMetaFunctionPtr + AbstractMetaBuilderPrivate::traverseFunction(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass) { const auto *tdb = TypeDatabase::instance(); if (!functionItem->templateParameters().isEmpty()) - return nullptr; + return {}; if (functionItem->isDeleted()) { switch (functionItem->functionType()) { @@ -2000,7 +1996,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio default: break; } - return nullptr; + return {}; } const QString &functionName = functionItem->name(); const QString className = currentClass != nullptr ? @@ -2010,7 +2006,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio // Skip enum helpers generated by Q_ENUM if ((currentClass == nullptr || currentClass->isNamespace()) && (functionName == u"qt_getEnumMetaObject" || functionName == u"qt_getEnumName")) { - return nullptr; + return {}; } // Clang: Skip qt_metacast(), qt_metacall(), expanded from Q_OBJECT @@ -2018,10 +2014,10 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (currentClass != nullptr) { if (functionName == u"qt_check_for_QGADGET_macro" || functionName.startsWith(u"qt_meta")) { - return nullptr; + return {}; } if (functionName == u"metaObject" && className != u"QObject") - return nullptr; + return {}; } } // PySide extensions @@ -2029,7 +2025,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (tdb->isFunctionRejected(className, functionName, &rejectReason)) { rejectFunction(functionItem, currentClass, AbstractMetaBuilder::GenerationDisabled, rejectReason); - return nullptr; + return {}; } const QString &signature = functionSignature(functionItem); @@ -2040,22 +2036,22 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).", qPrintable(className), qPrintable(signature), qPrintable(rejectReason)); } - return nullptr; + return {}; } if (functionItem->isFriend()) - return nullptr; + return {}; const auto cppAttributes = functionItem->attributes(); const bool deprecated = cppAttributes.testFlag(FunctionAttribute::Deprecated); if (deprecated && m_skipDeprecated) { rejectFunction(functionItem, currentClass, AbstractMetaBuilder::GenerationDisabled, u" is deprecated."_s); - return nullptr; + return {}; } AbstractMetaFunction::Flags flags; - auto *metaFunction = new AbstractMetaFunction(functionName); + auto metaFunction = std::make_shared<AbstractMetaFunction>(functionName); metaFunction->setCppAttributes(cppAttributes); const QByteArray cSignature = signature.toUtf8(); const QString unresolvedSignature = @@ -2088,8 +2084,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (tdb->isReturnTypeRejected(className, returnType.toString(), &rejectReason)) { rejectFunction(functionItem, currentClass, AbstractMetaBuilder::GenerationDisabled, rejectReason); - delete metaFunction; - return nullptr; + return {}; } TranslateTypeFlags flags; @@ -2103,8 +2098,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio qPrintable(msgSkippingFunction(functionItem, signature, reason))); rejectFunction(functionItem, currentClass, AbstractMetaBuilder::UnmatchedReturnType, reason); - delete metaFunction; - return nullptr; + return {}; } metaFunction->setType(type.value()); @@ -2135,8 +2129,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (tdb->isArgumentTypeRejected(className, arg->type().toString(), &rejectReason)) { rejectFunction(functionItem, currentClass, AbstractMetaBuilder::GenerationDisabled, rejectReason); - delete metaFunction; - return nullptr; + return {}; } TranslateTypeFlags flags; @@ -2163,8 +2156,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio qPrintable(msgSkippingFunction(functionItem, signature, reason))); rejectFunction(functionItem, currentClass, AbstractMetaBuilder::UnmatchedArgumentType, reason); - delete metaFunction; - return nullptr; + return {}; } auto metaType = metaTypeO.value(); @@ -2187,8 +2179,8 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio AbstractMetaArgumentList &metaArguments = metaFunction->arguments(); const FunctionModificationList functionMods = currentClass - ? AbstractMetaFunction::findClassModifications(metaFunction, currentClass) - : AbstractMetaFunction::findGlobalModifications(metaFunction); + ? AbstractMetaFunction::findClassModifications(metaFunction.get(), currentClass) + : AbstractMetaFunction::findGlobalModifications(metaFunction.get()); applyCachedFunctionModifications(metaFunction, functionMods); @@ -2213,7 +2205,7 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio && metaFunction->argumentName(i + 1, false, currentClass).isEmpty()) { qCWarning(lcShiboken, "%s", qPrintable(msgUnnamedArgumentDefaultExpression(currentClass, i + 1, - className, metaFunction))); + className, metaFunction.get()))); } } @@ -3128,80 +3120,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); - } - } 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; + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); } 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; @@ -3291,7 +3306,7 @@ AbstractMetaFunctionPtr } QString errorMessage; - if (!applyArrayArgumentModifications(f->modifications(subclass), f.get(), + if (!applyArrayArgumentModifications(f->modifications(subclass), f, &errorMessage)) { qCWarning(lcShiboken, "While specializing %s (%s): %s", qPrintable(subclass->name()), qPrintable(templateClass->name()), 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..4e337339e 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h @@ -98,20 +98,21 @@ public: const QSet<QString> &enumsDeclarations); void traverseEnums(const ScopeModelItem &item, const AbstractMetaClassPtr &parent, const QStringList &enumsDeclarations); - AbstractMetaFunctionRawPtrList classFunctionList(const ScopeModelItem &scopeItem, - AbstractMetaClass::Attributes *constructorAttributes, - const AbstractMetaClassPtr ¤tClass); + AbstractMetaFunctionList classFunctionList(const ScopeModelItem &scopeItem, + AbstractMetaClass::Attributes *constructorAttributes, + const AbstractMetaClassPtr ¤tClass); void traverseFunctions(const ScopeModelItem& item, const AbstractMetaClassPtr &parent); - static void applyFunctionModifications(AbstractMetaFunction *func); + static void applyFunctionModifications(const AbstractMetaFunctionPtr &func); void traverseFields(const ScopeModelItem &item, const AbstractMetaClassPtr &parent); bool traverseStreamOperator(const FunctionModelItem &functionItem, const AbstractMetaClassPtr ¤tClass); void traverseOperatorFunction(const FunctionModelItem &item, const AbstractMetaClassPtr ¤tClass); - AbstractMetaFunction *traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, - const AbstractMetaClassPtr &metaClass, - QString *errorMessage); + AbstractMetaFunctionPtr + traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, + const AbstractMetaClassPtr &metaClass, + QString *errorMessage); bool traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc, QString *errorMessage); bool traverseAddedMemberFunction(const AddedFunctionPtr &addedFunc, @@ -121,8 +122,9 @@ public: const AbstractMetaClassPtr ¤tClass, AbstractMetaBuilder::RejectReason reason, const QString &rejectReason); - AbstractMetaFunction *traverseFunction(const FunctionModelItem &function, - const AbstractMetaClassPtr ¤tClass); + AbstractMetaFunctionPtr + traverseFunction(const FunctionModelItem &function, + const AbstractMetaClassPtr ¤tClass); std::optional<AbstractMetaField> traverseField(const VariableModelItem &field, const AbstractMetaClassCPtr &cls); void checkFunctionModifications() const; @@ -142,7 +144,7 @@ public: * said class. * \param metaFunction conversion operator function to be fixed. */ - static void fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction); + static void fixReturnTypeOfConversionOperator(const AbstractMetaFunctionPtr &metaFunction); void parseQ_Properties(const AbstractMetaClassPtr &metaClass, const QStringList &declarations); @@ -190,8 +192,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, @@ -213,7 +214,8 @@ public: void sortLists(); void setInclude(const TypeEntryPtr &te, const QString &path) const; - static void fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods); + static void fixArgumentNames(const AbstractMetaFunctionPtr &func, + const FunctionModificationList &mods); void fillAddedFunctions(const AbstractMetaClassPtr &metaClass); AbstractMetaClassCPtr resolveTypeSystemTypeDef(const AbstractMetaType &t) const; diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h b/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h index 802f549cf..27321ca2d 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h +++ b/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h @@ -30,6 +30,7 @@ using AbstractMetaEnumValueList = QList<AbstractMetaEnumValue>; using AbstractMetaFieldList = QList<AbstractMetaField>; using AbstractMetaFunctionRawPtrList = QList<AbstractMetaFunction *>; using AbstractMetaFunctionCList = QList<AbstractMetaFunctionCPtr>; +using AbstractMetaFunctionList = QList<AbstractMetaFunctionPtr>; using AbstractMetaTypeList = QList<AbstractMetaType>; using UsingMembers = QList<UsingMember>; 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..786cd0783 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,15 @@ 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; + + // FIXME PYSIDE 7: Make global scope the default behavior? + // Note: Also requires changing SmartPointerTypeEntry::getTargetName() + // to not strip namespaces from unnamed instances. + const bool globalScope = name.startsWith("::"_L1); + if (globalScope) { + name.remove(0, 2); + parentTypeEntry = typeSystemTypeEntry(ste); + } auto colonPos = name.lastIndexOf(u"::"); const bool withinNameSpace = colonPos != -1; @@ -617,19 +624,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..5a433bbeb 100644 --- a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp @@ -7,6 +7,7 @@ #include "enumtypeentry.h" #include "flagstypeentry.h" +#include "smartpointertypeentry.h" ApiExtractorResult::ApiExtractorResult() = default; @@ -50,6 +51,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..170595660 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; @@ -980,3 +995,10 @@ QString msgInvalidLanguageLevel(const QString &l) { return u"Invalid argument for language level: \""_s + l + u"\"."_s; } + +QString msgCannotFindImage(const QString &href, const QString &context, + const QString &candidate) +{ + return "Cannot resolve image "_L1 + href + " for "_L1 + context + + " (tried "_L1 + QDir::toNativeSeparators(candidate) + ")."_L1; +} diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index 2899cbdfa..5216b26a7 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); @@ -257,4 +261,7 @@ QString msgMissingProjectFileMarker(const QString &name, const QByteArray &start QString msgInvalidLanguageLevel(const QString &l); +QString msgCannotFindImage(const QString &href, const QString &context, + const QString &candidate); + #endif // MESSAGES_H 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/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index f65b79bb4..e979c4ee2 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -721,6 +721,9 @@ found in the code will be generated. The type name might optionally be followed an equal sign and the Python type name, for example ``instantiations="int=IntPtr,double=DoublePtr"``. It is also possible to specify a namespace delimited by ``::``. +By default, the type will be in the namespace of the smart pointer, +for example, ``std`` for ``std::shared_ptr``. Preceding +the type name by ``::`` causes it to be in the global namespace. The *optional* attribute **type** specifies the 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/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index 2797ff254..1634a7e83 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -970,8 +970,10 @@ static QStringList enumListToToc(const AbstractMetaEnumList &enums) static QChar sortKey(const QString &key) { const auto size = key.size(); - if (size >= 2 && (key.at(0) == u'Q' || key.at(0) == u'q') && key.at(1).isUpper()) - return key.at(1); // "QClass" -> 'C', "qSin()" -> 'S' + if (size >= 2 && (key.at(0) == u'Q' || key.at(0) == u'q') + && (key.at(1).isUpper() || key.at(1).isDigit())) { + return key.at(1); // "QClass" -> 'C', "qSin()" -> 'S', 'Q3DSurfaceWidget' -> '3' + } if (size >= 3 && key.startsWith("Q_"_L1)) return key.at(2).toUpper(); // "Q_ARG" -> 'A' if (size >= 4 && key.startsWith("QT_"_L1)) @@ -1564,3 +1566,26 @@ QtXmlToSphinxLink QtDocGenerator::resolveLink(const QtXmlToSphinxLink &link) con } return resolved; } + +QtXmlToSphinxDocGeneratorInterface::Image + QtDocGenerator::resolveImage(const QString &href, const QString &context) const +{ + QString relativeSourceDir = href; + const QString source = m_options.parameters.docDataDir + u'/' + relativeSourceDir; + if (!QFileInfo::exists(source)) + throw Exception(msgCannotFindImage(href, context,source)); + + // Determine target directory from context, "Pyside2.QtGui.QPainter" ->"Pyside2/QtGui". + // FIXME: Not perfect yet, should have knowledge about namespaces (DataVis3D) or + // nested classes "Pyside2.QtGui.QTouchEvent.QTouchPoint". + QString relativeTargetDir = context; + const auto lastDot = relativeTargetDir.lastIndexOf(u'.'); + if (lastDot != -1) + relativeTargetDir.truncate(lastDot); + relativeTargetDir.replace(u'.', u'/'); + if (!relativeTargetDir.isEmpty()) + relativeTargetDir += u'/'; + relativeTargetDir += href; + + return {relativeSourceDir, relativeTargetDir}; +} diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.h b/sources/shiboken6/generator/qtdoc/qtdocgenerator.h index 3b1c82e74..56e15e2a1 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.h +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.h @@ -48,6 +48,7 @@ public: const QString &methodName) const override; const QLoggingCategory &loggingCategory() const override; QtXmlToSphinxLink resolveLink(const QtXmlToSphinxLink &) const override; + Image resolveImage(const QString &href, const QString &context) const override; static QString getFuncName(const AbstractMetaFunctionCPtr &cppFunc); static QString formatArgs(const AbstractMetaFunctionCPtr &func); diff --git a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp index 55c1d2090..b8fec836c 100644 --- a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp +++ b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp @@ -1240,36 +1240,17 @@ WebXmlTag QtXmlToSphinx::parentTag() const // Copy images that are placed in a subdirectory "images" under the webxml files // by qdoc to a matching subdirectory under the "rst/PySide6/<module>" directory -static bool copyImage(const QString &href, const QString &docDataDir, - const QString &context, const QString &outputDir, +static bool copyImage(const QString &docDataDir, const QString &relativeSourceFile, + const QString &outputDir, const QString &relativeTargetFile, const QLoggingCategory &lc, QString *errorMessage) { - const QChar slash = u'/'; - const auto lastSlash = href.lastIndexOf(slash); - const QString imagePath = lastSlash != -1 ? href.left(lastSlash) : QString(); - const QString imageFileName = lastSlash != -1 ? href.right(href.size() - lastSlash - 1) : href; - QFileInfo imageSource(docDataDir + slash + href); - if (!imageSource.exists()) { - QTextStream(errorMessage) << "Image " << href << " does not exist in " - << QDir::toNativeSeparators(docDataDir); - return false; - } - // Determine directory from context, "Pyside2.QtGui.QPainter" ->"Pyside2/QtGui". - // FIXME: Not perfect yet, should have knowledge about namespaces (DataVis3D) or - // nested classes "Pyside2.QtGui.QTouchEvent.QTouchPoint". - QString relativeTargetDir = context; - const auto lastDot = relativeTargetDir.lastIndexOf(u'.'); - if (lastDot != -1) - relativeTargetDir.truncate(lastDot); - relativeTargetDir.replace(u'.', slash); - if (!imagePath.isEmpty()) - relativeTargetDir += slash + imagePath; - - const QString targetDir = outputDir + slash + relativeTargetDir; - const QString targetFileName = targetDir + slash + imageFileName; + QString targetFileName = outputDir + u'/' + relativeTargetFile; if (QFileInfo::exists(targetFileName)) return true; - if (!QFileInfo::exists(targetDir)) { + + QString relativeTargetDir = relativeTargetFile; + relativeTargetDir.truncate(qMax(relativeTargetDir.lastIndexOf(u'/'), qsizetype(0))); + if (!relativeTargetDir.isEmpty() && !QFileInfo::exists(outputDir + u'/' + relativeTargetDir)) { const QDir outDir(outputDir); if (!outDir.mkpath(relativeTargetDir)) { QTextStream(errorMessage) << "Cannot create " << QDir::toNativeSeparators(relativeTargetDir) @@ -1278,28 +1259,29 @@ static bool copyImage(const QString &href, const QString &docDataDir, } } - QFile source(imageSource.absoluteFilePath()); + QFile source(docDataDir + u'/' + relativeSourceFile); if (!source.copy(targetFileName)) { QTextStream(errorMessage) << "Cannot copy " << QDir::toNativeSeparators(source.fileName()) << " to " << QDir::toNativeSeparators(targetFileName) << ": " << source.errorString(); return false; } - qCDebug(lc).noquote().nospace() << __FUNCTION__ << " href=\"" - << href << "\", context=\"" << context << "\", docDataDir=\"" - << docDataDir << "\", outputDir=\"" << outputDir << "\", copied \"" - << source.fileName() << "\"->\"" << targetFileName << '"'; + + qCDebug(lc).noquote().nospace() << __FUNCTION__ << " \"" << relativeSourceFile + << "\"->\"" << relativeTargetFile << '"'; return true; } bool QtXmlToSphinx::copyImage(const QString &href) const { QString errorMessage; - const bool result = - ::copyImage(href, m_parameters.docDataDir, m_context, - m_parameters.outputDirectory, - m_generator->loggingCategory(), - &errorMessage); + const auto imagePaths = m_generator->resolveImage(href, m_context); + const bool result = ::copyImage(m_parameters.docDataDir, + imagePaths.source, + m_parameters.outputDirectory, + imagePaths.target, + m_generator->loggingCategory(), + &errorMessage); if (!result) throw Exception(errorMessage); return result; diff --git a/sources/shiboken6/generator/qtdoc/qtxmltosphinxinterface.h b/sources/shiboken6/generator/qtdoc/qtxmltosphinxinterface.h index 16eefad83..d4a098a12 100644 --- a/sources/shiboken6/generator/qtdoc/qtxmltosphinxinterface.h +++ b/sources/shiboken6/generator/qtdoc/qtxmltosphinxinterface.h @@ -53,6 +53,15 @@ public: virtual QtXmlToSphinxLink resolveLink(const QtXmlToSphinxLink &) const = 0; + // Resolve images paths relative to doc data directory/output directory. + struct Image + { + QString source; + QString target; + }; + + virtual Image resolveImage(const QString &href, const QString &context) const = 0; + virtual ~QtXmlToSphinxDocGeneratorInterface() = default; }; diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 6c9cc5fec..97a38a08d 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -59,6 +59,7 @@ using namespace Qt::StringLiterals; static const char shibokenErrorsOccurred[] = "Shiboken::Errors::occurred() != nullptr"; static constexpr auto virtualMethodStaticReturnVar = "result"_L1; +static constexpr auto initFuncPrefix = "init_"_L1; static constexpr auto sbkObjectTypeF = "SbkObject_TypeF()"_L1; static const char initInheritanceFunction[] = "initInheritance"; @@ -1337,7 +1338,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"; @@ -1598,11 +1600,17 @@ static void writePointerToPythonConverter(TextStream &c, c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n" << "if (pyOut) {\n" << indent << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent - << "}\n" - << "auto *tCppIn = reinterpret_cast<const " << typeName << R"( *>(cppIn); -const char *typeName = )"; + << "}\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 @@ -5098,6 +5106,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 { @@ -5109,17 +5128,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(); @@ -5176,7 +5200,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; @@ -5595,6 +5619,9 @@ void CppGenerator::writeClassRegister(TextStream &s, writeConverterRegister(s, metaClass, classContext); s << '\n'; + if (classContext.forSmartPointer()) + writeSmartPointerConverterInitialization(s, classContext.preciseType()); + // class inject-code target/beginning if (!classTypeEntry->codeSnips().isEmpty()) { writeClassCodeSnips(s, classTypeEntry->codeSnips(), @@ -6030,16 +6057,20 @@ void CppGenerator::writeNbBoolFunction(const GeneratorContext &context, // Write declaration and invocation of the init function for the module init // function. -void CppGenerator::writeInitFunc(TextStream &declStr, TextStream &callStr, - const QString &initFunctionName, - const TypeEntryCPtr &enclosingEntry, - const QString &pythonName, bool lazy) +static void writeInitFuncDecl(TextStream &declStr, + const QString &functionName) { - const QString functionName = "init_"_L1 + initFunctionName; - const bool hasParent = enclosingEntry && enclosingEntry->type() != TypeEntry::TypeSystemType; - declStr << "PyTypeObject *" << functionName << "(PyObject *" - << (hasParent ? "enclosingClass" : "module") << ");\n"; + declStr << "PyTypeObject *" << functionName << "(PyObject *enclosing);\n"; +} +// Write declaration and invocation of the init function for the module init +// function. +void CppGenerator::writeInitFuncCall(TextStream &callStr, + const QString &functionName, + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy) +{ + const bool hasParent = enclosingEntry && enclosingEntry->type() != TypeEntry::TypeSystemType; if (!lazy) { const QString enclosing = hasParent ? "reinterpret_cast<PyObject *>("_L1 + cpythonTypeNameExt(enclosingEntry) + u')' @@ -6048,18 +6079,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"; } } @@ -6116,9 +6143,10 @@ bool CppGenerator::finishGeneration() s_classInitDecl << te->configCondition() << '\n'; s_classPythonDefines << te->configCondition() << '\n'; } - writeInitFunc(s_classInitDecl, s_classPythonDefines, - getSimpleClassInitFunctionName(cls), - targetLangEnclosingEntry(te), cls->name()); + const QString initFunc = initFuncPrefix + getSimpleClassInitFunctionName(cls); + writeInitFuncDecl(s_classInitDecl, initFunc); + writeInitFuncCall(s_classPythonDefines, initFunc, + targetLangEnclosingEntry(te), cls->name()); if (cls->hasStaticFields()) { s_classInitDecl << "PyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(cls) << "(PyObject *module);\n"; @@ -6135,13 +6163,12 @@ 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()); + const QString initFunc = initFuncPrefix + getInitFunctionName(context); + writeInitFuncDecl(s_classInitDecl, initFunc); + writeInitFuncCall(s_classPythonDefines, + initFunc, enclosingTypeEntry, smp.specialized->name()); includes.insert(smp.type.instantiations().constFirst().typeEntry()->include()); } @@ -6335,18 +6362,6 @@ bool CppGenerator::finishGeneration() s << '\n'; } - // Implicit smart pointers conversions - const auto &smartPointersList = api().instantiatedSmartPointers(); - if (!smartPointersList.isEmpty()) { - s << "// SmartPointers converters.\n\n"; - for (const auto &smp : smartPointersList) { - s << "// C++ to Python conversion for smart pointer type '" - << smp.type.cppSignature() << "'.\n"; - writeSmartPointerConverterFunctions(s, smp.type); - } - s << '\n'; - } - s << "static struct PyModuleDef moduledef = {\n" << " /* m_base */ PyModuleDef_HEAD_INIT,\n" << " /* m_name */ \"" << moduleName() << "\",\n" @@ -6405,9 +6420,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" @@ -6456,14 +6476,6 @@ bool CppGenerator::finishGeneration() s << '\n'; } - if (!smartPointersList.isEmpty()) { - s << '\n'; - for (const auto &smp : smartPointersList) { - writeSmartPointerConverterInitialization(s, smp.type); - s << '\n'; - } - } - if (!extendedConverters.isEmpty()) { s << '\n'; for (ExtendedConverterData::const_iterator it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) { diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index a31c2ca14..5920c9a3a 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -60,10 +60,10 @@ private: void generateIncludes(TextStream &s, const GeneratorContext &classContext, const IncludeGroupList &includes = {}, const AbstractMetaClassCList &innerClasses = {}) const; - static void writeInitFunc(TextStream &declStr, TextStream &callStr, - const QString &initFunctionName, - const TypeEntryCPtr &enclosingEntry, - const QString &pythonName, bool lazy = true); + static void writeInitFuncCall(TextStream &callStr, + const QString &functionName, + const TypeEntryCPtr &enclosingEntry, + const QString &pythonName, bool lazy = true); static void writeCacheResetNative(TextStream &s, const GeneratorContext &classContext); void writeConstructorNative(TextStream &s, const GeneratorContext &classContext, const AbstractMetaFunctionCPtr &func) const; @@ -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..44b76f181 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(); @@ -225,6 +213,8 @@ void CppGenerator::generateSmartPointerClass(TextStream &s, const GeneratorConte s << '\n'; writeConverterFunctions(s, metaClass, classContext); + // Implicit smart pointers conversions + writeSmartPointerConverterFunctions(s, classContext.preciseType()); writeClassRegister(s, metaClass, classContext, signatureStream); // class inject-code native/end @@ -252,8 +242,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 +298,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..7cec9c38e 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -198,9 +198,6 @@ void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s, const auto typeEntry = metaClass->typeEntry(); InheritedOverloadSet inheritedOverloads; - // write license comment - s << licenseComment(); - // Class s << "class " << wrapperName << " : public " << metaClass->qualifiedCppName() @@ -706,8 +703,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 c11fbbfb2..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> @@ -771,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() @@ -786,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(); @@ -980,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()); @@ -1466,7 +1482,8 @@ static PyObject *newObjectWithHeuristicsHelper(PyTypeObject *instanceType, // Try type discovery in these cases. if (exactType == nullptr || exactType == instanceType) { auto resolved = BindingManager::instance().findDerivedType(cptr, instanceType); - if (resolved.first != nullptr) { + if (resolved.first != nullptr + && Shiboken::ObjectType::canDowncastTo(instanceType, resolved.first)) { exactType = resolved.first; cptr = resolved.second; } diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index f926107e2..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)); } 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/sbkenum.cpp b/sources/shiboken6/libshiboken/sbkenum.cpp index d39369979..4c0597bda 100644 --- a/sources/shiboken6/libshiboken/sbkenum.cpp +++ b/sources/shiboken6/libshiboken/sbkenum.cpp @@ -3,6 +3,7 @@ #include "sbkenum.h" #include "sbkstring.h" +#include "helper.h" #include "sbkstaticstrings.h" #include "sbkstaticstrings_p.h" #include "sbkconverter.h" @@ -306,6 +307,8 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule, enumName = PyDict_GetItem(sotp->enumTypeDict, name); } + SBK_UNUSED(getPyEnumMeta()); // enforce PyEnumModule creation + assert(PyEnumModule != nullptr); AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, enumName)); assert(PyEnumType.object()); bool isFlag = PyObject_IsSubclass(PyEnumType, PyFlag); 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/sbkfeature_base.cpp b/sources/shiboken6/libshiboken/sbkfeature_base.cpp index f31b8f4f7..971835c53 100644 --- a/sources/shiboken6/libshiboken/sbkfeature_base.cpp +++ b/sources/shiboken6/libshiboken/sbkfeature_base.cpp @@ -92,8 +92,12 @@ void disassembleFrame(const char *marker) PyErr_Restore(error_type, error_value, error_traceback); } -// python 3.12 -static int const CALL = 171; +// Python 3.13 +static int const LOAD_ATTR_313 = 82; +static int const CALL_313 = 53; +static int const PUSH_NULL_313 = 34; +// Python 3.12 +static int const CALL_312 = 171; // Python 3.11 static int const PRECALL = 166; // we have "big instructions" with gaps after them @@ -105,13 +109,16 @@ static int const LOAD_METHOD = 160; static int const CALL_METHOD = 161; // Python 3.6 static int const CALL_FUNCTION = 131; -static int const LOAD_ATTR = 106; +static int const LOAD_ATTR_312 = 106; // NoGil (how long will this exist in this form?) static int const LOAD_METHOD_NOGIL = 55; static int const CALL_METHOD_NOGIL = 72; static bool currentOpcode_Is_CallMethNoArgs() { + static auto number = _PepRuntimeVersion(); + static int LOAD_ATTR = number < 0x030D00 ? LOAD_ATTR_312 : LOAD_ATTR_313; + static int CALL = number < 0x030D00 ? CALL_312 : CALL_313; // PYSIDE-2221: Special case for the NoGil version: // Find out if we have such a version. // We could also ask the variable `Py_NOGIL`. @@ -148,7 +155,6 @@ static bool currentOpcode_Is_CallMethNoArgs() } uint8_t opcode2 = co_code[f_lasti + 2]; uint8_t oparg2 = co_code[f_lasti + 3]; - static auto number = _PepRuntimeVersion(); if (number < 0x030B00) return opcode1 == LOAD_METHOD && opcode2 == CALL_METHOD && oparg2 == 0; @@ -158,7 +164,7 @@ static bool currentOpcode_Is_CallMethNoArgs() // don't need to take care of them. if (opcode1 == LOAD_METHOD) f_lasti += LOAD_METHOD_GAP_311; - else if (opcode1 == LOAD_ATTR) + else if (opcode1 == LOAD_ATTR_312) f_lasti += LOAD_ATTR_GAP_311; else return false; @@ -176,6 +182,11 @@ static bool currentOpcode_Is_CallMethNoArgs() else return false; + if (number >= 0x030D00) { + int opcode3 = co_code[f_lasti + 2]; + if (opcode3 == PUSH_NULL_313) + f_lasti += 2; + } opcode2 = co_code[f_lasti + 2]; oparg2 = co_code[f_lasti + 3]; diff --git a/sources/shiboken6/libshiboken/sbkmodule.cpp b/sources/shiboken6/libshiboken/sbkmodule.cpp index 4153df27f..a94fbe279 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; } @@ -208,6 +248,9 @@ static PyMethodDef module_methods[] = { // Python 3.8 - 3.12 static int const LOAD_CONST_312 = 100; static int const IMPORT_NAME_312 = 108; +// Python 3.13 +static int const LOAD_CONST_313 = 83; +static int const IMPORT_NAME_313 = 75; static bool isImportStar(PyObject *module) { @@ -220,6 +263,9 @@ static bool isImportStar(PyObject *module) static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts"); static PyObject *const _co_names = Shiboken::String::createStaticString("co_names"); + static int LOAD_CONST = _PepRuntimeVersion() < 0x030D00 ? LOAD_CONST_312 : LOAD_CONST_313; + static int IMPORT_NAME = _PepRuntimeVersion() < 0x030D00 ? IMPORT_NAME_312 : IMPORT_NAME_313; + auto *obFrame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); if (obFrame == nullptr) return true; // better assume worst-case. @@ -239,7 +285,7 @@ static bool isImportStar(PyObject *module) PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len); uint8_t opcode2 = co_code[f_lasti]; uint8_t opcode1 = co_code[f_lasti - 2]; - if (opcode1 == LOAD_CONST_312 && opcode2 == IMPORT_NAME_312) { + if (opcode1 == LOAD_CONST && opcode2 == IMPORT_NAME) { uint8_t oparg1 = co_code[f_lasti - 1]; uint8_t oparg2 = co_code[f_lasti + 1]; AutoDecRef dec_co_consts(PyObject_GetAttr(dec_f_code, _co_consts)); @@ -260,8 +306,6 @@ static bool isImportStar(PyObject *module) // PYSIDE-2404: These modules produce ambiguous names which we cannot handle, yet. static std::unordered_set<std::string> dontLazyLoad{ - "sample", - "smart", "testbinding" }; @@ -292,24 +336,22 @@ static bool shouldLazyLoad(PyObject *module) return std::strncmp(modName, "PySide6.", 8) == 0; } -void AddTypeCreationFunction(PyObject *module, - const char *name, - TypeCreationFunction func) +static int lazyLoadDefault() { - static const char *flag = getenv("PYSIDE6_OPTION_LAZY"); - static const int value = flag != nullptr ? std::atoi(flag) : 1; +#ifndef PYPY_VERSION + int result = 1; +#else + int result = 0; +#endif + if (auto *flag = getenv("PYSIDE6_OPTION_LAZY")) + result = std::atoi(flag); + return result; +} - // - 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; +void checkIfShouldLoadImmediately(PyObject *module, const std::string &name, + const NameToTypeFunctionMap &nameToFunc) +{ + static const int value = lazyLoadDefault(); // PYSIDE-2404: Lazy Loading // @@ -319,56 +361,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) @@ -441,11 +483,11 @@ PyObject *create(const char * /* modName */, void *moduleData) // Install the getattr patch. origModuleGetattro = PyModule_Type.tp_getattro; PyModule_Type.tp_getattro = PyModule_lazyGetAttro; - // Add the lazy import redirection. + // Add the lazy import redirection, keeping a reference. origImportFunc = PyDict_GetItemString(builtins, "__import__"); - auto *func = PyCFunction_NewEx(lazy_methods, nullptr, nullptr); + Py_INCREF(origImportFunc); + AutoDecRef func(PyCFunction_NewEx(lazy_methods, nullptr, nullptr)); PyDict_SetItemString(builtins, "__import__", func); - // Everything is set. lazy_init = true; } // PYSIDE-2404: Nuitka inserts some additional code in standalone mode @@ -455,6 +497,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/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 cd9910687..9f90c43a7 100644 --- a/sources/shiboken6/tests/libother/othermultiplederived.h +++ b/sources/shiboken6/tests/libother/othermultiplederived.h @@ -6,6 +6,7 @@ #include "libothermacros.h" #include "multiple_derived.h" +#include "objecttype.h" #include "virtualmethods.h" class ObjectType; 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..472f807f2 100644 --- a/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp +++ b/sources/shiboken6/tests/libsmart/stdsharedptrtestbench.cpp @@ -51,6 +51,46 @@ void StdSharedPtrTestBench::printInt(const std::shared_ptr<int> &p) std::cerr << '\n'; } +std::shared_ptr<double> StdSharedPtrTestBench::createDouble(double v) +{ + return std::make_shared<double>(v); +} + +std::shared_ptr<double> StdSharedPtrTestBench::createNullDouble() +{ + return {}; +} + +void StdSharedPtrTestBench::printDouble(const std::shared_ptr<double> &p) +{ + std::cerr << __FUNCTION__ << ' '; + if (p.get()) + std::cerr << *p; + else + std::cerr << "nullptr"; + 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..9d4c207b5 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,14 @@ 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<double> createDouble(double v = 42); + static std::shared_ptr<double> createNullDouble(); + static void printDouble(const std::shared_ptr<double> &); + + 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/qtxmltosphinx/main.cpp b/sources/shiboken6/tests/qtxmltosphinx/main.cpp index 27aaee7d1..5b0624376 100644 --- a/sources/shiboken6/tests/qtxmltosphinx/main.cpp +++ b/sources/shiboken6/tests/qtxmltosphinx/main.cpp @@ -40,6 +40,7 @@ public: const QString &) const override; const QLoggingCategory &loggingCategory() const override; QtXmlToSphinxLink resolveLink(const QtXmlToSphinxLink &link) const override; + Image resolveImage(const QString &href, const QString &) const; }; // QtXmlToSphinxDocGeneratorInterface @@ -63,11 +64,18 @@ const QLoggingCategory &QtXmlToSphinxDocGenerator::loggingCategory() const return lcQtXmlToSphinx(); } -QtXmlToSphinxLink QtXmlToSphinxDocGenerator::resolveLink(const QtXmlToSphinxLink &link) const +QtXmlToSphinxLink + QtXmlToSphinxDocGenerator::resolveLink(const QtXmlToSphinxLink &link) const { return link; } +QtXmlToSphinxDocGeneratorInterface::Image + QtXmlToSphinxDocGenerator::resolveImage(const QString &href, const QString &) const +{ + return {href, href}; +} + static bool run(const QString &fileName) { QtXmlToSphinxDocGenerator generator; diff --git a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp index 45cecd1a1..3ba77196f 100644 --- a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp +++ b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.cpp @@ -39,6 +39,12 @@ QtXmlToSphinxLink QtXmlToSphinxTest::resolveLink(const QtXmlToSphinxLink &link) return link; } +QtXmlToSphinxDocGeneratorInterface::Image + QtXmlToSphinxTest::resolveImage(const QString &href, const QString &) const +{ + return {href, href}; +} + QString QtXmlToSphinxTest::transformXml(const QString &xml) const { return QtXmlToSphinx(this, m_parameters, xml).result(); diff --git a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.h b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.h index 0a210b7a0..5108ef452 100644 --- a/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.h +++ b/sources/shiboken6/tests/qtxmltosphinxtest/qtxmltosphinxtest.h @@ -19,6 +19,7 @@ public: const QString &) const override; const QLoggingCategory &loggingCategory() const override; QtXmlToSphinxLink resolveLink(const QtXmlToSphinxLink &link) const override; + Image resolveImage(const QString &href, const QString &context) const override; private slots: void testTable_data(); diff --git a/sources/shiboken6/tests/smartbinding/CMakeLists.txt b/sources/shiboken6/tests/smartbinding/CMakeLists.txt index 2e729321e..594744840 100644 --- a/sources/shiboken6/tests/smartbinding/CMakeLists.txt +++ b/sources/shiboken6/tests/smartbinding/CMakeLists.txt @@ -18,8 +18,10 @@ ${CMAKE_CURRENT_BINARY_DIR}/smart/smart_integer2_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/sharedptr_integer2_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrtestbench_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/smart/stdsharedptrvirtualmethodtester_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/smart/std_shared_ptr_double_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..a37a307a5 100644 --- a/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py +++ b/sources/shiboken6/tests/smartbinding/std_shared_ptr_test.py @@ -10,7 +10,7 @@ from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from smart import Integer, StdSharedPtrTestBench, StdSharedPtrVirtualMethodTester, std +from smart import Integer, StdDoublePtr, StdSharedPtrTestBench, StdSharedPtrVirtualMethodTester, std def call_func_on_ptr(ptr): @@ -49,6 +49,24 @@ class StdSharedPtrTests(unittest.TestCase): self.assertFalse(np) p = StdSharedPtrTestBench.createInt() StdSharedPtrTestBench.printInt(p) + ip = std.StdIntPtr(42) + StdSharedPtrTestBench.printInt(ip) + + def testDouble(self): + np = StdSharedPtrTestBench.createNullDouble() + StdSharedPtrTestBench.printDouble(np) + self.assertFalse(np) + p = StdSharedPtrTestBench.createDouble(67) + StdSharedPtrTestBench.printDouble(p) + dp = StdDoublePtr(42) + StdSharedPtrTestBench.printDouble(dp) + + 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..e479e4ddf 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,double=::StdDoublePtr,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", |