/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 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 @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. Running console utilities and tests on the device don't need much more, in case you want to develop actual applications, you need also Gradle and SDK platform + SDK platform build tools for version of your choice. 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-26.0.2](https://aur.archlinux.org/packages/android-sdk-build-tools-26.0.2/) - [android-platform-25](https://aur.archlinux.org/packages/android-platform-25/) - [android-sdk-cmake](https://aur.archlinux.org/packages/android-sdk-cmake/) Gradle requires Android SDK version of CMake, which is currently at version 3.6. See below for an experimental way to @ref platforms-android-system-cmake "use the system CMake" instead. @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 simply as this (adapt version numbers and ABIs as needed): @code{.sh} mkdir build-android-arm64 && cd build-android-arm64 cmake .. \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=22 \ -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \ -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -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/src/my-application`, the workflow would be like this: @code{.sh} adb push build-android-arm64/src/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 Building of graphics applications is managed fully using Gradle, which also builds your CMake project internally. It's possible to use other means such as `ndk-build`, but CMake is the officially preferred way. 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. 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 The @ref Platform::AndroidApplication also contains a fully configured bootstrap project that's ready to build and deploy. Check its documentation for details. The first thing you need compared to building an app for other platforms is creating 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 Then you need to 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. The [official documentation](https://developer.android.com/studio/projects/gradle-external-native-builds.html#configure-gradle) contains a more complete overview of all possibilities. Another important file is `src/main/AndroidManifest.xml`, which says some properties about the Android package. The location is also important, it has to be placed inside `src/main` subdirectory, *not* straight besides the `build.gradle` file. 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 says that the minimal OpenGL ES version is 2.0, change it in case you require a different version. Consult [the Android developer documentation](https://developer.android.com/guide/topics/manifest/manifest-intro.html) for further information about the manifest 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 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: @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_NDK_TOOLCHAIN_VERSION=clang \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/platforms/android-24/arch-arm/usr \ -DMAGNUM_INCLUDE_INSTALL_PREFIX=/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_NDK_TOOLCHAIN_VERSION=clang \ -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/platforms/android-24/arch-arm64/usr \ -DMAGNUM_INCLUDE_INSTALL_PREFIX=/sysroot/usr cmake --build . --target install @endcode And the `build.gradle` for your app then looks 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 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.6. 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.10.2' } } } @endcode However, be aware that this is an experimental feature and may be broken. At the time of writing (March 2018), it didn't work for me with NDK r16b, Android buid plugin 3.0.1 and CMake 3.10. @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-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 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-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. @code{.sh} TERM=xterm gradle build @endcode @subsection platforms-android-troubleshooting-licenses Accepting SDK licenses for Gradle Gradle might refuse to build a project if SDK licenses are not accepted. 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 The tool doesn't provide any diagnostic output 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, you might want to force it with @cb{.sh} sudo @ce. @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. */ }