Skip to content

Add cmake build system#345

Draft
jamesobutler wants to merge 33 commits intoMeVisLab:mainfrom
jamesobutler:add-cmake-build-system
Draft

Add cmake build system#345
jamesobutler wants to merge 33 commits intoMeVisLab:mainfrom
jamesobutler:add-cmake-build-system

Conversation

@jamesobutler
Copy link
Copy Markdown
Contributor

This PR is an effort previously discussed to bring in the CMake build system for modern cross compilation of C++ libraries. This PR includes the CMake build system as of https://github.com/commontk/PythonQt/tree/patched-v4.0.1-2026-03-16-5cd9b581f. The CommonTK organization is used by applications such as 3D Slicer and MITK. Using CMake to build PythonQt has been utilized successfully by 3D Slicer for well over a decade. Integration of this work back to the upstream has taken a long time due to multiple factors including MeVisLab's PythonQt source repository not on GitHub and 3D Slicer maintaining a series of patches that had not been contributed back to the upstream. Both situations have changed where PythonQt is on GitHub and the long list of patches were contributed to the upstream in the fall of 2025.

A large majority of this CMake work is due to the fantastic work by @jcfr who also worked to contribute the patches back to this upstream repo. Late last year JC made a job move to Nvidia and therefore contributions to 3D Slicer and the ecosystem of supporting open-source libraries has been limited. I submit this PR to keep this work going to bring the CommonTK fork of PythonQt back in sync with the MeVisLab PythonQt upstream.

I provide all the commits here for the CMake development for historic reference, but maintainers could consider squashing of commits for less noise.

I have created a new GitHub actions workflow as part of testing the CMake build system similar to the existing CI infrastructure. This PR doesn't currently remove the existing build system, though maintainers here could consider a replacement based on stabilization here and then avoid the complexity of maintaining 2 build systems.

By checking if an enum member has already been cached it is not required
anymore to skip it if is a QFlags.

Additionally, the wrapping of QFlags can now be done by using
only Q_FLAGS without having a corresponding Q_ENUMS.

Cherry-picked from commontk/PythonQt@86068fd
@jamesobutler jamesobutler force-pushed the add-cmake-build-system branch 10 times, most recently from da92bd6 to d716b30 Compare March 29, 2026 23:39
jcfr and others added 19 commits March 29, 2026 21:56
… commontk/PythonQt fork

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.

(cherry picked from commit MeVisLab/pythonqt@93c546e)

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>
PythonQt_QtBindings.cpp and PythonQt_QtBindings.h were copied over from the generated_cpp_511 directory.
This changes the `PYTHONQT_SUPPORT_NAME_PROPERTY` definition from a global `add_definition`
to a target-specific compile definition and adds the CMake option `PythonQt_SUPPORT_NAME_PROPERTY`
set to ON by default.
…perty

This converts the `PYTHONQT_USE_RELEASE_PYTHON_FALLBACK` definition from a
global add_definition to a target-specific compile definition and adds
the CMake option `PythonQt_USE_RELEASE_PYTHON_FALLBACK` set to ON by default.
This changes the include directories from global include_directories to
target-specific target_include_directories. This improves the
encapsulation and modularity of the build.
This changes the PYTHONQT_DEBUG definition from a global add_definition
to a target-specific compile definition.
This simplifies Qt5 integration by using Qt5 imported targets instead of
manually including directories and definitions.
…ated_cpp_5.15

This removes obsolete support for generated_cpp configurations other than 5.15.
The FindPythonLib CMake module is deprecated since CMake 3.12 and starting
with CMake 3.27 the module is not provided unless CMP0148 is set to OLD.

To integrate PythonQt in other project, the CMake variables `Python3_LIBRARY`
and `Python3_INCLUDE_DIR` may be set instead of `PYTHON_LIBRARY`, `PYTHON_INCLUDE_DIR`
and `PYTHON_INCLUDE_DIR2`.

Options `Python3_ROOT_DIR`, `Python3_LIBRARY_DEBUG` and `Python3_LIBRARY_RELEASE`
may also be set.

Co-authored-by: Hans Johnson <hans-johnson@uiowa.edu>
Introduce `PythonQtConfigure.h` to derive `PYTHONQT_USE_RELEASE_PYTHON_FALLBACK`
from the CMake option `PythonQt_USE_RELEASE_PYTHON_FALLBACK`.

