7zip
as a dependency because I didn’t
understand how to use aapt
, thanks to Joel Auterson
for his (much better) article on manually building apks for helping me improve this!
In a previous article, I lamented not finishing a Quest 2 demo I’d worked on. Commercial game engines start to grate on me after a while, and I lose motivation. So, last week, armed with my existing knowledge of OpenXR and OpenGL, I decided to have a go at native Quest 2 development without an engine.
Writing the code went fine! Building it… not so much.
Build System Blues #
That first attempt went… poorly! I wrote a twitter thread documenting my slow descent into madness but to summarise:
- The Meta OpenXR docs tell you to download the wrong SDK
- Syncing the
build.gradle
in the correct SDK crashes Java - The
hello_xr
example from Khronos requires python to build - All of these require
gradle
andcmake
Look I get it, Android uses Java! But I’m writing C++ game code that may run on other platforms one day, I will go through hell and high water to avoid messing that code up with a horrific build system.
Just to prove this story has a happy ending, here’s a video of what I eventually got working. If you’re curious how I got there, read on.
No Build System? #
The build system is, in my opinion, the single most important part of a game engine. If you bugger it up your code will be slow to build and fragile for all time, regardless of what cool graphical or gameplay innovations you make. So, any action necessary to build for a target platform that requires some other build system must be broken down into its parts.
So to make a build system, you first need to know how to build something without one!
My first port of call for building that understanding was a repo I was already aware of, tsopenxr, by Charles Lohr (AKA cnlohr
).
I’d referenced it when I applied to the Khronos RFP to make an OpenXR equivalent of Vulkan Tutorial,
because it was the only example I was aware of, of how to build a Quest APK without gradle.
I figured that most engine devs would be about as excited to use gradle as they would be to use the STL, RTTI, or exceptions. However, I suspect my proposal to write the tutorials in C99 and not use a build system made it a bit too eccentric for the working group’s tastes, but I stand by it.
There’s a nice little Makefile
in that repo which outlines how to manually make an apk
using the android command line tools,
but I’m on a consumer copy of windows, which for whatever reason doesn’t play as nice with make
as the enterprise ones,
plus I wanted something that worked with native windows tools.
Building the APK #
So I ran make
with --just-print
to see what commands were being invoked and started carefully porting
them over to work with powershell and standard windows utilities. Here’s what I ended up with.
Tools #
The build process depends on the following tools:
aapt
the Android Asset Packaging Tool which ships with the Android SDKadb
the Android Debug Bridge, used to install the apkclang
the clang compiler that ships with the Android SDK (more specifically, the NDK)jarsigner
in the Android JDK, used to sign our apk (instead of apksigner, since we’re Android < 30)keytool
in the Android JDK, used to generate a key store for signingzipalign
optimises the archive so it can bemmap
ed, which ships with the Android SDK
Compared to building an OpenXR app locally, which requires just a single clang
invocation, that’s a lot!
I wish Meta would put some effort into consolidating these into a standalone binary and shipping it with
their own SDK. Currently they ship a bunch of python scripts, no thank you!
In turn, that copy of clang
has next to it all the platform specific headers you’ll need to build.
Steps #
With those tools in hand, we then must:
- (once) Generate a debug key with
keytool
- Make our build directories
- Compile our app using
clang
to get (here)libquestxrexample.so
- Run
aapt
to get an unaligned, unsigned apk - Sign it with
jarsigner
- Run
zipalign
on the signedtemp.apk
to output our finalquestxrexample.apk
And we have a valid apk! Now to run it we:
- Install it with
adb
- Start it with
adb
- Logcat it with
adb
to make see our logs
Now, this is silly, but it’s not magic! Exactly the kind of thing we can imagine adding to a build tool ourselves. Understanding (mostly) achieved. Along the way to this there was a lot of pain though.
- Trying to use
apksigner
since that seemed better, nope - Trying to figure out how to get
aapt
to just put my library files in the apk itself, nope - Using the wrong copies of
keytool
andjarsigner
- Misunderstanding the NativeActivity entrypoint model and leaving out the
android_main
entrypoint - Didn’t realise I could just pass a folder to
aapt
so was manually unzipping and rezipping to get my.so
in there, D’oh!
There’s just so much you can get wrong and the documentation for this process is scattered at best.
The quest_xr
repo
#
Follow that? I know I wouldn’t. I’ve put together a repository with a fully worked example application and careful build instructions using powershell to help myself and others understand the process. It’s not authoritative, not perfect, but it should work.
Minimal example building Quest 2 OpenXR application with no build system, IDE, or scripting langs.
Please check it out! At the least, give the src/main.cpp
a read. My pet peeve about “example code”
that most big orgs ship is that they come with these enormous, nested frameworks that make it impossible
to see what the actual API calls are. This “C++” code is just one big context struct and some methods
to break things up, and would be trivially portable to C. It’s basically what I wish I had when I was
learning OpenXR.
Hope you find it useful, and please reach out on twitter if you have any questions.