Following my recent post on porting KDE Android applications to Qt 6 and Ingo’s post on the CI/CD changes for APKs here are some more details about packaging changes for KDE’s Android apps.

APK packaging so far

The basic flow for creating APKs so far is:

  • ECM identifies the various pieces of information needed for androiddeployqt, such as the application binary, the location of the Android manifest file, Java source code, and install prefixes of dependencies.
  • androiddeployqt gathers all of this together into a temporary directory, considering things like link dependencies and additional dependency metadata. Offering very little external control this tends to err on the side of including too much in the package.
  • Gradle is then used to compile the Java code (if any) and create the actual APK. This offers some limited amount of control for excluding unused files.

This isn’t pretty but kinda worked so far. With the KF6 transition we hit a limit there though, with the combination of androiddeployqt and Gradle failing to properly strip and exclude all debug information. That’s not breaking the APK as such, but it massively bloats its size.

Craft

Our APK builds are driven by Craft, which manages the build environment and takes care of building all dependencies as well. It however also has infrastructure for packaging, something we haven’t used for APKs so far.

That changed now though, solving the above mentioned debug information problem and giving us much more fine-grained control over what goes into the APK, and doing that in a way that’s easy to manage even (ECM MR, Craft MR).

Craft has multi-level exclusion lists for defining patterns that should not be in the package:

  • A global list that applies to all package on all platforms. This typically contains development files and development tools that are needed to build the application but that aren’t needed at runtime.
  • A platform specific exclusion list. For Android this could contain for example Qt Quick Controls styles we don’t use there, or translations for locales not available on Android.
  • Application-specific exclusion lists, for anything not used by a particular app.

Being able to manage the first two in a shared place is a major benefit, as those lists tend to be long and change quite a bit over time as libraries/dependencies evolve.

Adapting applications

No changes are required for applications to benefit from this for the most part, only adding an application-specific exclusion list requires a minor change to the Craft blueprint.

class Package(CMakePackageBase):
    def __init__(self):
        CMakePackageBase.__init__(self)
        # register application-specific exclusion list
        self.blacklist_file.append(self.packageDir() / "blacklist.txt")

The format of the exclusion list file itself is rather intuitive, consisting of one regular expression applied to the full path per line.

# data files for ki18n that we don't need
share/iso-codes/.*
share/locale/.*/LC_MESSAGES/iso_.*\.mo

# other KF6 data files we don't need
share/mime/packages/kde6\.xml

See the Kongress blueprint as an example.

Also, the previous mechanisms excluding files via build.gradle still works and is applied on top of this.