diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8686914..2dd5e36 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,15 +7,9 @@ find_package(ament_cmake_python REQUIRED)
# Copy python package into binary directory
file(COPY src/${PROJECT_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
-# Avoid find_package(QT NAMES Qt6 Qt5 ...) due to CMake's default ascending path resolution
-find_package(Qt6 QUIET COMPONENTS Widgets Core)
-if(Qt6_FOUND)
- set(QT_VERSION_MAJOR 6)
-else()
- find_package(Qt5 REQUIRED COMPONENTS Widgets Core)
- set(QT_VERSION_MAJOR 5)
-endif()
-set(python_qt_binding_QT_MAJOR_VERSION "${QT_VERSION_MAJOR}" CACHE STRING "The major version of Qt to use (5 or 6)")
+find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
+# Kept for downstream packages that still read this variable.
+set(python_qt_binding_QT_MAJOR_VERSION "6" CACHE STRING "The major version of Qt to use")
# Generate a version.py file inside the copied package directory
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}/version.py
diff --git a/README.md b/README.md
index 52795bb..d5ae426 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
This stack provides Python bindings for Qt.
There are two providers: pyside and pyqt.
-PySide2 is available under the GPL, LGPL and a commercial license.
+PySide6 is available under the GPL, LGPL and a commercial license.
PyQt is released under the GPL.
Both the bindings and tools to build bindings are included from each available provider.
diff --git a/cmake/generate_pyproject_toml.cmake b/cmake/generate_pyproject_toml.cmake
new file mode 100644
index 0000000..dd75578
--- /dev/null
+++ b/cmake/generate_pyproject_toml.cmake
@@ -0,0 +1,14 @@
+# Called via cmake -P from build_sip_binding() in sip_helper.cmake.
+# Performs @VAR@ substitution on pyproject.toml.in and writes the result.
+#
+# Required variables (passed via -DVAR=value on the cmake command line):
+# PROJECT_NAME
+# python_qt_binding_QMAKE_EXECUTABLE
+# SIP_BUILD_DIR
+# sip_SOURCE_DIR
+# SIP_FILE_NAME
+# __PYQT_BINDINGS_DIR
+# __QT_SIP_ABI_VERSION
+# TEMPLATE_FILE
+# OUTPUT_FILE
+configure_file("${TEMPLATE_FILE}" "${OUTPUT_FILE}" @ONLY)
diff --git a/cmake/shiboken_helper.cmake b/cmake/shiboken_helper.cmake
index 7624157..0ca3871 100644
--- a/cmake/shiboken_helper.cmake
+++ b/cmake/shiboken_helper.cmake
@@ -21,37 +21,27 @@ if(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED)
endif()
set(__PYTHON_QT_BINDING_SHIBOKEN_HELPER_INCLUDED TRUE)
-# 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()
+find_package(Shiboken6 QUIET)
+if(Shiboken6_FOUND)
+ message(STATUS "Found Shiboken6 version ${Shiboken6_VERSION}")
+ get_property(SHIBOKEN_INCLUDE_DIR TARGET Shiboken6::libshiboken PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
+ get_property(SHIBOKEN_LIBRARY TARGET Shiboken6::libshiboken PROPERTY LOCATION)
+ set(SHIBOKEN_BINARY Shiboken6::shiboken6)
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)
- endif()
+find_package(PySide6 QUIET)
+if(PySide6_FOUND)
+ message(STATUS "Found PySide6 version ${PySide6_VERSION}")
+ get_property(PYSIDE_INCLUDE_DIR TARGET PySide6::pyside6 PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
+ get_property(PYSIDE_LIBRARY TARGET PySide6::pyside6 PROPERTY LOCATION)
message(STATUS "Using PYSIDE_INCLUDE_DIR: ${PYSIDE_INCLUDE_DIR}")
message(STATUS "Using PYSIDE_LIBRARY: ${PYSIDE_LIBRARY}")
endif()
-if(Shiboken2_FOUND AND PySide2_FOUND)
+if(Shiboken6_FOUND AND PySide6_FOUND)
message(STATUS "Shiboken binding generator available.")
set(shiboken_helper_FOUND TRUE)
else()
@@ -64,7 +54,9 @@ 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)
list(APPEND SHIBOKEN_HELPER_INCLUDE_DIRS
- ${QT_INCLUDE_DIR}
+ ${Qt6Core_INCLUDE_DIRS}
+ ${Qt6Gui_INCLUDE_DIRS}
+ ${Qt6Widgets_INCLUDE_DIRS}
${PYSIDE_INCLUDE_DIR}
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
# See ticket https://code.ros.org/trac/ros-pkg/ticket/5219
@@ -157,8 +149,9 @@ function(shiboken_target_link_libraries PROJECT_NAME QT_COMPONENTS)
)
foreach(component ${QT_COMPONENTS})
- string(TOUPPER ${component} component)
- set(shiboken_LINK_LIBRARIES ${shiboken_LINK_LIBRARIES} ${QT_${component}_LIBRARY})
+ # QT_COMPONENTS are given as QtCore/QtGui/QtWidgets; Qt6 targets drop the prefix.
+ string(REGEX REPLACE "^Qt" "" component "${component}")
+ set(shiboken_LINK_LIBRARIES ${shiboken_LINK_LIBRARIES} Qt6::${component})
endforeach()
target_link_libraries(${PROJECT_NAME} ${shiboken_LINK_LIBRARIES})
diff --git a/cmake/sip_helper.cmake b/cmake/sip_helper.cmake
index f5c2a4c..6b6247a 100644
--- a/cmake/sip_helper.cmake
+++ b/cmake/sip_helper.cmake
@@ -11,15 +11,13 @@ set(Python3_FIND_UNVERSIONED_NAMES FIRST)
find_package(Python3 ${Python3_VERSION} REQUIRED COMPONENTS Interpreter Development)
-# Find the directory containing the SIP bindings shipped by PyQt.
-#
-# :param python_qt_binding_QT_MAJOR_VERSION: The major version of Qt (e.g., 5 or 6).
+# Find the directory containing the SIP bindings shipped by PyQt6.
#
# :out __PYQT_BINDINGS_DIR: Path to the directory containing QT*.sip files.
# :out __PYQT_BINDINGS_FOUND: Boolean indicating if the bindings were located.
#
-function(__find_qt_sip_files python_qt_binding_QT_MAJOR_VERSION)
- set(MODULE_NAME "PyQt${python_qt_binding_QT_MAJOR_VERSION}")
+function(__find_qt_sip_files)
+ set(MODULE_NAME "PyQt6")
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import ${MODULE_NAME}.bindings as pb; print(pb.__path__[0])"
@@ -76,9 +74,9 @@ else()
endif()
# Find Qt's installed SIP files
-__find_qt_sip_files(${python_qt_binding_QT_MAJOR_VERSION})
+__find_qt_sip_files()
if(NOT __PYQT_BINDINGS_FOUND)
- message(FATAL_ERROR "PyQt${python_qt_binding_QT_MAJOR_VERSION} SIP bindings are required but were not found.")
+ message(FATAL_ERROR "PyQt6 SIP bindings are required but were not found.")
endif()
# Extract the sip-abi-version from PyQt
@@ -87,8 +85,8 @@ __find_qt_sip_abi("${__PYQT_BINDINGS_DIR}")
# Find qmake
find_program(python_qt_binding_QMAKE_EXECUTABLE
NAMES
- qmake${python_qt_binding_QT_MAJOR_VERSION}
- qmake-qt${python_qt_binding_QT_MAJOR_VERSION}
+ qmake6
+ qmake-qt6
qmake
REQUIRED)
@@ -162,8 +160,17 @@ function(build_sip_binding PROJECT_NAME SIP_FILE)
COMMENT "Generating C++ code for ${PROJECT_NAME} Python bindings using sip-build..."
)
+ # Drive the generator from a target of its own. Generating the code only
+ # needs the sip files and the headers, not the libraries the extension is
+ # later linked against. Keeping it out of lib${PROJECT_NAME} means it is
+ # not ordered after those libraries have been built and can run
+ # concurrently with them.
+ add_custom_target(${PROJECT_NAME}_codegen DEPENDS ${GENERATED_CPP})
+ set_source_files_properties(${GENERATED_CPP} PROPERTIES GENERATED TRUE)
+
# Build the cPython extension natively using CMake
python3_add_library(lib${PROJECT_NAME} MODULE ${GENERATED_CPP})
+ add_dependencies(lib${PROJECT_NAME} ${PROJECT_NAME}_codegen)
# Link project dependencies against this target
target_include_directories(lib${PROJECT_NAME} PRIVATE ${${PROJECT_NAME}_INCLUDE_DIRS} ${SIP_BUILD_DIR})
diff --git a/package.xml b/package.xml
index 1eb4466..fa36cf6 100644
--- a/package.xml
+++ b/package.xml
@@ -3,7 +3,7 @@
2.6.1
This stack provides Python bindings for Qt.
- There are two providers: pyside and pyqt. PySide2 is available under
+ There are two providers: pyside and pyqt. PySide6 is available under
the GPL, LGPL and a commercial license. PyQt is released under the GPL.
Both the bindings and tools to build bindings are included from each
@@ -28,11 +28,18 @@
ament_cmake
+
python3-qt-bindings
+ qt6-base-dev
+ qt6-qmake
python3-qt-bindings
python3-dev
- python3-qt-bindings
+ python3-pyqt6
ament_cmake_pytest
ament_lint_auto
diff --git a/python_qt_binding-extras.cmake.in b/python_qt_binding-extras.cmake.in
index ea85bbc..717c91a 100644
--- a/python_qt_binding-extras.cmake.in
+++ b/python_qt_binding-extras.cmake.in
@@ -1 +1 @@
-set(python_qt_binding_QT_MAJOR_VERSION "@python_qt_binding_QT_MAJOR_VERSION@" CACHE STRING "The major version of Qt to use (5 or 6)")
\ No newline at end of file
+set(python_qt_binding_QT_MAJOR_VERSION "@python_qt_binding_QT_MAJOR_VERSION@" CACHE STRING "The major version of Qt to use")
\ No newline at end of file
diff --git a/src/python_qt_binding/__init__.py b/src/python_qt_binding/__init__.py
index 1e209de..b19fef6 100644
--- a/src/python_qt_binding/__init__.py
+++ b/src/python_qt_binding/__init__.py
@@ -33,7 +33,7 @@
"""
Abstraction for different Python Qt bindings.
-Supported Python Qt 5 bindings are PyQt and PySide.
+Supported Python Qt 6 bindings are PyQt6 and PySide6.
The Qt modules can be imported like this:
from python_qt_binding.QtCore import QObject
diff --git a/src/python_qt_binding/binding_helper.py b/src/python_qt_binding/binding_helper.py
index cb6b835..3df1772 100644
--- a/src/python_qt_binding/binding_helper.py
+++ b/src/python_qt_binding/binding_helper.py
@@ -36,8 +36,6 @@
import sys
import traceback
-from .version import QT_MAJOR_VERSION
-
QT_BINDING = None
QT_BINDING_MODULES = {}
@@ -75,7 +73,7 @@ def _select_qt_binding(binding_name=None, binding_order=None):
'QtMultimedia',
'QtMultimediaWidgets',
'QtNetwork',
- 'QNetworkAuth',
+ 'QtNetworkAuth',
'QtNfc',
'QtOpenGL',
'QtPositioning',
@@ -83,22 +81,17 @@ def _select_qt_binding(binding_name=None, binding_order=None):
'QtQml',
'QtQuick',
'QtQuickWidgets',
- 'QtScript',
- 'QtScriptTools',
'QtSensors',
'QtSerialPort',
'QtSql',
'QtSvg',
'QtTest',
'QtWebChannel',
- 'QtWebEngine', # Qt 5.6 and higher
+ 'QtWebEngine',
'QtWebEngineCore',
'QtWebEngineWidgets',
- 'QtWebKitWidgets', # Qt 5.0 - 5.5
'QtWebSockets',
- 'QtX11Extras',
'QtXml',
- 'QtXmlPatterns',
]
# try to load preferred bindings
@@ -152,29 +145,22 @@ def _load_pyqt(required_modules, optional_modules):
# register required and optional PyQt modules
for module_name in required_modules:
- _named_import(f'PyQt{QT_MAJOR_VERSION}.{module_name}')
+ _named_import(f'PyQt6.{module_name}')
for module_name in optional_modules:
- _named_optional_import(f'PyQt{QT_MAJOR_VERSION}.{module_name}')
+ _named_optional_import(f'PyQt6.{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:
- Qwt = builtins.__import__(f'PyQt{QT_MAJOR_VERSION}.Qwt{QT_MAJOR_VERSION}', fromlist=['*'])
- _register_binding_module('Qwt', Qwt)
- except ImportError:
- pass
-
global _loadUi
def _loadUi(uifile, baseinstance=None, custom_widgets_=None):
- uic = builtins.__import__(f'PyQt{QT_MAJOR_VERSION}.uic', fromlist=['*'])
+ uic = builtins.__import__('PyQt6.uic', fromlist=['*'])
return uic.loadUi(uifile, baseinstance=baseinstance)
- QtCore = builtins.__import__(f'PyQt{QT_MAJOR_VERSION}.QtCore', fromlist=['*'])
+ QtCore = builtins.__import__('PyQt6.QtCore', fromlist=['*'])
return QtCore.PYQT_VERSION_STR
@@ -182,13 +168,7 @@ def _load_pyside(required_modules, optional_modules):
# set environment variable QT_API for matplotlib
os.environ['QT_API'] = 'pyside'
- # Determine the PySide module based on the Qt version
- if str(QT_MAJOR_VERSION) == '6':
- pyside_module = 'PySide6'
- elif str(QT_MAJOR_VERSION) == '5':
- pyside_module = 'PySide2'
- else:
- raise RuntimeError('Only Qt5 and Qt6 are supported')
+ pyside_module = 'PySide6'
# register required and optional PySide modules
for module_name in required_modules:
@@ -201,13 +181,6 @@ def _load_pyside(required_modules, optional_modules):
sys.modules['QtCore'].pyqtSlot = sys.modules['QtCore'].Slot
sys.modules['QtCore'].pyqtProperty = sys.modules['QtCore'].Property
- # try to register PySideQwt module
- try:
- PySideQwt = builtins.__import__(f'{pyside_module}Qwt', fromlist=['*'])
- _register_binding_module('Qwt', PySideQwt)
- except ImportError:
- pass
-
global _loadUi
def _loadUi(uifile, baseinstance=None, custom_widgets=None):