1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
# SPDX-FileCopyrightText: 2014 Aleix Pol i Gonzalez <aleixpol@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
AndroidToolchain
----------------
Enable easy compilation of cmake projects on Android.
By using this android toolchain, the projects will be set up to compile the
specified project targeting an Android platform, depending on its input.
Furthermore, if desired, an APK can be directly generated by using the
`androiddeployqt <https://doc.qt.io/qt-5/deployment-android.html>`_ tool.
CMake upstream has Android support now. This module will still give us some
useful features offering androiddeployqt integration and adequate executables
format for our Android applications.
Since we are using CMake Android support, any information from CMake documentation
still applies:
https://cmake.org/cmake/help/v3.7/manual/cmake-toolchains.7.html#cross-compiling-for-android
.. note::
This module requires CMake 3.18.
Since 1.7.0.
Usage
=====
To use this file, you need to set the ``CMAKE_TOOLCHAIN_FILE`` to point to
``Android.cmake`` on the command line::
cmake -DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake
You will also need to provide the locations of the Android NDK and SDK. This
can be done on the commandline or with environment variables; in either case
the variable names are:
``CMAKE_ANDROID_NDK``
The NDK root path.
``ANDROID_SDK_ROOT``
The SDK root path.
Additional options are specified as cache variables (eg: on the command line):
``ANDROID_ABI``
The ABI to use. See the ``sources/cxx-stl/gnu-libstdc++/*/libs``
directories in the NDK. Default: ``armeabi-v7a``.
``ANDROID_SDK_COMPILE_API``
The platform API level to compile against. May be different from the NDK
target. Default: newest installed version (e.g. android-30).
``ANDROID_SDK_BUILD_TOOLS_REVISION``
The build tools version to use.
Default: newest installed version (e.g. ``30.0.2``).
``ANDROID_EXTRA_LIBS``
The ";"-separated list of full paths to libs to include in resulting APK.
For integrating other libraries which are not part of the Android toolchain,
like Qt5, and installed to a separate prefix on the host system, the install
prefixes of those libraries would be passed as alternative roots as list via
``ECM_ADDITIONAL_FIND_ROOT_PATH``. Since 5.30.0.
For example, for integrating a Qt5 for Android present at
``~/Qt/5.14.2/android/`` and some other libraries installed to
the prefix ``/opt/android/foo``, you would use::
cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
-DECM_ADDITIONAL_FIND_ROOT_PATH="~/Qt/5.14.2/android/;/opt/android/foo"
If your project uses ``find_package()`` to locate build tools on the host
system, make sure to pass ``CMAKE_FIND_ROOT_PATH_BOTH`` or
``NO_CMAKE_FIND_ROOT_PATH`` as argument in the call. See the
``find_package()`` documentation for more details.
Deploying Qt Applications
=========================
After building the application, you will need to generate an APK that can be
deployed to an Android device. This module integrates androiddeployqt support
to help with this for Qt-based projects. To enable this, set the
``QTANDROID_EXPORTED_TARGET`` variable to the targets you wish to export as an
APK (in a ;-separed list), as well as ``ANDROID_APK_DIR`` to a directory
containing some basic information. This will create a ``create-apk-<target>``
target that will generate the APK file. See the `Qt on Android deployment
documentation <https://doc.qt.io/qt-5/deployment-android.html>`_ for more
information.
For example, you could do::
cmake \
-DCMAKE_TOOLCHAIN_FILE=/usr/share/ECM/toolchain/Android.cmake \
-DQTANDROID_EXPORTED_TARGET=myapp \
-DANDROID_APK_DIR=myapp-apk
make
make create-apk-myapp
You can specify the APK output directory by setting ``ANDROID_APK_OUTPUT_DIR``.
Otherwise the APK can be found in ``myapp_build_apk/`` in the build directory.
The create-apk-myapp target will be able to take an ARGS parameter with further
arguments for androiddeployqt. For example, one can use::
make create-apk-myapp ARGS="--install"
To install the apk to test. To generate a signed apk, one can do it with the
following syntax::
make create-apk-myapp ARGS="--sign ~/my.keystore alias_name"
In case it's needed for your application to set the APK directory from cmake
scripting you can also set the directory as the ANDROID_APK_DIR property of
the create-apk-myapp target.
See Android documentation on how to create a keystore to use
Advanced Options
================
The following packaging options are mainly interesting for automation or integration
with CI/CD pipelines:
``ANDROID_APK_OUTPUT_DIR``
Specifies a folder where the generated APK files should be placed.
``ANDROID_FASTLANE_METADATA_OUTPUT_DIR``
Specifies a folder where the generated metadata for the F-Droid store
should be placed.
``ANDROIDDEPLOYQT_EXTRA_ARGS``
Allows to pass additional arguments to `androiddeployqt`. This is an alternative to
the `ARGS=` argument for `make` and unlike that works with all CMake generators.
#]=======================================================================]
cmake_minimum_required(VERSION "3.18")
macro(set_deprecated_variable actual_variable deprecated_variable default_value)
set(${deprecated_variable} "${default_value}" CACHE STRING "Deprecated. Use ${actual_variable}")
if (NOT DEFINED ${actual_variable})
set(${actual_variable} ${${deprecated_variable}})
endif()
endmacro()
set_deprecated_variable(CMAKE_ANDROID_NDK ANDROID_NDK "$ENV{ANDROID_NDK}")
set_deprecated_variable(CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION ANDROID_GCC_VERSION "clang")
set_deprecated_variable(CMAKE_ANDROID_API ANDROID_API_LEVEL "21")
if(NOT DEFINED ENV{ANDROID_ARCH})
set(ENV{ANDROID_ARCH} "arm")
endif()
set_deprecated_variable(CMAKE_ANDROID_ARCH ANDROID_ARCHITECTURE $ENV{ANDROID_ARCH})
if(NOT DEFINED ENV{ANDROID_ARCH_ABI})
set(ENV{ANDROID_ARCH_ABI} "armeabi-v7a")
endif()
set_deprecated_variable(CMAKE_ANDROID_ARCH_ABI ANDROID_ABI "$ENV{ANDROID_ARCH_ABI}")
set(ANDROID_SDK_ROOT "$ENV{ANDROID_SDK_ROOT}" CACHE PATH "Android SDK path")
file(GLOB platforms LIST_DIRECTORIES TRUE RELATIVE ${ANDROID_SDK_ROOT}/platforms ${ANDROID_SDK_ROOT}/platforms/*)
list(SORT platforms COMPARE NATURAL)
list(GET platforms -1 _default_platform)
set(ANDROID_SDK_COMPILE_API "${_default_platform}" CACHE STRING "Android API Level")
if(ANDROID_SDK_COMPILE_API MATCHES "^android-([0-9]+)$")
set(ANDROID_SDK_COMPILE_API ${CMAKE_MATCH_1})
endif()
file(GLOB build-tools LIST_DIRECTORIES TRUE RELATIVE ${ANDROID_SDK_ROOT}/build-tools ${ANDROID_SDK_ROOT}/build-tools/*)
list(SORT build-tools COMPARE NATURAL)
list(GET build-tools -1 _default_sdk)
set(ANDROID_SDK_BUILD_TOOLS_REVISION "${_default_sdk}" CACHE STRING "Android Build Tools version")
set(CMAKE_SYSTEM_VERSION ${CMAKE_ANDROID_API})
set(CMAKE_SYSTEM_NAME Android)
if (NOT CMAKE_ANDROID_STL_TYPE)
set(CMAKE_ANDROID_STL_TYPE c++_shared)
endif()
# Workaround link failure at FindThreads in CXX-only mode,
# armv7 really doesn't like mixing PIC/PIE code.
# Since we only have to care about a single compiler,
# hard-code the values here.
# Qt6 fixes this and breaks if we define Threads::Threads here.
# We cannot use our usual Qt version check at this point though yet,
# se check whether we are chainloaded by the Qt toolchain as an indicator
# for Qt6.
if (NOT TARGET Threads::Threads AND NOT DEFINED __qt_chainload_toolchain_file)
set(Threads_FOUND TRUE)
set(CMAKE_THREAD_LIBS_INIT "-pthread")
add_library(Threads::Threads INTERFACE IMPORTED)
set_property(TARGET Threads::Threads PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
set_property(TARGET Threads::Threads PROPERTY INTERFACE_LINK_LIBRARIES "-pthread")
endif()
# let the Android NDK toolchain file do the actual work
set(ANDROID_PLATFORM "android-${CMAKE_ANDROID_API}")
set(ANDROID_STL ${CMAKE_ANDROID_STL_TYPE})
include(${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake REQUIRED)
# Export configurable variables for the try_compile() command.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
CMAKE_ANDROID_NDK
CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION
CMAKE_ANDROID_API
CMAKE_ANDROID_ARCH
CMAKE_ANDROID_ARCH_ABI
ANDROID_SDK_ROOT
ANDROID_SDK_COMPILE_API
)
## HACK: Remove when we can depend on NDK r23
# Workaround issue https://github.com/android/ndk/issues/929
if(ANDROID_NDK_MAJOR VERSION_LESS 23)
unset(CMAKE_SYSROOT)
set(ANDROID_SYSROOT_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/sysroot/usr")
list(APPEND CMAKE_SYSTEM_INCLUDE_PATH
"${ANDROID_SYSROOT_PREFIX}/include/${CMAKE_LIBRARY_ARCHITECTURE}")
list(APPEND CMAKE_SYSTEM_INCLUDE_PATH "${ANDROID_SYSROOT_PREFIX}/include")
# Prepend in reverse order
list(PREPEND CMAKE_SYSTEM_LIBRARY_PATH
"${ANDROID_SYSROOT_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}")
list(PREPEND CMAKE_SYSTEM_LIBRARY_PATH
"${ANDROID_SYSROOT_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}/${ANDROID_PLATFORM_LEVEL}")
endif()
# these aren't set yet at this point by the Android toolchain, but without
# those the find_package() call in ECMAndroidDeployQt will fail
set(CMAKE_FIND_LIBRARY_PREFIXES "lib")
set(CMAKE_FIND_LIBRARY_SUFFIXES "_${CMAKE_ANDROID_ARCH_ABI}.so" ".so" ".a")
# Work around Qt messing with CMAKE_SHARED_LIBRARY_SUFFIX and thus breaking find_library()
# Unfortunately, just setting CMAKE_FIND_LIBRARY_SUFFIXES here won't help, as this will
# be subsequently overwritten.
macro(addAbiSuffix _var _access)
if (${_access} STREQUAL "MODIFIED_ACCESS")
list(PREPEND CMAKE_FIND_LIBRARY_SUFFIXES "_${CMAKE_ANDROID_ARCH_ABI}.so")
endif()
endmacro()
variable_watch(CMAKE_FIND_LIBRARY_SUFFIXES addAbiSuffix)
# determine STL architecture, which is using a different format than ANDROID_ARCH_ABI
string(REGEX REPLACE "-(clang)?([0-9].[0-9])?$" "" ECM_ANDROID_STL_ARCH ${ANDROID_TOOLCHAIN_NAME})
if (NOT DEFINED ECM_ADDITIONAL_FIND_ROOT_PATH)
SET(ECM_ADDITIONAL_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})
endif()
LIST(APPEND CMAKE_FIND_ROOT_PATH ${ECM_ADDITIONAL_FIND_ROOT_PATH})
#we want executables to be shared libraries, hooks will invoke the exported cmake function
set(CMAKE_CXX_LINK_EXECUTABLE
"<CMAKE_CXX_COMPILER> <CMAKE_SHARED_LIBRARY_CXX_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS> <SONAME_FLAG><TARGET_SONAME> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>"
)
set(ECM_DIR "${CMAKE_CURRENT_LIST_DIR}/../cmake" CACHE STRING "")
######### generation
# Need to ensure we only get in here once, as this file is included twice:
# from CMakeDetermineSystem.cmake and from CMakeSystem.cmake generated within the
# build directory.
if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk")
get_filename_component(_CMAKE_ANDROID_DIR "${CMAKE_TOOLCHAIN_FILE}" PATH)
list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount)
include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake)
math(EXPR last "${targetsCount}-1")
foreach(idx RANGE 0 ${last})
list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget)
list(GET ANDROID_APK_DIR ${idx} APK_DIR)
if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR)
message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}")
elseif(NOT APK_DIR)
get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE)
set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/")
endif()
ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}")
set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}")
endforeach()
else()
message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET=<targetname> and -DANDROID_APK_DIR=<paths>")
endif()
|