Add Qt 6 support (dual Qt 5 / Qt 6 build) - #1335
Open
drnic wants to merge 18 commits into
Open
Conversation
Make apm_planner build and link against both Qt5 and Qt6 so the project
can transition off end-of-life Qt5 gradually. Verified: both
`cmake -DCMAKE_PREFIX_PATH=$(brew --prefix qt)` (Qt6) and `qt@5` (Qt5)
configure, build, and produce a working apmplanner2.app.
Build system:
- Root, opmapcontrol, and quazip CMakeLists use the version-agnostic
`find_package(QT NAMES Qt6 Qt5)` + `Qt${QT_VERSION_MAJOR}` pattern.
- Bump C++ standard 14 -> 17 (required by Qt6, valid for Qt5).
- Qt6 also links Core5Compat (legacy QRegExp/QTextCodec), OpenGLWidgets
(OpenGL split in Qt6), and SvgWidgets (QGraphicsSvgItem moved there).
Source ports (all dual-safe across Qt5 and Qt6):
- QString methods taking QRegExp (split/replace/contains/indexOf) are
removed in Qt6 and NOT bridged by Core5Compat -> ported those call
sites to QRegularExpression; configuration.h now returns
QRegularExpression. Pure QRegExp-class files just include <QRegExp>.
- toTime_t/fromTime_t/setTime_t -> *SecsSinceEpoch.
- QString::SkipEmptyParts -> Qt::SkipEmptyParts.
- QPainter::HighQualityAntialiasing -> Antialiasing.
- QLayout::setMargin -> setContentsMargins; QFileDialog::DirectoryOnly ->
Directory; QSortFilterProxyModel::setFilterRegExp ->
setFilterRegularExpression; qSort -> std::sort; trUtf8 -> tr.
- QTextStream::setCodec guarded -> setEncoding(QStringConverter::Utf8);
QTextCodec dropped (QString::fromUtf8) / linked via Core5Compat.
- QGLWidget viewport -> QOpenGLWidget + QSurfaceFormat; drop dead
QGLWidget/QDesktopWidget includes; add explicit QStandardPaths include.
- Explicit QChar() for int->QChar and bool/QString concatenations now
that QChar(int) is explicit in Qt6; cast qsizetype narrowing.
- Guard `using namespace QtDataVisualization` (namespace gone in Qt6).
- QGC.h: replace isnan/isinf macros (clobbered QtQml's std::isinf) with
`using std::isnan/isinf`.
- qcustomplot.h: skip the Q_GADGET-in-namespace QCP trick under the
Qt 6.9+ moc (its enum reflection is unused here).
…struction On Qt6, adding pages to a QStackedWidget delivers show/hide events synchronously. During MainWindow construction this fired show/hideEvent handlers on config pages that call MainWindow::instance()->toolBar(), but the instance() singleton pointer was not yet assigned, so each call built another MainWindow and recursed infinitely (app appeared stuck on the "Starting Communication Links" splash at ~98% CPU). - MainWindow::instance() now uses a static m_instance member that the constructor assigns to `this` as its first action, so re-entrant calls during construction return the partially-built window. - Add MainWindow::isInitialised() (false until the constructor finishes). - Guard the show/hide handlers in AccelCalibrationConfig, ApmCustomFirmwareConfig and Radio3DRConfig to bail out early when the main window is not yet initialised. Also update README build instructions for the Qt6/Qt5 dual build.
Appends [QtN] to the window name so it's obvious at a glance whether a running build is the Qt5 or Qt6 variant during the dual-build migration.
Two Qt5->Qt6 QML/widget regressions in the Flight Data view: - Toolbar icons were invisible. The relative image paths in ApmToolBar.qml are assigned into the Button component living in qml/components/. Qt5 resolved them against ApmToolBar.qml; Qt6 resolves them against Button.qml, yielding qml/components/resources/... which does not exist. Wrap the paths in Qt.resolvedUrl() so they resolve in ApmToolBar.qml's context under both Qt versions. - Button.qml sized the icon with 'width: image.width', where image is the source url (url.width is undefined). Qt5 fell back to the implicit size; Qt6 turns undefined into 0. Bind to buttonImage.implicitWidth/ implicitHeight instead. - OPMapWidget (a QGraphicsView) never set a scrollbar policy, so the default ScrollBarAsNeeded let Qt6 show a stray vertical scrollbar on resize. The map is drag-to-pan; force both bars off.
STATUSTEXT is a fixed 50-byte buffer the firmware NUL-pads. Building the QString via QString(const QByteArray&) kept the whole buffer including the padding, and Qt6 rendered the embedded NULs as .notdef boxes (a grid of tofu squares over the PFD). Build from the C string instead so the text stops at the first NUL.
QtQuick.Controls 1.2 and QtQuick.Controls.Styles 1.2 (ProgressBar with minimumValue/maximumValue/orientation/style: ProgressBarStyle) were removed in Qt6. Reimplement the vertical bar gauge with plain QtQuick Rectangle primitives so it builds and runs on both Qt5 and Qt6, with no QtQuick.Controls dependency. All public properties and the warn/fail color thresholds are preserved. Used by the Vibration Monitor and EKF Monitor tool screens.
macOS uses the native SpeechChannel API for audio output, so the QtMultimedia module is not actually used. Dropping the dependency and its include avoids requiring QtMultimedia for Qt6 builds.
Qt6 dropped the legacy Q_OS_MACX macro in favor of Q_OS_MACOS. Update all platform guards to use the current macro so they keep compiling under Qt6 while remaining valid on Qt5.
QGLWidget and its convertToGLFormat() helper were removed in Qt6. Switch CameraView to QOpenGLWidget and provide a local convertToGLFormat() that produces a bottom-up RGBA8888 image for glDrawPixels, working on both Qt5 and Qt6. QOpenGLWidget only binds its framebuffer/context around paintGL(), so schedule a repaint via update() instead of calling paintGL() directly. The fixed-function GL calls (glDrawPixels/glOrtho) need the GL headers explicitly now, so include them and link OpenGL::GL via CMake on both Qt versions. Silence the deprecated fixed-function pipeline warnings on macOS.
Complete the dual Qt5/Qt6 migration's build-tooling layer:
- apm_planner.pro: bump c++14 -> c++17 and add a dual-safe guard
(QT += core5compat openglwidgets svgwidgets) under Qt6, matching the
Core5Compat bridge and the OpenGL/Svg module splits.
- Delete dead qgcunittest.pro and qgcvideo.pro (referenced removed files
such as phonon, webkit, SerialLink and the qgcvideo app; broken on Qt5
too). CMake is the canonical build.
- Packaging moves to Qt6 + CMake: debian/control Qt6 build-deps,
debian/rules and redhat spec build via cmake, deploy_osx.sh uses the
Qt6 macdeployqt.
- Replace defunct .travis.yml with a GitHub Actions workflow building a
{Qt5,Qt6} x {ubuntu,macOS} matrix; release artifact pinned to Qt6.
Build-only for now: no qgcunittest CMake target exists yet.
The published Qt6 macOS artifact was arm64-only (built on macos-latest) and shipped as a zip. Build it universal instead: - Add osx_archs matrix var; the Qt6 macOS leg sets "x86_64;arm64" and passes it via CMAKE_OSX_ARCHITECTURES (empty elsewhere: ignored on Linux, native on the Qt5/Intel leg). This also fans out to the in-tree quazip/alglib/opmapcontrol subprojects. - Homebrew sdl2 is single-arch and cannot link into a fat binary, so the universal leg builds SDL2 from source for both arches and exposes it to find_package(SDL2) via CMAKE_PREFIX_PATH. - Package with macdeployqt -dmg and assert both slices with lipo so a thin build fails CI rather than shipping arm64-only.
Qt 6's moc emits TypeAndForceComplete<QCP, ...> for the Q_GADGET-in-a-fake- class trick, which fails to compile because QCP is really a namespace. This already affects Qt 6.8, not just 6.9+, so lower the workaround threshold from 6.9.0 to 6.0.0 and skip generating the (unused) QCP gadget metaobject.
qtserialport and qtmultimedia ship in the base Qt5 install and are not separately-installable aqt modules for 5.15.2, so aqt errored with 'packages not found'. Drop them from the Qt5 legs, keeping only qtdatavis3d. Also comment out the macos-13 leg while the other legs are being debugged.
Qt::AA_X11InitThreads was removed in Qt6, so setting it there fails to compile on the Linux legs. Wrap it in a Qt-version check so it is only applied on Qt5.
AGL.framework was removed from the macOS SDK in Xcode 26, but Qt's macOS CMake package still lists AGL in Qt::Gui's link interface, so the universal Qt6 build failed to link with 'ld: framework AGL not found'. Scrub any AGL reference out of every imported Qt target's INTERFACE_LINK_LIBRARIES.
actions/checkout@v4 and actions/upload-artifact@v4 declare the deprecated Node.js 20 runtime; bump both to the @v5 majors, which run on Node 24 and are drop-in for our usage. install-qt-action@v4 already resolves to a Node 24 build (v4.3.1) so it is left as-is. Replace the Intel macos-13 Qt5 leg (those runners are queue-starved and no longer reliably picked up) with a macos-latest Qt5 leg. Qt 5.15.2 has no arm64 macOS binaries, so it cross-compiles x86_64 on the arm64 runner (the x86_64 Qt host tools run under Rosetta); it is build-only and ships nothing. The Qt6 leg stays universal (x86_64;arm64) and remains the shipped .dmg. Both macOS legs now target an arch set containing x86_64 that Homebrew's arm64 sdl2 bottle can't satisfy, so SDL2 is always built from source on macOS (previously gated on the universal leg only). Add a job-level 30m timeout-minutes as a safety net so a hung leg is killed promptly (the slowest healthy leg finishes in ~19m) instead of burning the default 6h of runner time.
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Adds Qt 6 support to APM Planner 2.0 while keeping the existing Qt 5 build working — the same source tree compiles and runs on either toolkit (dual build).
Why dual-build rather than a hard cutover? Honestly: because it keeps things low-risk and easy to land. Existing Qt 5 contributors and CI aren't disrupted, and reviewers can adopt Qt 6 at their own pace.
Build
Want to try this branch? Clone it and build from source with CMake + Ninja.
Qt 6 (preferred) — on macOS, install deps with
brew install qt cmake ninja sdl2:Qt 5 (still supported) — install deps with
brew install qt@5 cmake ninja sdl2:On Linux,
cmake -G Ninja ..(withoutCMAKE_PREFIX_PATH) auto-detects the system Qt. Once built, launch withopen apmplanner2.appon macOS or./apmplanner2on Linux.Windows — from a Developer PowerShell for VS (VS 2019/2022 with the C++ workload, plus SDL2 via vcpkg), auto-detect the installed Qt kit instead of hardcoding a version:
See the README for full per-platform dependency lists.
Methodology
I let Claude Opus 4.8 do this project. For an unmaintained project, our LLM overloads are our best friend. I created a plan for the upgrade, and worked through it.
Github Actions
Travis was dead afaik; so I setup github actions.
Testing
I've tested the build + run of the basic app on my MacOS Tahoe 26.
It would be great for others on other OS to build and confirm it is running in the comments.
What changed
apm_planner.pro) and CMake updated to build against Qt 5 or Qt 6; Debian, RedHat and macOS packaging/deploy scripts updated..travis.ymlwith a GitHub Actions workflow (.github/workflows/build.yml).CameraViewfrom the removedQGLWidgettoQOpenGLWidget.BarGaugewithout Qt5-only Controls 1.x; fixed toolbar icons, a phantom map scrollbar, and STATUSTEXT "tofu" boxes under Qt 6.Q_OS_MACXwithQ_OS_MACOS; dropped the QtMultimedia dependency on macOS.MainWindowconstruction.src/ui,libs/opmapcontrol, andlibs/thirdParty/quazip.