Create Android APK with cmake
-
I developed an Android program written mostly in C++ and a little Java. Because I started with it years ago, it was originally designed for Qt5. Now it is for Qt6 also. With QtCreator I can build it for Desktops like Linux and MacOS as well as iOS. For Android only with Qt5.
Then I found out that I can create an Android APK with cmake and even without QtCreator. But the documemntation on this is very limited and I had no success. I have two problems. Although I set the variable ANDROID_PLATFORM to 30 it is ignored. The value is set correct in the resulting bild files. But when I start cmake --build . it creates some more build files for each CPU type and in this build files the platform is set to the default of 23. Because of this my code doesn't compile, because it need at least version 28 to be able to use iconv.
The second problem is that it doesn't find the Qt headers like QDialog and others.
Does anybody have some idea what I've done wrong or what I'm missing?
This is what I've tried so far. I wrote a small shell script. Here is a lightweight version of it:
#!/bin/bash # # Set the following paths according to your installation. # export QT_VERSION="6.5.2" export QT_VERSION_MAJOR=6 export QTBASE="/opt/Qt/$QT_VERSION" export QTDIR="${QTBASE}/android_x86_64" export ANDROID_HOME="$HOME/Android/Sdk" export ANDROID_NDK_HOST="linux-x86_64" export ANDROID_NDK_PLATFORM="30" export ANDROID_NDK_ROOT="${ANDROID_HOME}/ndk/25.2.9519653" export ANDROID_NDK_HOME="${ANDROID_NDK_ROOT}" export ANDROID_SDK_ROOT="${ANDROID_HOME}" export ANDROID_PLATFORM="android-$ANDROID_NDK_PLATFORM" export JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64" export PATH="${QTDIR}/bin:${QTDIR}/../android_x86/bin:${QTDIR}/../android_arm64_v8a/bin:${QTDIR}/../android_armv7/bin:$PATH" export OPENSSLDIR="$HOME/Android/openssl/ssl_3" ANDROID_TOOLCHAIN="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" ANDROID_API_VERSION="android-$ANDROID_NDK_PLATFORM" mkdir build cd build cmake -DCMAKE_GENERATOR:STRING="Ninja" \ -DCMAKE_BUILD_TYPE:STRING="$BUILDTYPE" \ -DANDROID_PLATFORM:STRING="$ANDROID_API_VERSION" \ -DANDROID_NDK:PATH="${ANDROID_NDK_ROOT}" \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH="${ANDROID_TOOLCHAIN}" \ -DANDROID_USE_LEGACY_TOOLCHAIN_FILE:BOOL="OFF" \ -DANDROID_STL:STRING="c++_shared" \ -DQT_NO_GLOBAL_APK_TARGET_PART_OF_ALL:BOOL="ON" \ -DANDROID_SDK_ROOT:PATH="${ANDROID_HOME}" \ -DCMAKE_SYSTEM_VERSION:STRING="$ANDROID_NDK_PLATFORM" \ .. cmake --build .
This is creating the build files and starts compiling the source. Here is The CMakeLists.txt file:
cmake_minimum_required(VERSION 3.20) project(tpanel VERSION 1.4.0 LANGUAGES CXX) ################################################################################ # take Qt from the QTDIR environment variable ################################################################################ if(DEFINED ENV{QTDIR}) set(CMAKE_PREFIX_PATH $ENV{QTDIR} ${CMAKE_PREFIX_PATH}) else() message(FATAL_ERROR "ERROR: Environment variable QTDIR is not set. Please locate your Qt folder.") endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) if(NOT DEFINED QT_VERSION) set(QT_VERSION $ENV{QT_VERSION}) endif() if(NOT DEFINED QT_VERSION_MAJOR) set(QT_VERSION_MAJOR $ENV{QT_VERSION_MAJOR}) endif() set(QT_ANDROID_BUILD_ALL_ABIS TRUE) set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" CACHE INTERNAL "") if(ANDROID_ABI STREQUAL armeabi-v7a) set(ABI "android_armv7") else() set(ABI "android_${ANDROID_ABI}") endif() set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{QTBASE}/${ABI}/lib/cmake) set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} $ENV{QTBASE}/${ABI}/lib/cmake) message("** Start to find Qt${QT_VERSION_MAJOR} packages for ${ANDROID_ABI} ...") find_package(Qt6Core) find_package(Qt6Gui) find_package(Qt6Widgets) find_package(Qt6Multimedia) find_package(Qt6MultimediaWidgets) include_directories($ENV{EXTRA_PATH}/expat/include) set(SKIA_INCLUDE_DIRS $ENV{EXT_LIB_PATH}/skia/include) include_directories(${SKIA_INCLUDE_DIRS}) set(LIBSKIA_LIBRARY $ENV{EXT_LIB_PATH}/skia/${ANDROID_ABI}) include_directories($ENV{EXT_LIB_PATH}/pjsip/include) set(OPENSSL_INCLUDE_DIRS $ENV{OPENSSLDIR}/include) set(OPENSSL_SSL_LIBRARY $ENV{OPENSSLDIR}/${ANDROID_ABI}) include_directories(${OPENSSL_INCLUDE_DIRS}) if(QT_VERSION_MAJOR LESS 6) set(LIBS ${LIBS} -L${OPENSSL_SSL_LIBRARY} crypto ssl) else() set(LIBS ${LIBS} -L${OPENSSL_SSL_LIBRARY} crypto_3 ssl_3) endif() # This are the phone libraries used/needed for PJSIP. if(ANDROID_ABI STREQUAL "arm64-v8a") set(LIBS ${LIBS} $ENV{EXT_LIB_PATH}/pjsip/lib/libpj-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjlib-util-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-audiodev-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-codec-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjnath-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-simple-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-ua-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsua-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libresample-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libspeex-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libsrtp-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libgsmcodec-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libwebrtc-aarch64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libilbccodec-aarch64-unknown-linux-android.a ) elseif(ANDROID_ABI STREQUAL "armeabi-v7a") set(LIBS ${LIBS} $ENV{EXT_LIB_PATH}/pjsip/lib/libpj-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjlib-util-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-audiodev-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-codec-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjnath-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-simple-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-ua-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsua-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libresample-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libspeex-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libsrtp-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libgsmcodec-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libwebrtc-armv7-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libilbccodec-armv7-unknown-linux-android.a ) elseif(ANDROID_ABI STREQUAL "x86_64") set(LIBS ${LIBS} $ENV{EXT_LIB_PATH}/pjsip/lib/libpj-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjlib-util-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-audiodev-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-codec-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjnath-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-simple-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-ua-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsua-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libresample-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libspeex-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libsrtp-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libgsmcodec-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libwebrtc-x86_64-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libilbccodec-x86_64-unknown-linux-android.a ) elseif(ANDROID_ABI STREQUAL "x86") set(LIBS ${LIBS} $ENV{EXT_LIB_PATH}/pjsip/lib/libpj-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjlib-util-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-audiodev-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjmedia-codec-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjnath-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-simple-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsip-ua-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libpjsua-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libresample-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libspeex-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libsrtp-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libgsmcodec-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libwebrtc-i686-unknown-linux-android.a $ENV{EXT_LIB_PATH}/pjsip/lib/libilbccodec-i686-unknown-linux-android.a ) endif() set(QT_USE_QTMULTIMEDIA TRUE) set(QT_USE_QTMULTIMEDIAWIDGETS TRUE) if(CMAKE_VERSION VERSION_LESS "3.7.0") set(CMAKE_INCLUDE_CURRENT_DIR ON) endif() find_package(Qt6 COMPONENTS Widgets REQUIRED) find_package(Qt6 COMPONENTS Multimedia REQUIRED) find_package(Qt6 COMPONENTS MultimediaWidgets REQUIRED) find_package(Qt6 COMPONENTS Positioning REQUIRED) add_subdirectory(ftplib) set(PROJECT_SOURCES tmain.cpp tconfig.cpp tconfig.h terror.cpp terror.h tsocket.cpp tsocket.h tnameformat.cpp tnameformat.h tsettings.cpp tsettings.h tpagelist.cpp tpagelist.h tpagemanager.cpp tpagemanager.h tpage.cpp tpage.h tsubpage.cpp tsubpage.h tpageinterface.cpp tpageinterface.h tbutton.cpp tbutton.h tsystembutton.cpp tsystembutton.h tvalidatefile.cpp tvalidatefile.h tresources.cpp tresources.h tcolor.cpp tcolor.h tpalette.cpp tpalette.h tobject.cpp tobject.h tfont.cpp tfont.h ticons.cpp ticons.h tprjresources.cpp tprjresources.h thttpclient.cpp thttpclient.h timagerefresh.cpp timagerefresh.h tqtmain.cpp tqtmain.h tqtsettings.cpp tqtsettings.h tqtsettings.ui tqkeyboard.cpp tqkeyboard.h keyboard.ui tqkeypad.cpp tqkeypad.h keypad.ui tqtphone.cpp tqtphone.h tqtphone.ui tqemitqueue.cpp tqemitqueue.h tqgesturefilter.cpp tqgesturefilter.h tamxnet.cpp tamxnet.h tamxcommands.cpp tamxcommands.h tmap.cpp tmap.h tdrawimage.cpp tdrawimage.h tdirectory.cpp tdirectory.h texpand.cpp texpand.h ttimer.cpp ttimer.h ttpinit.cpp ttpinit.h texternal.cpp texternal.h tsystemsound.cpp tsystemsound.h tsystemdraw.cpp tsystemdraw.h timgcache.cpp timgcache.h tfsfreader.cpp tfsfreader.h readtp4.cpp readtp4.h expand.cpp expand.h tsipclient.cpp tsipclient.h texcept.cpp texcept.h tsystem.cpp tsystem.h turl.cpp turl.h base64.cpp base64.h texpat++.cpp texpat++.h tqdownload.cpp tqdownload.h download.ui tqeditline.cpp tqeditline.h tqsingleline.cpp tqsingleline.h tqmultiline.cpp tqmultiline.h tqtwait.cpp tqtwait.h wait.ui tqscrollarea.cpp tqscrollarea.h tbitmap.cpp tbitmap.h tintborder.cpp tintborder.h testmode.cpp testmode.h tpanel.qrc) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(tpanel MANUAL_FINALIZATION ${PROJECT_SOURCES}) # Define target properties for Android with Qt 6 as: # set_property(TARGET testpj APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation else() add_library(tpanel SHARED ${PROJECT_SOURCES}) endif() add_definitions(-D_REENTRANT) add_definitions(-D_GNU_SOURCE) add_definitions(-DPJ_AUTOCONF) add_definitions(-D_OPAQUE_SKIA_) include(GNUInstallDirs) include_directories(${OPENSSL_INCLUDE_DIRS}) if (DEBUG) add_definitions(-g) endif(DEBUG) add_definitions(-pedantic -fexceptions -Wextra -Wno-attributes) install(TARGETS tpanel BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(tpanel) endif()
To find the full source of this project look at tpanel
-
@JoeCFD said in Create Android APK with cmake:
One idea is to use cmake-gui to check what are not set correctly and add the correct settings in your cmake file. You are able to get a better view at your settings.
Unfortunately this didn't help. But I found the problem. The Variable
ANDROID_PLATFORM
is not defined in the macros for creating multi architecture APK files. In the file<path to qt>/Qt/6.5.2/android_x86_64/lib/cmake/Qt6Core/Qt6AndroidMacros.cmake
on line 1181 I added the following lines:if(ANDROID_PLATFORM) list(APPEND extra_cmake_args "-DANDROID_PLATFORM=${ANDROID_PLATFORM}") endif()
Then the correct platform level is set not only for the default (main) architecture, but also for all additional architectures. This seems to be an error in build macros for Android.
A.T.
-
-
@TheoSys said in Create Android APK with cmake:
ANDROID_PLATFORM
Good you figured that out. Actually, cmake has its own set of macros for Android:
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android
You may check it out. CMAKE_SYSTEM_VERSION may be the one you are looking for. -
@JoeCFD said in Create Android APK with cmake:
You may check it out. CMAKE_SYSTEM_VERSION may be the one you are looking for.
Tried that out, but doesn't work. This sets only the correct level for the main architecture, x86_64 in my case, but not on the other architectures. Only when I add the lines explained above for all architectures, it works.
A.T.