Files
astromaddie--lambda1-vr/Projects/Android/build.gradle
T
Madison b3058f76f6 Add a Steam Frame build type with the Khronos OpenXR loader
The Frame runs Android apps in a container (Lepton) that has no OpenXR
loader broker, so the APK needs its own loader. This adds a "frame" build
type: ./gradlew assembleFrame.

It's release plus:
- applicationId .frame, minSdk 30 (the container is Android 11)
- a manifest overlay that drops the headtracking requirement and the
  Quest/Pico metadata, since the install fails with it
- the Khronos loader 1.1.63, fetched from Maven Central at build time,
  checked against a SHA-256 and unpacked into build/ (not committed)
- L1VR_STEAM_FRAME for the native code, BuildConfig.STEAM_FRAME for Java

Quest builds don't see any of this. I also added the gradle wrapper script
to Projects/Android, since the wrapper jar and properties were already
there, and -Wall for the VR sources.

Haven't run it on the headset yet.
2026-10-04 18:36:18 +09:00

226 lines
7.4 KiB
Groovy

buildscript {
repositories {
google() // For Gradle 4.0+
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.1'
}
}
apply plugin: 'com.android.application'
// Steam Frame build (./gradlew assembleFrame, see docs/STEAM_FRAME_VR.md).
// The Frame's Android container has no OpenXR loader broker, so the Khronos
// loader goes into the APK. It comes from Maven Central, gets checked against
// this hash and is unpacked into build/ (never committed). Quest builds don't
// touch any of this.
def khronosLoaderVersion = '1.1.63'
def khronosLoaderSha256 = '622419d2f6741c3443a3beb4779af0764318edd01830de967f24c741ebcded73'
def khronosLoaderDir = file("build/openxr-loader/${khronosLoaderVersion}")
def khronosLoaderSo = new File(khronosLoaderDir, 'libopenxr_loader.so')
def fetchKhronosLoader = tasks.register('fetchKhronosLoader') {
outputs.file khronosLoaderSo
doLast {
def url = "https://repo1.maven.org/maven2/org/khronos/openxr/openxr_loader_for_android/${khronosLoaderVersion}/openxr_loader_for_android-${khronosLoaderVersion}.aar"
def aar = new File(khronosLoaderDir, 'openxr_loader_for_android.aar')
khronosLoaderDir.mkdirs()
new URL(url).withInputStream { input -> aar.withOutputStream { it << input } }
def digest = java.security.MessageDigest.getInstance('SHA-256')
aar.eachByte(8192) { buf, len -> digest.update(buf, 0, len) }
def sha = digest.digest().collect { String.format('%02x', it) }.join()
if (sha != khronosLoaderSha256) {
aar.delete()
throw new GradleException("OpenXR loader AAR has the wrong SHA-256 (${sha}), expected ${khronosLoaderSha256}")
}
def zip = new java.util.zip.ZipFile(aar)
try {
def entry = zip.getEntry('jni/arm64-v8a/libopenxr_loader.so')
if (entry == null) throw new GradleException('libopenxr_loader.so (arm64-v8a) is not in the loader AAR')
zip.getInputStream(entry).withStream { input -> khronosLoaderSo.withOutputStream { it << input } }
} finally {
zip.close()
}
aar.delete()
}
}
android {
namespace = "com.drbeef.lambda1vr"
// This is the name of the generated apk file, which will have
// -debug.apk or -release.apk appended to it.
// The filename doesn't effect the Android installation process.
// Use only letters to remain compatible with the package name.
project.archivesBaseName = "lambda1vr"
defaultConfig {
// Gradle replaces the manifest package with this value, which must
// be unique on a system. If you don't change it, a new app
// will replace an older one.
applicationId "com.drbeef." + project.archivesBaseName
externalNativeBuild {
ndk {
abiFilters 'arm64-v8a'
}
ndkBuild {
def numProcs = Runtime.runtime.availableProcessors()
arguments "V=0", "-j$numProcs", "-C$project.buildDir.parent", "APP_PLATFORM=android-32", "NDK_TOOLCHAIN_VERSION=clang", "APP_STL=c++_static"
abiFilters 'arm64-v8a'
}
}
minSdkVersion 32
targetSdkVersion 32
// True only in the frame build type
buildConfigField 'boolean', 'STEAM_FRAME', 'false'
}
buildFeatures {
buildConfig true
}
externalNativeBuild {
ndkBuild {
path file('jni/Android.mk')
}
}
signingConfigs {
def keystorePath = (project.hasProperty('key.store')) ?
new File(project.getProperty('key.store')) :
project.file('android.debug.keystore')
def keystorePassword = (project.hasProperty('key.store.password')) ?
project.getProperty('key.store.password') : 'android'
def keystoreKeyAlias = (project.hasProperty('key.alias')) ?
project.getProperty('key.alias') : 'androiddebugkey'
def keystoreKeyPassword = (project.hasProperty('key.alias.password')) ?
project.getProperty('key.alias.password') : 'android'
debug {
storeFile keystorePath
storePassword keystorePassword
keyAlias keystoreKeyAlias
keyPassword keystoreKeyPassword
v2SigningEnabled true
}
release {
storeFile keystorePath
storePassword keystorePassword
keyAlias keystoreKeyAlias
keyPassword keystoreKeyPassword
v2SigningEnabled true
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
debuggable true
jniDebuggable true
externalNativeBuild {
ndkBuild {
arguments "NDK_DEBUG=1","USE_ASAN=1"
}
}
}
release {
signingConfig signingConfigs.release
debuggable false
jniDebuggable false
externalNativeBuild {
ndkBuild {
arguments "NDK_DEBUG=0","USE_ASAN=0"
}
}
}
// Valve Steam Frame (Lepton, Android 11). Same as release, plus the
// Khronos OpenXR loader, a manifest overlay (src/frame) and L1VR_STEAM_FRAME
// for the native code. Signed with the debug key, it's only sideloaded.
frame {
initWith release
matchingFallbacks = ['release']
applicationIdSuffix ".frame"
versionNameSuffix "-frame"
signingConfig signingConfigs.debug
buildConfigField 'boolean', 'STEAM_FRAME', 'true'
externalNativeBuild {
ndkBuild {
arguments "L1VR_STEAM_FRAME=1", "L1VR_OPENXR_LOADER=${khronosLoaderSo.absolutePath}", "APP_PLATFORM=android-30"
}
}
}
}
sourceSets.main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['../../java']
jniLibs.srcDir 'libs'
res.srcDirs = ['../../res']
assets.srcDirs = ['../../assets']
jni.srcDirs = ['jni']
}
lintOptions {
checkReleaseBuilds false
disable 'ExpiredTargetSdkVersion'
}
//packagingOptions {
// exclude 'lib/arm64-v8a/libopenxr_loader.so'
//}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
compileSdkVersion = 32
buildToolsVersion = '29.0.3'
ndkVersion '25.1.8937393'
}
// Lepton runs Android 11, the Quest build needs 12L (API 32)
androidComponents {
beforeVariants(selector().withBuildType('frame')) { variantBuilder ->
variantBuilder.minSdk = 30
}
}
// The loader has to be there before ndk-build links against it
tasks.configureEach { task ->
if (task.name == 'preFrameBuild') {
task.dependsOn fetchKhronosLoader
}
}
dependencies {
implementation "com.android.support:support-compat:28.0.0"
implementation "com.android.support:support-core-utils:28.0.0"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
repositories {
google()
}
// Workaround to fix issue in Android Studio Chipmunk 2021.2.1 and later
// where opening a project would result in a 'prepareKotlinBuildScriptModel'
// not found error
if (!tasks.findByName("prepareKotlinBuildScriptModel")) {
tasks.register("prepareKotlinBuildScriptModel") {}
}