Export build-tree binary include dir and install the configured header so
`#include <PythonQtConfigure.h>` works for both build and install trees.

Stop requiring downstreams to set the macro via compile definitions; users can
still override by predefining `PYTHONQT_USE_RELEASE_PYTHON_FALLBACK`.
make PythonQt_QtAll export macro treat both PYTHONQT_QTALL_EXPORTS and PYTHONQT_EXPORTS as build context

Co-authored-by: James Butler <james.butler@revvity.com>
- Leverages automatic execution of MOC and RCC.
- Removes obsolete install rules for `build_*.txt` and `typesystem_*.xml`.
- Uses "usage requirements" and aligns source lists with `parser/rxx.pro`,
  `simplecpp/simplecpp.pri`, `generator.pri`, and `generator.pro`.
jcfr and others added 6 commits March 29, 2026 22:01
Partially reverts dd82ba4b ("[commontk] cmake: Simplify build-system
leveraging AUTOMOC capability", 2025-10-23) to fix a build error
reported when AUTOMOC merges all `moc_*.cpp` into one Translation Unit
(TU) (`<target>_autogen/mocs_compilation*.cpp`).

This change includes:
- Reverting to manual specification of moc sources.
- Disabling AUTOMOC for specified sources to avoid QMetaTypeId
  specialization conflicts.

This fixes the following error:

```
In file included from /path/to/Support/Qt/6.9.1/gcc_64/include/QtCore/qvariant.h:10,
                 from /path/to/Support/Qt/6.9.1/gcc_64/include/QtCore/qmetaobject.h:10,
                 from /path/to/Support/Qt/6.9.1/gcc_64/include/QtCore/QMetaMethod:1,
                 from /path/to/Projects/PythonQt-CTK/src/PythonQtUtils.h:49,
                 from /path/to/Projects/PythonQt-CTK/src/PythonQt.h:46,
                 from /path/to/Projects/PythonQt-CTK-cmake-Qt6-Release/PythonQt_autogen/CRAGYDUSE3/../../../CTK-Qt6-build/PythonQtGenerator-output-6.9.1/generated_cpp/com_trolltech_qt_core/com_trolltech_qt_core0.h:1,
                 from /path/to/Projects/PythonQt-CTK-cmake-Qt6-Release/PythonQt_autogen/CRAGYDUSE3/moc_com_trolltech_qt_core0.cpp:9,
                 from /path/to/Projects/PythonQt-CTK-cmake-Qt6-Release/PythonQt_autogen/mocs_compilation.cpp:2:
/path/to/Support/Qt/6.9.1/gcc_64/include/QtPrintSupport/qprintengine.h:12:1: error: specialization of ‘QMetaTypeId<QMarginsF>’ after instantiation
   12 | Q_DECLARE_METATYPE(QMarginsF)
      | ^~~~~~~~~~~~~~~~~~
/path/to/Support/Qt/6.9.1/gcc_64/include/QtPrintSupport/qprintengine.h:12:1: error: redefinition of ‘struct QMetaTypeId<QMarginsF>’
/path/to/Support/Qt/6.9.1/gcc_64/include/QtCore/qmetatype.h:1232:8: note: previous definition of ‘struct QMetaTypeId<QMarginsF>’
```
This changes the minimum required Qt6 version from 6.9.0 to 6.4.0 to
ensure compatibility with Ubuntu 24.04, which ships with Qt 6.4.2.
@jamesobutler jamesobutler force-pushed the add-cmake-build-system branch from d716b30 to 436435c Compare March 30, 2026 02:03
@jamesobutler jamesobutler marked this pull request as ready for review March 30, 2026 02:33
@jamesobutler
Copy link
Copy Markdown
Contributor Author

jamesobutler commented Mar 30, 2026

@mrbean-bremen Let me know what you think here. I’ve been testing this specific branch in the context of 3D Slicer/CommonTK and have had success.

The CommonTK based CMake external project for building:
https://github.com/jamesobutler/CTK/blob/1d55f252926477b3de0d2454e85da56f6128cee8/CMakeExternals/PythonQt.cmake#L146-L167

Would Mevislab adopt CMake for PythonQt building at some point too? 3D Slicer and CommonTK has been a fully CMake build system for over a decade and Qt 6 switched to building with CMake as well.

@mrbean-bremen
Copy link
Copy Markdown
Contributor

Thanks for this!

Would Mevislab adopt CMake for PythonQt building at some point too?

We actually use CMake to build PythonQt for a few years now, albeit our implementation may not be generic enough.
We had been discussing merging this back, and IIRC, @jcfr at the time had volunteered to merge the Slicer version upstream instead, so we decided to wait for this.
I will leave the actual review to @usiems, who was involved in the switch to CMake in the first place.

As for the CI builds - I also think that in the long run we can remove the qmake jobs, at least for newer versions of Qt.

@jamesobutler
Copy link
Copy Markdown
Contributor Author

jamesobutler commented Mar 30, 2026

We actually use CMake to build PythonQt for a few years now, albeit our implementation may not be generic enough.

That's great to hear as we will then have more people supporting the CMake build infrastructure here.

As for the CI builds - I also think that in the long run we can remove the qmake jobs, at least for newer versions of Qt.

Yeah all the various jobs was definitely starting to get confusing to me because there were say Windows jobs from the build.yml workflow as well as build_latest.yml and I wasn't sure why both existed. Then the Windows CMake jobs just added more on top of that.

Is there also still a need to support older Qt versions such as 5.9 and 5.11 in the latest main branch? Or can we support only Qt 5.15+ (aka supporting folks jumping to Qt6 from Qt 5.15)? There are always the older tags of PythonQt that will be forever around and unchanging so it is not like the code is disappearing. That would help reduce the complexity around the testing matrix of latest code.

@mrbean-bremen
Copy link
Copy Markdown
Contributor

Is there also still a need to support older Qt versions such as 5.9 and 5.11 in the latest main branch?

From my perspective no (@he-hesce may disagree though 😀).

@he-hesce
Copy link
Copy Markdown
Contributor

he-hesce commented Mar 31, 2026

We need support for Qt 5.9 which is in use on RHEL 7. However, as RHEL 7 is using Python 2.7, we are stuck on PythonQt 3.6.1 until RHEL 7 can be retired (~3-4 years). Then we can upgrade to PythonQt 4.x series. Qt 5.15 is what is used on RHEL 9 which means that in the 4.x series, we only need support for Qt 5.15.

We would like that if possible that PythonQt kept qmake support in parallel with cmake support as we build PythonQt as part of our code base which uses Qt 5.15 and qmake which makes things nice and integrated without having to support/use another build system (cmake) from within qmake. I think it would be a good idea to support qmake as long as Qt 5.15 is supported.

We can of course still get stuck on some specific 4.x revision eventually. It's likely we will be using Qt 5.15 for another 9 years at least as we need to support RHEL 9 until 2035 if not beyond the official EOL. We won't be moving to Qt 6.x (and thus cmake) until we move on to RHEL 10/11 and can drop support for RHEL 9/Qt 5.15 (so around 2035-2038).

We prefer to use a single PythonQt version on all supported platforms as it makes building, testing, and deployment easier. Obviously I do understand that our needs cannot dictate overall PythonQt project policy and I appreciate the backward compatibility which exist so far.

Our active support matrix for our scientific geophysical visualization software using PythonQt 3.6.1 on all platforms:

  • RHEL 7 - Python 2.7 - Qt 5.9 - EOS (End of Support) 2030;
  • RHEL 8 - Python 3.6 - Qt 5.15 - EOS 2033;
  • RHEL 9 - Python 3.9/3.11/3.12 - Qt 5.15 - EOS 2038;
  • Ubuntu 22.04 LTS - Python 3.10 - Qt 5.15 - EOS 2028;
  • Ubuntu 24.04 LTS - Python 3.12 - Qt 5.15 - EOS 2030.

@jamesobutler
Copy link
Copy Markdown
Contributor Author

jamesobutler commented Mar 31, 2026

For @he-hesce's use case it seems like creating a 4.0 branch to serve as an LTS for over a decade of support seems reasonable. @mrbean-bremen Maybe you can make this maintenance branch based off the v4.0.1 tag? There appears to already have been some maintenance branch's like this such as 3.2 and 3.0. It is not that @he-hesce 's application will never switch to Qt6 or CMake, but just far in the future. So the main branch developers could continue with consolidating on a single build system and optimizing it with the help of the MeVisLab and Slicer community and @he-hesce could help maintain the 4.0 branch for LTS support until jumping to a new LTS branch far in the future. The LTS branches would represent absolute minimal changes based on the apparent desire to not make changes unless absolutely necessary.

@he-hesce
Copy link
Copy Markdown
Contributor

he-hesce commented Mar 31, 2026

@jamesobutler: Sounds good.

I think for me, as PythonQt is "feature complete", a stable LTS branch would best be supported by ensuring newer Qt 6.x and Python 3.x releases are working (while not breaking older releases) as we don't know yet what Qt 6.x and Python 3.x will be the LTS-supported ones on RHEL 11 and Ubuntu 26.04 LTS and beyond. On RHEL 10, it appears they are currently supporting Qt 5.15/6.9 and Python 3.12 (thus making Qt 5.15 supported by RHEL until 31 May 2038 [!] and thus supported in our product until 2041).

Thus backporting compatibility fixes from the main branch to LTS branches would be a good idea I think. Other than fixing critical/show-stopper bugs which are found (though these seem rare).

@jamesobutler
Copy link
Copy Markdown
Contributor Author

Thus backporting compatibility fixes to the main branch to LTS branches would be a good idea I think.

Yes, with your help to support the backporting efforts to the LTS branch for your needs we can make this plan.

@he-hesce
Copy link
Copy Markdown
Contributor

@jamesobutler:

For supporting an LTS branch, I think providing clean and separate commits for such changes on the main branch is the best approach, if feasible. This makes it easier to cherry-pick commits from the main branch for an LTS branch. E.g., not intermingle support for Python 3.15 with cmake build system in same commit.

@jamesobutler
Copy link
Copy Markdown
Contributor Author

jamesobutler commented Mar 31, 2026

Yes, that is something we practice in all the repos related to 3D Slicer. MeVisLab devs can aim to commit to the same if they don't already. Then in the year 2041 or 2042, you can switch to the new branch that is Qt6 only and CMake build system only. Though at that point in time, who knows what type of dev tools we will have to support maintenance activities. This may not be something you have to worry about it since you are using the existing code pretty much without modifications for the next 16 years.

@he-hesce
Copy link
Copy Markdown
Contributor

he-hesce commented Mar 31, 2026

I also think it would be a best practice approach to pick LTS distributions for basing support need on. Basically, keep in the testing matrix those Python and Qt versions which are provided by the LTS releases by Red Hat and let's say Ubuntu. Thus there would/should be no need unless requested to support other Qt and Python 3 versions than those which are long-term-supported by the platform vendor themselves. I.e., a PythonQt LTS branch should build and pass test suites on RHEL and Ubuntu LTS platforms in active vendor support window. E.g., there should be no need to support Python 3.x versions on RHEL 10 which are not supported by RH themselves on RHEL 10.

https://endoflife.date/rhel

https://endoflife.date/ubuntu

@he-hesce
Copy link
Copy Markdown
Contributor

he-hesce commented Mar 31, 2026

@jamesobutler: I expect to be retired by 2041 :-) but I do need to keep the 40-80 year lifespan of the software under consideration for future generations of developers. The move-fast-and-break-things crew underestimate the lifespan and support window of scientific/production-use software. (We are still using and supporting scientific libraries from the 70s/80s as the science has not changed and thus the library does not need to be rewritten potentially introducing new errors.)

