Skip to content

Commit 3b2f727

Browse files
jcfrflorianlinkmwoehlke-kitwaremsmolenspatmarion
authored andcommitted
cmake: Enable CMake support importing historical improvements from commontk/PythonQt fork
This commit brings in CMake support from historical changes developed in the `commontk/PythonQt` fork between 2011 and 2026. Co-authored-by: Florian Link <5535644+florianlink@users.noreply.github.com> Co-authored-by: Matthew Woehlke <matthew.woehlke@kitware.com> Co-authored-by: Max Smolens <max.smolens@kitware.com> Co-authored-by: Pat Marion <james.patrick.marion@gmail.com> Co-authored-by: Francois Budin <francois.budin@kitware.com> Co-authored-by: Christoph Willing <chris.willing@linux.com> Co-authored-by: Stefan Dinkelacker <s.dinkelacker@dkfz-heidelberg.de> Co-authored-by: Sylvain Bernhardt <sylvain.bernhardt@smith-nephew.com>
1 parent 51a6376 commit 3b2f727

11 files changed

Lines changed: 677 additions & 102 deletions

CMakeLists.txt

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
2+
cmake_minimum_required(VERSION 3.20.6)
3+
4+
project(PythonQt)
5+
6+
#----------------------------------------------------------------------------
7+
# Qt version
8+
9+
set(_default_qt_major_version 5)
10+
if(DEFINED Qt6_DIR)
11+
set(_default_qt_major_version 6)
12+
endif()
13+
14+
# Set PythonQt_QT_VERSION
15+
set(PythonQt_QT_VERSION ${_default_qt_major_version} CACHE STRING "Qt major version (5 or 6)")
16+
set_property(CACHE PythonQt_QT_VERSION PROPERTY STRINGS "5" "6")
17+
if(NOT "${PythonQt_QT_VERSION}" MATCHES "^(5|6)$")
18+
message(FATAL_ERROR "error: PythonQt_QT_VERSION must be 5 or 6.")
19+
endif()
20+
21+
# Requirements
22+
set(minimum_required_qt5_version "5.15.0")
23+
set(minimum_required_qt6_version "6.4.0")
24+
set(minimum_required_qt_version ${minimum_required_qt${PythonQt_QT_VERSION}_version})
25+
26+
find_package(Qt${PythonQt_QT_VERSION} ${minimum_required_qt_version} QUIET)
27+
28+
#----------------------------------------------------------------------------
29+
# Qt components
30+
set(qtlibs
31+
Core
32+
Widgets
33+
Network
34+
OpenGL
35+
Qml
36+
Quick
37+
Sql
38+
Svg
39+
Multimedia
40+
WebEngineWidgets
41+
UiTools
42+
Xml
43+
)
44+
# XmlPatterns and WebKitWidgets are removed from Qt 6
45+
if(PythonQt_QT_VERSION VERSION_EQUAL "5")
46+
list(APPEND qtlibs
47+
XmlPatterns
48+
WebKitWidgets
49+
)
50+
endif()
51+
52+
#-----------------------------------------------------------------------------
53+
# Python libraries
54+
55+
find_package(Python3 COMPONENTS Development REQUIRED)
56+
57+
#-----------------------------------------------------------------------------
58+
# Build options
59+
60+
option(PythonQt_SUPPORT_NAME_PROPERTY "Enable PythonQt name-property support" ON)
61+
option(PythonQt_USE_RELEASE_PYTHON_FALLBACK "Fallback to Release python when Debug missing" ON)
62+
option(PythonQt_DEBUG "Enable PythonQt debug output" OFF)
63+
option(PythonQt_BUILD_QTALL "Build PythonQt_QtAll bindings as a separate library" ON)
64+
65+
if(NOT DEFINED PythonQt_INSTALL_RUNTIME_DIR)
66+
set(PythonQt_INSTALL_RUNTIME_DIR bin)
67+
endif()
68+
69+
if(NOT DEFINED PythonQt_INSTALL_LIBRARY_DIR)
70+
set(PythonQt_INSTALL_LIBRARY_DIR lib${LIB_SUFFIX})
71+
endif()
72+
73+
if(NOT DEFINED PythonQt_INSTALL_ARCHIVE_DIR)
74+
set(PythonQt_INSTALL_ARCHIVE_DIR lib${LIB_SUFFIX})
75+
endif()
76+
77+
if(NOT DEFINED PythonQt_INSTALL_INCLUDE_DIR)
78+
set(PythonQt_INSTALL_INCLUDE_DIR include/PythonQt)
79+
endif()
80+
81+
#-----------------------------------------------------------------------------
82+
# Set qtlib_to_wraplib_* variables
83+
84+
set(qtlib_to_wraplib_Widgets gui)
85+
set(qtlib_to_wraplib_WebKitWidgets webkit)
86+
87+
set(qt_wrapped_lib_depends_gui Multimedia PrintSupport)
88+
if(PythonQt_QT_VERSION VERSION_EQUAL "6")
89+
list(APPEND qt_wrapped_lib_depends_gui Widgets OpenGL)
90+
endif()
91+
set(qt_wrapped_lib_depends_multimedia MultimediaWidgets)
92+
set(qt_wrapped_lib_depends_quick QuickWidgets)
93+
set(qt_wrapped_lib_depends_svg )
94+
if(PythonQt_QT_VERSION VERSION_EQUAL "6")
95+
list(APPEND qt_wrapped_lib_depends_svg SvgWidgets)
96+
endif()
97+
set(qt_wrapped_lib_depends_webenginewidgets )
98+
set(qt_wrapped_lib_depends_webkit )
99+
100+
foreach(qtlib ${qtlibs})
101+
string(TOLOWER ${qtlib} qtlib_lowercase)
102+
if(DEFINED qtlib_to_wraplib_${qtlib})
103+
set(qtlib_lowercase ${qtlib_to_wraplib_${qtlib}})
104+
endif()
105+
set(qtlib_to_wraplib_${qtlib} ${qtlib_lowercase})
106+
endforeach()
107+
108+
#-----------------------------------------------------------------------------
109+
# Define PythonQt_Wrap_Qt* options
110+
option(PythonQt_Wrap_QtAll "Make all Qt components available in python" OFF)
111+
foreach(qtlib ${qtlibs})
112+
OPTION(PythonQt_Wrap_Qt${qtlib_to_wraplib_${qtlib}} "Make all of Qt${qtlib} available in python" OFF)
113+
endforeach()
114+
115+
#-----------------------------------------------------------------------------
116+
# Force option if it applies
117+
if(PythonQt_Wrap_QtAll)
118+
foreach(qtlib ${qtlibs})
119+
# XXX xmlpatterns wrapper does *NOT* build at all :(
120+
if(${qtlib} STREQUAL "XmlPatterns")
121+
continue()
122+
endif()
123+
set(qt_wrapped_lib ${qtlib_to_wraplib_${qtlib}})
124+
if(NOT ${PythonQt_Wrap_Qt${qt_wrapped_lib}})
125+
set(PythonQt_Wrap_Qt${qt_wrapped_lib} ON CACHE BOOL "Make all of Qt${qt_wrapped_lib} available in python" FORCE)
126+
message(STATUS "Enabling [PythonQt_Wrap_Qt${qt_wrapped_lib}] because of [PythonQt_Wrap_QtAll] evaluates to True")
127+
endif()
128+
endforeach()
129+
endif()
130+
131+
#-----------------------------------------------------------------------------
132+
# Setup Qt
133+
134+
# Required components
135+
set(qt_required_components Core Widgets)
136+
foreach(qtlib ${qtlibs})
137+
set(qt_wrapped_lib ${qtlib_to_wraplib_${qtlib}})
138+
if(${PythonQt_Wrap_Qt${qt_wrapped_lib}})
139+
list(APPEND qt_required_components ${qtlib} ${qt_wrapped_lib_depends_${qt_wrapped_lib}})
140+
endif()
141+
endforeach()
142+
if(BUILD_TESTING)
143+
list(APPEND qt_required_components Test)
144+
endif()
145+
if("${Qt${PythonQt_QT_VERSION}_VERSION}" VERSION_GREATER_EQUAL "6.10.0")
146+
list(APPEND qt_required_components CorePrivate)
147+
endif()
148+
list(REMOVE_DUPLICATES qt_required_components)
149+
150+
message(STATUS "${PROJECT_NAME}: Required Qt components [${qt_required_components}]")
151+
find_package(Qt${PythonQt_QT_VERSION} ${minimum_required_qt_version} COMPONENTS ${qt_required_components} REQUIRED)
152+
153+
set(QT_VERSION_MAJOR ${Qt${PythonQt_QT_VERSION}_VERSION_MAJOR})
154+
set(QT_VERSION_MINOR ${Qt${PythonQt_QT_VERSION}_VERSION_MINOR})
155+
156+
set(QT_LIBRARIES )
157+
foreach(qt_component ${qt_required_components})
158+
list(APPEND QT_LIBRARIES Qt${PythonQt_QT_VERSION}::${qt_component})
159+
endforeach()
160+
161+
if(UNIX)
162+
find_package(OpenGL)
163+
if(OPENGL_FOUND)
164+
list(APPEND QT_LIBRARIES ${OPENGL_LIBRARIES})
165+
endif()
166+
endif()
167+
168+
#-----------------------------------------------------------------------------
169+
# Pre-generated wrappers default to Qt 5.15 set (generated_cpp_515). Users may override.
170+
if(PythonQt_QT_VERSION VERSION_EQUAL "5")
171+
set(generated_cpp_suffix "_515")
172+
set(_default_generated_path "${CMAKE_CURRENT_SOURCE_DIR}/generated_cpp${generated_cpp_suffix}")
173+
else()
174+
set(_default_generated_path "PythonQt_GENERATED_PATH-NOTFOUND")
175+
endif()
176+
177+
set(PythonQt_GENERATED_PATH "${_default_generated_path}"
178+
CACHE PATH "Directory containing pre-generated PythonQt Qt wrappers for Qt ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR} (e.g., generated_cpp_515)")
179+
180+
if(NOT IS_DIRECTORY "${PythonQt_GENERATED_PATH}")
181+
message(FATAL_ERROR
182+
"PythonQt: missing generated wrapper sources for Qt ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.\n"
183+
"Expected: ${PythonQt_GENERATED_PATH}\n"
184+
"Hint: pass -DPythonQt_GENERATED_PATH:PATH=/path/to/generated_cpp")
185+
endif()
186+
187+
#-----------------------------------------------------------------------------
188+
# Build targets
189+
add_subdirectory(src)
190+
if(PythonQt_BUILD_QTALL)
191+
add_subdirectory(extensions/PythonQt_QtAll)
192+
endif()
193+
194+
#-----------------------------------------------------------------------------
195+
# Testing
196+
197+
option(BUILD_TESTING "Build the testing tree." OFF)
198+
include(CTest)
199+
200+
if(BUILD_TESTING)
201+
create_test_sourcelist(test_sources PythonQtCppTests.cpp
202+
tests/PythonQtTestMain.cpp
203+
)
204+
205+
set_property(SOURCE tests/PythonQtTestMain.cpp PROPERTY COMPILE_DEFINITIONS "main=tests_PythonQtTestMain")
206+
207+
list(APPEND test_sources
208+
tests/PythonQtTests.cpp
209+
tests/PythonQtTests.h
210+
)
211+
212+
if(PythonQt_Wrap_QtCore AND PythonQt_BUILD_QTALL)
213+
list(APPEND test_sources
214+
tests/PythonQtTestCleanup.cpp
215+
tests/PythonQtTestCleanup.h
216+
)
217+
218+
set_property(SOURCE tests/PythonQtTestMain.cpp APPEND PROPERTY COMPILE_DEFINITIONS "PythonQt_Wrap_QtCore")
219+
endif()
220+
221+
add_executable(PythonQtCppTests ${test_sources})
222+
223+
target_include_directories(PythonQtCppTests
224+
PRIVATE
225+
$<$<BOOL:${PythonQt_Wrap_QtCore}>:${PythonQt_GENERATED_PATH}>
226+
$<$<AND:$<BOOL:${PythonQt_Wrap_QtCore}>,$<BOOL:${PythonQt_BUILD_QTALL}>>:${CMAKE_CURRENT_SOURCE_DIR}/extensions/PythonQt_QtAll>
227+
)
228+
229+
target_link_libraries(PythonQtCppTests PythonQt)
230+
if(PythonQt_Wrap_QtCore AND PythonQt_BUILD_QTALL)
231+
target_link_libraries(PythonQtCppTests PythonQt_QtAll)
232+
endif()
233+
234+
set_target_properties(PythonQtCppTests
235+
PROPERTIES
236+
AUTOMOC TRUE
237+
)
238+
239+
target_compile_definitions(PythonQtCppTests
240+
PRIVATE
241+
$<$<BOOL:${PythonQt_SUPPORT_NAME_PROPERTY}>:PYTHONQT_SUPPORT_NAME_PROPERTY>
242+
)
243+
244+
if(WIN32)
245+
# Python3::Python may resolve to the import library directory (e.g. .../libs),
246+
# while pythonXY.dll typically lives one level above. Add both.
247+
get_filename_component(_python3_lib_dir "${Python3_LIBRARY}" DIRECTORY)
248+
get_filename_component(_python3_root_dir "${_python3_lib_dir}" DIRECTORY)
249+
250+
set(_pythonqt_test_path_entries
251+
"$<TARGET_FILE_DIR:PythonQtCppTests>"
252+
"$<TARGET_FILE_DIR:PythonQt>"
253+
"$<TARGET_FILE_DIR:Qt${PythonQt_QT_VERSION}::Core>"
254+
"$<TARGET_FILE_DIR:Python3::Python>"
255+
"${_python3_lib_dir}"
256+
"${_python3_root_dir}"
257+
)
258+
if(PythonQt_BUILD_QTALL)
259+
list(APPEND _pythonqt_test_path_entries "$<TARGET_FILE_DIR:PythonQt_QtAll>")
260+
endif()
261+
list(JOIN _pythonqt_test_path_entries ";" _pythonqt_test_path_prefix)
262+
set(_pythonqt_test_env "PATH=${_pythonqt_test_path_prefix};$ENV{PATH}")
263+
264+
add_test(
265+
NAME tests_PythonQtTestMain
266+
COMMAND ${CMAKE_COMMAND} -E env "${_pythonqt_test_env}" $<TARGET_FILE:PythonQtCppTests> tests/PythonQtTestMain
267+
)
268+
else()
269+
add_test(
270+
NAME tests_PythonQtTestMain
271+
COMMAND $<TARGET_FILE:PythonQtCppTests> tests/PythonQtTestMain
272+
)
273+
endif()
274+
endif()
275+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#-----------------------------------------------------------------------------
2+
# Build PythonQt_QtAll bindings as a dedicated shared library.
3+
4+
set(pythonqt_qtall_sources
5+
PythonQt_QtAll.cpp
6+
)
7+
8+
set(pythonqt_qtall_headers
9+
PythonQt_QtAll.h
10+
)
11+
12+
set(pythonqt_qtall_moc_headers)
13+
set(pythonqt_qtall_components)
14+
set(pythonqt_qtall_defines)
15+
16+
foreach(qtlib ${qtlibs})
17+
set(qt_wrapped_lib ${qtlib_to_wraplib_${qtlib}})
18+
19+
if(PythonQt_Wrap_Qt${qt_wrapped_lib})
20+
string(TOUPPER ${qt_wrapped_lib} qt_wrapped_lib_uppercase)
21+
list(APPEND pythonqt_qtall_defines PYTHONQT_WITH_${qt_wrapped_lib_uppercase})
22+
23+
list(APPEND pythonqt_qtall_components ${qtlib} ${qt_wrapped_lib_depends_${qt_wrapped_lib}})
24+
25+
set(file_prefix com_trolltech_qt_${qt_wrapped_lib}/com_trolltech_qt_${qt_wrapped_lib})
26+
27+
# Collect generated wrapper files without relying on a fixed index range.
28+
file(GLOB generated_cpp_files "${PythonQt_GENERATED_PATH}/${file_prefix}[0-9]*.cpp")
29+
list(SORT generated_cpp_files)
30+
list(APPEND pythonqt_qtall_sources ${generated_cpp_files})
31+
32+
file(GLOB generated_h_files "${PythonQt_GENERATED_PATH}/${file_prefix}[0-9]*.h")
33+
list(SORT generated_h_files)
34+
list(APPEND pythonqt_qtall_moc_headers ${generated_h_files})
35+
36+
set(init_cpp_file "${PythonQt_GENERATED_PATH}/${file_prefix}_init.cpp")
37+
if(EXISTS "${init_cpp_file}")
38+
list(APPEND pythonqt_qtall_sources ${init_cpp_file})
39+
endif()
40+
endif()
41+
endforeach()
42+
43+
list(REMOVE_DUPLICATES pythonqt_qtall_components)
44+
45+
qt_wrap_cpp(pythonqt_qtall_sources ${pythonqt_qtall_moc_headers})
46+
47+
add_library(PythonQt_QtAll SHARED ${pythonqt_qtall_sources})
48+
set_target_properties(PythonQt_QtAll PROPERTIES DEFINE_SYMBOL PYTHONQT_QTALL_EXPORTS)
49+
50+
set_target_properties(PythonQt_QtAll
51+
PROPERTIES
52+
AUTOMOC TRUE
53+
)
54+
55+
# Disable AUTOMOC for specified moc sources to avoid QMetaTypeId specialization conflicts.
56+
foreach(moc_source IN LISTS pythonqt_qtall_moc_headers)
57+
set_property(SOURCE ${moc_source} PROPERTY SKIP_AUTOMOC ON)
58+
endforeach()
59+
60+
target_compile_options(PythonQt_QtAll PRIVATE
61+
$<$<CXX_COMPILER_ID:MSVC>:/bigobj>
62+
)
63+
64+
target_compile_definitions(PythonQt_QtAll
65+
PRIVATE
66+
${pythonqt_qtall_defines}
67+
)
68+
69+
target_include_directories(PythonQt_QtAll
70+
PUBLIC
71+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
72+
$<INSTALL_INTERFACE:${PythonQt_INSTALL_INCLUDE_DIR}>
73+
)
74+
75+
target_link_libraries(PythonQt_QtAll
76+
PUBLIC
77+
PythonQt
78+
)
79+
80+
foreach(qt_component IN LISTS pythonqt_qtall_components)
81+
target_link_libraries(PythonQt_QtAll PUBLIC Qt${PythonQt_QT_VERSION}::${qt_component})
82+
endforeach()
83+
84+
install(TARGETS PythonQt_QtAll
85+
RUNTIME DESTINATION ${PythonQt_INSTALL_RUNTIME_DIR}
86+
LIBRARY DESTINATION ${PythonQt_INSTALL_LIBRARY_DIR}
87+
ARCHIVE DESTINATION ${PythonQt_INSTALL_ARCHIVE_DIR}
88+
)
89+
install(FILES ${pythonqt_qtall_headers} DESTINATION ${PythonQt_INSTALL_INCLUDE_DIR})

extensions/PythonQt_QtAll/PythonQt_QtAll.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ void PythonQt_init_QtQuick(PyObject*);
7171
void PythonQt_init_QtUiTools(PyObject*);
7272
#endif
7373

74+
#ifdef PYTHONQT_WITH_WEBENGINEWIDGETS
75+
void PythonQt_init_QtWebEngineWidgets(PyObject*);
76+
#endif
77+
7478
#ifdef PYTHONQT_WITH_WEBKIT
7579
void PythonQt_init_QtWebKit(PyObject*);
7680
#endif
@@ -117,5 +121,8 @@ PYTHONQT_QTALL_EXPORT void init()
117121
#ifdef PYTHONQT_WITH_UITOOLS
118122
PythonQt_init_QtUiTools(0);
119123
#endif
124+
#ifdef PYTHONQT_WITH_WEBENGINEWIDGETS
125+
PythonQt_init_QtWebEngineWidgets(0);
126+
#endif
120127
}
121128
}

0 commit comments

Comments
 (0)