# SPDX-FileCopyrightText: 2014 Aleix Pol i Gonzalez # # 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 `_ 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 that will generate the APK file. See the `Qt on Android deployment documentation `_ 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, # se 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. if (NOT DEFINED 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 " -o " ) # 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= and -DANDROID_APK_DIR=") endif()