From a00b85713b1d5e54d5697df9164f6519d9589ef0 Mon Sep 17 00:00:00 2001 From: Max Smolens Date: Fri, 11 Sep 2015 09:17:59 -0400 Subject: [PATCH 1/8] [commontk] Prevent crashes during and after cleanup This commit prevents crashes by handling scenarios such as: (a) object destruction after the Python interpreter has been finalized (b) object destruction after cleanup, i.e. the singleton no longer exists Any usage of a Qt enum demonstrates (a). One example that demonstrates (b) is a QTimer object which is created with a QApplication parent. PythonQt::cleanup() is called before the QApplication is completely destroyed, so the code that handles wrapping the QTimer object must handle the case when the PythonQt singleton no longer exists. Co-authored-by: Jean-Christophe Fillion-Robin --- src/PythonQt.h | 2 +- src/PythonQtObjectPtr.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PythonQt.h b/src/PythonQt.h index 432e6c98..d98212b5 100644 --- a/src/PythonQt.h +++ b/src/PythonQt.h @@ -559,7 +559,7 @@ class PYTHONQT_EXPORT PythonQt : public QObject { //@{ //! get access to internal data (should not be used on the public API, but is used by some C functions) - static PythonQtPrivate* priv() { return _self ? _self->_p : nullptr; } + static PythonQtPrivate* priv() { return _self ? _self->_p : NULL; } //! clear all NotFound entries on all class infos, to ensure that //! newly loaded wrappers can add methods even when the object was wrapped by PythonQt before the wrapper was loaded diff --git a/src/PythonQtObjectPtr.cpp b/src/PythonQtObjectPtr.cpp index 8a214ec8..566c7fa8 100644 --- a/src/PythonQtObjectPtr.cpp +++ b/src/PythonQtObjectPtr.cpp @@ -109,7 +109,7 @@ PythonQtObjectPtr::PythonQtObjectPtr(PythonQtSafeObjectPtr &&p) :_object(p.takeO PythonQtObjectPtr::~PythonQtObjectPtr() { - if (Py_IsInitialized()) Py_XDECREF(_object); + if (_object && Py_IsInitialized()) Py_XDECREF(_object); } void PythonQtObjectPtr::setNewRef(PyObject* o) From 9338e9f413c910a32f4a671b23d4ea681dfe354f Mon Sep 17 00:00:00 2001 From: Max Smolens Date: Fri, 11 Sep 2015 16:36:51 -0400 Subject: [PATCH 2/8] [commontk] Add cleanup/finalization tests Add tests that check for clean cleanup and finalization in different scenarios. Co-authored-by: Jean-Christophe Fillion-Robin Co-authored-by: Pat Marion --- tests/PythonQtTestCleanup.cpp | 57 ++++++----------------------------- tests/PythonQtTestCleanup.h | 7 ++--- tests/PythonQtTestMain.cpp | 3 ++ 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/tests/PythonQtTestCleanup.cpp b/tests/PythonQtTestCleanup.cpp index c10681d1..796c0eba 100644 --- a/tests/PythonQtTestCleanup.cpp +++ b/tests/PythonQtTestCleanup.cpp @@ -1,6 +1,6 @@ #include "PythonQtTestCleanup.h" #include "PythonQt.h" -#include "PythonQt_QtAll.h" +#include "PythonQt_QtBindings.h" void PythonQtTestCleanup::initTestCase() { @@ -15,7 +15,7 @@ void PythonQtTestCleanup::init() // Initialize before each test PythonQt::init(PythonQt::IgnoreSiteModule); - PythonQt_QtAll::init(); + PythonQt_init_QtBindings(); _helper = new PythonQtTestCleanupHelper(); PythonQtObjectPtr main = PythonQt::self()->getMainModule(); @@ -24,15 +24,18 @@ void PythonQtTestCleanup::init() void PythonQtTestCleanup::cleanup() { - // Cleanup PythonQt resources before finalizing Python - PythonQt::cleanup(); + // Finalize and cleanup after each test + + PythonQtObjectPtr main = PythonQt::self()->getMainModule(); + PythonQt::self()->removeVariable(main, "obj"); + delete _helper; + _helper = NULL; if (Py_IsInitialized()) { Py_Finalize(); } - delete _helper; - _helper = nullptr; + PythonQt::cleanup(); } void PythonQtTestCleanup::testQtEnum() @@ -44,7 +47,7 @@ void PythonQtTestCleanup::testQtEnum() )); } -void PythonQtTestCleanup::testCallQtMethodInDestructorOwnedQTimer() +void PythonQtTestCleanup::testCallQtMethodInDel() { QVERIFY(_helper->runScript( "import PythonQt.QtCore\n" \ @@ -54,50 +57,10 @@ void PythonQtTestCleanup::testCallQtMethodInDestructorOwnedQTimer() " def __del__(self):\n" \ " self.timer.setSingleShot(True)\n" \ "x = TimerWrapper()\n" \ - "del x\n" \ - "obj.setPassed()\n" - )); -} - -void PythonQtTestCleanup::testCallQtMethodInDestructorWeakRefGuarded() -{ - QVERIFY(_helper->runScript( - "import weakref\n" \ - "import PythonQt.QtCore\n" \ - "class TimerWrapper(object):\n" \ - " def __init__(self):\n" \ - " self.timerWeakRef = weakref.ref(PythonQt.QtCore.QTimer())\n" \ - " def __del__(self):\n" \ - " if self.timerWeakRef():\n" \ - " self.timerWeakRef().setSingleShot(True)\n" \ - "x = TimerWrapper()\n" \ "obj.setPassed()\n" )); } -void PythonQtTestCleanup::testSignalReceiverCleanup() -{ - PythonQtObjectPtr main = PythonQt::self()->getMainModule(); - - // Test that PythonQtSignalReceiver is cleaned up properly, - // i.e. PythonQt::cleanup() doesn't segfault - QVERIFY(_helper->runScript( - "import PythonQt.QtCore\n" \ - "timer = PythonQt.QtCore.QTimer(obj)\n" \ - "timer.connect('destroyed()', obj.onDestroyed)\n" \ - "obj.setPassed()\n" - )); -} - -void PythonQtTestCleanup::testPyFinalizeThenPythonQtCleanup() -{ - if (Py_IsInitialized()) { - Py_Finalize(); - } - - PythonQt::cleanup(); -} - bool PythonQtTestCleanupHelper::runScript(const char* script) { _passed = false; diff --git a/tests/PythonQtTestCleanup.h b/tests/PythonQtTestCleanup.h index c7e593e2..2c4879e2 100644 --- a/tests/PythonQtTestCleanup.h +++ b/tests/PythonQtTestCleanup.h @@ -1,6 +1,7 @@ #ifndef _PYTHONQTTESTCLEANUP_H #define _PYTHONQTTESTCLEANUP_H +#include "PythonQt.h" #include class PythonQtTestCleanupHelper; @@ -17,10 +18,7 @@ private Q_SLOTS: void cleanup(); void testQtEnum(); - void testCallQtMethodInDestructorOwnedQTimer(); - void testCallQtMethodInDestructorWeakRefGuarded(); - void testSignalReceiverCleanup(); - void testPyFinalizeThenPythonQtCleanup(); + void testCallQtMethodInDel(); private: PythonQtTestCleanupHelper* _helper; @@ -39,7 +37,6 @@ class PythonQtTestCleanupHelper : public QObject public Q_SLOTS: void setPassed() { _passed = true; } - void onDestroyed(QObject *) { } private: bool _passed; diff --git a/tests/PythonQtTestMain.cpp b/tests/PythonQtTestMain.cpp index ca752bed..c939ec09 100644 --- a/tests/PythonQtTestMain.cpp +++ b/tests/PythonQtTestMain.cpp @@ -77,6 +77,9 @@ int main(int argc, char *argv[]) Py_Finalize(); } + PythonQtTestCleanup cleanup; + failCount += QTest::qExec(&cleanup, argc, argv); + if (failCount) { std::cerr << "Tests failed: " << failCount << std::endl; } else { From 941306d6ab91ba35f47f1371a6a98eea9d162202 Mon Sep 17 00:00:00 2001 From: Max Smolens Date: Tue, 15 Sep 2015 15:45:30 -0400 Subject: [PATCH 3/8] [commontk] Fix PythonQtSignalReceiver crash during cleanup This commit fixes a crash during PythonQt::cleanup(). While destroying the PythonQtPrivate instance, any remaining PythonQtSignalReceivers that are kept alive only by their parent object will be destroyed. The crash occurs when PythonQtSignalReceiver's destructor calls back into PythonQtPrivate::removeSignalEmitter() when the PythonQtPrivate instance is mostly destroyed. Includes test case that crashes without the fix. --- tests/PythonQtTestCleanup.cpp | 21 ++++++++++++++++----- tests/PythonQtTestCleanup.h | 2 ++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/PythonQtTestCleanup.cpp b/tests/PythonQtTestCleanup.cpp index 796c0eba..b60c7023 100644 --- a/tests/PythonQtTestCleanup.cpp +++ b/tests/PythonQtTestCleanup.cpp @@ -26,16 +26,14 @@ void PythonQtTestCleanup::cleanup() { // Finalize and cleanup after each test - PythonQtObjectPtr main = PythonQt::self()->getMainModule(); - PythonQt::self()->removeVariable(main, "obj"); - delete _helper; - _helper = NULL; - if (Py_IsInitialized()) { Py_Finalize(); } PythonQt::cleanup(); + + delete _helper; + _helper = NULL; } void PythonQtTestCleanup::testQtEnum() @@ -61,6 +59,19 @@ void PythonQtTestCleanup::testCallQtMethodInDel() )); } +void PythonQtTestCleanup::testSignalReceiverCleanup() +{ + PythonQtObjectPtr main = PythonQt::self()->getMainModule(); + + // Test that PythonQtSignalReceiver is cleaned up properly, + // i.e. PythonQt::cleanup() doesn't segfault + main.evalScript( + "import PythonQt.QtCore\n" \ + "timer = PythonQt.QtCore.QTimer(obj)\n" \ + "timer.connect('destroyed()', obj.onDestroyed)\n" \ + ); +} + bool PythonQtTestCleanupHelper::runScript(const char* script) { _passed = false; diff --git a/tests/PythonQtTestCleanup.h b/tests/PythonQtTestCleanup.h index 2c4879e2..b2a15886 100644 --- a/tests/PythonQtTestCleanup.h +++ b/tests/PythonQtTestCleanup.h @@ -19,6 +19,7 @@ private Q_SLOTS: void testQtEnum(); void testCallQtMethodInDel(); + void testSignalReceiverCleanup(); private: PythonQtTestCleanupHelper* _helper; @@ -37,6 +38,7 @@ class PythonQtTestCleanupHelper : public QObject public Q_SLOTS: void setPassed() { _passed = true; } + void onDestroyed(QObject *) { } private: bool _passed; From 672b3ace0175f78c60cae45612b447af524bf6c0 Mon Sep 17 00:00:00 2001 From: Steve Pieper Date: Thu, 12 Jul 2012 16:11:06 -0400 Subject: [PATCH 4/8] [commontk] Fix mac build error with C standard lib macros This fix errors of that sort: moc_PythonQtStdDecorators.cxx:152:25: error: expected unqualified-id case 4: _t->emit((*reinterpret_cast< QObject*(*)>(_a[1])),(*reinterpret_cast< const Add fix for mac build error related C standard lib macros to main header By updating PythonQtPythonIncludes.h which is included in all PythonQt headers, it is ensured the fix will be applied consistently. --- src/PythonQtPythonInclude.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/PythonQtPythonInclude.h b/src/PythonQtPythonInclude.h index d414a515..e179d36b 100644 --- a/src/PythonQtPythonInclude.h +++ b/src/PythonQtPythonInclude.h @@ -144,5 +144,27 @@ #undef toupper #endif +/* + * The following undefs for C standard library macros prevent + * build errors of the following type on mac ox 10.7.4 and XCode 4.3.3 + * +/usr/include/c++/4.2.1/bits/localefwd.h:57:21: error: too many arguments provided to function-like macro invocation + isspace(_CharT, const locale&); + ^ +/usr/include/c++/4.2.1/bits/localefwd.h:56:5: error: 'inline' can only appear on functions + inline bool + ^ +/usr/include/c++/4.2.1/bits/localefwd.h:57:5: error: variable 'isspace' declared as a template + isspace(_CharT, const locale&); + ^ +*/ +#undef isspace +#undef isupper +#undef islower +#undef isalpha +#undef isalnum +#undef toupper +#undef tolower + #endif From 60d4ba2802d771537c012b1cc86c86051c2ffcf9 Mon Sep 17 00:00:00 2001 From: Max Smolens Date: Fri, 28 Aug 2015 14:16:08 -0400 Subject: [PATCH 5/8] [commontk] Add missing refcount decrements when creating wrappers PyObject_GetAttrString returns a new reference. PyDict_SetItemString does not steal a reference, so Py_DECREF should be called after PyDict_SetItemString. --- src/PythonQt.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PythonQt.cpp b/src/PythonQt.cpp index e5c4a93a..593d9aab 100644 --- a/src/PythonQt.cpp +++ b/src/PythonQt.cpp @@ -822,6 +822,7 @@ PythonQtClassWrapper* PythonQtPrivate::createNewPythonQtClassWrapper(PythonQtCla // create the new type object by calling the type result = (PythonQtClassWrapper *)PyObject_Call((PyObject *)&PythonQtClassWrapper_Type, args, nullptr); + Py_DECREF(moduleName); Py_DECREF(baseClasses); Py_DECREF(typeDict); Py_DECREF(moduleName); @@ -857,6 +858,7 @@ PyObject* PythonQtPrivate::createNewPythonQtEnumWrapper(const char* enumName, Py // create the new int derived type object by calling the core type result = PyObject_Call((PyObject *)&PyType_Type, args, nullptr); + Py_DECREF(module); Py_DECREF(baseClasses); Py_DECREF(module); Py_DECREF(typeDict); From e748ed7a53ef85e7a14cb10b0829fb95f1dc1852 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Sat, 21 May 2016 15:30:32 -0400 Subject: [PATCH 6/8] [commontk] addDecorators: Check for null object --- src/PythonQt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PythonQt.cpp b/src/PythonQt.cpp index 593d9aab..63fc2478 100644 --- a/src/PythonQt.cpp +++ b/src/PythonQt.cpp @@ -1550,7 +1550,7 @@ PythonQtClassInfo* PythonQtPrivate::currentClassInfoForClassWrapperCreation() void PythonQtPrivate::addDecorators(QObject* o, int decoTypes) { - if (o == nullptr) + if (!o) { return; } From 96869db744c430caea44fcbb2d9fc935d6299426 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Wed, 2 Jul 2025 16:36:10 -0400 Subject: [PATCH 7/8] [commontk] Re-enable CMake support importing historical improvements from commontk/PythonQt fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit partially reverts r431 (which removed unsupported files and added documentation) and consolidates consolidates historical changes developed in the `commontk/PythonQt` fork between 2011 and 2021. Summary: * Qt 5support: * Added initial and ongoing support for Qt5, including modules like `PrintSupport`, `QuickWidgets`, `Multimedia`, and `OpenGL`. * Removed Qt4 support; fixed CMake logic for building across Qt 5.3–5.9. * Introduced `pythonqt_wrap_cpp` macro and cleaned up legacy Qt macros. * Build system enhancements: * Re-enabled and expanded CMake support with configurable install paths and testing. * Removed use of `INSTALL_NAME_DIR` to support relocatable installs. * Addressed build issues across toolchains (e.g., MSVC `/bigobj` workaround, debug mode fixes). * Added missing source files and build flags. * Code quality and compatibility: * Replaced deprecated/legacy constructs (e.g., use of `nullptr`, fixed property aliasing with `name` → `objectName`). * Added support for 511 wrappers. * Resolved warnings and linkage issues with optional build flags (e.g., `PythonQt_Wrap_Qtcore`). * Testing and cleanup: * Added cleanup/finalization unit tests. * Ensured test code compiles cleanly when wrapping is partially disabled. Co-authored-by: Florian Link <5535644+florianlink@users.noreply.github.com> Co-authored-by: Matthew Woehlke Co-authored-by: Max Smolens Co-authored-by: Pat Marion Co-authored-by: Francois Budin Co-authored-by: Christoph Willing Co-authored-by: Stefan Dinkelacker Co-authored-by: Sylvain Bernhardt --- CMakeLists.txt | 249 ++++++++++++++++--------------------- tests/PythonQtTestMain.cpp | 3 + 2 files changed, 109 insertions(+), 143 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8abaf44..f97e8a55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,27 @@ +#----------------------------------------------------------------------------- +# NOTE: The CMake files have been contributed to PythonQt and have not been tested with the current +# PythonQt version. They have not yet been updated to support Qt 5 and/or Python 3. +# +# If you are not a CMake expert, you should better use the provided qmake profiles. +#----------------------------------------------------------------------------- -cmake_minimum_required(VERSION 3.20.6) +cmake_minimum_required(VERSION 3.5) project(PythonQt) #---------------------------------------------------------------------------- # Qt version -set(_default_qt_major_version 5) -if(DEFINED Qt6_DIR) - set(_default_qt_major_version 6) -endif() - # Set PythonQt_QT_VERSION -set(PythonQt_QT_VERSION ${_default_qt_major_version} CACHE STRING "Qt major version (5 or 6)") -set_property(CACHE PythonQt_QT_VERSION PROPERTY STRINGS "5" "6") -if(NOT "${PythonQt_QT_VERSION}" MATCHES "^(5|6)$") - message(FATAL_ERROR "error: PythonQt_QT_VERSION must be 5 or 6.") -endif() +set(PythonQt_QT_VERSION 5) # Requirements -set(minimum_required_qt5_version "5.15.0") -set(minimum_required_qt6_version "6.4.0") +set(minimum_required_qt5_version "5.3.0") set(minimum_required_qt_version ${minimum_required_qt${PythonQt_QT_VERSION}_version}) -find_package(Qt${PythonQt_QT_VERSION} ${minimum_required_qt_version} QUIET) +find_package(Qt5 ${minimum_required_qt_version} QUIET) +set(QT_VERSION_MAJOR ${Qt5_VERSION_MAJOR}) +set(QT_VERSION_MINOR ${Qt5_VERSION_MINOR}) #---------------------------------------------------------------------------- # Qt components @@ -32,33 +30,39 @@ set(qtlibs Widgets Network OpenGL - Qml - Quick Sql Svg Multimedia UiTools Xml + XmlPatterns ) -# XmlPatterns is removed from Qt 6 -if(PythonQt_QT_VERSION VERSION_EQUAL "5") +# Webkit has been removed in Qt >= 5.6 +if("${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}" VERSION_LESS "5.7") list(APPEND qtlibs - XmlPatterns - ) + WebKitWidgets + ) +endif() +if("${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}" VERSION_GREATER "5.5") + list(APPEND qtlibs + Qml + Quick + ) endif() #----------------------------------------------------------------------------- # Python libraries -find_package(Python3 COMPONENTS Development REQUIRED) +find_package(PythonLibs REQUIRED) +include_directories("${PYTHON_INCLUDE_DIR}") +add_definitions( + -DPYTHONQT_USE_RELEASE_PYTHON_FALLBACK + -DPYTHONQT_SUPPORT_NAME_PROPERTY + ) #----------------------------------------------------------------------------- # Build options -option(PythonQt_SUPPORT_NAME_PROPERTY "Enable PythonQt name-property support" ON) -option(PythonQt_USE_RELEASE_PYTHON_FALLBACK "Fallback to Release python when Debug missing" ON) -option(PythonQt_DEBUG "Enable PythonQt debug output" OFF) - if(NOT DEFINED PythonQt_INSTALL_RUNTIME_DIR) set(PythonQt_INSTALL_RUNTIME_DIR bin) endif() @@ -81,20 +85,9 @@ endif() set(qtlib_to_wraplib_Widgets gui) set(qtlib_to_wraplib_WebKitWidgets webkit) -set(qt_wrapped_lib_depends_gui Multimedia PrintSupport) -if(PythonQt_QT_VERSION VERSION_EQUAL "6") - list(APPEND qt_wrapped_lib_depends_gui Widgets OpenGL) -endif() -set(qt_wrapped_lib_depends_multimedia MultimediaWidgets) -set(qt_wrapped_lib_depends_quick QuickWidgets) -set(qt_wrapped_lib_depends_svg ) -if(PythonQt_QT_VERSION VERSION_EQUAL "6") - list(APPEND qt_wrapped_lib_depends_svg SvgWidgets) -endif() -set(qt_wrapped_lib_depends_webkit ) -if(PythonQt_QT_VERSION VERSION_EQUAL "6") - list(APPEND qt_wrapped_lib_depends_webkit WebKitWidgets) -endif() +set(qt5_wrapped_lib_depends_gui Multimedia PrintSupport) +set(qt5_wrapped_lib_depends_multimedia MultimediaWidgets) +set(qt5_wrapped_lib_depends_quick QuickWidgets) foreach(qtlib ${qtlibs}) string(TOLOWER ${qtlib} qtlib_lowercase) @@ -127,6 +120,13 @@ if(PythonQt_Wrap_QtAll) endforeach() endif() +option(PythonQt_DEBUG "Enable/Disable PythonQt debug output" OFF) +if(PythonQt_DEBUG) + add_definitions(-DPYTHONQT_DEBUG) +else() + remove_definitions(-DPYTHONQT_DEBUG) +endif() + #----------------------------------------------------------------------------- # Setup Qt @@ -135,7 +135,7 @@ set(qt_required_components Core Widgets) foreach(qtlib ${qtlibs}) set(qt_wrapped_lib ${qtlib_to_wraplib_${qtlib}}) if(${PythonQt_Wrap_Qt${qt_wrapped_lib}}) - list(APPEND qt_required_components ${qtlib} ${qt_wrapped_lib_depends_${qt_wrapped_lib}}) + list(APPEND qt_required_components ${qtlib} ${qt${PythonQt_QT_VERSION}_wrapped_lib_depends_${qt_wrapped_lib}}) endif() endforeach() if(BUILD_TESTING) @@ -144,16 +144,22 @@ endif() list(REMOVE_DUPLICATES qt_required_components) message(STATUS "${PROJECT_NAME}: Required Qt components [${qt_required_components}]") -find_package(Qt${PythonQt_QT_VERSION} ${minimum_required_qt_version} COMPONENTS ${qt_required_components} REQUIRED) - -set(QT_VERSION_MAJOR ${Qt${PythonQt_QT_VERSION}_VERSION_MAJOR}) -set(QT_VERSION_MINOR ${Qt${PythonQt_QT_VERSION}_VERSION_MINOR}) +find_package(Qt5 ${minimum_required_qt_version} COMPONENTS ${qt_required_components} REQUIRED) set(QT_LIBRARIES ) -foreach(qt_component ${qt_required_components}) - list(APPEND QT_LIBRARIES Qt${PythonQt_QT_VERSION}::${qt_component}) +foreach(qtlib ${qt_required_components}) + include_directories(${Qt5${qtlib}_INCLUDE_DIRS}) + add_definitions(${Qt5${qtlib}_DEFINITIONS}) + list(APPEND QT_LIBRARIES ${Qt5${qtlib}_LIBRARIES}) endforeach() +# Required for use of "QtCore/private/qmetaobjectbuilder_p.h" in "PythonQt.cpp" +include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS}) + +macro(pythonqt_wrap_cpp) + qt5_wrap_cpp(${ARGV}) +endmacro() + if(UNIX) find_package(OpenGL) if(OPENGL_FOUND) @@ -162,22 +168,21 @@ if(UNIX) endif() #----------------------------------------------------------------------------- -# Pre-generated wrappers default to Qt 5.15 set (generated_cpp_515). Users may override. -if(PythonQt_QT_VERSION VERSION_EQUAL "5") - set(generated_cpp_suffix "_515") - set(_default_generated_path "${CMAKE_CURRENT_SOURCE_DIR}/generated_cpp${generated_cpp_suffix}") -else() - set(_default_generated_path "PythonQt_GENERATED_PATH-NOTFOUND") -endif() - -set(PythonQt_GENERATED_PATH "${_default_generated_path}" - CACHE PATH "Directory containing pre-generated PythonQt Qt wrappers for Qt ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR} (e.g., generated_cpp_515)") - -if(NOT IS_DIRECTORY "${PythonQt_GENERATED_PATH}") - message(FATAL_ERROR - "PythonQt: missing generated wrapper sources for Qt ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.\n" - "Expected: ${PythonQt_GENERATED_PATH}\n" - "Hint: pass -DPythonQt_GENERATED_PATH:PATH=/path/to/generated_cpp") +# The variable "generated_cpp_suffix" allows to conditionnally compile the generated wrappers +# associated with the Qt version being used. + +set(generated_cpp_suffix_52 _50) +set(generated_cpp_suffix_51 _50) + +set(generated_cpp_suffix "_${QT_VERSION_MAJOR}${QT_VERSION_MINOR}") +if(DEFINED generated_cpp_suffix_${QT_VERSION_MAJOR}${QT_VERSION_MINOR}) + set(generated_cpp_suffix "${generated_cpp_suffix_${QT_VERSION_MAJOR}${QT_VERSION_MINOR}}") +elseif("${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}" VERSION_GREATER "5.10") + set(generated_cpp_suffix "_511") +elseif("${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}" VERSION_GREATER "5.5") + set(generated_cpp_suffix "_56") +elseif("${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}" VERSION_GREATER "5.3") + set(generated_cpp_suffix "_54") endif() #----------------------------------------------------------------------------- @@ -206,16 +211,16 @@ set(sources src/PythonQtThreadSupport.cpp src/gui/PythonQtScriptingConsole.cpp - extensions/PythonQt_QtAll/PythonQt_QtAll.cpp + generated_cpp${generated_cpp_suffix}/PythonQt_QtBindings.cpp - ${PythonQt_GENERATED_PATH}/com_trolltech_qt_core_builtin/com_trolltech_qt_core_builtin0.cpp - ${PythonQt_GENERATED_PATH}/com_trolltech_qt_core_builtin/com_trolltech_qt_core_builtin_init.cpp - ${PythonQt_GENERATED_PATH}/com_trolltech_qt_gui_builtin/com_trolltech_qt_gui_builtin0.cpp - ${PythonQt_GENERATED_PATH}/com_trolltech_qt_gui_builtin/com_trolltech_qt_gui_builtin_init.cpp + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_core_builtin/com_trolltech_qt_core_builtin0.cpp + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_core_builtin/com_trolltech_qt_core_builtin_init.cpp + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_gui_builtin/com_trolltech_qt_gui_builtin0.cpp + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_gui_builtin/com_trolltech_qt_gui_builtin_init.cpp ) #----------------------------------------------------------------------------- -# List headers. This list is used for the install command. +# List headers. This is list is used for the install command. set(headers src/PythonQtBoolResult.h @@ -245,13 +250,20 @@ set(headers src/PythonQtUtils.h src/PythonQtVariants.h src/PythonQtPythonInclude.h - extensions/PythonQt_QtAll/PythonQt_QtAll.h + generated_cpp${generated_cpp_suffix}/PythonQt_QtBindings.h ) #----------------------------------------------------------------------------- # Headers that should run through moc set(moc_sources + src/PythonQt.h + src/PythonQtSignalReceiver.h + src/PythonQtStdDecorators.h + src/gui/PythonQtScriptingConsole.h + + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_core_builtin/com_trolltech_qt_core_builtin0.h + generated_cpp${generated_cpp_suffix}/com_trolltech_qt_gui_builtin/com_trolltech_qt_gui_builtin0.h ) #----------------------------------------------------------------------------- @@ -263,95 +275,53 @@ foreach(qtlib ${qtlibs}) if (${PythonQt_Wrap_Qt${qt_wrapped_lib}}) - # Variable expected by PythonQt_QtAll.cpp - string(TOUPPER ${qt_wrapped_lib} qt_wrapped_lib_uppercase) - ADD_DEFINITIONS(-DPYTHONQT_WITH_${qt_wrapped_lib_uppercase}) + ADD_DEFINITIONS(-DPYTHONQT_WRAP_Qt${qt_wrapped_lib}) - set(file_prefix com_trolltech_qt_${qt_wrapped_lib}/com_trolltech_qt_${qt_wrapped_lib}) + set(file_prefix generated_cpp${generated_cpp_suffix}/com_trolltech_qt_${qt_wrapped_lib}/com_trolltech_qt_${qt_wrapped_lib}) foreach(index RANGE 0 12) # Source files - if(EXISTS ${PythonQt_GENERATED_PATH}/${file_prefix}${index}.cpp) - list(APPEND sources ${PythonQt_GENERATED_PATH}/${file_prefix}${index}.cpp) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file_prefix}${index}.cpp) + list(APPEND sources ${file_prefix}${index}.cpp) endif() # Headers that should run through moc - if(EXISTS ${PythonQt_GENERATED_PATH}/${file_prefix}${index}.h) - list(APPEND moc_sources ${PythonQt_GENERATED_PATH}/${file_prefix}${index}.h) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file_prefix}${index}.h) + list(APPEND moc_sources ${file_prefix}${index}.h) endif() endforeach() - list(APPEND sources ${PythonQt_GENERATED_PATH}/${file_prefix}_init.cpp) + list(APPEND sources ${file_prefix}_init.cpp) endif() endforeach() #----------------------------------------------------------------------------- # Do wrapping -qt_wrap_cpp(sources ${moc_sources}) - -#----------------------------------------------------------------------------- -# Configure header - -# Map the CMake option `PythonQt_USE_RELEASE_PYTHON_FALLBACK` (camelCase) -# to a plain variable used by the configured header. The public-facing -# preprocessor symbol remains `PYTHONQT_USE_RELEASE_PYTHON_FALLBACK`. -set(PYTHONQT_USE_RELEASE_PYTHON_FALLBACK ${PythonQt_USE_RELEASE_PYTHON_FALLBACK}) -configure_file( - src/PythonQtConfigure.h.in - ${CMAKE_CURRENT_BINARY_DIR}/src/PythonQtConfigure.h - ) -unset(PYTHONQT_USE_RELEASE_PYTHON_FALLBACK) - -list(APPEND headers - ${CMAKE_CURRENT_BINARY_DIR}/src/PythonQtConfigure.h - ) +pythonqt_wrap_cpp(gen_moc_sources ${moc_sources}) #----------------------------------------------------------------------------- # Build the library -add_library(PythonQt SHARED ${sources}) -set_target_properties(PythonQt PROPERTIES DEFINE_SYMBOL PYTHONQT_EXPORTS) - -set_target_properties(PythonQt - PROPERTIES - AUTOMOC TRUE +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/src ) -# Disable AUTOMOC for specified moc sources to avoid QMetaTypeId specialization conflicts. -foreach(moc_source IN LISTS moc_sources) - set_property(SOURCE ${moc_source} PROPERTY SKIP_AUTOMOC ON) -endforeach() - -target_compile_definitions(PythonQt - PRIVATE - $<$:PYTHONQT_DEBUG> - $<$:PYTHONQT_SUPPORT_NAME_PROPERTY> - # No need to export PYTHONQT_USE_RELEASE_PYTHON_FALLBACK publicly. the - # configured header handles consumers. +add_library(PythonQt SHARED + ${sources} + ${gen_moc_sources} ) +set_target_properties(PythonQt PROPERTIES DEFINE_SYMBOL PYTHONQT_EXPORTS) target_compile_options(PythonQt PRIVATE $<$:/bigobj> ) -target_include_directories(PythonQt - PUBLIC - $ - $ - $ - $ - ) - target_link_libraries(PythonQt - PUBLIC - Python3::Python - ${QT_LIBRARIES} - PRIVATE - # Required for use of "QtCore/private/qmetaobjectbuilder_p.h" in "PythonQt.cpp" - Qt${PythonQt_QT_VERSION}::CorePrivate + ${PYTHON_LIBRARY} + ${QT_LIBRARIES} ) #----------------------------------------------------------------------------- @@ -381,34 +351,27 @@ if(BUILD_TESTING) tests/PythonQtTests.h ) + pythonqt_wrap_cpp(test_sources + tests/PythonQtTests.h + ) + if(PythonQt_Wrap_Qtcore) + include_directories(generated_cpp${generated_cpp_suffix}) + list(APPEND test_sources tests/PythonQtTestCleanup.cpp tests/PythonQtTestCleanup.h ) + pythonqt_wrap_cpp(test_sources + tests/PythonQtTestCleanup.h + ) set_property(SOURCE tests/PythonQtTestMain.cpp APPEND PROPERTY COMPILE_DEFINITIONS "PythonQt_Wrap_Qtcore") endif() add_executable(PythonQtCppTests ${test_sources}) - - target_include_directories(PythonQtCppTests - PRIVATE - $<$:${PythonQt_GENERATED_PATH}> - ) - target_link_libraries(PythonQtCppTests PythonQt) - set_target_properties(PythonQtCppTests - PROPERTIES - AUTOMOC TRUE - ) - - target_compile_definitions(PythonQtCppTests - PRIVATE - $<$:PYTHONQT_SUPPORT_NAME_PROPERTY> - ) - add_test( NAME tests_PythonQtTestMain COMMAND ${Slicer_LAUNCH_COMMAND} $ tests/PythonQtTestMain diff --git a/tests/PythonQtTestMain.cpp b/tests/PythonQtTestMain.cpp index c939ec09..0dc87947 100644 --- a/tests/PythonQtTestMain.cpp +++ b/tests/PythonQtTestMain.cpp @@ -77,6 +77,7 @@ int main(int argc, char *argv[]) Py_Finalize(); } +#ifdef PythonQt_Wrap_Qtcore PythonQtTestCleanup cleanup; failCount += QTest::qExec(&cleanup, argc, argv); @@ -85,6 +86,8 @@ int main(int argc, char *argv[]) } else { std::cout << "All tests passed successfully." << std::endl; } +#endif + return failCount != 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From f56ac879f5fa19012f5bc31f508343ed66e16c63 Mon Sep 17 00:00:00 2001 From: Hans Johnson Date: Tue, 15 Jul 2025 17:20:38 -0500 Subject: [PATCH 8/8] COMP: Prefer to use find_package(Python3 ) The Python3 find_package is more robust and allows more control over ABI and version selections. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f97e8a55..83d222c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,8 @@ endif() #----------------------------------------------------------------------------- # Python libraries -find_package(PythonLibs REQUIRED) -include_directories("${PYTHON_INCLUDE_DIR}") +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +include_directories("${Python3_INCLUDE_DIRS}") add_definitions( -DPYTHONQT_USE_RELEASE_PYTHON_FALLBACK -DPYTHONQT_SUPPORT_NAME_PROPERTY