@jamesobutler
Copy link
Copy Markdown
Contributor Author

Yes, for us we are trying to solve "today's problems today" so staying on latest tooling, libraries (e.g. Qt6), operating systems (Win11, macOS 26, etc) are critical and expected of our customers in the scientific and medical imaging space. We can keep an LTS, but it appears our group and MeVisLab will aim to solve development issues we run into in the near term.

@he-hesce
Copy link
Copy Markdown
Contributor

@jamesobutler: Good you have customers which upgrade. I have seen MRI machines still running Windows 2000 this year. :-)

@jamesobutler
Copy link
Copy Markdown
Contributor Author

Yes, we have customers with CT, Ultrasound and Optical imaging instruments switching from Windows 10 to Windows 11 based on Windows 10 no longer receiving security updates and otherwise not allowed for use at their institution.

@he-hesce
Copy link
Copy Markdown
Contributor

he-hesce commented Mar 31, 2026

We have software which would take 20-30 years to rewrite thus it will never be funded, happen, or even work out as imagine a 30-year project being successful. Thus it is patching and supporting old software and systems to keep going forever which is the way. Security is less of an issue as nothing is exposed directly to the Internet and RH does still provide security support for RHEL 7. Managed to get all systems off RHEL 6 about two years ago and get them moved to RHEL 7. Those systems might be ported and able to be moved to RHEL 9 in about 3-4 years but that may be too optimistic. Tendency is to be able to move to a new platform when that platform is already approaching EOL. (It takes about 5-10 years to do the porting, testing, and acceptance.)

