Preliminary CPack APK Support

I’ve long ached for a way to build Android applications solely in my own CMake-based projects. I simply despise Android Studio as I’m mostly interested in building C & C++ heavy applications with little to no need for Java. Something I’ve done for a long time is roll a custom target that would invoke the Android SDK tools to package an APK myself. However after recently learning a bit more about CPack I thought “Why can’t CPack create an APK?”

After rummaging through the CPack code I managed to add a custom APK generator that is very similar to the NuGet generator. You can find the code at my CMake fork on Github here: GitHub - Honeybunch/CMake: Mirror of CMake upstream repository

Most of the actual logic is housed in a CPackAPK.cmake script and cmCPackApkGenerator.cxx:
(I can’t link the full links due to being a new user :grinning:)
Modules/Internal/CPack/CPackAPK.cmake
Source/CPack/cmCPackAPKGenerator.cxx

You can also see how I’m using this in a personal project of mine here:

I’m making this topic for a few reasons:
#1 To gauge interest: Do people besides me actually want this?
#2 To gauge correctness of what I have so far: I’m new to hacking on CMake and I’d like some extra eyes on what I’ve done so far. I’ve read what docs and code I can find about what I’m trying to do but would love some feedback.

So, some questions I have:
Is this the sort of feature that Kitware would even accept? I have no idea if this is maybe some sort of ground that’s been covered before that I just can’t find the history on.
What kinds of horrible things have I done in my CPackAPK module? There are some obvious flaws that need correction (a more robust way of detecting the Android SDK) but I wonder what lurks beneath that someone as green as me would miss.
The CPackAPK module currently relies on the user having setup their install procedure to install files to a “normal-ish” android-style directory structure. How reasonable is this? I’ve found that the APK packaging tools are a bit picky about directories but I may just be using them wrong.
I see there are some tests for CPack but I haven’t made it there yet due to the above concerns. I assume that skipping tests would not be acceptable for submission?

1 Like

Cc: @brad.king

See the externalNativeBuild tag in Android Studio’s Gradle plugin. It invokes CMake from Gradle, and is IMHO the most supported way of building Android apps that are primarily C++.

I get what you’re saying. Gradle is the intended way to handle APK packaging. However, Gradle has caused me much pain and I figured I could solve the problem with CPack instead. My vision is that it would also be possible to create a CPack generator for iOS IPAs. So you’d be able to target Windows, Linux, macOS, Android and iOS with just CMake and the relevant native build tools installed.

If the larger argument that you’re making is that you need the Android SDK to do this anyway and that includes Gradle, I hear you. But I would rather only maintain one build system in my projects.

2 Likes

At Qt we have a similar issue, see [QTBUG-106495] Deploy to Android without gradle - Qt Bug Tracker

From my point of view cpack should allow packaging of a project that has been built only via CMake. This is the case for Qt applications.

1 Like

At the bugreport above I mentioned the update of the fork and pushed the changes at Files · cpack-apk · Cristian Adam / CMake · GitLab (kitware.com).

With the following Packaging.cmake file I was able to get a Qt Quick Application to work with Qt 6.6.0 on macOS with android-31. Gradle was failing due to a requirement to upgrade to android-33 due to the androidx.core.

if (NOT ANDROID)
    return()
endif()

# Copy Qt Android xml templates
file(
    COPY "${Qt6Core_DIR}/../../../src/android/templates/"
    DESTINATION ${CMAKE_BINARY_DIR}/android-build
)

# Run androiddeployqt to modify the xml templates and copy the dependencies
find_program(androiddeployqt_program androiddeployqt PATHS ${QT_HOST_PATH}/bin)
install(CODE
    "execute_process(
        COMMAND ${androiddeployqt_program} --input ${CMAKE_BINARY_DIR}/android-${CMAKE_PROJECT_NAME}-deployment-settings.json
                    --output ${CMAKE_BINARY_DIR}/android-build
                    --aux-mode
     )

     # Fix issue with ${applicationId} breaking aapt

     file(READ ${CMAKE_BINARY_DIR}/android-build/AndroidManifest.xml AndroidManifestXml)
     string(REPLACE \"\\\${applicationId}\" \"org.qtproject.qt.android.bindings\" AndroidManifestXml \"\${AndroidManifestXml}\")
     file(WRITE ${CMAKE_BINARY_DIR}/android-build/AndroidManifest.xml \"\${AndroidManifestXml}\")
    "
)

# We need the libc++_shared.so STL file
find_library(android_stl_lib ${ANDROID_STL})
install(FILES ${android_stl_lib} DESTINATION lib/${ANDROID_ABI})

# For the android:name="androidx.core.content.FileProvider" permissions
if (NOT EXISTS ${CMAKE_BINARY_DIR}/android-build/core-1.10.1.zip)
    file(DOWNLOAD "https://maven.google.com/androidx/core/core/1.10.1/core-1.10.1.aar"
                  ${CMAKE_BINARY_DIR}/android-build/core-1.10.1.zip)
    file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/android-build/core-1.10.1.zip
         DESTINATION ${CMAKE_BINARY_DIR}/android-build/libs
         PATTERNS *.jar
    )
endif()

# Install the resources so that cpack can package them
install(
    DIRECTORY
        ${CMAKE_BINARY_DIR}/android-build/assets
        ${CMAKE_BINARY_DIR}/android-build/res
    DESTINATION /
)

install(
    DIRECTORY
        ${CMAKE_BINARY_DIR}/android-build/libs/
    DESTINATION /lib
)

set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME})
set(CPACK_PACKAGE_VENDOR "The Qt Company Ltd")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_NAME}")
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_INSTALL_PREFIX "/")
set(CPACK_SET_DESTDIR ON)
set(CPACK_STRIP_FILES ON)

set(CPACK_GENERATOR APK)

set(CPACK_APK_NDK_PATH ${ANDROID_NDK})
set(CPACK_APK_TOOL_VERSION 31.0.0)
set(CPACK_APK_ANDROID_VERSION 31)
set(CPACK_APK_KEYSTORE_PATH ".keystore")
set(CPACK_APK_JAVA_BINDINGS_DIR "${Qt6Core_DIR}/../../../src/android/java/src")
set(CPACK_APK_MANIFEST_PATH ${CMAKE_BINARY_DIR}/android-build/AndroidManifest.xml)
set(CPACK_APK_PACKAGE_LIST lib)

# Make Qt Creator happy, to find the apk
file(WRITE ${CMAKE_BINARY_DIR}/postcpack.cmake
    "
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/android-build/build/outputs/apk/)
    file(COPY_FILE \${CPACK_TEMPORARY_DIRECTORY}/\${CPACK_PACKAGE_NAME}-\${CPACK_PACKAGE_VERSION}.apk
          ${CMAKE_BINARY_DIR}/android-build/build/outputs/apk/android-build-debug.apk
          ONLY_IF_DIFFERENT
     )"
)
set(CPACK_POST_BUILD_SCRIPTS ${CMAKE_BINARY_DIR}/postcpack.cmake)

include(CPack)

It would be great to have the compiling of java bindings files and do the d8 and aapt magic.

1 Like