During the last weeks we have been trying to migrate KDE’s Android applications to Qt 5.14. For a minor version Qt 5.14 comes with a surprising amount of rather invasive changes that require quite a few adjustments in our build infrastructure, frameworks and applications. Here’s the current state of the migration, hopefully providing some hints for others facing the same problem.

Changes in Qt 5.14

Eskil and Bogdan have described what changed in Qt 5.14 already, and why. Working towards supporting multi-arch AAB packages and improving startup performance and resource consumption certainly makes sense.

However, this is unfortunately sorely lacking more information on how to adapt applications to those changes. Given the invasiveness, I’d expect that hardly anything beyond a simple QMake based app will be able to upgrade without some porting work. Some of Qt’s own modules being broken in Qt 5.14.0 hints at this as well.

So, what are the breaking changes? There are three main sets of commits that did prove challenging while trying to upgrade:

  • The introduction of multi-arch builds, or rather the changes around androiddeployqt’s parameters, file naming and install layouts as well as necessary entries in the AndroidManifest.xml file. Fixes necessary in other Qt modules following this would already indicate this is a breaking change, but the real impact isn’t even visible from those, as it’s affecting application code using androiddeployqt.

  • A somewhat related change affected plugin loading, which also contributed to changes in the install layout. This was followed by an even larger set of changes adapting other Qt modules, like this one.

  • And finally the way non-code assets are deployed changed. Previously those were unpacked into the local file system during the first start, now they remain inside the Android asset file system of the APK, either directly or wrapped inside a QResource .rcc bundle. While this has very noticable benefits for the first startup, the necessary changes to e.g. qtdeclarative and qtquickcontrols show the potential wide-spread fallout of this.

There are a few more known issues of Qt 5.14 on Android are listed on the corresponding Qt wiki page. There’s also an upgrade for the NDK and Gradle that goes with this, with some smaller porting impact as well.

KDE Frameworks Porting

As many Qt modules beyond qtbase needed adjustments to those changes, unsurprisingly this is similar in KDE Frameworks:

  • Kirigami isn’t finding its QML and icon assets anymore, due to them no longer being in the local file system. Fixed in D27525 by pointing to the asset .rcc generated by androiddeployqt now.

  • Similarly, KI18n isn’t finding its translation catalogs anymore. This one isn’t as straightforward to fix though, as the consumer of those files, libintl-lite, can only read from the actual file system, not from the QResource or Android asset systems. D27550 addresses this by again unpacking the catalog file into the local file system. This is only done on demand, so while not perfect, it’s still strictly better than the previous approach. Ultimately, adding the ability to read from the Android asset system to libintl-lite might be even better though.

  • Loading .qm translation catalogs for QTranslator has the same issue, however QTranslator fortunately uses QFile internally and therefore can consume QResources or Android assets directly. D27596 fixes this in the ECM .qm catalog loader.

  • KNotification as the currently only framework with a Java-based part needed some adjustments to the multi-arch install layout, done in D26713.

So the thing that hit us the hardest here is the asset deployment change. While it is generally a good thing, this would have been nicer as an opt-in change to not break compatibility that badly.

Build System Changes

Regarding actually building things, two major changes were required as well, in ECM and our SDK Docker images:

  • The Android toolchain file in ECM, which calls androiddeployqt, needed to be adjusted to the new parameters androiddeployqt expects. Since those are incompatible with older versions we essentially have that code twice now, in order to not break Qt 5.13 based builds before we even have the Qt 5.14 based ones anywhere close to working. The changes can be found in D26749.

  • The SDK Docker images were hit by the fallout of the multi-architecture build changes, in particular QTBUG-80938 and QTBUG-80862. This requires a complete redesign to do all architecture builds in a single image now, rather than the previous per-architecture split (merge request). Subsequently that then also requires changes to the CI setup.

Application Porting

Everything so far could be implemented in a way that it works with both Qt 5.14 and older versions. That’s important not because we want to support older Qt versions indefinitely, but because we have to transition almost 30 applications and the team behind those, which is much easier if we can do it step by step. Unfortunately, on the application level the seemingly small change require in the AndroidManifest.xml makes that very hard.

Specifically, the following line has to be added:

<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>

Failing to do that will cause a runtime error on startup. Additionally, the following two lines have to be removed:

<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>

Keeping them will trigger a compilation error during APK generation. And for Qt 5.13 the situation is exactly the opposite, having the new line it it required for Qt 5.14 will cause the APK generation to fail.

So we only have a number of bad and/or ugly options here:

  • Make a one time hard switch and require everyone to update to Qt 5.14, applications without adapted manifest files will fail to build.
  • Preprocess the manifest file using CMake. There’s two options on how to do it:
    • Put the result into the source directory, into the location the manifest is in now. This will not require changes to the packaging step, as that requires the relative location of all the Android specific files to be fixed. However, generating output in the source directory is usually a no-go for good reasons (people might waste time trying to edit a generated file, or check the generated files into Git, etc).
    • Copy the entire Android folder into the build directory, and generate the preprocessed manifest there. The packaging process would then use that as input. Rather expensive though, and brings in a number of new error scenarios, like an incompletely updated copy, etc.
  • Put all three lines into the manifest file, and provide empty dummy array resources instead, for the resources missing in the current Qt version.
    • Generating those via CMake into the source directory: This would be less risky than preprocessing the manifest there, as the file with the dummy resources would contain nothing else a developer might want to edit.
    • Checking in a modified libs.xml template with all array resources. That would usually be provided by androiddeployqt but just as with the manifest, it considers application provided ones if present. D27715 discovered this option. The downside of course is a longer term maintenance cost by not benefiting from updates to the libs.xml template automatically.

Maybe there’s a better idea on how to approach this?

Remaining Work

It is possible that there’s more things that need to be fixed in other frameworks or applications, with all of the above I at least can build a working KDE Itinerary again, on top of Qt 5.14. Aleix reported similar success with KAlgebra. On the upside, color emoji rendering seems to work correctly now, due to the FreeType update shipped with Qt.

Not all of the necessary changes are merged or integrated yet, T12520 tracks the progress.