Luckily we do not need to support Windows nor macOS with their brief support windows. We shifted from Solaris to Linux 20 years ago; and from VMS to Solaris some decades before that. Still using scientific libraries originally written for VMS in Fortran 77.

Moving some (20%, thus not yet all) scientific visualization from Motif to Qt took 15 years. Though the lion share of visualization still running on Motif and will continue to do so for a decade or longer. A major issue being RHEL 10 dropping Motif support so RHEL 9 might be in use for a very long time (beyond EOL).

The benefit of moving from Motif to Qt and thanks to PythonQt, embedded scripting (extensions) could move from a bespoke scripting language to Python. Python then also enabling extensions and interchange of data with Obspy and the Python scientific code community. (Young, <50yo) Scientists also do/know more Python these days than C/C++ for writing extensions.

@mrbean-bremen
Copy link
Copy Markdown
Contributor

For @he-hesce's use case it seems like creating a 4.0 branch to serve as an LTS for over a decade of support seems reasonable.

We can always create a maintenance branch from a tag, as soon as the need arrives to back-port something. From my experience, this is rarely needed for small projects like this one, and we can decide how to do it when needed. Declaring an official LTS may be overkill given the size of the user base.

a stable LTS branch would best be supported by ensuring newer Qt 6.x and Python 3.x releases are working (while not breaking older releases)

