This is a minimal repo to build an application for Quest 2 (Android, OpenXR) on the Windows powershell.
- No IDE
- No build system
- No scripting languages (other than some trivial powershell path interpolation)
Just command line tool invocations, each explained. All the significant source code is in a single C++ file src/main.cpp.
This repo is inspired by the work of cnlohr on tsopenxr. Much of the code
here is based on that example of how to build a Quest 2 apk just using the plain command line tools. However I've adapted the code to be all in one place, and adapted the build to no
longer depend on make, or tools like zip.
I also wrote an article about what lead me to make this repo, if you're curious.
Note: A previous version of this repo used 7z to unpack and repack the .apk. Thanks to Joel Auterson,
who wrote a better article about manually making apk's long before I did, for helping me improve this repo!
First, ensure you have the relevant Android SDK, NDK, and build tools installed, the official docs get this bit right, so just follow them. Make sure you actually open Android Studio, then open a blank project as well, because these actually seems to be required to complete the installation... alternatively, there are the plain command line tools (scroll down) but I haven't tested a fresh install with those yet.
The build process depends on the following executables:
aaptthe Android Asset Packaging Tool which ships with the Android SDKadbthe Android Debug Bridge, used to install the apkclangthe clang compiler that ships with the Android SDK (more specifically, the NDK)jarsignerin the Android JDK, used to sign our apk (instead of apksigner, since we're Android < 30)keytoolin the Android JDK, used to generate a key store for signingzipalignwhich is a zip alignment tool optimises the archive so it can bemmaped, which ships with the Android SDK
Let's define some paths to make the following commands somewhat readable. These paths
may be different for you, for example the JDK tools might be in C:/Program Files/Android/Android Studio/jbr/bin, and of course your username wont be User.
# Shared paths
$ANDROID_SDK_HOME = "C:/Users/User/AppData/Local/Android/Sdk"
$ANDROID_JAR = "$ANDROID_SDK_HOME/platforms/android-29/android.jar"
$ANDROID_NDK_HOME = "$ANDROID_SDK_HOME/ndk/25.2.9519653"
$ANDROID_LLVM = "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/windows-x86_64"
$ANDROID_LIBS = "$ANDROID_LLVM/sysroot/usr/include"
$ANDROID_LIBS_LINK = "$ANDROID_LLVM/sysroot/usr/lib/aarch64-linux-android/29"
# Command line tools
$AAPT = "$ANDROID_SDK_HOME/build-tools/34.0.0/aapt"
$ADB = "$ANDROID_SDK_HOME/platform-tools/adb.exe"
$CLANG = "$ANDROID_LLVM/bin/clang"
$JARSIGNER = "C:/Program Files/Android/jdk/jdk-8.0.302.8-hotspot/jdk8u302-b08/bin/jarsigner.exe"
$KEYTOOL = "C:/Program Files/Android/jdk/jdk-8.0.302.8-hotspot/jdk8u302-b08/bin/keytool.exe"
$ZIPALIGN = "$ANDROID_SDK_HOME/build-tools/34.0.0/zipalign"The first time you run this, you'll want to generate an android debug key. Do that with keytool like:
& $KEYTOOL -genkey -v -keystore debug.keystore -storepass android `
-alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 `
-validity 10000 -dname "C=US, O=Android, CN=Android Debug"Now, create the directories we'll use for the build, ignoring errors if they already exist.
mkdir -ea 0 build/assets
mkdir -ea 0 build/lib/arm64-v8aCompile the application with:
& $CLANG --target=aarch64-linux-android29 -ffunction-sections -Os -fdata-sections `
-Wall -fvisibility=hidden -m64 -Os -fPIC -DANDROIDVERSION=29 -DANDROID `
-Ideps/include -I./src -I$ANDROID_LIBS -I$ANDROID_LIBS/android `
src/main.cpp deps/src/android_native_app_glue.c deps/lib/libopenxr_loader.so `
-L$ANDROID_LIBS_LINK -s -lm -lGLESv3 -lEGL -landroid -llog `
-shared -uANativeActivity_onCreate `
-o build/lib/arm64-v8a/libquestxrexample.soCopy our assets into the build directory, package it with aapt to get an unsigned, unaligned apk.
cp -ea 0 -r assets build
cp -ea 0 deps/lib/libopenxr_loader.so build/lib/arm64-v8a/
& $AAPT package -f -F temp.apk -I $ANDROID_JAR -M src/AndroidManifest.xml `
-S resources -A build/assets -v --target-sdk-version 29 buildSign the application with jarsigner (not apksigner, that's an android 30+ thing), and run
zipalign on it.
& $JARSIGNER -sigalg SHA1withRSA -digestalg SHA1 -verbose -keystore debug.keystore `
-storepass android build.apk androiddebugkey
& $ZIPALIGN -f -v 4 build.apk questxrexample.apkDelete the build artifacts, don't delete the keystore!
rm -ea 0 -r build
rm -ea 0 temp.apk
rm -ea 0 build.apkTo run, we'll use adb to install and run the apk. Make sure your Quest 2 is set up in
dev mode
following the docs and plug it in, verify it's connected with:
& $ADB devicesThen install the apk:
& $ADB install -r questxrexample.apkNow run it and run logcat with some filters to see what happens:
& $ADB shell am start -n org.cshenton.questxrexample/android.app.NativeActivity
& $ADB logcat OpenXR:D questxrexample:D *:S -v colorChuck on your headset and you should see a grid on the floor and some cubes on your controllers. You may need to install/start again if it gets into a weird state.
Yes that's the point, of course you want some sort of build automation. But you probably want your build automation. The point of these commands is to make it transparent so you can automate it for yourself! Whether that's using Make, CMake, or your own in-house build tool.
You can also port all of these commands to bash if you like, I just wanted something that worked with native windows tools.
You probably don't want my name all over your Quest 2 app. Here are all the locations you need to change. Implied
is that you need to change the string cshenton to your org name and questxrexample to your app name.
resources/values/strings.xmlsrc/AndroidManifest.xmlsrc/main.cppdeps/src/android_native_app_glue.c, yes powershell refused to not eat the quotes in-DAPPNAME="questxrexample"so I caved- The above build "script"
-o build/lib/arm64-v8a/libquestxrexample.so
- The adb command
Okay, but where did the vendored dependencies come from?
-
From
ovr_openxr_mobile_sdk_55.0deps/include/openxrwas copied from3rdParty/khronos/openxr/OpenXR-SDK/includedeps/lib/libopenxr_loader.sowas copied fromOpenXR/Libs/Android/arm64-v8a/Release
-
From
github.com/cnlohr/tsopenxrdeps/include/android_native_app_glue.hwas copied frommeta_quest/deps/src/android_native_app_glue.cwas copied frommeta_quest/
Why not just use the android_native_app_glue that ships with the NDK? Yeah good question. It didn't work, this one did.
For reference the headers are the same (save a #pragma once vs a regular header guard), I need to properly bisect the
source files to figure out the key difference.
- SDK
android_native_app_gluedidn't work - Controllers appear to stop providing inputs on idle -> resume
- Load / Resume occasionally shows frame buffers in world space quads