cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
323 lines
14 KiB
CMake
323 lines
14 KiB
CMake
# 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 (Qt 5 only).
|
|
|
|
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 5 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 in nested CMake calls (e.g. try_compile) if we
|
|
# define Threads::Threads here, at least if the "C" language is enabled. In
|
|
# CXX-only projects we still need to do this unconditionally...
|
|
#
|
|
# We cannot use our usual Qt version check at this point though yet,
|
|
# so check whether we are chainloaded by the Qt toolchain as an indicator
|
|
# for Qt6.
|
|
# When building Qt6Base itself the check does not work, hence we have
|
|
# ECM_THREADS_WORKAROUND for that case which set to OFF in the Craft blueprints.
|
|
# As that doesn't propagate to nested CMake calls (e.g. tye_compile), we also check
|
|
# whether that is set via an environment variable (necessary with Qt >= 6.8.1).
|
|
if (NOT DEFINED ECM_THREADS_WORKAROUND AND NOT DEFINED ENV{ECM_THREADS_WORKAROUND})
|
|
set(ECM_THREADS_WORKAROUND TRUE)
|
|
endif()
|
|
get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES)
|
|
if (ECM_THREADS_WORKAROUND AND NOT TARGET Threads::Threads AND (NOT DEFINED __qt_chainload_toolchain_file OR NOT "C" IN_LIST _languages))
|
|
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
|
|
)
|
|
|
|
# needed for androiddeployqt's stdcpp-path setting
|
|
if (ANDROID_TOOLCHAIN_ROOT)
|
|
set(ANDROID_SYSROOT_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/sysroot/usr")
|
|
else()
|
|
set(ANDROID_SYSROOT_PREFIX "${CMAKE_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr")
|
|
endif()
|
|
|
|
## 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)
|
|
|
|
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()
|
|
|
|
# Qt6 still expects ABI suffixes but only applies them in its own qt_add_[executable|library] macros
|
|
# as we typically don't use those we need another way to get those
|
|
if (DEFINED __qt_chainload_toolchain_file) # indicator for Qt6, see above
|
|
set(CMAKE_MODULE_LIBRARY_SUFFIX_C "_${CMAKE_ANDROID_ARCH_ABI}.so")
|
|
set(CMAKE_MODULE_LIBRARY_SUFFIX_CXX "_${CMAKE_ANDROID_ARCH_ABI}.so")
|
|
set(CMAKE_SHARED_LIBRARY_SUFFIX_C "_${CMAKE_ANDROID_ARCH_ABI}.so")
|
|
set(CMAKE_SHARED_LIBRARY_SUFFIX_CXX "_${CMAKE_ANDROID_ARCH_ABI}.so")
|
|
endif()
|
|
|
|
# these aren't set yet at this point by the Android toolchain, but without
|
|
# those the find_package() call in ECMAndroidDeployQt5 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>"
|
|
)
|
|
|
|
# As our executables are shared libraries, we also need them build with position independent code (PIC).
|
|
# Qt 5 forces that anyway, but in Qt 6 that is no longer the case for exectuables (which we pretend to build here),
|
|
# and so we end up with just PIE (coming from CMake).
|
|
# And as subsequent steps overwrite that setting again, we have to watch for that and redo our change.
|
|
set(CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC")
|
|
macro(resetPieOption _var _access)
|
|
if (${_access} STREQUAL "MODIFIED_ACCESS")
|
|
set(CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC")
|
|
endif()
|
|
endmacro()
|
|
variable_watch(CMAKE_CXX_COMPILE_OPTIONS_PIE resetPieOption)
|
|
|
|
set(ECM_DIR "${CMAKE_CURRENT_LIST_DIR}/../cmake" CACHE STRING "")
|
|
|
|
######### generation (Qt 5 only)
|
|
|
|
# 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" AND NOT __qt_chainload_toolchain_file)
|
|
get_filename_component(_CMAKE_ANDROID_DIR "${CMAKE_TOOLCHAIN_FILE}" PATH)
|
|
list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount)
|
|
include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt5.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_androiddeployqt5("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}")
|
|
set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}")
|
|
endforeach()
|
|
elseif (NOT __qt_chainload_toolchain_file)
|
|
message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET=<targetname> and -DANDROID_APK_DIR=<paths>")
|
|
endif()
|