Up to a point. As we have ditched Python 2 support, we will probably not support some older Python and Qt versions in the future - that depends on the kind of changes needed for newer versions. Planning ahead 10 years is generally way to ambitious for me for any project (especially small ones) 😀

I think providing clean and separate commits for such changes on the main branch is the best approach

Yes, I think this is our common approach here.

We can keep an LTS, but it appears our group and MeVisLab will aim to solve development issues we run into in the near term.

Exactly.

I have seen MRI machines still running Windows 2000 this year. :-)

I can readily believe this. I was involved in making a patch release for software running under Windows 2000 a few years ago - in a VM with W2K, 4GB HD and 64MB RAM, that was really fun... I have also seen crashed ATMs showing the Windows NT 4 panic screen not so long ago.

Copy link
Copy Markdown
Contributor

@usiems usiems left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please combine the commits as much as useful. The history of these changes is not that interesting for other users, the only reason to have separate commits is if one might perhaps want to merge or revert only parts of this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please have a separate merge request for the flags stuff? Since this is totally unrelated to the CMake stuff?

content = f.read()

# #cmakedefine VAR [rest] -> #define VAR [rest]
content = re.sub(r"^#cmakedefine\b", "#define", content, flags=re.MULTILINE)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me as if this script always changes #cmakedefine into #define, so there is no way to opt out of this option when using qmake. Too retain backwards compatibility it should be changed into something which does not set this variable.

Multimedia
UiTools
Xml
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that WebEngineWidgets is missing from this list.

CMakeLists.txt Outdated

add_test(
NAME tests_PythonQtTestMain
COMMAND ${Slicer_LAUNCH_COMMAND} $<TARGET_FILE:PythonQtCppTests> tests/PythonQtTestMain
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems awfully Slicer specific... ;)

CMakeLists.txt Outdated

set(file_prefix com_trolltech_qt_${qt_wrapped_lib}/com_trolltech_qt_${qt_wrapped_lib})

foreach(index RANGE 0 12)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this loop. We should have the generator generate .cmake files to include, like it generates .pri files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not compile the Qt bindings into PythonQt, make it a separate library, same as with the QMake build system. And use separate CMakeLists.txt files. Probably best to put it into src for PythonQt itself, and into extensions for the bindings similar to qmake (since generated_cpp is only created on-the-fly, but I'm open to other suggestions), and use add_subdirectory in the top-level CMakeLists.txt. Perhaps call the bindings project PythonQt_QtAll for now.

In MeVisLab the Qt bindings are dynamically loaded through Python imports, with each module having its own DLL/.so file, so we want to build those separately anyway. It would be easier for us to merge this part back if the bindings are built on their own anyway.

@jamesobutler jamesobutler marked this pull request as draft April 1, 2026 02:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants