cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) # store the current source directory for future use set(QT_ANDROID_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) # check the JAVA_HOME environment variable # (I couldn't find a way to set it from this script, it has to be defined outside) set(JAVA_HOME $ENV{JAVA_HOME}) if(NOT JAVA_HOME) message(FATAL_ERROR "The JAVA_HOME environment variable is not set. Please set it to the root directory of the JDK.") endif() # make sure that the Android toolchain is used if(NOT ANDROID) message(FATAL_ERROR "Trying to use the CMake Android package without the Android toolchain. Please use the provided toolchain (toolchain/android.toolchain.cmake)") endif() if(NOT QT_VERSION_MAJOR) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) endif() # find the Qt root directory if(NOT Qt${QT_VERSION_MAJOR}Core_DIR) find_package(Qt${QT_VERSION_MAJOR}Core REQUIRED) endif() get_filename_component(QT_ANDROID_QT_ROOT "${Qt${QT_VERSION_MAJOR}Core_DIR}/../../.." ABSOLUTE) message(STATUS "Found Qt for Android: ${QT_ANDROID_QT_ROOT}") # find the Android SDK if(NOT QT_ANDROID_SDK_ROOT) set(QT_ANDROID_SDK_ROOT $ENV{ANDROID_SDK}) if(NOT QT_ANDROID_SDK_ROOT) set(QT_ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) if(NOT QT_ANDROID_SDK_ROOT) message(FATAL_ERROR "Could not find the Android SDK. Please set either the ANDROID_SDK environment variable, or the QT_ANDROID_SDK_ROOT CMake variable to the root directory of the Android SDK") endif() endif() endif() string(REPLACE "\\" "/" QT_ANDROID_SDK_ROOT ${QT_ANDROID_SDK_ROOT}) # androiddeployqt doesn't like backslashes in paths message(STATUS "Found Android SDK: ${QT_ANDROID_SDK_ROOT}") # find the Android NDK if(NOT QT_ANDROID_NDK_ROOT) set(QT_ANDROID_NDK_ROOT $ENV{ANDROID_NDK}) if(NOT QT_ANDROID_NDK_ROOT) set(QT_ANDROID_NDK_ROOT ${ANDROID_NDK}) if(NOT QT_ANDROID_NDK_ROOT) message(FATAL_ERROR "Could not find the Android NDK. Please set either the ANDROID_NDK environment or CMake variable, or the QT_ANDROID_NDK_ROOT CMake variable to the root directory of the Android NDK") endif() endif() endif() string(REPLACE "\\" "/" QT_ANDROID_NDK_ROOT ${QT_ANDROID_NDK_ROOT}) # androiddeployqt doesn't like backslashes in paths message(STATUS "Found Android NDK: ${QT_ANDROID_NDK_ROOT}") include(CMakeParseArguments) # define a macro to create an Android APK target # # example: # add_qt_android_apk(my_app_apk my_app # NAME "My App" # VERSION_CODE 12 # PACKAGE_NAME "org.mycompany.myapp" # PACKAGE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/my-android-sources # KEYSTORE ${CMAKE_CURRENT_LIST_DIR}/mykey.keystore myalias # KEYSTORE_PASSWORD xxxx # DEPENDS a_linked_target "path/to/a_linked_library.so" ... # INSTALL #) # macro(add_qt_android_apk TARGET SOURCE_TARGET) # parse the macro arguments cmake_parse_arguments(ARG "INSTALL" "NAME;VERSION_CODE;PACKAGE_NAME;PACKAGE_SOURCES;KEYSTORE_PASSWORD" "DEPENDS;KEYSTORE" ${ARGN}) # extract the full path of the source target binary set(QT_ANDROID_APP_PATH "$") # full file path to the app's main shared library if(${Qt5Core_VERSION} VERSION_GREATER_EQUAL 5.14) set(QT_ANDROID_SUPPORT_MULTI_ABI ON) endif() if(QT_ANDROID_SUPPORT_MULTI_ABI) # qtandroideploy will append by itself the ANDROID_ABI to the target name set(QT_ANDROID_APPLICATION_BINARY "${SOURCE_TARGET}") else() set(QT_ANDROID_APPLICATION_BINARY ${QT_ANDROID_APP_PATH}) endif() # define the application name if(ARG_NAME) set(QT_ANDROID_APP_NAME ${ARG_NAME}) else() set(QT_ANDROID_APP_NAME ${SOURCE_TARGET}) endif() # define the application package name if(ARG_PACKAGE_NAME) set(QT_ANDROID_APP_PACKAGE_NAME ${ARG_PACKAGE_NAME}) else() set(QT_ANDROID_APP_PACKAGE_NAME org.qtproject.${SOURCE_TARGET}) endif() # detect latest Android SDK build-tools revision set(QT_ANDROID_SDK_BUILDTOOLS_REVISION "0.0.0") file(GLOB ALL_BUILD_TOOLS_VERSIONS RELATIVE ${QT_ANDROID_SDK_ROOT}/build-tools ${QT_ANDROID_SDK_ROOT}/build-tools/*) foreach(BUILD_TOOLS_VERSION ${ALL_BUILD_TOOLS_VERSIONS}) # find subfolder with greatest version if (${BUILD_TOOLS_VERSION} VERSION_GREATER ${QT_ANDROID_SDK_BUILDTOOLS_REVISION}) set(QT_ANDROID_SDK_BUILDTOOLS_REVISION ${BUILD_TOOLS_VERSION}) endif() endforeach() message(STATUS "Found Android SDK build tools version: ${QT_ANDROID_SDK_BUILDTOOLS_REVISION}") # get version code from arguments, or generate a fixed one if not provided set(QT_ANDROID_APP_VERSION_CODE ${ARG_VERSION_CODE}) if(NOT QT_ANDROID_APP_VERSION_CODE) set(QT_ANDROID_APP_VERSION_CODE 1) endif() # try to extract the app version from the target properties, or use the version code if not provided get_property(QT_ANDROID_APP_VERSION TARGET ${SOURCE_TARGET} PROPERTY VERSION) if(NOT QT_ANDROID_APP_VERSION) if(PROJECT_VERSION) set(QT_ANDROID_APP_VERSION ${PROJECT_VERSION}) else() set(QT_ANDROID_APP_VERSION ${QT_ANDROID_APP_VERSION_CODE}) endif() endif() # check if the user provides a custom source package and its own manifest file if(ARG_PACKAGE_SOURCES) if(EXISTS "${ARG_PACKAGE_SOURCES}/AndroidManifest.xml") # custom manifest provided, use the provided source package directly set(QT_ANDROID_APP_PACKAGE_SOURCE_ROOT ${ARG_PACKAGE_SOURCES}) elseif(EXISTS "${ARG_PACKAGE_SOURCES}/AndroidManifest.xml.in") # custom manifest template provided set(QT_ANDROID_MANIFEST_TEMPLATE "${ARG_PACKAGE_SOURCES}/AndroidManifest.xml.in") endif() endif() # generate a source package directory if none was provided, or if we need to configure a manifest file if(NOT QT_ANDROID_APP_PACKAGE_SOURCE_ROOT) # create our own configured package directory in build dir set(QT_ANDROID_APP_PACKAGE_SOURCE_ROOT "${CMAKE_CURRENT_BINARY_DIR}/package") # create the manifest from the template file if(NOT QT_ANDROID_MANIFEST_TEMPLATE) set(QT_ANDROID_MANIFEST_TEMPLATE "${QT_ANDROID_SOURCE_DIR}/AndroidManifest.xml.in") endif() configure_file(${QT_ANDROID_MANIFEST_TEMPLATE} ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml @ONLY) # define commands that will be added before the APK target build commands, to refresh the source package directory set(QT_ANDROID_PRE_COMMANDS ${QT_ANDROID_PRE_COMMANDS} COMMAND ${CMAKE_COMMAND} -E remove_directory ${QT_ANDROID_APP_PACKAGE_SOURCE_ROOT}) # clean the destination directory set(QT_ANDROID_PRE_COMMANDS ${QT_ANDROID_PRE_COMMANDS} COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_ANDROID_APP_PACKAGE_SOURCE_ROOT}) # re-create it if(ARG_PACKAGE_SOURCES) set(QT_ANDROID_PRE_COMMANDS ${QT_ANDROID_PRE_COMMANDS} COMMAND ${CMAKE_COMMAND} -E copy_directory ${ARG_PACKAGE_SOURCES} ${QT_ANDROID_APP_PACKAGE_SOURCE_ROOT}) # copy the user package endif() set(QT_ANDROID_PRE_COMMANDS ${QT_ANDROID_PRE_COMMANDS} COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml ${QT_ANDROID_APP_PACKAGE_SOURCE_ROOT}/AndroidManifest.xml) # copy the generated manifest endif() # newer NDK toolchains don't define ANDROID_STL_PREFIX anymore, # so this is a fallback to the only supported value in recent versions if(NOT ANDROID_STL_PREFIX) if(ANDROID_STL MATCHES "^c\\+\\+_") set(ANDROID_STL_PREFIX llvm-libc++) endif() endif() if(NOT ANDROID_STL_PREFIX) message(WARNING "Failed to determine ANDROID_STL_PREFIX value for ANDROID_STL=${ANDROID_STL}") endif() if(QT_ANDROID_SUPPORT_MULTI_ABI) # from Qt 5.14 qtandroideploy will find the correct stl. set(QT_ANDROID_STL_PATH "${QT_ANDROID_NDK_ROOT}/sources/cxx-stl/${ANDROID_STL_PREFIX}/libs") else() # define the STL shared library path # up until NDK r18, ANDROID_STL_SHARED_LIBRARIES is populated by the NDK's toolchain file # since NDK r19, the only option for a shared STL library is libc++_shared if(ANDROID_STL_SHARED_LIBRARIES) list(GET ANDROID_STL_SHARED_LIBRARIES 0 STL_LIBRARY_NAME) # we can only give one to androiddeployqt if(ANDROID_STL_PATH) set(QT_ANDROID_STL_PATH "${ANDROID_STL_PATH}/libs/${ANDROID_ABI}/lib${ANDROID_STL}.so") else() set(QT_ANDROID_STL_PATH "${QT_ANDROID_NDK_ROOT}/sources/cxx-stl/${ANDROID_STL_PREFIX}/libs/${ANDROID_ABI}/lib${ANDROID_STL}.so") endif() elseif(ANDROID_STL STREQUAL c++_shared) set(QT_ANDROID_STL_PATH "${QT_ANDROID_NDK_ROOT}/sources/cxx-stl/${ANDROID_STL_PREFIX}/libs/${ANDROID_ABI}/libc++_shared.so") else() message(WARNING "ANDROID_STL (${ANDROID_STL}) isn't a known shared stl library." "You should consider setting ANDROID_STL to c++_shared (like Qt).") set(QT_ANDROID_STL_PATH "${QT_ANDROID_NDK_ROOT}/sources/cxx-stl/${ANDROID_STL_PREFIX}/libs/${ANDROID_ABI}/libc++_shared.so") endif() endif() # From Qt 5.14 qtandroideploy "target-architecture" is no longer valid in input file # It have been replaced by "architectures": { "${ANDROID_ABI}": "${ANDROID_ABI}" } # This allow to package multiple ABI in a single apk # For now we only support single ABI build with this script (to ensure it work with Qt5.14 & Qt5.15) if(QT_ANDROID_SUPPORT_MULTI_ABI) set(QT_ANDROID_ARCHITECTURES "\"${ANDROID_ABI}\":\"${ANDROID_ABI}\"") endif() # set the list of dependant libraries if(ARG_DEPENDS) foreach(LIB ${ARG_DEPENDS}) if(TARGET ${LIB}) # item is a CMake target, extract the library path set(LIB "$") endif() if(EXTRA_LIBS) set(EXTRA_LIBS "${EXTRA_LIBS},${LIB}") else() set(EXTRA_LIBS "${LIB}") endif() endforeach() set(QT_ANDROID_APP_EXTRA_LIBS "\"android-extra-libs\": \"${EXTRA_LIBS}\",") endif() # determine whether to use the gcc- or llvm/clang- toolchain; # if ANDROID_USE_LLVM was explicitly set, use its value directly, # otherwise ANDROID_TOOLCHAIN value (set by the NDK's toolchain file) # says whether llvm/clang or gcc is used if(DEFINED ANDROID_USE_LLVM) string(TOLOWER "${ANDROID_USE_LLVM}" QT_ANDROID_USE_LLVM) elseif(ANDROID_TOOLCHAIN STREQUAL clang) set(QT_ANDROID_USE_LLVM "true") else() set(QT_ANDROID_USE_LLVM "false") endif() # set some toolchain variables used by androiddeployqt; # unfortunately, Qt tries to build paths from these variables although these full paths # are already available in the toochain file, so we have to parse them if using gcc if(QT_ANDROID_USE_LLVM STREQUAL "true") set(QT_ANDROID_TOOLCHAIN_PREFIX "llvm") set(QT_ANDROID_TOOLCHAIN_VERSION) set(QT_ANDROID_TOOL_PREFIX "llvm") else() string(REGEX MATCH "${QT_ANDROID_NDK_ROOT}/toolchains/(.*)-(.*)/prebuilt/.*/bin/(.*)-" ANDROID_TOOLCHAIN_PARSED ${ANDROID_TOOLCHAIN_PREFIX}) if(ANDROID_TOOLCHAIN_PARSED) set(QT_ANDROID_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1}) set(QT_ANDROID_TOOLCHAIN_VERSION ${CMAKE_MATCH_2}) set(QT_ANDROID_TOOL_PREFIX ${CMAKE_MATCH_3}) else() message(FATAL_ERROR "Failed to parse ANDROID_TOOLCHAIN_PREFIX to get toolchain prefix and version and tool prefix") endif() endif() # make sure that the output directory for the Android package exists set(QT_ANDROID_APP_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${SOURCE_TARGET}-${ANDROID_ABI}) file(MAKE_DIRECTORY ${QT_ANDROID_APP_BINARY_DIR}/libs/${ANDROID_ABI}) # create the configuration file that will feed androiddeployqt # 1. replace placeholder variables at generation time configure_file(${QT_ANDROID_SOURCE_DIR}/qtdeploy.json.in ${CMAKE_CURRENT_BINARY_DIR}/qtdeploy.json.in @ONLY) # 2. evaluate generator expressions at build time file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/qtdeploy.json INPUT ${CMAKE_CURRENT_BINARY_DIR}/qtdeploy.json.in ) # 3. Configure build.gradle to properly work with Android Studio import set(QT_ANDROID_NATIVE_API_LEVEL ${ANDROID_NATIVE_API_LEVEL}) configure_file(${QT_ANDROID_SOURCE_DIR}/build.gradle.in ${QT_ANDROID_APP_BINARY_DIR}/build.gradle @ONLY) # check if the apk must be signed if(ARG_KEYSTORE) set(SIGN_OPTIONS --sign ${ARG_KEYSTORE}) if(ARG_KEYSTORE_PASSWORD) set(SIGN_OPTIONS ${SIGN_OPTIONS} --storepass ${ARG_KEYSTORE_PASSWORD}) endif() endif() # check if the apk must be installed to the device if(ARG_INSTALL) set(INSTALL_OPTIONS --reinstall) endif() # specify the Android API level if(ANDROID_PLATFORM_LEVEL) set(TARGET_LEVEL_OPTIONS --android-platform android-${ANDROID_PLATFORM_LEVEL}) endif() # determine the build type to pass to androiddeployqt if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND NOT ARG_KEYSTORE) set(QT_ANDROID_BUILD_TYPE --debug) elseif() set(QT_ANDROID_BUILD_TYPE --release) endif() # create a custom command that will run the androiddeployqt utility to prepare the Android package add_custom_target( ${TARGET} DEPENDS ${SOURCE_TARGET} ${QT_ANDROID_PRE_COMMANDS} # it seems that recompiled libraries are not copied if we don't remove them first COMMAND ${CMAKE_COMMAND} -E remove_directory ${QT_ANDROID_APP_BINARY_DIR}/libs/${ANDROID_ABI} COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_ANDROID_APP_BINARY_DIR}/libs/${ANDROID_ABI} COMMAND ${CMAKE_COMMAND} -E copy ${QT_ANDROID_APP_PATH} ${QT_ANDROID_APP_BINARY_DIR}/libs/${ANDROID_ABI} COMMAND ${QT_ANDROID_QT_ROOT}/bin/androiddeployqt --verbose --aab --output ${QT_ANDROID_APP_BINARY_DIR} --input ${CMAKE_CURRENT_BINARY_DIR}/qtdeploy.json --gradle ${QT_ANDROID_BUILD_TYPE} ${TARGET_LEVEL_OPTIONS} ${INSTALL_OPTIONS} ${SIGN_OPTIONS} ) endmacro()