From fa854d325ad4fa5f6e788d70b3ba9ccf9ee5c80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 3 Jul 2024 10:55:32 +0200 Subject: [PATCH 1/6] Support Qt6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero --- CMakeLists.txt | 1 + cmake/pyside_config.py | 342 ++++++++++++++++++++++++++++++++++++ cmake/shiboken_helper.cmake | 148 +++++++++++++--- 3 files changed, 462 insertions(+), 29 deletions(-) create mode 100755 cmake/pyside_config.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 919969e..e3ddec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ install(FILES cmake/shiboken_helper.cmake cmake/sip_configure.py cmake/sip_helper.cmake + cmake/pyside_config.py DESTINATION share/${PROJECT_NAME}/cmake) if(BUILD_TESTING) diff --git a/cmake/pyside_config.py b/cmake/pyside_config.py new file mode 100755 index 0000000..73342fc --- /dev/null +++ b/cmake/pyside_config.py @@ -0,0 +1,342 @@ +#!/usr/bin/python3 +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sysconfig +from enum import Enum +import glob +import os +import re +import sys + + +PYSIDE = 'pyside6' +PYSIDE_MODULE = 'PySide6' +SHIBOKEN = 'shiboken6' + + +class Package(Enum): + SHIBOKEN_MODULE = 1 + SHIBOKEN_GENERATOR = 2 + PYSIDE_MODULE = 3 + + +generic_error = ('Did you forget to activate your virtualenv? Or perhaps' + f' you forgot to build / install {PYSIDE_MODULE} into your currently active Python' + ' environment?') +pyside_error = f'Unable to locate {PYSIDE_MODULE}. {generic_error}' +shiboken_module_error = f'Unable to locate {SHIBOKEN}-module. {generic_error}' +shiboken_generator_error = f'Unable to locate shiboken-generator. {generic_error}' +pyside_libs_error = f'Unable to locate the PySide shared libraries. {generic_error}' +python_link_error = 'Unable to locate the Python library for linking.' +python_include_error = 'Unable to locate the Python include headers directory.' + +options = [] + +# option, function, error, description +options.append(("--shiboken-module-path", + lambda: find_shiboken_module(), + shiboken_module_error, + "Print shiboken module location")) +options.append(("--shiboken-generator-path", + lambda: find_shiboken_generator(), + shiboken_generator_error, + "Print shiboken generator location")) +options.append(("--pyside-path", lambda: find_pyside(), pyside_error, + f"Print {PYSIDE_MODULE} location")) + +options.append(("--python-include-path", + lambda: get_python_include_path(), + python_include_error, + "Print Python include path")) +options.append(("--shiboken-generator-include-path", + lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR), + pyside_error, + "Print shiboken generator include paths")) +options.append(("--pyside-include-path", + lambda: get_package_include_path(Package.PYSIDE_MODULE), + pyside_error, + "Print PySide6 include paths")) + +options.append(("--python-link-flags-qmake", lambda: python_link_flags_qmake(), python_link_error, + "Print python link flags for qmake")) +options.append(("--python-link-flags-cmake", lambda: python_link_flags_cmake(), python_link_error, + "Print python link flags for cmake")) + +options.append(("--shiboken-module-qmake-lflags", + lambda: get_package_qmake_lflags(Package.SHIBOKEN_MODULE), pyside_error, + "Print shiboken6 shared library link flags for qmake")) +options.append(("--pyside-qmake-lflags", + lambda: get_package_qmake_lflags(Package.PYSIDE_MODULE), pyside_error, + "Print PySide6 shared library link flags for qmake")) + +options.append(("--shiboken-module-shared-libraries-qmake", + lambda: get_shared_libraries_qmake(Package.SHIBOKEN_MODULE), pyside_libs_error, + "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for qmake")) +options.append(("--shiboken-module-shared-libraries-cmake", + lambda: get_shared_libraries_cmake(Package.SHIBOKEN_MODULE), pyside_libs_error, + "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for cmake")) + +options.append(("--pyside-shared-libraries-qmake", + lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error, + "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " + "for qmake")) +options.append(("--pyside-shared-libraries-cmake", + lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error, + f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " + "for cmake")) + +options_usage = '' +for i, (flag, _, _, description) in enumerate(options): + options_usage += f' {flag:<45} {description}' + if i < len(options) - 1: + options_usage += '\n' + +usage = f""" +Utility to determine include/link options of shiboken/PySide and Python for qmake/CMake projects +that would like to embed or build custom shiboken/PySide bindings. + +Usage: pyside_config.py [option] +Options: +{options_usage} + -a Print all options and their values + --help/-h Print this help +""" + +option = sys.argv[1] if len(sys.argv) == 2 else '-a' +if option == '-h' or option == '--help': + print(usage) + sys.exit(0) + + +def clean_path(path): + return path if sys.platform != 'win32' else path.replace('\\', '/') + + +def shared_library_suffix(): + if sys.platform == 'win32': + return 'lib' + elif sys.platform == 'darwin': + return 'dylib' + # Linux + else: + return 'so.*' + + +def import_suffixes(): + import importlib.machinery + return importlib.machinery.EXTENSION_SUFFIXES + + +def is_debug(): + debug_suffix = '_d.pyd' if sys.platform == 'win32' else '_d.so' + return any([s.endswith(debug_suffix) for s in import_suffixes()]) + + +def shared_library_glob_pattern(): + glob = '*.' + shared_library_suffix() + return glob if sys.platform == 'win32' else 'lib' + glob + + +def filter_shared_libraries(libs_list): + def predicate(lib_name): + basename = os.path.basename(lib_name) + if 'shiboken' in basename or 'pyside6' in basename: + return True + return False + result = [lib for lib in libs_list if predicate(lib)] + return result + + +# Return qmake link option for a library file name +def link_option(lib): + # On Linux: + # Since we cannot include symlinks with wheel packages + # we are using an absolute path for the libpyside and libshiboken + # libraries when compiling the project + baseName = os.path.basename(lib) + link = ' -l' + if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so' + link = lib + elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo' + link += os.path.splitext(baseName[3:])[0] + else: # Windows: 'libfoo.dll' -> 'libfoo.dll' + link += os.path.splitext(baseName)[0] + return link + + +# Locate PySide6 via sys.path package path. +def find_pyside(): + return find_package_path(PYSIDE_MODULE) + + +def find_shiboken_module(): + return find_package_path(SHIBOKEN) + + +def find_shiboken_generator(): + return find_package_path(f"{SHIBOKEN}_generator") + + +def find_package(which_package): + if which_package == Package.SHIBOKEN_MODULE: + return find_shiboken_module() + if which_package == Package.SHIBOKEN_GENERATOR: + return find_shiboken_generator() + if which_package == Package.PYSIDE_MODULE: + return find_pyside() + return None + + +def find_package_path(dir_name): + for p in sys.path: + if 'site-' in p or 'dist-' in p: + package = os.path.join(p, dir_name) + if os.path.exists(package): + return clean_path(os.path.realpath(package)) + return None + + +# Return version as "x.y" (e.g. 3.9, 3.12, etc) +def python_version(): + return str(sys.version_info[0]) + '.' + str(sys.version_info[1]) + + +def get_python_include_path(): + return sysconfig.get_path('include') + + +def python_link_flags_qmake(): + flags = python_link_data() + if sys.platform == 'win32': + libdir = flags['libdir'] + # This will add the "~1" shortcut for directories that + # contain white spaces + # e.g.: "Program Files" to "Progra~1" + for d in libdir.split("\\"): + if " " in d: + libdir = libdir.replace(d, d.split(" ")[0][:-1] + "~1") + lib_flags = flags['lib'] + return f'-L{libdir} -l{lib_flags}' + elif sys.platform == 'darwin': + libdir = flags['libdir'] + lib_flags = flags['lib'] + return f'-L{libdir} -l{lib_flags}' + else: + # Linux and anything else + libdir = flags['libdir'] + lib_flags = flags['lib'] + return f'-L{libdir} -l{lib_flags}' + + +def python_link_flags_cmake(): + flags = python_link_data() + libdir = flags['libdir'] + lib = re.sub(r'.dll$', '.lib', flags['lib']) + return f'{libdir};{lib}' + + +def python_link_data(): + # @TODO Fix to work with static builds of Python + libdir = sysconfig.get_config_var('LIBDIR') + if libdir is None: + libdir = os.path.abspath(os.path.join( + sysconfig.get_config_var('LIBDEST'), "..", "libs")) + version = python_version() + version_no_dots = version.replace('.', '') + + flags = {} + flags['libdir'] = libdir + if sys.platform == 'win32': + suffix = '_d' if is_debug() else '' + flags['lib'] = f'python{version_no_dots}{suffix}' + + elif sys.platform == 'darwin': + flags['lib'] = f'python{version}' + + # Linux and anything else + else: + flags['lib'] = f'python{version}{sys.abiflags}' + + return flags + + +def get_package_include_path(which_package): + print('which_package ', which_package) + package_path = find_package(which_package) + if package_path is None: + return None + + includes = f"{package_path}/include" + + return includes + + +def get_package_qmake_lflags(which_package): + package_path = find_package(which_package) + if package_path is None: + return None + + link = f"-L{package_path}" + glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + for lib in filter_shared_libraries(glob_result): + link += ' ' + link += link_option(lib) + return link + + +def get_shared_libraries_data(which_package): + package_path = find_package(which_package) + if package_path is None: + return None + + glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + filtered_libs = filter_shared_libraries(glob_result) + libs = [] + if sys.platform == 'win32': + for lib in filtered_libs: + libs.append(os.path.realpath(lib)) + else: + for lib in filtered_libs: + libs.append(lib) + return libs + + +def get_shared_libraries_qmake(which_package): + libs = get_shared_libraries_data(which_package) + if libs is None: + return None + + if sys.platform == 'win32': + if not libs: + return '' + dlls = '' + for lib in libs: + dll = os.path.splitext(lib)[0] + '.dll' + dlls += dll + ' ' + + return dlls + else: + libs_string = '' + for lib in libs: + libs_string += lib + ' ' + return libs_string + + +def get_shared_libraries_cmake(which_package): + libs = get_shared_libraries_data(which_package) + result = ';'.join(libs) + return result + + +print_all = option == "-a" +for argument, handler, error, _ in options: + if option == argument or print_all: + handler_result = handler() + if handler_result is None: + sys.exit(error) + + line = handler_result + if print_all: + line = f"{argument:<40}: {line}" + print(line) diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake index 7624157..ad7561a 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -21,45 +21,99 @@ if(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED) endif() set(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED TRUE) +find_package(QT NAMES Qt5 Qt6 REQUIRED) + # In CMake 3.27 and later, FindPythonInterp and FindPythonLibs are deprecated. # However, Shiboken2 as packaged in Ubuntu 24.04 still use them, so set CMP0148 to # "OLD" to silence this warning. if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27.0") cmake_policy(SET CMP0148 OLD) endif() -find_package(Shiboken2 QUIET) -if(Shiboken2_FOUND) - message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") - if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") - get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) - set(SHIBOKEN_BINARY Shiboken2::shiboken2) + +if(${QT_VERSION_MAJOR} GREATER "5") + # Macro to get various pyside / python include / link flags and paths. + # Uses the not entirely supported utils/pyside_config.py file. + + # Use provided python interpreter if given. + if(NOT python_interpreter) + find_program(python_interpreter "python3") + if(NOT python_interpreter) + message(FATAL_ERROR + "No Python interpreter could be found. Make sure python is in PATH.") + endif() + endif() + message(STATUS "Using python interpreter: ${python_interpreter}") + + macro(pyside_config option output_var) + if(${ARGC} GREATER 2) + set(is_list ${ARGV2}) + else() + set(is_list "") + endif() + + find_package(python_qt_binding REQUIRED) + message("Alex " ${python_interpreter} ${python_qt_binding_DIR}/pyside_config.py + ${option} ) + + execute_process( + COMMAND ${python_interpreter} ${python_qt_binding_DIR}/pyside_config.py + ${option} + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if ("${${output_var}}" STREQUAL "") + message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.") + endif() + if(is_list) + string (REPLACE " " ";" ${output_var} "${${output_var}}") + endif() + endmacro() + + pyside_config(--pyside-shared-libraries-cmake pyside6_lib) + pyside_config(--pyside-include-path pyside6_includes) + pyside_config(--shiboken-module-shared-libraries-cmake shiboken6_lib) + pyside_config(--shiboken-generator-include-path shiboken6_includes) + pyside_config(--shiboken-generator-path shiboken6_generator_path) + + set(PYSIDE_LIBRARY pyside6_lib) + set(PYSIDE_INCLUDE_DIR pyside6_includes) + set(SHIBOKEN_LIBRARY shiboken6_lib) + set(SHIBOKEN_INCLUDE_DIR shiboken6_includes) + set(SHIBOKEN_BINARY "${shiboken6_generator_path}/shiboken6") +else() + find_package(Shiboken2 QUIET) + if(Shiboken2_FOUND) + message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") + if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") + get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) + set(SHIBOKEN_BINARY Shiboken2::shiboken2) + endif() + message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") + message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") + message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") endif() - message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") - message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") - message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") -endif() -find_package(PySide2 QUIET) -if(PySide2_FOUND) - message(STATUS "Found PySide2 version ${PySide2_VERSION}") - if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") - get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) + find_package(PySide2 QUIET) + if(PySide2_FOUND) + message(STATUS "Found PySide2 version ${PySide2_VERSION}") + if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") + get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) + endif() + message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") + message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") endif() - message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") - message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") -endif() -if(Shiboken2_FOUND AND PySide2_FOUND) - message(STATUS "Shiboken binding generator available.") - set(shiboken_helper_FOUND TRUE) -else() - message(STATUS "Shiboken binding generator NOT available.") - set(shiboken_helper_NOTFOUND TRUE) + if(Shiboken2_FOUND AND PySide2_FOUND) + message(STATUS "Shiboken binding generator available.") + set(shiboken_helper_FOUND TRUE) + else() + message(STATUS "Shiboken binding generator NOT available.") + set(shiboken_helper_NOTFOUND TRUE) + endif() endif() - macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) # Add includes from current directory, Qt, PySide and compiler specific dirs get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) @@ -78,10 +132,35 @@ macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) --enable-pyside-extensions -std=c++17 --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS} - --typesystem-paths=${PYSIDE_TYPESYSTEMS} - --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM}) + --typesystem-paths="/usr/local/lib/python3.12/dist-packages/PySide6/typesystems/" + --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM} + --no-suppress-warnings) endmacro() +macro(_shiboken_generator_command6 VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) + # Add includes from current directory, Qt, PySide and compiler specific dirs + get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) + list(APPEND SHIBOKEN_HELPER_INCLUDE_DIRS + ${QT_INCLUDE_DIR} + ${PYSIDE_INCLUDE_DIR} + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + # See ticket https://code.ros.org/trac/ros-pkg/ticket/5219 + set(SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS "") + foreach(dir ${SHIBOKEN_HELPER_INCLUDE_DIRS}) + set(SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS "${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS}:${dir}") + endforeach() + string(REPLACE ";" ":" INCLUDE_PATH_WITH_COLONS "${INCLUDE_PATH}") + set(${VAR} ${SHIBOKEN_BINARY} + --generator-set=shiboken + --enable-pyside-extensions + -std=c++17 + --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS} + --typesystem-paths="/usr/local/lib/python3.12/dist-packages/PySide6/typesystems/" + --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM} + --enable-parent-ctor-heuristic + --enable-return-value-heuristic --use-isnull-as-nb-bool + --avoid-protected-hack) +endmacro() # # Run the Shiboken generator. @@ -115,6 +194,17 @@ function(shiboken_generator PROJECT_NAME GLOBAL TYPESYSTEM WORKING_DIR GENERATED ) endfunction() +function(shiboken_generator6 PROJECT_NAME GLOBAL TYPESYSTEM WORKING_DIR GENERATED_SRCS HDRS INCLUDE_PATH BUILD_DIR) + _shiboken_generator_command6(COMMAND "${GLOBAL}" "${TYPESYSTEM}" "${INCLUDE_PATH}" "${BUILD_DIR}") + message("comand ${COMMAND} ") + add_custom_command( + OUTPUT ${GENERATED_SRCS} + COMMAND ${COMMAND} + DEPENDS ${GLOBAL} ${TYPESYSTEM} ${HDRS} + WORKING_DIRECTORY ${WORKING_DIR} + COMMENT "Running Shiboken generator for ${PROJECT_NAME} Python bindings..." + ) +endfunction() # # Add the Shiboken/PySide specific include directories. From bd88c0d5d51add58e329c40bba20a7b04c3df063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 3 Jul 2024 13:48:11 +0200 Subject: [PATCH 2/6] make linters happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero --- cmake/pyside_config.py | 90 ++++++++++++++++++------------------- cmake/shiboken_helper.cmake | 7 +-- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/cmake/pyside_config.py b/cmake/pyside_config.py index 73342fc..85cfeb9 100755 --- a/cmake/pyside_config.py +++ b/cmake/pyside_config.py @@ -2,12 +2,12 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -import sysconfig from enum import Enum -import glob +from glob import glob import os import re import sys +import sysconfig PYSIDE = 'pyside6' @@ -22,8 +22,8 @@ class Package(Enum): generic_error = ('Did you forget to activate your virtualenv? Or perhaps' - f' you forgot to build / install {PYSIDE_MODULE} into your currently active Python' - ' environment?') + f' you forgot to build / install {PYSIDE_MODULE} into your currently' + ' active Python environment?') pyside_error = f'Unable to locate {PYSIDE_MODULE}. {generic_error}' shiboken_module_error = f'Unable to locate {SHIBOKEN}-module. {generic_error}' shiboken_generator_error = f'Unable to locate shiboken-generator. {generic_error}' @@ -34,57 +34,57 @@ class Package(Enum): options = [] # option, function, error, description -options.append(("--shiboken-module-path", +options.append(('--shiboken-module-path', lambda: find_shiboken_module(), shiboken_module_error, - "Print shiboken module location")) -options.append(("--shiboken-generator-path", + 'Print shiboken module location')) +options.append(('--shiboken-generator-path', lambda: find_shiboken_generator(), shiboken_generator_error, - "Print shiboken generator location")) -options.append(("--pyside-path", lambda: find_pyside(), pyside_error, - f"Print {PYSIDE_MODULE} location")) + 'Print shiboken generator location')) +options.append(('--pyside-path', lambda: find_pyside(), pyside_error, + f'Print {PYSIDE_MODULE} location')) -options.append(("--python-include-path", +options.append(('--python-include-path', lambda: get_python_include_path(), python_include_error, - "Print Python include path")) -options.append(("--shiboken-generator-include-path", + 'Print Python include path')) +options.append(('--shiboken-generator-include-path', lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR), pyside_error, - "Print shiboken generator include paths")) -options.append(("--pyside-include-path", + 'Print shiboken generator include paths')) +options.append(('--pyside-include-path', lambda: get_package_include_path(Package.PYSIDE_MODULE), pyside_error, - "Print PySide6 include paths")) + 'Print PySide6 include paths')) -options.append(("--python-link-flags-qmake", lambda: python_link_flags_qmake(), python_link_error, - "Print python link flags for qmake")) -options.append(("--python-link-flags-cmake", lambda: python_link_flags_cmake(), python_link_error, - "Print python link flags for cmake")) +options.append(('--python-link-flags-qmake', lambda: python_link_flags_qmake(), python_link_error, + 'Print python link flags for qmake')) +options.append(('--python-link-flags-cmake', lambda: python_link_flags_cmake(), python_link_error, + 'Print python link flags for cmake')) -options.append(("--shiboken-module-qmake-lflags", +options.append(('--shiboken-module-qmake-lflags', lambda: get_package_qmake_lflags(Package.SHIBOKEN_MODULE), pyside_error, - "Print shiboken6 shared library link flags for qmake")) -options.append(("--pyside-qmake-lflags", + 'Print shiboken6 shared library link flags for qmake')) +options.append(('--pyside-qmake-lflags', lambda: get_package_qmake_lflags(Package.PYSIDE_MODULE), pyside_error, - "Print PySide6 shared library link flags for qmake")) + 'Print PySide6 shared library link flags for qmake')) -options.append(("--shiboken-module-shared-libraries-qmake", +options.append(('--shiboken-module-shared-libraries-qmake', lambda: get_shared_libraries_qmake(Package.SHIBOKEN_MODULE), pyside_libs_error, "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for qmake")) -options.append(("--shiboken-module-shared-libraries-cmake", +options.append(('--shiboken-module-shared-libraries-cmake', lambda: get_shared_libraries_cmake(Package.SHIBOKEN_MODULE), pyside_libs_error, "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for cmake")) -options.append(("--pyside-shared-libraries-qmake", +options.append(('--pyside-shared-libraries-qmake', lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error, "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " - "for qmake")) -options.append(("--pyside-shared-libraries-cmake", + 'for qmake')) +options.append(('--pyside-shared-libraries-cmake', lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error, f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " - "for cmake")) + 'for cmake')) options_usage = '' for i, (flag, _, _, description) in enumerate(options): @@ -130,7 +130,7 @@ def import_suffixes(): def is_debug(): debug_suffix = '_d.pyd' if sys.platform == 'win32' else '_d.so' - return any([s.endswith(debug_suffix) for s in import_suffixes()]) + return any(s.endswith(debug_suffix) for s in import_suffixes()) def shared_library_glob_pattern(): @@ -175,7 +175,7 @@ def find_shiboken_module(): def find_shiboken_generator(): - return find_package_path(f"{SHIBOKEN}_generator") + return find_package_path(f'{SHIBOKEN}_generator') def find_package(which_package): @@ -197,7 +197,7 @@ def find_package_path(dir_name): return None -# Return version as "x.y" (e.g. 3.9, 3.12, etc) +# Return version as 'x.y' (e.g. 3.9, 3.12, etc) def python_version(): return str(sys.version_info[0]) + '.' + str(sys.version_info[1]) @@ -210,12 +210,12 @@ def python_link_flags_qmake(): flags = python_link_data() if sys.platform == 'win32': libdir = flags['libdir'] - # This will add the "~1" shortcut for directories that + # This will add the '~1' shortcut for directories that # contain white spaces - # e.g.: "Program Files" to "Progra~1" - for d in libdir.split("\\"): - if " " in d: - libdir = libdir.replace(d, d.split(" ")[0][:-1] + "~1") + # e.g.: 'Program Files' to 'Progra~1' + for d in libdir.split('\\'): + if ' ' in d: + libdir = libdir.replace(d, d.split(' ')[0][:-1] + '~1') lib_flags = flags['lib'] return f'-L{libdir} -l{lib_flags}' elif sys.platform == 'darwin': @@ -241,7 +241,7 @@ def python_link_data(): libdir = sysconfig.get_config_var('LIBDIR') if libdir is None: libdir = os.path.abspath(os.path.join( - sysconfig.get_config_var('LIBDEST'), "..", "libs")) + sysconfig.get_config_var('LIBDEST'), '..', 'libs')) version = python_version() version_no_dots = version.replace('.', '') @@ -267,7 +267,7 @@ def get_package_include_path(which_package): if package_path is None: return None - includes = f"{package_path}/include" + includes = f'{package_path}/include' return includes @@ -277,8 +277,8 @@ def get_package_qmake_lflags(which_package): if package_path is None: return None - link = f"-L{package_path}" - glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + link = f'-L{package_path}' + glob_result = glob(os.path.join(package_path, shared_library_glob_pattern())) for lib in filter_shared_libraries(glob_result): link += ' ' link += link_option(lib) @@ -290,7 +290,7 @@ def get_shared_libraries_data(which_package): if package_path is None: return None - glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + glob_result = glob(os.path.join(package_path, shared_library_glob_pattern())) filtered_libs = filter_shared_libraries(glob_result) libs = [] if sys.platform == 'win32': @@ -329,7 +329,7 @@ def get_shared_libraries_cmake(which_package): return result -print_all = option == "-a" +print_all = option == '-a' for argument, handler, error, _ in options: if option == argument or print_all: handler_result = handler() @@ -338,5 +338,5 @@ def get_shared_libraries_cmake(which_package): line = handler_result if print_all: - line = f"{argument:<40}: {line}" + line = f'{argument:<40}: {line}' print(line) diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake index ad7561a..afae499 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -52,20 +52,17 @@ if(${QT_VERSION_MAJOR} GREATER "5") endif() find_package(python_qt_binding REQUIRED) - message("Alex " ${python_interpreter} ${python_qt_binding_DIR}/pyside_config.py - ${option} ) - execute_process( COMMAND ${python_interpreter} ${python_qt_binding_DIR}/pyside_config.py ${option} OUTPUT_VARIABLE ${output_var} OUTPUT_STRIP_TRAILING_WHITESPACE) - if ("${${output_var}}" STREQUAL "") + if("${${output_var}}" STREQUAL "") message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.") endif() if(is_list) - string (REPLACE " " ";" ${output_var} "${${output_var}}") + string(REPLACE " " ";" ${output_var} "${${output_var}}") endif() endmacro() From d710e1afb2ac0effed1e8d6ab90eee53354366bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 3 Jul 2024 14:16:36 +0200 Subject: [PATCH 3/6] Fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero --- cmake/pyside_config.py | 1 - cmake/shiboken_helper.cmake | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cmake/pyside_config.py b/cmake/pyside_config.py index 85cfeb9..2282e43 100755 --- a/cmake/pyside_config.py +++ b/cmake/pyside_config.py @@ -262,7 +262,6 @@ def python_link_data(): def get_package_include_path(which_package): - print('which_package ', which_package) package_path = find_package(which_package) if package_path is None: return None diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake index afae499..b8d6c61 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -72,11 +72,12 @@ if(${QT_VERSION_MAJOR} GREATER "5") pyside_config(--shiboken-generator-include-path shiboken6_includes) pyside_config(--shiboken-generator-path shiboken6_generator_path) - set(PYSIDE_LIBRARY pyside6_lib) - set(PYSIDE_INCLUDE_DIR pyside6_includes) - set(SHIBOKEN_LIBRARY shiboken6_lib) - set(SHIBOKEN_INCLUDE_DIR shiboken6_includes) + set(PYSIDE_LIBRARY ${pyside6_lib}) + set(PYSIDE_INCLUDE_DIR ${pyside6_includes}) + set(SHIBOKEN_LIBRARY ${shiboken6_lib}) + set(SHIBOKEN_INCLUDE_DIR ${shiboken6_includes};${shiboken6_generator_path}/include) set(SHIBOKEN_BINARY "${shiboken6_generator_path}/shiboken6") + set(shiboken_helper_FOUND TRUE) else() find_package(Shiboken2 QUIET) if(Shiboken2_FOUND) @@ -86,9 +87,6 @@ else() get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) set(SHIBOKEN_BINARY Shiboken2::shiboken2) endif() - message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") - message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") - message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") endif() find_package(PySide2 QUIET) @@ -98,8 +96,6 @@ else() get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) endif() - message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") - message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") endif() if(Shiboken2_FOUND AND PySide2_FOUND) @@ -111,6 +107,12 @@ else() endif() endif() +message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") +message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") +message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") +message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") +message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") + macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) # Add includes from current directory, Qt, PySide and compiler specific dirs get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) From bd6e9d75524d9bd7489038876033bdddb570a18b Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Wed, 4 Feb 2026 11:45:46 +0100 Subject: [PATCH 4/6] updates Signed-off-by: Alejandro Hernandez Cordero --- cmake/shiboken_helper.cmake | 66 +++++++++++++------------ cmake/sip_configure.py | 34 ++++++------- cmake/sip_helper.cmake | 7 ++- package.xml | 4 +- src/python_qt_binding/__init__.py | 5 ++ src/python_qt_binding/binding_helper.py | 35 ++++++------- 6 files changed, 81 insertions(+), 70 deletions(-) diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake index b8d6c61..acd3855 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -29,8 +29,8 @@ find_package(QT NAMES Qt5 Qt6 REQUIRED) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27.0") cmake_policy(SET CMP0148 OLD) endif() - -if(${QT_VERSION_MAJOR} GREATER "5") +# set(QT_VERSION_MAJOR 6) +# if(${QT_VERSION_MAJOR} GREATER "5") # Macro to get various pyside / python include / link flags and paths. # Uses the not entirely supported utils/pyside_config.py file. @@ -78,34 +78,34 @@ if(${QT_VERSION_MAJOR} GREATER "5") set(SHIBOKEN_INCLUDE_DIR ${shiboken6_includes};${shiboken6_generator_path}/include) set(SHIBOKEN_BINARY "${shiboken6_generator_path}/shiboken6") set(shiboken_helper_FOUND TRUE) -else() - find_package(Shiboken2 QUIET) - if(Shiboken2_FOUND) - message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") - if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") - get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) - set(SHIBOKEN_BINARY Shiboken2::shiboken2) - endif() - endif() +# else() +# find_package(Shiboken2 QUIET) +# if(Shiboken2_FOUND) +# message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") +# if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") +# get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +# get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) +# set(SHIBOKEN_BINARY Shiboken2::shiboken2) +# endif() +# endif() - find_package(PySide2 QUIET) - if(PySide2_FOUND) - message(STATUS "Found PySide2 version ${PySide2_VERSION}") - if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") - get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) - endif() - endif() +# find_package(PySide2 QUIET) +# if(PySide2_FOUND) +# message(STATUS "Found PySide2 version ${PySide2_VERSION}") +# if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") +# get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +# get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) +# endif() +# endif() - if(Shiboken2_FOUND AND PySide2_FOUND) - message(STATUS "Shiboken binding generator available.") - set(shiboken_helper_FOUND TRUE) - else() - message(STATUS "Shiboken binding generator NOT available.") - set(shiboken_helper_NOTFOUND TRUE) - endif() -endif() +# if(Shiboken2_FOUND AND PySide2_FOUND) +# message(STATUS "Shiboken binding generator available.") +# set(shiboken_helper_FOUND TRUE) +# else() +# message(STATUS "Shiboken binding generator NOT available.") +# set(shiboken_helper_NOTFOUND TRUE) +# endif() +# endif() message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") @@ -116,6 +116,7 @@ message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) # Add includes from current directory, Qt, PySide and compiler specific dirs get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) + message("**** SHIBOKEN_HELPER_INCLUDE_DIRS " ${SHIBOKEN_HELPER_INCLUDE_DIRS}) list(APPEND SHIBOKEN_HELPER_INCLUDE_DIRS ${QT_INCLUDE_DIR} ${PYSIDE_INCLUDE_DIR} @@ -149,12 +150,13 @@ macro(_shiboken_generator_command6 VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) set(SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS "${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS}:${dir}") endforeach() string(REPLACE ";" ":" INCLUDE_PATH_WITH_COLONS "${INCLUDE_PATH}") + message(SHIBOKEN_BINARY ${SHIBOKEN_BINARY}) set(${VAR} ${SHIBOKEN_BINARY} --generator-set=shiboken --enable-pyside-extensions - -std=c++17 - --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS} - --typesystem-paths="/usr/local/lib/python3.12/dist-packages/PySide6/typesystems/" + --language-level=c++17 + --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS}:/home/ahcorde/ros2_rolling/qt6-env/lib/python3.12/site-packages/shiboken6/include + --typesystem-paths="${PYSIDE_INCLUDE_DIR}/../typesystems/" --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM} --enable-parent-ctor-heuristic --enable-return-value-heuristic --use-isnull-as-nb-bool @@ -226,7 +228,7 @@ function(shiboken_include_directories PROJECT_NAME QT_COMPONENTS) set(shiboken_INCLUDE_DIRECTORIES ${shiboken_INCLUDE_DIRECTORIES} ${PYSIDE_INCLUDE_DIR}/${component}) endforeach() - include_directories(${PROJECT_NAME} ${shiboken_INCLUDE_DIRECTORIES}) + include_directories(${PROJECT_NAME} ${shiboken_INCLUDE_DIRECTORIES} /home/ahcorde/ros2_rolling/qt6-env/lib/python3.12/site-packages/shiboken6/include) endfunction() diff --git a/cmake/sip_configure.py b/cmake/sip_configure.py index 5210ee5..619ed1b 100644 --- a/cmake/sip_configure.py +++ b/cmake/sip_configure.py @@ -6,19 +6,19 @@ import sys import tempfile -import PyQt5 -from PyQt5 import QtCore +import PyQt6 +from PyQt6 import QtCore import sipconfig -libqt5_rename = False +libqt6_rename = False class Configuration(sipconfig.Configuration): def __init__(self): env = copy.copy(os.environ) - env['QT_SELECT'] = '5' - qmake_exe = 'qmake-qt5' if shutil.which('qmake-qt5') else 'qmake' + env['QT_SELECT'] = '6' + qmake_exe = 'qmake-qt6' if shutil.which('qmake-qt6') else 'qmake' qtconfig = subprocess.check_output( [qmake_exe, '-query'], env=env, universal_newlines=True) qtconfig = dict(line.split(':', 1) for line in qtconfig.splitlines()) @@ -36,15 +36,15 @@ def __init__(self): if os.path.exists(os.path.join(qtconfig['QT_INSTALL_LIBS'], 'QtCore.framework')): pyqtconfig['qt_framework'] = 1 else: - global libqt5_rename - libqt5_rename = True + global libqt6_rename + libqt6_rename = True sipconfig.Configuration.__init__(self, [pyqtconfig]) macros = sipconfig._default_macros.copy() macros['INCDIR_QT'] = qtconfig['QT_INSTALL_HEADERS'] macros['LIBDIR_QT'] = qtconfig['QT_INSTALL_LIBS'] - macros['MOC'] = 'moc-qt5' if shutil.which('moc-qt5') else 'moc' + macros['MOC'] = 'moc-qt6' if shutil.which('moc-qt6') else 'moc' self.set_build_macros(macros) @@ -70,20 +70,20 @@ def get_sip_dir_flags(config): candidate_sip_dirs = [] # Archlinux installs sip files here by default - candidate_sip_dirs.append(os.path.join(PyQt5.__path__[0], 'bindings')) + candidate_sip_dirs.append(os.path.join(PyQt6.__path__[0], 'bindings')) # sip4 installs here by default - candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'PyQt5')) + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'PyQt6')) # Homebrew installs sip files here by default - candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'Qt5')) + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'Qt6')) for sip_dir in candidate_sip_dirs: if os.path.exists(sip_dir): return sip_dir, sip_flags - raise FileNotFoundError('The sip directory for PyQt5 could not be located. Please ensure' + - ' that PyQt5 is installed') + raise FileNotFoundError('The sip directory for PyQt6 could not be located. Please ensure' + + ' that PyQt6 is installed') if len(sys.argv) != 8: @@ -177,10 +177,10 @@ def custom_platform_lib_function(self, clib, framework=0): if os.path.isabs(clib) or clib.startswith('-l'): return clib - global libqt5_rename - # sip renames libs to Qt5 automatically on Linux, but not on macOS - if libqt5_rename and not framework and clib.startswith('Qt') and not clib.startswith('Qt5'): - return '-lQt5' + clib[2:] + global libqt6_rename + # sip renames libs to Qt6 automatically on Linux, but not on macOS + if libqt6_rename and not framework and clib.startswith('Qt') and not clib.startswith('Qt6'): + return '-lQt6' + clib[2:] return default_platform_lib_function(self, clib, framework) diff --git a/cmake/sip_helper.cmake b/cmake/sip_helper.cmake index a5ac3c2..a263fea 100644 --- a/cmake/sip_helper.cmake +++ b/cmake/sip_helper.cmake @@ -93,10 +93,13 @@ function(build_sip_binding PROJECT_NAME SIP_FILE) set(LIBRARY_DIRS ${${PROJECT_NAME}_LIBRARY_DIRS}) set(LDFLAGS_OTHER ${${PROJECT_NAME}_LDFLAGS_OTHER}) + make_directory(${SIP_BUILD_DIR}) + add_custom_command( OUTPUT ${SIP_BUILD_DIR}/Makefile - COMMAND ${Python3_EXECUTABLE} ${sip_SIP_CONFIGURE} ${SIP_BUILD_DIR} ${SIP_FILE} ${sip_LIBRARY_DIR} - \"${INCLUDE_DIRS}\" \"${LIBRARIES}\" \"${LIBRARY_DIRS}\" \"${LDFLAGS_OTHER}\" + # COMMAND ${Python3_EXECUTABLE} ${sip_SIP_CONFIGURE} ${SIP_BUILD_DIR} ${SIP_FILE} ${sip_LIBRARY_DIR} + # \"${INCLUDE_DIRS}\" \"${LIBRARIES}\" \"${LIBRARY_DIRS}\" \"${LDFLAGS_OTHER}\" + COMMAND ${Python3_EXECUTABLE} -m sipbuild.tools.build --build-dir ${SIP_BUILD_DIR} --no-compile DEPENDS ${sip_SIP_CONFIGURE} ${SIP_FILE} ${sip_DEPENDS} WORKING_DIRECTORY ${sip_SOURCE_DIR} COMMENT "Running SIP generator for ${PROJECT_NAME} Python bindings..." diff --git a/package.xml b/package.xml index 34703d6..7a454d2 100644 --- a/package.xml +++ b/package.xml @@ -28,7 +28,7 @@ ament_cmake - qtbase5-dev + qt6-base-dev python3-qt5-bindings python3-qt5-bindings @@ -36,7 +36,7 @@ ament_cmake_pytest ament_lint_auto ament_lint_common - + ament_cmake diff --git a/src/python_qt_binding/__init__.py b/src/python_qt_binding/__init__.py index 1e209de..59a8769 100644 --- a/src/python_qt_binding/__init__.py +++ b/src/python_qt_binding/__init__.py @@ -58,6 +58,11 @@ from python_qt_binding.binding_helper import QT_BINDING_MODULES from python_qt_binding.binding_helper import QT_BINDING_VERSION # noqa: F401 +print('QT_BINDING', QT_BINDING) +for module, value in QT_BINDING_MODULES.items(): + print('QT_BINDING_MODULES', module, value) +print('QT_BINDING_VERSION', QT_BINDING_VERSION) + # register binding modules as sub modules of this package (python_qt_binding) for easy importing for module_name, module in QT_BINDING_MODULES.items(): sys.modules[__name__ + '.' + module_name] = module diff --git a/src/python_qt_binding/binding_helper.py b/src/python_qt_binding/binding_helper.py index 27c3237..d5ff4be 100644 --- a/src/python_qt_binding/binding_helper.py +++ b/src/python_qt_binding/binding_helper.py @@ -51,10 +51,11 @@ def _select_qt_binding(binding_name=None, binding_order=None): global QT_BINDING, QT_BINDING_VERSION # order of default bindings can be changed here - if platform.system() == 'Darwin': - DEFAULT_BINDING_ORDER = ['pyside'] - else: - DEFAULT_BINDING_ORDER = ['pyqt', 'pyside'] + DEFAULT_BINDING_ORDER = ['pyside'] + # if platform.system() == 'Darwin': + # DEFAULT_BINDING_ORDER = ['pyside'] + # else: + # DEFAULT_BINDING_ORDER = ['pyside', 'pyqt'] binding_order = binding_order or DEFAULT_BINDING_ORDER @@ -155,9 +156,9 @@ def _load_pyqt(required_modules, optional_modules): # register required and optional PyQt modules for module_name in required_modules: - _named_import('PyQt5.%s' % module_name) + _named_import('PyQt6.%s' % module_name) for module_name in optional_modules: - _named_optional_import('PyQt5.%s' % module_name) + _named_optional_import('PyQt6.%s' % module_name) # set some names for compatibility with PySide sys.modules['QtCore'].Signal = sys.modules['QtCore'].pyqtSignal @@ -166,19 +167,19 @@ def _load_pyqt(required_modules, optional_modules): # try to register Qwt module try: - import PyQt5.Qwt5 - _register_binding_module('Qwt', PyQt5.Qwt5) + import PyQt6.Qwt6 + _register_binding_module('Qwt', PyQt6.Qwt6) except ImportError: pass global _loadUi def _loadUi(uifile, baseinstance=None, custom_widgets_=None): - from PyQt5 import uic + from PyQt6 import uic return uic.loadUi(uifile, baseinstance=baseinstance) - import PyQt5.QtCore - return PyQt5.QtCore.PYQT_VERSION_STR + import PyQt6.QtCore + return PyQt6.QtCore.PYQT_VERSION_STR def _load_pyside(required_modules, optional_modules): @@ -187,9 +188,9 @@ def _load_pyside(required_modules, optional_modules): # register required and optional PySide modules for module_name in required_modules: - _named_import('PySide2.%s' % module_name) + _named_import('PySide6.%s' % module_name) for module_name in optional_modules: - _named_optional_import('PySide2.%s' % module_name) + _named_optional_import('PySide6.%s' % module_name) # set some names for compatibility with PyQt sys.modules['QtCore'].pyqtSignal = sys.modules['QtCore'].Signal @@ -206,8 +207,8 @@ def _load_pyside(required_modules, optional_modules): global _loadUi def _loadUi(uifile, baseinstance=None, custom_widgets=None): - from PySide2.QtUiTools import QUiLoader - from PySide2.QtCore import QMetaObject + from PySide6.QtUiTools import QUiLoader + from PySide6.QtCore import QMetaObject class CustomUiLoader(QUiLoader): class_aliases = { @@ -253,8 +254,8 @@ def createWidget(self, class_name, parent=None, name=''): QMetaObject.connectSlotsByName(ui) return ui - import PySide2 - return PySide2.__version__ + import PySide6 + return PySide6.__version__ def loadUi(uifile, baseinstance=None, custom_widgets=None): From 2ef407b5a5dc0a714e72f2fd2fec778b12d73dbd Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Thu, 12 Feb 2026 10:35:02 +0100 Subject: [PATCH 5/6] updates Signed-off-by: Alejandro Hernandez Cordero --- cmake/shiboken_helper.cmake | 153 ++++++++---------------------------- cmake/sip_configure.py | 36 ++++----- cmake/sip_helper.cmake | 8 +- 3 files changed, 53 insertions(+), 144 deletions(-) diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake index acd3855..008ec9f 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -21,102 +21,48 @@ if(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED) endif() set(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED TRUE) -find_package(QT NAMES Qt5 Qt6 REQUIRED) - # In CMake 3.27 and later, FindPythonInterp and FindPythonLibs are deprecated. # However, Shiboken2 as packaged in Ubuntu 24.04 still use them, so set CMP0148 to # "OLD" to silence this warning. if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27.0") cmake_policy(SET CMP0148 OLD) endif() -# set(QT_VERSION_MAJOR 6) -# if(${QT_VERSION_MAJOR} GREATER "5") - # Macro to get various pyside / python include / link flags and paths. - # Uses the not entirely supported utils/pyside_config.py file. - - # Use provided python interpreter if given. - if(NOT python_interpreter) - find_program(python_interpreter "python3") - if(NOT python_interpreter) - message(FATAL_ERROR - "No Python interpreter could be found. Make sure python is in PATH.") - endif() +find_package(Shiboken2 QUIET) +if(Shiboken2_FOUND) + message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") + if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") + get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) + set(SHIBOKEN_BINARY Shiboken2::shiboken2) endif() - message(STATUS "Using python interpreter: ${python_interpreter}") - - macro(pyside_config option output_var) - if(${ARGC} GREATER 2) - set(is_list ${ARGV2}) - else() - set(is_list "") - endif() - - find_package(python_qt_binding REQUIRED) - execute_process( - COMMAND ${python_interpreter} ${python_qt_binding_DIR}/pyside_config.py - ${option} - OUTPUT_VARIABLE ${output_var} - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if("${${output_var}}" STREQUAL "") - message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.") - endif() - if(is_list) - string(REPLACE " " ";" ${output_var} "${${output_var}}") - endif() - endmacro() + message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") + message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") + message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") +endif() - pyside_config(--pyside-shared-libraries-cmake pyside6_lib) - pyside_config(--pyside-include-path pyside6_includes) - pyside_config(--shiboken-module-shared-libraries-cmake shiboken6_lib) - pyside_config(--shiboken-generator-include-path shiboken6_includes) - pyside_config(--shiboken-generator-path shiboken6_generator_path) +find_package(PySide2 QUIET) +if(PySide2_FOUND) + message(STATUS "Found PySide2 version ${PySide2_VERSION}") + if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") + get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) + endif() + message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") + message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") +endif() - set(PYSIDE_LIBRARY ${pyside6_lib}) - set(PYSIDE_INCLUDE_DIR ${pyside6_includes}) - set(SHIBOKEN_LIBRARY ${shiboken6_lib}) - set(SHIBOKEN_INCLUDE_DIR ${shiboken6_includes};${shiboken6_generator_path}/include) - set(SHIBOKEN_BINARY "${shiboken6_generator_path}/shiboken6") +if(Shiboken2_FOUND AND PySide2_FOUND) + message(STATUS "Shiboken binding generator available.") set(shiboken_helper_FOUND TRUE) -# else() -# find_package(Shiboken2 QUIET) -# if(Shiboken2_FOUND) -# message(STATUS "Found Shiboken2 version ${Shiboken2_VERSION}") -# if(NOT ${Shiboken2_VERSION} VERSION_LESS "5.13") -# get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken2::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES) -# get_property(SHIBOKEN_LIBRARY TARGET Shiboken2::libshiboken PROPERTY LOCATION) -# set(SHIBOKEN_BINARY Shiboken2::shiboken2) -# endif() -# endif() - -# find_package(PySide2 QUIET) -# if(PySide2_FOUND) -# message(STATUS "Found PySide2 version ${PySide2_VERSION}") -# if(NOT ${PySide2_VERSION} VERSION_LESS "5.13") -# get_property(PYSIDE_INCLUDE_DIR TARGET PySide2::pyside2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) -# get_property(PYSIDE_LIBRARY TARGET PySide2::pyside2 PROPERTY LOCATION) -# endif() -# endif() - -# if(Shiboken2_FOUND AND PySide2_FOUND) -# message(STATUS "Shiboken binding generator available.") -# set(shiboken_helper_FOUND TRUE) -# else() -# message(STATUS "Shiboken binding generator NOT available.") -# set(shiboken_helper_NOTFOUND TRUE) -# endif() -# endif() +else() + message(STATUS "Shiboken binding generator NOT available.") + set(shiboken_helper_NOTFOUND TRUE) +endif() -message(STATUS "Using SHIBOKEN_INCLUDE_DIR: ${SHIBOKEN_INCLUDE_DIR}") -message(STATUS "Using SHIBOKEN_LIBRARY: ${SHIBOKEN_LIBRARY}") -message(STATUS "Using SHIBOKEN_BINARY: ${SHIBOKEN_BINARY}") -message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") -message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) # Add includes from current directory, Qt, PySide and compiler specific dirs get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) - message("**** SHIBOKEN_HELPER_INCLUDE_DIRS " ${SHIBOKEN_HELPER_INCLUDE_DIRS}) list(APPEND SHIBOKEN_HELPER_INCLUDE_DIRS ${QT_INCLUDE_DIR} ${PYSIDE_INCLUDE_DIR} @@ -132,36 +78,10 @@ macro(_shiboken_generator_command VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) --enable-pyside-extensions -std=c++17 --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS} - --typesystem-paths="/usr/local/lib/python3.12/dist-packages/PySide6/typesystems/" - --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM} - --no-suppress-warnings) + --typesystem-paths=${PYSIDE_TYPESYSTEMS} + --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM}) endmacro() -macro(_shiboken_generator_command6 VAR GLOBAL TYPESYSTEM INCLUDE_PATH BUILD_DIR) - # Add includes from current directory, Qt, PySide and compiler specific dirs - get_directory_property(SHIBOKEN_HELPER_INCLUDE_DIRS INCLUDE_DIRECTORIES) - list(APPEND SHIBOKEN_HELPER_INCLUDE_DIRS - ${QT_INCLUDE_DIR} - ${PYSIDE_INCLUDE_DIR} - ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - # See ticket https://code.ros.org/trac/ros-pkg/ticket/5219 - set(SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS "") - foreach(dir ${SHIBOKEN_HELPER_INCLUDE_DIRS}) - set(SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS "${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS}:${dir}") - endforeach() - string(REPLACE ";" ":" INCLUDE_PATH_WITH_COLONS "${INCLUDE_PATH}") - message(SHIBOKEN_BINARY ${SHIBOKEN_BINARY}) - set(${VAR} ${SHIBOKEN_BINARY} - --generator-set=shiboken - --enable-pyside-extensions - --language-level=c++17 - --include-paths=${INCLUDE_PATH_WITH_COLONS}${SHIBOKEN_HELPER_INCLUDE_DIRS_WITH_COLONS}:/home/ahcorde/ros2_rolling/qt6-env/lib/python3.12/site-packages/shiboken6/include - --typesystem-paths="${PYSIDE_INCLUDE_DIR}/../typesystems/" - --output-directory=${BUILD_DIR} ${GLOBAL} ${TYPESYSTEM} - --enable-parent-ctor-heuristic - --enable-return-value-heuristic --use-isnull-as-nb-bool - --avoid-protected-hack) -endmacro() # # Run the Shiboken generator. @@ -195,17 +115,6 @@ function(shiboken_generator PROJECT_NAME GLOBAL TYPESYSTEM WORKING_DIR GENERATED ) endfunction() -function(shiboken_generator6 PROJECT_NAME GLOBAL TYPESYSTEM WORKING_DIR GENERATED_SRCS HDRS INCLUDE_PATH BUILD_DIR) - _shiboken_generator_command6(COMMAND "${GLOBAL}" "${TYPESYSTEM}" "${INCLUDE_PATH}" "${BUILD_DIR}") - message("comand ${COMMAND} ") - add_custom_command( - OUTPUT ${GENERATED_SRCS} - COMMAND ${COMMAND} - DEPENDS ${GLOBAL} ${TYPESYSTEM} ${HDRS} - WORKING_DIRECTORY ${WORKING_DIR} - COMMENT "Running Shiboken generator for ${PROJECT_NAME} Python bindings..." - ) -endfunction() # # Add the Shiboken/PySide specific include directories. @@ -228,7 +137,7 @@ function(shiboken_include_directories PROJECT_NAME QT_COMPONENTS) set(shiboken_INCLUDE_DIRECTORIES ${shiboken_INCLUDE_DIRECTORIES} ${PYSIDE_INCLUDE_DIR}/${component}) endforeach() - include_directories(${PROJECT_NAME} ${shiboken_INCLUDE_DIRECTORIES} /home/ahcorde/ros2_rolling/qt6-env/lib/python3.12/site-packages/shiboken6/include) + include_directories(${PROJECT_NAME} ${shiboken_INCLUDE_DIRECTORIES}) endfunction() @@ -253,4 +162,4 @@ function(shiboken_target_link_libraries PROJECT_NAME QT_COMPONENTS) endforeach() target_link_libraries(${PROJECT_NAME} ${shiboken_LINK_LIBRARIES}) -endfunction() +endfunction() \ No newline at end of file diff --git a/cmake/sip_configure.py b/cmake/sip_configure.py index 619ed1b..a25c832 100644 --- a/cmake/sip_configure.py +++ b/cmake/sip_configure.py @@ -6,19 +6,19 @@ import sys import tempfile -import PyQt6 -from PyQt6 import QtCore +import PyQt5 +from PyQt5 import QtCore import sipconfig -libqt6_rename = False +libqt5_rename = False class Configuration(sipconfig.Configuration): def __init__(self): env = copy.copy(os.environ) - env['QT_SELECT'] = '6' - qmake_exe = 'qmake-qt6' if shutil.which('qmake-qt6') else 'qmake' + env['QT_SELECT'] = '5' + qmake_exe = 'qmake-qt5' if shutil.which('qmake-qt5') else 'qmake' qtconfig = subprocess.check_output( [qmake_exe, '-query'], env=env, universal_newlines=True) qtconfig = dict(line.split(':', 1) for line in qtconfig.splitlines()) @@ -36,15 +36,15 @@ def __init__(self): if os.path.exists(os.path.join(qtconfig['QT_INSTALL_LIBS'], 'QtCore.framework')): pyqtconfig['qt_framework'] = 1 else: - global libqt6_rename - libqt6_rename = True + global libqt5_rename + libqt5_rename = True sipconfig.Configuration.__init__(self, [pyqtconfig]) macros = sipconfig._default_macros.copy() macros['INCDIR_QT'] = qtconfig['QT_INSTALL_HEADERS'] macros['LIBDIR_QT'] = qtconfig['QT_INSTALL_LIBS'] - macros['MOC'] = 'moc-qt6' if shutil.which('moc-qt6') else 'moc' + macros['MOC'] = 'moc-qt5' if shutil.which('moc-qt5') else 'moc' self.set_build_macros(macros) @@ -70,20 +70,20 @@ def get_sip_dir_flags(config): candidate_sip_dirs = [] # Archlinux installs sip files here by default - candidate_sip_dirs.append(os.path.join(PyQt6.__path__[0], 'bindings')) + candidate_sip_dirs.append(os.path.join(PyQt5.__path__[0], 'bindings')) # sip4 installs here by default - candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'PyQt6')) + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'PyQt5')) # Homebrew installs sip files here by default - candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'Qt6')) + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'Qt5')) for sip_dir in candidate_sip_dirs: if os.path.exists(sip_dir): return sip_dir, sip_flags - raise FileNotFoundError('The sip directory for PyQt6 could not be located. Please ensure' + - ' that PyQt6 is installed') + raise FileNotFoundError('The sip directory for PyQt5 could not be located. Please ensure' + + ' that PyQt5 is installed') if len(sys.argv) != 8: @@ -177,10 +177,10 @@ def custom_platform_lib_function(self, clib, framework=0): if os.path.isabs(clib) or clib.startswith('-l'): return clib - global libqt6_rename - # sip renames libs to Qt6 automatically on Linux, but not on macOS - if libqt6_rename and not framework and clib.startswith('Qt') and not clib.startswith('Qt6'): - return '-lQt6' + clib[2:] + global libqt5_rename + # sip renames libs to Qt5 automatically on Linux, but not on macOS + if libqt5_rename and not framework and clib.startswith('Qt') and not clib.startswith('Qt5'): + return '-lQt5' + clib[2:] return default_platform_lib_function(self, clib, framework) @@ -228,4 +228,4 @@ def split_paths(paths): makefile.LIBS.set(libs) # Generate the Makefile itself -makefile.generate() +makefile.generate() \ No newline at end of file diff --git a/cmake/sip_helper.cmake b/cmake/sip_helper.cmake index a263fea..dfc9b7d 100644 --- a/cmake/sip_helper.cmake +++ b/cmake/sip_helper.cmake @@ -93,13 +93,13 @@ function(build_sip_binding PROJECT_NAME SIP_FILE) set(LIBRARY_DIRS ${${PROJECT_NAME}_LIBRARY_DIRS}) set(LDFLAGS_OTHER ${${PROJECT_NAME}_LDFLAGS_OTHER}) - make_directory(${SIP_BUILD_DIR}) + # make_directory(${SIP_BUILD_DIR}) add_custom_command( OUTPUT ${SIP_BUILD_DIR}/Makefile - # COMMAND ${Python3_EXECUTABLE} ${sip_SIP_CONFIGURE} ${SIP_BUILD_DIR} ${SIP_FILE} ${sip_LIBRARY_DIR} - # \"${INCLUDE_DIRS}\" \"${LIBRARIES}\" \"${LIBRARY_DIRS}\" \"${LDFLAGS_OTHER}\" - COMMAND ${Python3_EXECUTABLE} -m sipbuild.tools.build --build-dir ${SIP_BUILD_DIR} --no-compile + COMMAND ${Python3_EXECUTABLE} ${sip_SIP_CONFIGURE} ${SIP_BUILD_DIR} ${SIP_FILE} ${sip_LIBRARY_DIR} + \"${INCLUDE_DIRS}\" \"${LIBRARIES}\" \"${LIBRARY_DIRS}\" \"${LDFLAGS_OTHER}\" + # COMMAND ${Python3_EXECUTABLE} -m sipbuild.tools.build --build-dir ${SIP_BUILD_DIR} --no-compile DEPENDS ${sip_SIP_CONFIGURE} ${SIP_FILE} ${sip_DEPENDS} WORKING_DIRECTORY ${sip_SOURCE_DIR} COMMENT "Running SIP generator for ${PROJECT_NAME} Python bindings..." From 3517178461b09a9659af41d784746176d8ee1b1e Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Fri, 13 Feb 2026 15:50:38 +0100 Subject: [PATCH 6/6] More changes Signed-off-by: Alejandro Hernandez Cordero --- CMakeLists.txt | 13 +- cmake/sip_configure_6.py | 231 ++++++++++++++++++ src/python_qt6_binding/__init__.py | 73 ++++++ src/python_qt6_binding/binding_helper.py | 287 +++++++++++++++++++++++ src/python_qt_binding/binding_helper.py | 37 ++- 5 files changed, 620 insertions(+), 21 deletions(-) create mode 100644 cmake/sip_configure_6.py create mode 100644 src/python_qt6_binding/__init__.py create mode 100644 src/python_qt6_binding/binding_helper.py diff --git a/CMakeLists.txt b/CMakeLists.txt index e3ddec5..eb9792d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,18 @@ project(python_qt_binding) find_package(ament_cmake REQUIRED) find_package(ament_cmake_python REQUIRED) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) -ament_python_install_package(${PROJECT_NAME} - PACKAGE_DIR src/${PROJECT_NAME}) +if (${QT_VERSION_MAJOR} GREATER "5") +message("lol" ${QT_VERSION_MAJOR}) + ament_python_install_package(python_qt_binding + PACKAGE_DIR src/python_qt6_binding) +else() +message("no lol" ${QT_VERSION_MAJOR}) + ament_python_install_package(python_qt_binding + PACKAGE_DIR src/python_qt_binding + ) +endif() install(FILES cmake/shiboken_helper.cmake diff --git a/cmake/sip_configure_6.py b/cmake/sip_configure_6.py new file mode 100644 index 0000000..619ed1b --- /dev/null +++ b/cmake/sip_configure_6.py @@ -0,0 +1,231 @@ +import copy +import os +import re +import shutil +import subprocess +import sys +import tempfile + +import PyQt6 +from PyQt6 import QtCore +import sipconfig + +libqt6_rename = False + + +class Configuration(sipconfig.Configuration): + + def __init__(self): + env = copy.copy(os.environ) + env['QT_SELECT'] = '6' + qmake_exe = 'qmake-qt6' if shutil.which('qmake-qt6') else 'qmake' + qtconfig = subprocess.check_output( + [qmake_exe, '-query'], env=env, universal_newlines=True) + qtconfig = dict(line.split(':', 1) for line in qtconfig.splitlines()) + pyqtconfig = { + 'qt_archdata_dir': qtconfig['QT_INSTALL_DATA'], + 'qt_data_dir': qtconfig['QT_INSTALL_DATA'], + 'qt_dir': qtconfig['QT_INSTALL_PREFIX'], + 'qt_inc_dir': qtconfig['QT_INSTALL_HEADERS'], + 'qt_lib_dir': qtconfig['QT_INSTALL_LIBS'], + 'qt_threaded': 1, + 'qt_version': QtCore.QT_VERSION, + 'qt_winconfig': 'shared exceptions', + } + if sys.platform == 'darwin': + if os.path.exists(os.path.join(qtconfig['QT_INSTALL_LIBS'], 'QtCore.framework')): + pyqtconfig['qt_framework'] = 1 + else: + global libqt6_rename + libqt6_rename = True + + sipconfig.Configuration.__init__(self, [pyqtconfig]) + + macros = sipconfig._default_macros.copy() + macros['INCDIR_QT'] = qtconfig['QT_INSTALL_HEADERS'] + macros['LIBDIR_QT'] = qtconfig['QT_INSTALL_LIBS'] + macros['MOC'] = 'moc-qt6' if shutil.which('moc-qt6') else 'moc' + self.set_build_macros(macros) + + +def get_sip_dir_flags(config): + """ + Get the extra SIP flags needed by the imported qt module, and locate PyQt5 sip install files. + + Note that this normally only includes those flags (-x and -t) that relate to SIP's versioning + system. + """ + try: + sip_dir = config.pyqt_sip_dir + sip_flags = config.pyqt_sip_flags + return sip_dir, sip_flags + except AttributeError: + pass + + # We didn't find the sip_dir and sip_flags from the config, continue looking + + # sipconfig.Configuration does not have a pyqt_sip_dir or pyqt_sip_flags AttributeError + sip_flags = QtCore.PYQT_CONFIGURATION['sip_flags'] + + candidate_sip_dirs = [] + + # Archlinux installs sip files here by default + candidate_sip_dirs.append(os.path.join(PyQt6.__path__[0], 'bindings')) + + # sip4 installs here by default + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'PyQt6')) + + # Homebrew installs sip files here by default + candidate_sip_dirs.append(os.path.join(sipconfig._pkg_config['default_sip_dir'], 'Qt6')) + + for sip_dir in candidate_sip_dirs: + if os.path.exists(sip_dir): + return sip_dir, sip_flags + + raise FileNotFoundError('The sip directory for PyQt6 could not be located. Please ensure' + + ' that PyQt6 is installed') + + +if len(sys.argv) != 8: + print('usage: %s build-dir sip-file output_dir include_dirs libs lib_dirs ldflags' % + sys.argv[0]) + sys.exit(1) + +# The SIP build folder, the SIP file, the output directory, the include +# directories, the libraries, the library directories and the linker +# flags. +build_dir, sip_file, output_dir, include_dirs, libs, lib_dirs, ldflags = sys.argv[1:] + +# The name of the SIP build file generated by SIP and used by the build system. +build_file = 'pyqtscripting.sbf' + +# Get the PyQt configuration information. +config = Configuration() + +sip_dir, sip_flags = get_sip_dir_flags(config) + +try: + os.makedirs(build_dir) +except OSError: + pass + +# Run SIP to generate the code. Note that we tell SIP where to find the qt +# module's specification files using the -I flag. + +sip_bin = config.sip_bin +# Without the .exe, this might actually be a directory in Windows +if sys.platform == 'win32' and os.path.isdir(sip_bin): + sip_bin += '.exe' + +# SIP4 has an incompatibility with Qt 5.15.6. In particular, Qt 5.15.6 uses a new SIP directive +# called py_ssize_t_clean in QtCoremod.sip that SIP4 does not understand. +# +# Unfortunately, the combination of SIP4 and Qt 5.15.6 is common. Archlinux, Ubuntu 22.04 +# and RHEL-9 all have this combination. On Ubuntu 22.04, there is a custom patch to SIP4 +# to make it understand the py_ssize_t_clean tag, so the combination works. But on most +# other platforms, it fails. +# +# To workaround this, copy all of the SIP files into a temporary directory, remove the offending +# line, and then use that temporary directory as the include path. This is unnecessary on +# Ubuntu 22.04, but shouldn't hurt anything there. +with tempfile.TemporaryDirectory() as tmpdirname: + shutil.copytree(sip_dir, tmpdirname, dirs_exist_ok=True) + + output = '' + with open(os.path.join(tmpdirname, 'QtCore', 'QtCoremod.sip'), 'r') as infp: + for line in infp: + if line.startswith('%Module(name='): + result = re.sub(r', py_ssize_t_clean=True', '', line) + output += result + else: + output += line + + with open(os.path.join(tmpdirname, 'QtCore', 'QtCoremod.sip'), 'w') as outfp: + outfp.write(output) + + cmd = [ + sip_bin, + '-c', build_dir, + '-b', os.path.join(build_dir, build_file), + '-I', tmpdirname, + '-w' + ] + cmd += sip_flags.split(' ') + cmd.append(sip_file) + + subprocess.check_call(cmd) + +# Create the Makefile. The QtModuleMakefile class provided by the +# pyqtconfig module takes care of all the extra preprocessor, compiler and +# linker flags needed by the Qt library. +makefile = sipconfig.SIPModuleMakefile( + dir=build_dir, + configuration=config, + build_file=build_file, + qt=['QtCore', 'QtGui'] +) + +# hack to override makefile behavior which always prepend -l to libraries +# which is wrong for absolute paths +default_platform_lib_function = sipconfig.SIPModuleMakefile.platform_lib + + +def custom_platform_lib_function(self, clib, framework=0): + if not clib or clib.isspace(): + return None + # Only add '-l' if a library doesn't already start with '-l' and is not an absolute path + if os.path.isabs(clib) or clib.startswith('-l'): + return clib + + global libqt6_rename + # sip renames libs to Qt6 automatically on Linux, but not on macOS + if libqt6_rename and not framework and clib.startswith('Qt') and not clib.startswith('Qt6'): + return '-lQt6' + clib[2:] + + return default_platform_lib_function(self, clib, framework) + + +sipconfig.SIPModuleMakefile.platform_lib = custom_platform_lib_function + + +# split paths on whitespace +# while dealing with whitespaces within the paths if they are escaped with backslashes +def split_paths(paths): + paths = re.split('(?<=[^\\\\]) ', paths) + return paths + + +for include_dir in split_paths(include_dirs): + include_dir = include_dir.replace('\\', '') + makefile.extra_include_dirs.append(include_dir) +for lib in split_paths(libs): + makefile.extra_libs.append(lib) +for lib_dir in split_paths(lib_dirs): + lib_dir = lib_dir.replace('\\', '') + makefile.extra_lib_dirs.append(lib_dir) +for ldflag in ldflags.split('\\ '): + makefile.LFLAGS.append(ldflag) + +# redirect location of generated library +makefile._target = '"%s"' % os.path.join(output_dir, makefile._target) + +# Force c++17 +if sys.platform == 'win32': + makefile.extra_cxxflags.append('/std:c++17') + # The __cplusplus flag is not properly set on Windows for backwards + # compatibilty. This flag sets it correctly + makefile.CXXFLAGS.append('/Zc:__cplusplus') +else: + makefile.extra_cxxflags.append('-std=c++17') + +# Finalise the Makefile, preparing it to be saved to disk +makefile.finalise() + +# Replace Qt variables from libraries +libs = makefile.LIBS.as_list() +for i in range(len(libs)): + libs[i] = libs[i].replace('$$[QT_INSTALL_LIBS]', config.build_macros()['LIBDIR_QT']) +makefile.LIBS.set(libs) + +# Generate the Makefile itself +makefile.generate() diff --git a/src/python_qt6_binding/__init__.py b/src/python_qt6_binding/__init__.py new file mode 100644 index 0000000..59a8769 --- /dev/null +++ b/src/python_qt6_binding/__init__.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +# Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the TU Darmstadt nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +Abstraction for different Python Qt bindings. + +Supported Python Qt 5 bindings are PyQt and PySide. +The Qt modules can be imported like this: + +from python_qt_binding.QtCore import QObject +from python_qt_binding import QtGui, loadUi + +The name of the selected binding is available in QT_BINDING. +The version of the selected binding is available in QT_BINDING_VERSION. +All available Qt modules are listed in QT_BINDING_MODULES. + +The default binding order ('pyqt', 'pyside') can be overridden with a +SELECT_QT_BINDING_ORDER attribute on sys: + setattr(sys, 'SELECT_QT_BINDING_ORDER', [FIRST_NAME, NEXT_NAME, ..]) + +A specific binding can be selected with a SELECT_QT_BINDING attribute on sys: + setattr(sys, 'SELECT_QT_BINDING', MY_BINDING_NAME) +""" + +import sys + +from python_qt_binding.binding_helper import loadUi # noqa: F401 +from python_qt_binding.binding_helper import QT_BINDING # noqa: F401 +from python_qt_binding.binding_helper import QT_BINDING_MODULES +from python_qt_binding.binding_helper import QT_BINDING_VERSION # noqa: F401 + +print('QT_BINDING', QT_BINDING) +for module, value in QT_BINDING_MODULES.items(): + print('QT_BINDING_MODULES', module, value) +print('QT_BINDING_VERSION', QT_BINDING_VERSION) + +# register binding modules as sub modules of this package (python_qt_binding) for easy importing +for module_name, module in QT_BINDING_MODULES.items(): + sys.modules[__name__ + '.' + module_name] = module + setattr(sys.modules[__name__], module_name, module) + del module_name + del module + +del sys diff --git a/src/python_qt6_binding/binding_helper.py b/src/python_qt6_binding/binding_helper.py new file mode 100644 index 0000000..bd5bca5 --- /dev/null +++ b/src/python_qt6_binding/binding_helper.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python + +# Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the TU Darmstadt nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +try: + import __builtin__ as builtins +except ImportError: + # since the 'future' package provides a 'builtins' module in Python 2 + # this must not be checked second + import builtins +import os +import platform +import sys +import traceback + + +QT_BINDING = None +QT_BINDING_MODULES = {} +QT_BINDING_VERSION = None + + +def _select_qt_binding(binding_name=None, binding_order=None): + global QT_BINDING, QT_BINDING_VERSION + + # order of default bindings can be changed here + DEFAULT_BINDING_ORDER = ['pyqt'] + # if platform.system() == 'Darwin': + # DEFAULT_BINDING_ORDER = ['pyside'] + # else: + # DEFAULT_BINDING_ORDER = ['pyside', 'pyqt'] + + binding_order = binding_order or DEFAULT_BINDING_ORDER + + # determine binding preference + if binding_name: + if binding_name not in binding_order: + raise ImportError("Qt binding '%s' is unknown" % binding_name) + binding_order = [binding_name] + + required_modules = [ + 'QtCore', + 'QtGui', + 'QtWidgets', + ] + optional_modules = [ + 'QtBluetooth', + 'QtDBus', + 'QtDesigner', + 'QtHelp', + 'QtLocation', + 'QtMultimedia', + 'QtMultimediaWidgets', + 'QtNetwork', + 'QNetworkAuth', + 'QtNfc', + 'QtOpenGL', + 'QtPositioning', + 'QtPrintSupport', + 'QtQml', + 'QtQuick', + 'QtQuickWidgets', + 'QtScript', + 'QtScriptTools', + 'QtSensors', + 'QtSerialPort', + 'QtSql', + 'QtSvg', + 'QtTest', + 'QtWebChannel', + 'QtWebEngine', # Qt 5.6 and higher + 'QtWebEngineCore', + 'QtWebEngineWidgets', + 'QtWebKitWidgets', # Qt 5.0 - 5.5 + 'QtWebSockets', + 'QtX11Extras', + 'QtXml', + 'QtXmlPatterns', + ] + + # try to load preferred bindings + error_msgs = [] + for binding_name in binding_order: + try: + binding_loader = getattr(sys.modules[__name__], '_load_%s' % binding_name, None) + if binding_loader: + QT_BINDING_VERSION = binding_loader(required_modules, optional_modules) + QT_BINDING = binding_name + break + else: + error_msgs.append(" Binding loader '_load_%s' not found." % binding_name) + except ImportError as e: + error_msgs.append(" ImportError for '%s': %s\n%s" % + (binding_name, e, traceback.format_exc())) + + if not QT_BINDING: + raise ImportError( + 'Could not find Qt binding (looked for: %s):\n%s' % + (', '.join(["'%s'" % b for b in binding_order]), '\n'.join(error_msgs))) + + +def _register_binding_module(module_name, module): + # register module using only its own name (TODO: legacy compatibility, remove when possible) + sys.modules[module_name] = module + # add module to the binding modules + QT_BINDING_MODULES[module_name] = module + + +def _named_import(name): + parts = name.split('.') + assert len(parts) >= 2 + module = builtins.__import__(name) + for m in parts[1:]: + module = module.__dict__[m] + module_name = parts[-1] + _register_binding_module(module_name, module) + + +def _named_optional_import(name): + try: + _named_import(name) + except ImportError: + pass + + +def _load_pyqt(required_modules, optional_modules): + # set environment variable QT_API for matplotlib + os.environ['QT_API'] = 'pyqt' + + # register required and optional PyQt modules + for module_name in required_modules: + print('PyQt6.%s' % module_name) + _named_import('PyQt6.%s' % module_name) + for module_name in optional_modules: + _named_optional_import('PyQt6.%s' % module_name) + + # set some names for compatibility with PySide + sys.modules['QtCore'].Signal = sys.modules['QtCore'].pyqtSignal + sys.modules['QtCore'].Slot = sys.modules['QtCore'].pyqtSlot + sys.modules['QtCore'].Property = sys.modules['QtCore'].pyqtProperty + + # try to register Qwt module + try: + import PyQt6.Qwt6 + _register_binding_module('Qwt', PyQt6.Qwt6) + except ImportError: + pass + + global _loadUi + + def _loadUi(uifile, baseinstance=None, custom_widgets_=None): + from PyQt6 import uic + return uic.loadUi(uifile, baseinstance=baseinstance) + + import PyQt6.QtCore + return PyQt6.QtCore.PYQT_VERSION_STR + + +def _load_pyside(required_modules, optional_modules): + # set environment variable QT_API for matplotlib + os.environ['QT_API'] = 'pyside' + + # register required and optional PySide modules + for module_name in required_modules: + _named_import('PySide6.%s' % module_name) + for module_name in optional_modules: + _named_optional_import('PySide6.%s' % module_name) + + # set some names for compatibility with PyQt + sys.modules['QtCore'].pyqtSignal = sys.modules['QtCore'].Signal + sys.modules['QtCore'].pyqtSlot = sys.modules['QtCore'].Slot + sys.modules['QtCore'].pyqtProperty = sys.modules['QtCore'].Property + + # try to register PySideQwt module + try: + import PySideQwt + _register_binding_module('Qwt', PySideQwt) + except ImportError: + pass + + global _loadUi + + def _loadUi(uifile, baseinstance=None, custom_widgets=None): + from PySide6.QtUiTools import QUiLoader + from PySide6.QtCore import QMetaObject + + class CustomUiLoader(QUiLoader): + class_aliases = { + 'Line': 'QFrame', + } + + def __init__(self, baseinstance=None, custom_widgets=None): + super(CustomUiLoader, self).__init__(baseinstance) + self._base_instance = baseinstance + self._custom_widgets = custom_widgets or {} + + def createWidget(self, class_name, parent=None, name=''): + # don't create the top-level widget, if a base instance is set + if self._base_instance and not parent: + return self._base_instance + + if class_name in self._custom_widgets: + widget = self._custom_widgets[class_name](parent) + else: + widget = QUiLoader.createWidget(self, class_name, parent, name) + + if str(type(widget)).find(self.class_aliases.get(class_name, class_name)) < 0: + sys.modules['QtCore'].qDebug( + 'PySide.loadUi(): could not find widget class "%s", defaulting to "%s"' % + (class_name, type(widget))) + + if self._base_instance: + setattr(self._base_instance, name, widget) + + return widget + + loader = CustomUiLoader(baseinstance, custom_widgets) + + # instead of passing the custom widgets, they should be registered using + # QUiLoader.registerCustomWidget(), + # but this does not work in PySide 1.0.6: it simply segfaults... + # loader = CustomUiLoader(baseinstance) + # custom_widgets = custom_widgets or {} + # for custom_widget in custom_widgets.values(): + # loader.registerCustomWidget(custom_widget) + + ui = loader.load(uifile) + QMetaObject.connectSlotsByName(ui) + return ui + + import PySide6 + return PySide6.__version__ + + +def loadUi(uifile, baseinstance=None, custom_widgets=None): + """ + Load a provided UI file chosen Python Qt 5 binding. + + @type uifile: str + @param uifile: Absolute path of .ui file + @type baseinstance: QWidget + @param baseinstance: the optional instance of the Qt base class. + If specified then the user interface is created in + it. Otherwise a new instance of the base class is + automatically created. + @type custom_widgets: dict of {str:QWidget} + @param custom_widgets: Class name and type of the custom classes used + in uifile if any. This can be None if no custom + class is in use. (Note: this is only necessary + for PySide, see + http://answers.ros.org/question/56382/what-does-python_qt_bindingloaduis-3rd-arg-do-in-pyqt-binding/ + for more information) + """ + return _loadUi(uifile, baseinstance, custom_widgets) + + +_select_qt_binding( + getattr(sys, 'SELECT_QT_BINDING', None), + getattr(sys, 'SELECT_QT_BINDING_ORDER', None), +) diff --git a/src/python_qt_binding/binding_helper.py b/src/python_qt_binding/binding_helper.py index d5ff4be..63ccf92 100644 --- a/src/python_qt_binding/binding_helper.py +++ b/src/python_qt_binding/binding_helper.py @@ -51,11 +51,10 @@ def _select_qt_binding(binding_name=None, binding_order=None): global QT_BINDING, QT_BINDING_VERSION # order of default bindings can be changed here - DEFAULT_BINDING_ORDER = ['pyside'] - # if platform.system() == 'Darwin': - # DEFAULT_BINDING_ORDER = ['pyside'] - # else: - # DEFAULT_BINDING_ORDER = ['pyside', 'pyqt'] + if platform.system() == 'Darwin': + DEFAULT_BINDING_ORDER = ['pyside'] + else: + DEFAULT_BINDING_ORDER = ['pyqt', 'pyside'] binding_order = binding_order or DEFAULT_BINDING_ORDER @@ -156,9 +155,9 @@ def _load_pyqt(required_modules, optional_modules): # register required and optional PyQt modules for module_name in required_modules: - _named_import('PyQt6.%s' % module_name) + _named_import('PyQt5.%s' % module_name) for module_name in optional_modules: - _named_optional_import('PyQt6.%s' % module_name) + _named_optional_import('PyQt5.%s' % module_name) # set some names for compatibility with PySide sys.modules['QtCore'].Signal = sys.modules['QtCore'].pyqtSignal @@ -167,19 +166,19 @@ def _load_pyqt(required_modules, optional_modules): # try to register Qwt module try: - import PyQt6.Qwt6 - _register_binding_module('Qwt', PyQt6.Qwt6) + import PyQt5.Qwt5 + _register_binding_module('Qwt', PyQt5.Qwt5) except ImportError: pass global _loadUi def _loadUi(uifile, baseinstance=None, custom_widgets_=None): - from PyQt6 import uic + from PyQt5 import uic return uic.loadUi(uifile, baseinstance=baseinstance) - import PyQt6.QtCore - return PyQt6.QtCore.PYQT_VERSION_STR + import PyQt5.QtCore + return PyQt5.QtCore.PYQT_VERSION_STR def _load_pyside(required_modules, optional_modules): @@ -188,9 +187,9 @@ def _load_pyside(required_modules, optional_modules): # register required and optional PySide modules for module_name in required_modules: - _named_import('PySide6.%s' % module_name) + _named_import('PySide2.%s' % module_name) for module_name in optional_modules: - _named_optional_import('PySide6.%s' % module_name) + _named_optional_import('PySide2.%s' % module_name) # set some names for compatibility with PyQt sys.modules['QtCore'].pyqtSignal = sys.modules['QtCore'].Signal @@ -207,8 +206,8 @@ def _load_pyside(required_modules, optional_modules): global _loadUi def _loadUi(uifile, baseinstance=None, custom_widgets=None): - from PySide6.QtUiTools import QUiLoader - from PySide6.QtCore import QMetaObject + from PySide2.QtUiTools import QUiLoader + from PySide2.QtCore import QMetaObject class CustomUiLoader(QUiLoader): class_aliases = { @@ -254,8 +253,8 @@ def createWidget(self, class_name, parent=None, name=''): QMetaObject.connectSlotsByName(ui) return ui - import PySide6 - return PySide6.__version__ + import PySide2 + return PySide2.__version__ def loadUi(uifile, baseinstance=None, custom_widgets=None): @@ -283,4 +282,4 @@ class is in use. (Note: this is only necessary _select_qt_binding( getattr(sys, 'SELECT_QT_BINDING', None), getattr(sys, 'SELECT_QT_BINDING_ORDER', None), -) +) \ No newline at end of file