Skip to main content

Building a Quest 2 APK from Scratch

Table of Contents
Note: A previous version of this article needlessly introduced 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 and cmake

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 SDK
  • adb the Android Debug Bridge, used to install the apk
  • clang 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 signing
  • zipalign optimises the archive so it can be mmaped, 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 signed temp.apk to output our final questxrexample.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 and jarsigner
  • 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.

cshenton/quest_xr

Minimal example building Quest 2 OpenXR application with no build system, IDE, or scripting langs.

C++
0
0

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.