CPack APK Generator¶
Added in version 4.5.
The CPack APK generator packages an application for Android as an
APK file. It drives the Android SDK build tools (aapt2, d8,
zipalign and apksigner) and the JDK directly, so neither Gradle
nor a Java build system of any kind is required.
The generator is meant for applications whose Java code is shipped with
the project or available as .jar or .aar files, locally or in
Maven repositories. Nothing is downloaded unless the project lists
Maven dependencies in CPACK_APK_MAVEN_DEPENDENCIES; see
Maven dependencies. Projects whose builds need more of Gradle should
keep using Gradle.
How the staging tree is packaged¶
CPack installs the project into a staging tree and the generator turns
that tree into the contents of the APK. Most files are added to the APK
at the same relative path they were installed to, so a native library
installed to lib/arm64-v8a/ ends up at lib/arm64-v8a/ inside the
APK. The following entries of the staging tree are treated specially
instead, because the build tools consume them:
AndroidManifest.xmlUsed as the application manifest, unless
CPACK_APK_MANIFESTpoints elsewhere.res/Compiled and linked by
aapt2, unlessCPACK_APK_RESOURCE_DIRECTORIESis set.assets/Added to the APK by
aapt2, unlessCPACK_APK_ASSET_DIRECTORIESis set.*.jarand*.aarConverted to Dalvik bytecode by
d8and added asclasses.dex. Archives belowres/orassets/are left alone: those are resources of the application, not code. See Android archives for what is taken from an.aar.
AndroidManifest.xml, res/ and assets/ are never copied into
the APK as they are, because aapt2 puts its own compiled versions of
them there. Overriding the variables above changes what aapt2 is
given, not whether the staged copies are packaged.
A minimal example:
install(TARGETS my_application
LIBRARY DESTINATION lib/${CMAKE_ANDROID_ARCH_ABI})
install(FILES AndroidManifest.xml DESTINATION .)
install(DIRECTORY res DESTINATION .)
set(CPACK_GENERATOR APK)
set(CPACK_APK_JAVA_SOURCE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/java")
include(CPack)
Like the Android Gradle plugin, the generator packages the shared C++
runtime of the NDK, libc++_shared.so, into lib/<abi>/ for the ABIs
whose native libraries are linked to it, as those built with
CMAKE_ANDROID_STL_TYPE set to c++_shared are, unless the
project installs one itself. The libraries linked to it are those whose
dynamic section, as printed by <readelf> -d <library>, lists it as
NEEDED. CPACK_READELF_EXECUTABLE, which defaults to
CMAKE_READELF, the llvm-readelf of the NDK when cross-compiling for
Android, is used for that; when it is not set, the llvm-readelf of the
NDK is, found like the llvm-strip of
CPACK_APK_STRIP_EXECUTABLE. The runtime is taken from the
sysroot of that same NDK, or else of the NDK the llvm-strip or the
llvm-readelf belongs to. The generator warns when it finds no
readelf to check the libraries with, or no runtime to package.
The resulting APK is signed, so it can be installed with adb install
right away. By default it is signed with the same debug keystore the
rest of the Android tooling uses, debug.keystore in the directory
named by the ANDROID_USER_HOME environment variable or else in
~/.android, which is created if it does not exist yet. On Windows,
~ is the user's profile directory, %USERPROFILE%. Use
CPACK_APK_KEYSTORE and the related variables to sign with a
key of your own.
Multiple ABIs¶
A build tree targets a single Android ABI. To package the native code
of several ABIs into one APK, use a superbuild: a project that builds the
application once per ABI with ExternalProject, from the same
sources, and lists the build trees in
CPACK_INSTALL_CMAKE_PROJECTS. The superbuild names the build
trees itself and brings all of them up to date whenever it is built, so
the ABIs are always packaged from the same revision of the sources. CPack
installs each build tree into the same staging tree, so the native
libraries end up side by side in lib/<abi>/:
cmake_minimum_required(VERSION 4.5)
project(MySuperBuild LANGUAGES NONE)
include(ExternalProject)
# The NDK is given with -DANDROID_NDK=<path>.
foreach(abi IN ITEMS arm64-v8a x86_64)
set(binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${abi}")
ExternalProject_Add(app-${abi}
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/app"
BINARY_DIR "${binary_dir}"
CMAKE_ARGS
"-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake"
-DANDROID_ABI=${abi}
-DCMAKE_BUILD_TYPE=$<CONFIG>
BUILD_ALWAYS TRUE
INSTALL_COMMAND ""
)
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${binary_dir};MyApp;ALL;/")
endforeach()
set(CPACK_GENERATOR APK)
set(CPACK_APK_NDK_ROOT "${ANDROID_NDK}")
set(CPACK_APK_JAVA_SOURCE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/app/java")
include(CPack)
The package is then made with cmake --build followed by cpack in
the build tree of the superbuild. It is the configuration of the
superbuild that CPack reads, so the CPACK_APK_* variables are set
there rather than in the application. Since the superbuild does not
cross-compile, CPACK_APK_NDK_ROOT tells where to take the
llvm-strip, the llvm-readelf and the shared C++ runtime from,
unless the generator finds the NDK otherwise.
The native libraries have to be installed into lib/<abi>/, as in the
example of How the staging tree is packaged. The files the build
trees have in common, such as the manifest and the resources, are taken
from the one installed last. The shared C++ runtime and the native
libraries of Android archives are packaged for each of the ABIs.
Android archives¶
From an .aar, the generator takes:
classes.jarandlibs/*.jar, which are converted to Dalvik bytecode. TheRclass of the archive's package, which the archive does not ship, is generated along with the application's.res/andassets/, which the application's own resources and assets override.jni/<abi>/*.so, packaged aslib/<abi>/and stripped like the application's own libraries, seeCPACK_APK_STRIP_EXECUTABLE. Only the ABIs the application ships native code for, orCPACK_APK_ABI, are packaged; all of them when the application has no native code.The permissions, features,
<queries>, components,<meta-data>and libraries declared byAndroidManifest.xml, which are merged into the application manifest. An element the application manifest, or an archive before it, already declares under the sameandroid:nameis left out, except that the child elements of a component, such as the<meta-data>of theandroidx.startup.InitializationProviderthat several AndroidX libraries declare, are merged into the one declared first, unless it is markedtools:node="replace". Children declared under the sameandroid:nameare again left out. Beyond that, the merge rules of the Gradle manifest merger are not applied:tools:attributes are dropped, except that an element markedtools:node="remove", at any depth, is not merged. Such an element at the top level of the application manifest is not packaged either, and keeps the elements of the archives declared under the sameandroid:nameout.
Maven dependencies¶
Libraries such as those of AndroidX are published to Maven repositories
rather than installed with the SDK. Listing them in
CPACK_APK_MAVEN_DEPENDENCIES makes the generator download
them and the libraries they depend on at packaging time, and add them
to CPACK_APK_JARS:
set(CPACK_APK_MAVEN_DEPENDENCIES androidx.core:core:1.17.0)
This is opt-in: when the variable is not set, no repository is
contacted. Each file that is downloaded is reported, and downloaded
files are kept in CPACK_APK_MAVEN_CACHE_DIRECTORY, so that
later runs work offline. Dependencies are resolved like Gradle resolves
the runtime classpath of an Android application:
The Gradle module metadata (
.modulefile) of an artifact is used when the repository has it, which is the case for AndroidX, Kotlin and most libraries built with Gradle. Its variant for the Java runtime is selected, preferring the one for Android over the one for the JVM, and the one for the release build type over the one for debug. A variant published as a separate module, as Kotlin Multiplatform libraries do, is followed there. The files are checked against the SHA-256 hashes it lists.Otherwise the POM is used, for its dependencies of the
compileandruntimescopes that are not optional, and for thepackagingof the artifact. The dependencies of build plugins and of profiles are not used. The file is checked against the SHA-1 hash published next to it, if any. Parent POMs and imported BOMs are not read, so a version defined in one of them is an error. Theproject.groupId,project.artifactIdandproject.versionproperties, and those of the POM's own<properties>, are replaced.When an artifact is required in several versions, the highest one is used, in the order of Gradle:
1.0-alpha01is lower than1.0-rc01, which is lower than1.0. As with Gradle, the versions that an artifact requires stop counting once a higher version of it is selected instead. Dependency constraints, including those of the platforms that Gradle module metadata depends on, such as BOMs, can raise the version of an artifact that is required anyway, or give one to a dependency that has none. It is an error when no version is given. Version ranges are not supported, except for a single version in brackets, like[1.2.3].Exclusions of transitive dependencies, in POMs or in Gradle module metadata, leave an artifact out when every path to it excludes it.
The Android SDK¶
The SDK is looked for in CPACK_APK_SDK_ROOT, then in the
ANDROID_HOME and ANDROID_SDK_ROOT environment variables, and
finally next to the NDK named by CPACK_APK_NDK_ROOT or by the
ANDROID_NDK_ROOT, ANDROID_NDK_HOME and ANDROID_NDK
environment variables. When CPACK_APK_SDK_ROOT is set, it
must hold the SDK: the other locations are not considered. When the
project is cross-compiled for Android, CPACK_APK_NDK_ROOT
defaults to CMAKE_ANDROID_NDK.
A JDK is required as well: d8 and apksigner are Java programs,
and jar and keytool are used directly. Java sources are compiled
against the Java library of the Android platform rather than the one of
the JDK, using a system image built from the platform's
core-for-system-modules.jar with jmod and jlink. Platforms
older than API level 30 do not provide that file, in which case the Java
library of the JDK is used.
Variables Specific to CPack APK Generator¶
Relative paths given to the variables below are relative to the directory cpack runs in.
- CPACK_APK_SDK_ROOT¶
Root directory of the Android SDK.
- Default:
Detected as described above
- CPACK_APK_JAVA_HOME¶
Root directory of the JDK. The Java tools are taken from it, and the
JAVA_HOMEenvironment variable is set to it for the SDK build tools.- Default:
The
JAVA_HOMEenvironment variable, then on macOS the output of/usr/libexec/java_home, and finally the JDK thejavacfound on thePATHbelongs to
- CPACK_APK_NDK_ROOT¶
Root directory of the Android NDK. Used to locate the SDK it is installed in, the
llvm-stripofCPACK_APK_STRIP_EXECUTABLE, and thelibc++_shared.sopackaged for the native libraries linked to it.- Default:
CMAKE_ANDROID_NDKwhen cross-compiling for Android, otherwise unset
- CPACK_APK_BUILD_TOOLS_VERSION¶
Version of the SDK build tools to use, e.g.
35.0.0.- Default:
The highest version installed in
<sdk>/build-tools
- CPACK_APK_COMPILE_SDK_VERSION¶
API level whose
android.jarthe resources and the Java sources are compiled against, e.g.35.- Default:
The highest API level installed in
<sdk>/platforms
- CPACK_APK_MIN_SDK_VERSION¶
Minimum API level the application supports. Written into the
<uses-sdk>element of the manifest, replacing the value it declares. Either way,d8targets the resulting API level.- Default:
Unset
- CPACK_APK_TARGET_SDK_VERSION¶
API level the application targets. Written into the
<uses-sdk>element of the manifest, replacing the value it declares.- Default:
The value declared by the manifest, otherwise
CPACK_APK_COMPILE_SDK_VERSION
- CPACK_APK_ABI¶
Android ABI of the native code, e.g.
arm64-v8a. Shared libraries installed directly intolib/are moved tolib/<abi>/inside the APK, which is where Android expects them. Libraries already installed into an ABI sub-directory are left alone.- Default:
CMAKE_ANDROID_ARCH_ABIwhen cross-compiling for Android, otherwise unset
- CPACK_APK_STRIP_EXECUTABLE¶
Tool that strips the debug symbols from the native libraries packaged in
lib/<abi>/, those of Android archives included, as the Android Gradle plugin does. It is run as<strip> --strip-unneeded -o <output> <input>. Files that are not ELF are packaged as they are. When no tool is found, the libraries are packaged as they are, with a warning; when stripping a library fails, that one is, with a warning as well. A tool set explicitly that does not exist is an error.- Default:
CMAKE_STRIPwhen cross-compiling for Android, otherwise thellvm-stripof the NDK ofCPACK_APK_NDK_ROOT, of theANDROID_NDK_ROOT,ANDROID_NDK_HOMEorANDROID_NDKenvironment variables, or else of the highest version installed in<sdk>/ndk
- CPACK_APK_KEEP_DEBUG_SYMBOLS¶
List of file name patterns, in which
*and?are wildcards, of the native libraries to package with their debug symbols, like thekeepDebugSymbolspackaging option of the Android Gradle plugin. For example,*.sokeeps them in all libraries.- Default:
Unset
- CPACK_APK_MANIFEST¶
Path to the
AndroidManifest.xmlto package.- Default:
AndroidManifest.xmlat the top of the staging tree
- CPACK_APK_PACKAGE¶
Package of the application, e.g.
com.example.app. Written into thepackageattribute of the manifest, replacing the value it declares. Relative class names of the manifest resolve against it. This is what Gradle projects set as thenamespacein their build script, leaving it out of the manifest.- Default:
The value declared by the manifest, which must then declare one
- CPACK_APK_MANIFEST_PLACEHOLDERS¶
List of
<name>=<value>entries. Each${<name>}in the manifest is replaced by the corresponding value before the manifest is compiled. This is how the Gradle-style placeholders found in manifest templates, most notably${applicationId}, can be resolved. Unless listed here,${applicationId}is thepackageof the manifest.- Default:
Unset
- CPACK_APK_RESOURCE_DIRECTORIES¶
List of Android resource directories to compile into the APK. They are merged in order, with later directories overlaying earlier ones. The resources of Android archives come before all of them.
- Default:
resat the top of the staging tree, if present
- CPACK_APK_ASSET_DIRECTORIES¶
List of directories whose contents are packaged as Android assets. They are merged in order, with files of later directories replacing those of earlier ones. The assets of Android archives come before all of them.
- Default:
assetsat the top of the staging tree, if present
- CPACK_APK_JAVA_SOURCE_DIRECTORIES¶
List of directories to compile
.javafiles from. They are searched recursively. The generatedRclasses are compiled along with them.- Default:
Unset
- CPACK_APK_JAVA_VERSION¶
Java release to compile the Java sources for, passed to
javacas its-sourceand-targetoptions. Left to itself,javactargets the release of the JDK, whose class files thed8of older build tools cannot read. Set this to an empty string to pass neither option. It is not used whenCPACK_APK_JAVAC_EXTRA_OPTIONSholds one of the--release,-sourceor-targetoptions.- Default:
11
- CPACK_APK_JARS¶
List of additional
.jarand.aarfiles to convert to Dalvik bytecode and to compile the Java sources against. Archives found in the staging tree, and those ofCPACK_APK_MAVEN_DEPENDENCIES, are added to this list.- Default:
Unset
- CPACK_APK_MAVEN_DEPENDENCIES¶
List of Maven artifacts to download, with the artifacts they depend on, and to add to
CPACK_APK_JARS, each given as<group>:<artifact>:<version>. See Maven dependencies.- Default:
Unset: nothing is downloaded
- CPACK_APK_MAVEN_REPOSITORIES¶
List of Maven repositories to download
CPACK_APK_MAVEN_DEPENDENCIESfrom, tried in order for each file. An entry is a URL, or else a path to a local directory laid out as a Maven repository. The local repository of Maven itself,~/.m2/repository, is one, so listing it first reuses the artifacts Maven has already downloaded. Maven does not keep Gradle module metadata there, which is then still downloaded from the repositories listed after it:set(CPACK_APK_MAVEN_REPOSITORIES "$ENV{HOME}/.m2/repository" https://maven.google.com https://repo.maven.apache.org/maven2 )
On Windows, the user's home directory is
$ENV{USERPROFILE}. A~in an entry is not expanded.- Default:
https://maven.google.comandhttps://repo.maven.apache.org/maven2
- CPACK_APK_MAVEN_CACHE_DIRECTORY¶
Directory to download
CPACK_APK_MAVEN_DEPENDENCIESto. Files found there are not downloaded again.- Default:
_CPack_Packages/maveninCPACK_PACKAGE_DIRECTORY
- CPACK_APK_VERSION_CODE¶
Integer version code of the application. Written into the manifest, replacing the value it declares.
- Default:
The value declared by the manifest, otherwise
1
- CPACK_APK_VERSION_NAME¶
User visible version of the application. Written into the manifest, replacing the value it declares.
- Default:
The value declared by the manifest, otherwise
CPACK_PACKAGE_VERSION
- CPACK_APK_NO_COMPRESS_EXTENSIONS¶
List of file name extensions, leading dot included, to store in the APK uncompressed. Android maps uncompressed native libraries straight out of the APK, which is required when the manifest sets
android:extractNativeLibstofalse.- Default:
.so
- CPACK_APK_SIGN¶
Boolean controlling whether the APK is signed. An unsigned APK cannot be installed on a device.
- Default:
TRUE
- CPACK_APK_KEYSTORE¶
Path to the Java keystore holding the signing key.
- Default:
The debug keystore described above, generated if missing
- CPACK_APK_KEYSTORE_PASSWORD¶
Password of the keystore.
- Default:
android, together with the default keystore
- CPACK_APK_KEY_ALIAS¶
Alias of the key to sign with inside the keystore.
- Default:
androiddebugkey, together with the default keystore
- CPACK_APK_KEY_PASSWORD¶
Password of the key. A PKCS12 keystore, which is what
keytoolcreates by default since JDK 9, protects its keys with the keystore password and cannot hold a key with a password of its own. Leave this unset for such a keystore.- Default:
- CPACK_APK_ZIPALIGN_OPTIONS¶
Options passed to
zipalignahead of the input and output files. The default aligns uncompressed native libraries to 16 KB memory pages, which devices with such pages require to load them straight from the APK. Build tools older than version 35 only support 4 KB pages.- Default:
-P;16;-f;4, or-p;-f;4with build tools older than 35
- CPACK_APK_AAPT2_EXECUTABLE¶
- CPACK_APK_D8_EXECUTABLE¶
- CPACK_APK_ZIPALIGN_EXECUTABLE¶
- CPACK_APK_APKSIGNER_EXECUTABLE¶
- CPACK_APK_JAR_EXECUTABLE¶
- CPACK_APK_KEYTOOL_EXECUTABLE¶
Paths to the individual tools, overriding the ones that would be found in the SDK build tools directory and in the JDK.
- Default:
Detected
- CPACK_APK_AAPT2_LINK_EXTRA_OPTIONS¶
- CPACK_APK_JAVAC_EXTRA_OPTIONS¶
- CPACK_APK_D8_EXTRA_OPTIONS¶
- CPACK_APK_APKSIGNER_EXTRA_OPTIONS¶
Additional options to pass to the respective tool. To compile the Java sources for another Java release, set
CPACK_APK_JAVA_VERSIONor passjavacthe-sourceand-targetoptions rather than--release, which makes it compile against the Java library of the JDK instead of the one of the Android platform. For a target release older than 9,android.jaris used as the boot class path instead of a system image.d8is passed--release, which leaves the debugging information out of the Dalvik bytecode, unless--debugis given here.- Default:
Unset
- CPACK_APK_DEBUG¶
Boolean enabling verbose output of every tool invocation and its result.
- Default:
FALSE