/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ namespace Magnum { /** @page platforms-android Android @brief Building and deploying Android projects @tableofcontents @m_footernavigation @m_keywords{android_create_apk()} @todoc code coverage @todoc static plugins The following guide explains how to build Android projects using minimal command-line tools, without Android Studio involved. At the very least you need to have Android SDK and Android NDK installed, the guide assumes NDK r19 and newer. Running console utilities and tests on the device don't need much more, in case you want to develop actual applications, you need also the SDK and a platform + SDK platform build tools for version of your choice. For APK building it's possible to use either an experimental support in CMake or the official way with Gradle. Gradle is able to download all the dependencies on its own, however it's also possible to install system packages for a cleaner setup. @note On ArchLinux it's the `gradle` package and the following AUR packages, adapt the version numbers as necessary: @note - [android-sdk](https://aur.archlinux.org/packages/android-sdk/) - [android-ndk](https://aur.archlinux.org/packages/android-ndk/) - [android-sdk-build-tools](https://aur.archlinux.org/packages/android-sdk-build-tools/) - [android-sdk-platform-tools](https://aur.archlinux.org/packages/android-sdk-platform-tools/) - [android-platform-24](https://aur.archlinux.org/packages/android-platform-24/) - [android-sdk-cmake](https://aur.archlinux.org/packages/android-sdk-cmake/) Gradle requires Android SDK version of CMake, which is currently at version 3.10. See below for an experimental way to @ref platforms-android-system-cmake "use the system CMake" instead. On the other hand, while it's possible to use the CMake from Android SDK to build Magnum itself, the following guide is written with CMake 3.16 and newer in mind, which has Android NDK r19+ support built-in. In older versions you'd need to supply a toolchain file instead and the configuration values are different. @section platforms-android-console Building and running console applications Android allows to run arbitrary console utilities and tests via ADB. Assuming you have Magnum installed in the NDK path as described in @ref building-cross-android, build your project as below. The `CMAKE_FIND_ROOT_PATH` is unfortunately needed for CMake to correctly find Android libraries, otherwise it falls back to looking in native system locations. Adapt paths to your system, Android ABI and version and NDK location as needed: @m_class{m-console-wrap} @code{.sh} mkdir build-android-arm64 && cd build-android-arm64 cmake .. \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=24 \ -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_FIND_ROOT_PATH="/opt/android-ndk/platforms/android-24/arch-arm64;/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ -DCMAKE_BUILD_TYPE=Release cmake --build . @endcode After that you can use ADB to upload your executable to the device and run it there. The global temporary directory is `/data/local/tmp` and while the parent directories often don't have permissions, it's possible to @cb{.sh} cd @ce into it and create arbitrary files there. Assuming you built an executable in `build-android-arm64/Release/bin/my-application`, the workflow would be like this: @code{.sh} adb push build-android-arm64/Release/bin/my-application /data/local/tmp adb shell /data/local/tmp/my-application @endcode You can also use @cb{.sh} adb shell @ce to enter the device shell directly and continue from there. Besides plain command-line apps it's also possible to create an EGL context without any extra setup using @ref Platform::WindowlessEglApplication. See also @ref GL::OpenGLTester for information about OpenGL testing. @section platforms-android-apps Building and installing graphics apps In case you don't have an OpenGL ES build set up yet, you need to copy `FindEGL.cmake` and `FindOpenGLES2.cmake` (or `FindOpenGLES3.cmake`) from the [modules/](https://github.com/mosra/magnum/tree/master/modules) directory in Magnum source to the `modules/` dir in your project so it is able to find EGL and OpenGL ES libraries. Magnum provides Android application wrapper in @ref Platform::AndroidApplication. See its documentation for more information about general usage. You can also use the Android Native Activity directly or any other way. @note @ref Platform::AndroidApplication also contains a fully configured bootstrap project that's ready to build and deploy. Check its documentation for details. If you plan to use @ref Platform::AndroidApplication, be sure to request it only on Android, for example: @code{.cmake} if(CORRADE_TARGET_ANDROID) find_package(Magnum REQUIRED AndroidApplication) else() find_package(Magnum REQUIRED Sdl2Application) endif() @endcode Compared to building an app for other platforms, you need to create a shared library instead of an executable: @code{.cmake} if(NOT CORRADE_TARGET_ANDROID) add_executable(my-application MyApplication.cpp) else() add_library(my-application SHARED MyApplication.cpp) endif() @endcode @subsection platforms-android-apps-manifest Android manifest file For Android you additionally need an `AndroidManifest.xml` file, which describes various properties of the Android package. A minimal stripped-down version is: @code{.xml-jinja} @endcode Replace @cb{.jinja} {{ package }} @ce with Java-like package name for your app (in this case it could be e.g. @cpp "cz.mosra.magnum.my_application" @ce, for example), @cb{.jinja} {{ app_name }} @ce with human-readable app name that's displayed in the system (so e.g. @cpp "My Application" @ce) and finally the @cb{.jinja} {{ lib_name }} @ce is name of the library that you compiled with CMake, which in this case would be @cpp "my-application" @ce. The @cb{.xml} @ce line says that the minimal OpenGL ES version is 2.0, change it in case you require a different version. @anchor platforms-android-apps-manifest-screen-compatibility-mode The @cb{.xml} @ce line requests the minimal and target Android SDK version. Setting the version lower than this (or accidentally omitting this element) will enable the [Screen Compatibility Mode](http://www.androiddocs.com/guide/practices/screen-compat-mode.html), which doesn't report events in coordinates that match underlying framebuffer pixel size. That is not supported by @ref Platform::AndroidApplication since there's [no reliable way](https://stackoverflow.com/q/17481341) to get the actual size used for events in that case. @anchor platforms-android-apps-manifest-screen-resize The @cb{.xml} @ce attribute is needed in order to make the application properly receive a viewport event. By default it's being outright killed and recreated when device orientation changes for questionable "performance reasons". If you really want to handle orientation changes that way, remove `screenSize` from the set. Among other options is restricting the app to only portrait or landscape screen orientation. Consult [the Android developer documentation](https://developer.android.com/guide/topics/manifest/manifest-intro.html) for further information about the manifest file. With this set up, you have two options how to build the final APK, either using plain CMake or using Gradle. @subsection platforms-android-apps-cmake Using CMake The `toolchains` repository contains an `UseAndroid.cmake` module that allows you to create an APK in a significantly faster and simpler way than when using Gradle. @attention This feature is in an experimental stage and at this time it doesn't support compilation of any resources (such as icons) or Java sources. It also builds only the ABI and configuration that corresponds to the particular CMake build directory. It's thus recommended to employ this approach for fast iteration during development alongside the classic Gradle build @ref platforms-android-apps-gradle "described below" that will get used for the final release builds. Download contents of the toolchains repository from https://github.com/mosra/toolchains or add it as a Git submodule and include the `UseAndroid` module from there. Wrapping it in a check for presence of `CMAKE_ANDROID_NDK` will make it possible to have the pure CMake and a Gradle build coexist --- because Gradle internally uses CMake 3.6 which doesn't know about `CMAKE_ANDROID_NDK` yet. @code{.cmake} if(CORRADE_TARGET_ANDROID AND CMAKE_ANDROID_NDK) include(${PROJECT_SOURCE_DIR}/toolchains/modules/UseAndroid.cmake) endif() @endcode On the first run, the macro will attempt to detect SDK location, Android Build Tools version and Android Platfrom version and it prints them to the output like this: @code{.shell-session} $ cmake . … -- ANDROID_SDK not set, detected /opt/android-sdk -- ANDROID_BUILD_TOOLS_VERSION not set, detected 27.0.3 -- ANDROID_PLATFORM_VERSION not set, detected 27 … @endcode If you don't like what it detected (or the detection failed), edit these values in CMake cache. After that, pass your library target name and location of the `AndroidManifest.xml` file to @cmake android_create_apk() @ce. Continuing from the above: @code{.cmake} add_library(my-application SHARED MyApplication.cpp) android_create_apk(my-application AndroidManifest.xml) @endcode Note that even thought it doesn't make any sense, the `aapt` tool *demands* the manifest file to be called exactly `AndroidManifest.xml`. It can be, however, in any location. This will create an APK named `my-application.apk` and additionally also provide a new target, `my-application-deploy`, that will use `adb install -r` to install the built APK on the device. Building and uploading the application can be then done in a single step: @code{.shell-session} $ ninja my-application-deploy [5/5] Installing my-application.apk Success @endcode (Or e.g. `cmake --build . --target my-application-deploy` if you don't use Ninja.) For full compatibility with the Gradle build wrap the call again in a check for `CMAKE_ANDROID_NDK` and put the manifest in the `src/main` subdirectory, where Gradle expects it: @code{.cmake} add_library(my-application SHARED MyApplication.cpp) if(CMAKE_ANDROID_NDK) android_create_apk(my-application src/main/AndroidManifest.xml) endif() @endcode @subsection platforms-android-apps-gradle Using Gradle Besides plain CMake and Gradle it's also possible to use the classic `ndk-build`. but that's the least recommended way and it might not be supported in newer NDK builds. The following guide assumes you have Gradle installed in a system-wide location available in @cb{.sh} $PATH @ce. See the [Gradle installation docs](https://docs.gradle.org/current/userguide/installation.html) for more information, @ref platforms-android-gradlew "see below" if you want to use the `gradlew` wrappers instead. Create a `build.gradle` file that references your root `CMakeLists.txt`. Assuming it's saved right next to your root `CMakeLists.txt`, the most minimal version might look like this: @code{.gradle} buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' } } apply plugin: 'com.android.application' android { compileSdkVersion 25 defaultConfig { minSdkVersion 22 externalNativeBuild { cmake { arguments '-DANDROID_STL=c++_static' } } ndk { abiFilters 'arm64-v8a' } } externalNativeBuild { cmake { path 'CMakeLists.txt' } } } @endcode Important things are @cb{.gradle} compileSdkVersion @ce and @cb{.gradle} minSdkVersion @ce, which set SDK version that will be used to compile the project and minimal SDK version that the app can run on. You can add further CMake parameters in the @cb{.gradle} arguments @ce line (here it's just requesting to use static libc++) and the @cb{.gradle} abiFilters @ce allow you to restrict which ABIs will the project be built for --- Gradle by default builds for both 32 and 64-bit ARM, MIPS and x86, which might be quite annoying to wait for (during development at least). The @cb{.gradle} path @ce then references your `CMakeLists.txt` file. Gradle by default bundles all shared library targets defined in the CMake project, so there's no need to specify a particular library name. Gradle by default chooses Android SDK Build Tools version that corresponds to the @cb{.gradle} compileSdkVersion @ce. If you want to have control (for example to make it use Build Tools that you already have installed), specify it using @cb{.gradle} buildToolsVersion @ce: @code{.gradle} android { compileSdkVersion 27 buildToolsVersion '27.0.3' ... @endcode The [official documentation](https://developer.android.com/studio/projects/gradle-external-native-builds.html#configure-gradle) contains a more complete overview of all possibilities. For the `AndroidManifest.xml` file, Gradle expects it to be placed inside the `src/main` subdirectory, *not* straight besides the `build.gradle` file. With everything set up, you are now ready to build the project by simply executing the following from the directory with your `build.gradle`. During the first run, Gradle will download a huge amount of random stuff when building even the simplest thing. Close your eyes and ignore that it happened. @code{.sh} gradle build @endcode You can also use @cb{.sh} gradle assembleDebug @ce which is slightly faster as it doesn't build both Debug and Release and omits some unneeded checks. Installing on a connected device or emulator is then a matter of @code{.sh} gradle installDebug @endcode after which you can launch the app from your home screen. See the @ref platforms-android-troubleshooting section below if you ran into problems. @section platforms-android-multiple-abis Building for multiple ABIs and system versions The above guide simplifies things a bit and builds for just a single ARM64 ABI. In order to support multiple platforms, you need to separately build and install the dependencies for all ABIs of choice --- create separate build directories and run CMake with different `CMAKE_ANDROID_ARCH_ABI` and corresponding `CMAKE_INSTALL_PREFIX`. Similarly with SDK versions, adapt `CMAKE_SYSTEM_VERSION` and `CMAKE_INSTALL_PREFIX` to a desired version. The headers are shared and should be always installed into `<ndk>/sysroot/usr` regardless of ABI or SDK version. The supported ABI values are: ABI | Corresponding install prefix ----------- | ---------------------------- armeabi-v7a | <nk>/platforms/android-<version>/arch-arm/usr arm64-v8a | <nk>/platforms/android-<version>/arch-arm64/usr x86 | <nk>/platforms/android-<version>/arch-x86/usr x86_64 | <nk>/platforms/android-<version>/arch-x86_64/usr After that, you can add the additional ABIs to the `abiFilters` list in your `build.gradle`. For example, building Magnum for 32-bit and 64-bit ARM with SDK version 24 could look like this: @m_class{m-console-wrap} @code{.sh} mkdir build-android-arm && cd build-android-arm cmake .. \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=24 \ -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/platforms/android-24/arch-arm/usr \ -DCMAKE_FIND_ROOT_PATH="platforms/android-24/arch-arm;/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ -DMAGNUM_INCLUDE_INSTALL_PREFIX=/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr cmake --build . --target install cd .. mkdir build-android-arm64 && cd build-android-arm64 cmake .. \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=24 \ -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/platforms/android-24/arch-arm64/usr \ -DCMAKE_FIND_ROOT_PATH="platforms/android-24/arch-arm64;/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ -DMAGNUM_INCLUDE_INSTALL_PREFIX=/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr cmake --build . --target install @endcode The `build.gradle` for your app then looks like below. @attention Building multiple ABIs is currently not supported with the CMake @cmake android_create_apk() @ce macro, using Gradle is the only way. @code{.gradle} buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' } } apply plugin: 'com.android.application' android { compileSdkVersion 25 defaultConfig { minSdkVersion 24 externalNativeBuild { cmake { arguments '-DANDROID_STL=c++_static' } } ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } } externalNativeBuild { cmake { path 'CMakeLists.txt' } } } @endcode See [the official documentation about ABIs](https://developer.android.com/ndk/guides/abis.html) for more information. @section platforms-android-output-redirection Redirecting output to Android log buffer While printing to standard output and standard error output "just works" with command-line apps, you might want to redirect your @ref Corrade::Utility::Debug "Debug", @ref Corrade::Utility::Warning "Warning" and @ref Corrade::Utility::Error "Error" output to Android log buffer. so it can be accessed through the @cb{.sh} adb logcat @ce utility. See @ref Corrade::Utility::AndroidLogStreamBuffer for more information. The @ref Platform::AndroidApplication sets this up implicitly with a `magnum` tag, you can then filter it out like this, for example: @code{.shell-session} $ adb logcat *:S magnum ... 03-16 17:35:26.703 17726 17745 I magnum : Renderer: Mali-G71 by ARM 03-16 17:35:26.703 17726 17745 I magnum : OpenGL version: OpenGL ES 3.2 v1.r2p0 03-16 17:35:26.703 17726 17745 I magnum : Using optional features: 03-16 17:35:26.703 17726 17745 I magnum : GL_EXT_robustness @endcode @section platforms-android-system-cmake Using system-wide CMake installation According to the [official documentation](https://developer.android.com/studio/projects/add-native-code.html#vanilla_cmake), it's possible to use system CMake installation without needing to install Android SDK version of CMake 3.10. Simply update the @cb{.gradle} externalNativeBuild @ce in your `build.gradle` file to specify CMake version that you have installed in your system, for example: @code{.gradle} android { ... externalNativeBuild { cmake { path 'CMakeLists.txt' ... version '3.17' } } } @endcode However, be aware that this is an experimental feature and may be broken. @section platforms-android-gradlew Using gradlew wrappers instead of a system installation It's possible to bundle Gradle in the project itself as opposed to requiring a pre-existing system installation. It has the downside of having a bit more boilerplate files in your project, though. First, add the following to your `build.gradle` file: @code{.gradle} task wrapper(type: Wrapper) { gradleVersion = '4.0' } @endcode Then run this on a system that has Gradle installed: @code{.sh} gradle wrapper @endcode This will generate the following files that you can then add to version control: - `gradlew` shell script for Unix-like systems - `gradle.bat` batch script for Windows - `gradle/` directory with wrapper binaries With this in place, you can just use @cb{.sh} gradlew @ce instead of @cb{.sh} gradle @ce. @section platforms-android-travis Setting up Android build on Travis CI For simple compilation tests, add the following to your `.travis.yml` matrix builds. According to the [Travis Android documentation](https://docs.travis-ci.com/user/languages/android/), `build-tools-22.0.1` and `android-22` are always present, so your builds shouldn't get any extra delay when requesting them. The @cb{.sh} $TARGET @ce environment variable is used here only to disambiguate later, you might or might not need it. @code{.yml} matrix: include: # ... - language: android os: linux dist: trusty env: - TARGET=android android: components: - build-tools-22.0.1 - android-22 @endcode At the time of writing (March 2018), while the generic Ubuntu 14.04 images already have CMake 3.9.2, for some reason the Android Ubuntu 14.04 images have just CMake 3.2. Android support is builtin since version 3.7, but [an important fix](https://gitlab.kitware.com/cmake/cmake/issues/17253) for the LLVM toolchain was merged as late as in 3.9.2, so you may want to grab that version. Example `.travis.yml` setup that downloads the binary and extracts it to @cb{.sh} $HOME/cmake @ce, with @cb{.sh} $PATH @ce setup and caching: @code{.yml} cache: directories: - $HOME/cmake install: - > if [ "$TARGET" == "android" ] && [ ! -e "$HOME/cmake/bin" ]; then cd $HOME ; wget https://cmake.org/files/v3.9/cmake-3.9.2-Linux-x86_64.tar.gz && mkdir -p cmake && cd cmake && tar --strip-components=1 -xzf ../cmake-3.9.2-Linux-x86_64.tar.gz && cd $TRAVIS_BUILD_DIR ; fi - > if [ "$TARGET" == "android" ]; then export PATH=$HOME/cmake/bin:$PATH && cmake --version ; fi @endcode The NDK can be fetched as a simple `*.zip` file. However, version r16b has over 800 MB, so you might want to explore creation of a [Standalone Toolchain](https://developer.android.com/ndk/guides/standalone_toolchain.html) with only the things you need to speed up the build. Again, downlading it into @cb{.sh} $HOME/android-ndk-r16b @ce is a matter of adding this into your @cb{.yml} install: @ce section: @code{.yml} - > if [ "$TARGET" == "android" ]; then cd $HOME ; wget https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip && unzip -q android-*.zip && cd $TRAVIS_BUILD_DIR ; fi @endcode Travis CI discourages caching the NDK, as downloading the cache will take roughly the same amount of time as downloading it from upstream. Building your actual code is just a matter of setting up a correct NDK path. You can install the dependencies to any location as long as you specify the same location in `CMAKE_PREFIX_PATH` and `CMAKE_FIND_ROOT_PATH` in depending projects. Using `armeabi-v7a` instead of `arm64-v8a` ensures that you can run the code in a preinstalled emulator later, see below. @code{.sh} mkdir build-android-arm && cd build-android-arm cmake .. \ -DCMAKE_ANDROID_NDK=$HOME/android-ndk-r16b \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=22 \ -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \ -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_PREFIX_PATH=$HOME/deps \ -DCMAKE_FIND_ROOT_PATH=$HOME/deps \ ... @endcode @subsection platforms-android-travis-run Running tests on the emulator In order to run your tests on the emulator, you need to request some system image. Again, `sys-img-armeabi-v7a-android-22` is part of the default installation, so it shouldn't add any extra time to your build: @code{.yml} matrix: include: - language: android # ... android: components: # ... - sys-img-armeabi-v7a-android-22 @endcode As described [in the Travis documentation](https://docs.travis-ci.com/user/languages/android/#How-to-Create-and-Start-an-Emulator), create a system image and wait for the emulator to start (be prepared, it can easily take up *minutes*). Assuming you use the @ref TestSuite-Tester-running-cmake "Corrade::TestSuite Android integration", simply run your tests via `ctest` and optionally enable colored output for extra clarity: @code{.sh} echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a emulator -avd test -no-audio -no-window & android-wait-for-emulator CORRADE_TEST_COLOR=ON ctest -V @endcode @subsection platforms-android-travis-bundle APK bundle creation At the time of writing (March 2018), Travis Ubuntu 14.04 has Gradle 4.0, however the Android build plugin 3.0 requires at least Gradle 4.1, so you need to backport `gradle.build` to plugin version 2.3.3 compared to the @ref platforms-android-apps "template above". In particular, the `classpath` needs to be updated, `compileSdkVersion` and `minSdkVersion` adapted to versions defined in @cb{.yml} components: @ce in your `travis.yml` file and the `buildToolsVersion` explicitly specified, because that's needed in plugin versions before 3.0: @code{.gradle} buildscript { // ... dependencies { classpath 'com.android.tools.build:gradle:2.3.3' } } // ... android { compileSdkVersion 22 buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 22 // ... @endcode Gradle bundles its own CMake 3.6, downloading it on-demand and then failing because SDK licenses are not signed. Solution is to install CMake and sign its license explicitly beforehand. Add the following to your `.travis.yml`: @code{.yml} before_install: - if [ "$TARGET" == "android" ]; then yes | sdkmanager "cmake;3.6.4111459"; fi @endcode Unlike above, and especially if you build for multiple ABIs, it's better to install all dependencies where Gradle expects them. In particular, in case of Corrade and ARM64 ABI and NDK being in @cb{.sh} $HOME/android-ndk-r16b @ce, the install prefixes look like this: @code{.sh} cmake .. \ -DCMAKE_INSTALL_PREFIX=$HOME/android-ndk-r16b/platforms/android-22/arch-arm64/usr \ -DCORRADE_INCLUDE_INSTALL_PREFIX=$HOME/android-ndk-r16b/sysroot/usr \ ... @endcode Finally, you need to tell Gradle where the NDK is located and where to look for native binaries (for example the `corrade-rc` executable) using environment variables. At last, execute `gradle build` in the directory where `build.gradle` is: @code{.sh} export ANDROID_NDK_HOME=$HOME/android-ndk-r16b export CMAKE_PREFIX_PATH=$HOME/deps-native/ gradle build @endcode @section platforms-android-troubleshooting Troubleshooting @subsection platforms-android-cmake-too-old CMake or NDK is too old While the minimal CMake version that's required for building Magnum for Android is 3.7, NDK r19 and newer need at least CMake 3.16 to work. For older NDKs you might want to grab at least CMake 3.9.2, as it [fixes an issue with the Clang toolchain](https://gitlab.kitware.com/cmake/cmake/issues/17253). With NDK r18 and older you'll need to additionally supply `-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang`, on the other hand the `-DCMAKE_FIND_ROOT_PATH` isn't needed --- CMake is able to figure out the paths on its own in that case. @subsection platforms-android-compiler-fails-at-life Compiler suddenly fails at life If you're suddenly greeted with an overwhelming amount of strange errors coming from standard library C++ headers including things like @m_class{m-console-wrap} @code{.shell-session} /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:319:9: error: no member named 'isnormal' in the global namespace using ::isnormal; ~~^ /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:320:9: error: no member named 'isgreater' in the global namespace using ::isgreater; ~~^ /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:321:9: error: no member named 'isgreaterequal' in the global namespace using ::isgreaterequal; ~~^ /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:322:9: error: no member named 'isless' in the global namespace using ::isless; ~~^ /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:323:9: error: no member named 'islessequal' in the global namespace using ::islessequal; ~~^ /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/cmath:324:9: error: no member named 'islessgreater' in the global namespace using ::islessgreater; ~~^ @endcode @m_class{m-noindent} it might be due to `<ndk>/sysroot/usr/include` being present in your include path. For some reason the NDK has two copies of system includes, one in `<ndk>/toolchains/llvm/prebuilt/<host>/sysroot/usr` and one here, and the one in `<ndk>/sysroot` causes the above errors. Find the offending include dir and point it to the other location to fix the error. @subsection platforms-android-troubleshooting-windows-nsight CMake complaining about some NVIDIA Nsight Tegra Visual Studio Edition on Windows On Windows it's possible that you get the following CMake error when configuring an Android project: > CMAKE_SYSTEM_NAME is 'Android' but 'NVIDIA Nsight Tegra Visual Studio > Edition' is not installed. This is because Visual Studio Project Files as the default generator on Windows is not able to build for any other system than Windows itself. To fix it, use a different generator --- for example [Ninja](https://ninja-build.org/). Download the binary, put it somewhere on your `PATH` and pass `-G Ninja` to CMake. Alternatively, pass the path to it using `-DCMAKE_MAKE_PROGRAM=C:/path/to/ninja.exe`. @m_class{m-block m-success} @par Using Ninja that's bundled with Visual Studio If you have Visual Studio with CMake support installed, instead of downloading Ninja yourself, you can make use of the version that's bundled with it. In version 2017 it's in the following location, adjust the path for other versions: @par @parblock @m_class{m-console-wrap} @code{.bat} cmake -G Ninja -DCMAKE_MAKE_PROGRAM=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe ... @endcode @endparblock @subsection platforms-android-troubleshooting-multiple-devices ADB fails when more devices are connected If any `adb` command fails with @code{.shell-session} error: more than one device/emulator @endcode @m_class{m-noindent} you can pass the device ID via the `-s` parameter to each command. That is, however, verbose and doesn't work for example in case of running @ref Corrade::TestSuite tests via `ctest`. Instead, it's possible to supply the device ID via an environment variable, for example: @code{.sh} export ANDROID_SERIAL="bla" @endcode @subsection platforms-android-troubleshooting-signing-failed Signing the APK using CMake fails At the moment the location and passphrase for the Android signing keystore is implicitly set to @cb{.sh} $HOME/.android/debug.keystore @ce with `android` as a password, since that's the location (and password) that Gradle uses. If you see output similar to this: @code{.shell-session} [1/2] Signing my-application.apk FAILED: my-application.apk … Failed to load signer "signer #1" java.io.FileNotFoundException: /.android/debug.keystore @endcode then you either don't have the keystore generated yet or it's in some other location. Similar error can happen if the password is incrrect, in which case it will say the following instead: @code{.shell-session} Failed to load signer "signer #1" java.io.IOException: Keystore was tampered with, or password was incorrect @endcode Generating a debug keystore can be done by running through the Gradle build at least once. It's also possible to create a keystore [using Android Studio](https://developer.android.com/studio/publish/app-signing#generate-key) or using the `keytool` utility that's bundled with Java SDK. The following creates a key equivalent to the Gradle-generated one, adapt the location as necessary: @code{.sh} keytool -genkeypair -keystore $HOME/.android/debug.keystore \ -storepass android -alias androiddebugkey -keypass android \ -keyalg RSA -validity 10000 -dname CN=,OU=,O=,L=,S=,C= @endcode The location and password is controlled via the `ANDROID_APKSIGNER_KEY` variable, edit your CMake cache to point it to a different location or password. Note that the command parameters have to be delimited by `;` instead of a space. For example, setting the location to `/etc/android.keystore` with a password `secret` can be done like this: @code{.sh} cmake . -DANDROID_APKSIGNER_KEY="--ks;/etc/android.keystore;--ks-pass;pass:secret" @endcode You can also add further arguments to `apksigner` here. See the [official documentaton for apksigner](https://developer.android.com/studio/command-line/apksigner) for details. @subsection platforms-android-troubleshooting-ndk-home-deprecated Warning about ANDROID_NDK_HOME being deprecated If you see the following warning when building with Android Gradle Plugin 3.6.0 and newer, it's because an @cb{.sh} $ANDROID_NDK_HOME @ce environment variable is defined. @m_class{m-console-wrap} @code{.shell-session} $ gradle build > Configure project : WARNING: Support for ANDROID_NDK_HOME is deprecated and will be removed in the future. Use android.ndkVersion in build.gradle instead. Support for ANDROID_NDK_HOME is deprecated and will be removed in the future. Use android.ndkVersion in build.gradle instead. @endcode Ignore it, it's the best way. If you *really* want to get rid of the warning you can @cb{.sh} unset ANDROID_NDK_HOME @ce or edit your default system environment and remove this entry, but at that point Gradle will most probably suddenly want to download the NDK again somewhere inside the SDK installation directory, which may fail @ref platforms-android-troubleshooting-permissions "due to readonly directory permissions". At that point you're on your own, sorry. This path hasn't been investigated yet. @subsection platforms-android-troubleshooting-ndk-version Specifying NDK version in gradle.build With Android Gradle plugin 3.6.0 and newer it's possible to specify which NDK to use via `android.ndkVersion`. However it requires you to [specify the full version](https://developer.android.com/studio/projects/install-ndk#apply-specific-version) and doing something like @code{.gradle} android { ndkVersion "19" // ... @endcode @m_class{m-noindent} will result in the following error, or worse, a @ref platforms-android-troubleshooting-no-signature-applicable "cryptic error about some closure". @m_class{m-console-wrap} @code{.shell-session} > Specified android.ndkVersion '19' does not have enough precision. Use major.minor.micro in version. @endcode The complete NDK version isn't listed in any of the online Android docs, you have to look into the `source.properties` file in the NDK root, which might say for example the following. It may be also available through the GUI SDK manager. @code{.properties} Pkg.Desc = Android NDK Pkg.Revision = 19.0.5232133 @endcode @subsection platforms-android-troubleshooting-cmake Debugging Gradle CMake issues Gradle by default doesn't show any useful output for the CMake run, only the error output when something goes wrong. For general debugging, you have to run `gradle build -i`, which will spit out immense heaps of even more useless content, and the actual CMake output messages are interleaved with some JSON dumps in a way that makes them practically invisible. To find a start of the CMake log, it might help to redirect the output and then search for `Check for working CXX compiler`. To make things worse, the CMake build isn't stored in the `build/` directory, but rather in (hidden) `.externalNativeBuild/` or `.cxx/` directories, depending on which version you're at. Be sure to remove those when retrying a CMake configure step, otherwise CMake may not even get run. @subsubsection platforms-android-troubleshooting-cant-find Gradle CMake can't find dependencies Gradle by default searches only in the NDK install path. If you have your dependencies installed somewhere else (this goes especially for the *native* `corrade-rc` executable), you might want to point the `CMAKE_PREFIX_PATH` environment variable to your install location: @code{.sh} export CMAKE_PREFIX_PATH= gradle build @endcode You can also put that directly into the `gradle.build` file, although that's not recommended because it hardcodes your local system setup in the project: @m_class{m-console-wrap} @code{.gradle} android { ... defaultConfig { ... externalNativeBuild { cmake { // Append to the existing arguments that are there arguments '-DANDROID_STL=c++_static', '-DCMAKE_PREFIX_PATH=' } } @endcode If you have the dependencies installed in the NDK path, but it still fails, check that you installed for the same SDK version as in `minSdkVersion` and all ABIs mentioned in `abiFilters` inside your `build.gradle` file --- Gradle runs CMake once for each entry in the list so it might happen that it finds them for all but one ABI. See @ref platforms-android-multiple-abis above for more information. @subsection platforms-android-troubleshooting-cant-launch App can't launch If your application can't launch (or it just blinks and then disappears), you can inspect @cb{.sh} adb logcat @ce output to see what went wrong, but be quick, the log is spitting out a lot of info all the time. Possible causes: - Mismatch between actual library name and library referenced from `AndroidManifest.xml`, causing Java to fail loading it - The device has an ABI for which the app was not compiled (check the @cb{.gradle} abiFilters @ce option in `build.gradle`) or the app was compiled with SDK version that's not supported by the device yet. See the [official API level documentation](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels) for more information. - The device doesn't support OpenGL ES 3.0 yet. Rebuild Magnum and its dependencies with the `TARGET_GLES2` option enabled. See @ref building-features for more information. - Loading fails with `ANativeActivity_onCreate` symbol not being found. If you are using @ref Platform::AndroidApplication, this issue should be prevented, otherwise you need to add `-u ANativeActivity_onCreate` to your linker flags or reference the symbol some other way. See [android-ndk/ndk#381](https://github.com/android-ndk/ndk/issues/381) for details. - Additional `*.so` libraries are referenced by the main `*.so` but not bundled in the `*.apk`. One option is to switch to static libraries, another is explicitly specifying them in the `build.gradle` file. See [the official documentation](https://developer.android.com/studio/projects/gradle-external-native-builds.html#jniLibs) for details. @subsection platforms-android-troubleshooting-gradle-evaluation-listener Gradle aborting due to "failed to notify project evaluation listener" All these errors look the same to an untrained eye, starting with the following, however there's a broad variety of reasons and *of course* it won't tell you what is actually wrong: @code{.shell-session} $ gradle build FAILURE: Build failed with an exception. * What went wrong: … > Failed to notify project evaluation listener. @endcode The line right after is the key clue to know the root cause --- see the following sections @subsubsection platforms-android-troubleshooting-java8 Too new Java Older versions of Android build tools didn't support Java 9 or 10, which could manifest as any of the following: @code{.shell-session} > Failed to notify project evaluation listener. > javax/xml/bind/annotation/XmlSchema @endcode @code{.shell-session} > Failed to notify project evaluation listener. > Could not initialize class com.android.sdklib.repository.AndroidSdkHandler @endcode Solution is to point @cb{.sh} $JAVA_HOME @ce to a Java 8 installation, for example. This affects not just `gradle`, but also other tools like `sdkmanager` (see @ref platforms-android-troubleshooting-licenses "below"), so you may want to export it for the whole session: @code{.sh} export JAVA_HOME=/usr/lib/jvm/java-8-openjdk/ gradle build @endcode On ArchLinux, Java 8 is provided by the [jdk8-openjdk](https://www.archlinux.org/packages/extra/x86_64/jdk8-openjdk/) package, which is pulled in as a dependency of the `android-sdk` and `gradle` packages. @subsubsection platforms-android-troubleshooting-gradle6 Too new Gradle (or maybe NDK or...) Older version of Android build tools don't seem to work with new Gradle (in this case 6.6.1) anymore. Or that's the most plausible explanation, it might also be that these don't work with new NDK versions either (r19 in this case) but why is that reported by a throwing exception 1000 stack frames deep in some totally random ungoogleable API is just beyond me. Anyway --- the following may happen with Android Gradle plugin 2.3.3: @m_class{m-console-wrap} @code{.shell-session} > Failed to notify project evaluation listener. > org.gradle.api.tasks.compile.CompileOptions.setBootClasspath(Ljava/lang/String;)V @endcode While this with 3.0.0: @m_class{m-console-wrap} @code{.shell-session} > Failed to notify project evaluation listener. > org.gradle.api.internal.TaskInputsInternal.property(Ljava/lang/String;Ljava/lang/Object;)Lorg/gradle/api/tasks/TaskInputs; @endcode This for 3.3.0: @m_class{m-console-wrap} @code{.shell-session} > Failed to notify project evaluation listener. > org.gradle.api.file.ProjectLayout.directoryProperty(Lorg/gradle/api/provider/Provider;)Lorg/gradle/api/file/DirectoryProperty; @endcode And probably a dozen other variants, but I am not willing to waste my time any further by enumerating all possible embarrasing crashes of cursed tools. The oldest Android Gradle plugin version that worked with Gradle 6.6.1 and NDK r19 was 3.6.0, and to change it update the following entry in your `build.gradle`: @code{.gradle} buildscript { // ... dependencies { classpath 'com.android.tools.build:gradle:3.6.0' } } @endcode @subsection platform-android-troubleshooting-no-repositories-defined Gradle fails because no repositories are defined Android Gradle plugin 3.6 and newer may *greet* you with the following when doing a fresh build: @m_class{m-console-wrap} @code{.shell-session} $ gradle build > Task :mergeDebugResources FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':mergeDebugResources'. > Could not resolve all files for configuration ':_internal_aapt2_binary'. > Cannot resolve external dependency com.android.tools.build:aapt2:4.0.0-6051327 because no repositories are defined. Required by: project : @endcode Solution is to add the following to your `build.gradle`: @code{.gradle} allprojects { repositories { google() } } @endcode * *in addition* to the existing repositories defined inside `buildscript`: @code{.gradle} buildscript { repositories { jcenter() google() } // ... @endcode Both the two existing repositories inside `buildscript` are still needed, it will fail with different but no less cryptic errors if you attempt to remove either of them. When you add the `allprojects` entry, the problem may seemingly look like it disappeared, since removing `allprojects` again will still work. This is only true until you recreate the build directory from scratch so better keep it there. @subsection platforms-android-troubleshooting-licenses Accepting SDK licenses for Gradle Gradle might refuse to build a project if SDK licenses are not accepted, with an error that might look like this: @m_class{m-console-wrap} @code{.shell-session} $ gradle build FAILURE: Build failed with an exception. A problem occurred configuring root project 'MyApplication'. > You have not accepted the license agreements of the following SDK components: [Android SDK Platform 22]. Before building your project, you need to accept the license agreements and complete the installation of the missing components using the Android Studio SDK Manager. Alternatively, to learn how to transfer the license agreements from one workstation to another, go to http://d.android.com/r/studio-ui/export-licenses.html @endcode Depending on where your SDKs are installed, you might need to execute the following (assuming you have SDK version 26 at least): @code{.sh} sdkmanager --licenses # and then manually accept all of them @endcode @m_class{m-note m-danger} @par The tool says @cb{.shell-session} All SDK package licenses accepted @ce and * **returns with a success error code even if the accepting failed**, so be sure to verify that everything went well by executing @cb{.sh} sdkmanager --licenses @ce again. If it offers the same licenses again, it didn't succeed before. In that case you may want to force it with @cb{.sh} sudo @ce (and then verify once again). If the tool blows up with the following error, it's again because the Java version is too new, see @ref platforms-android-troubleshooting-java8 "above" for a solution: @m_class{m-console-wrap} @code{.shell-session} Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema at com.android.repository.api.SchemaModule$SchemaModuleVersion.(SchemaModule.java:156) … @endcode @subsection platforms-android-troubleshooting-marshalling-nonexistent-xml Gradle complains about an exception while marshalling some XML file that doesn't exist @m_class{m-console-wrap} @code{.shell-session} $ gradle build > Configure project : Exception while marshalling /opt/android-sdk/build-tools/29.0.3/package.xml. Probably the SDK is read-only … @endcode This message doesn't seem to result in a build error, however it might be annoying. The files it complains about don't exist on the filesystem and it's related to the @ref platforms-android-troubleshooting-licenses problem --- the `package.xml` files get created during the (`sudo`ed) run of `sdkmanager`, even if there are no licenses left to sign. @subsection platforms-android-troubleshooting-no-signature-applicable Gradle fails because no signature of method is applicable for argument types @m_class{m-console-wrap} @code{.shell-session} $ gradle build * Where: Build file '…/build.gradle' line: … * What went wrong: … > No signature of method: build_3w19jaw3zqx2h2gvdj91syekc.android() is applicable for argument types: (build_3w19jaw3zqx2h2gvdj91syekc$_run_closure1) values: [build_3w19jaw3zqx2h2gvdj91syekc$_run_closure1@393eb206] @endcode The particular cryptic value may differ, but this means you have some error in your Gradle file. Of course it won't tell you what the error is, so good luck commenting out parts of the file until it works or you reach a different error. @subsection platforms-android-troubleshooting-permissions Android SDK directory permissions Gradle is able to work with system-installed Android SDK. If it complains about directory permissions such as @code{.shell-session} > Failed to install the following SDK components: [Android SDK Build-Tools 26.0.2, Android SDK Platform 25] The SDK directory (/opt/android-sdk) is not writeable, please update the directory permissions. @endcode it's often enough to just install such packages. In case of ArchLinux, all relevant packages are available in AUR. For the above error in particular, the matching packages are [android-sdk-build-tools-26.0.2](https://aur.archlinux.org/packages/android-sdk-build-tools-26.0.2/) and [android-platform-25](https://aur.archlinux.org/packages/android-platform-25/). @section platforms-android-obsolete-troubleshooting Obsolete troubleshooting These issues should no longer appear in practice with reasonably recent versions of Android tools, however are still kept to remind us everything used to be even worse than it is now in case your project keeps using the older versions (which is recommended for sanity, there's not many things worse than upgrading Android NDK). @subsection platforms-android-troubleshooting-term Gradle aborting due to termcap If you see the following output, Gradle is crashing because @cb{.sh} $TERM @ce is set to `xterm-256color` or `xterm-24`: @code{.shell-session} $ gradle build FAILURE: Build failed with an exception. * What went wrong: Could not open terminal for stdout: could not get termcap entry @endcode Solution is to set @cb{.sh} TERM=xterm @ce. See [gradle/gradle#4440](https://github.com/gradle/gradle/issues/4440) for more information. Doesn't seem to be an issue with Gradle 6.6.1 anymore, but the exact version where this got fixed is unknown. @code{.sh} TERM=xterm gradle build @endcode */ }