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..2282e43 --- /dev/null +++ b/cmake/pyside_config.py @@ -0,0 +1,341 @@ +#!/usr/bin/python3 +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from enum import Enum +from glob import glob +import os +import re +import sys +import sysconfig + + +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): + 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(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(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..acd3855 100644 --- a/cmake/shiboken_helper.cmake +++ b/cmake/shiboken_helper.cmake @@ -21,48 +21,102 @@ 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) - 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() +# 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. -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) + # 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 PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}") - message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}") -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() -if(Shiboken2_FOUND AND PySide2_FOUND) - message(STATUS "Shiboken binding generator available.") + 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() + + 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};${shiboken6_generator_path}/include) + set(SHIBOKEN_BINARY "${shiboken6_generator_path}/shiboken6") set(shiboken_helper_FOUND TRUE) -else() - message(STATUS "Shiboken binding generator NOT available.") - set(shiboken_helper_NOTFOUND TRUE) -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() + +# 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}") +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} @@ -78,10 +132,36 @@ 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}") + 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. @@ -115,6 +195,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. @@ -137,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):