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() } } // Steam Frame build: the menu background tile with the Frame's button mapping on it. The release // tile (800_1_a_loading.tga, Team Beef's art, git-ignored) has the Quest's help baked in, so // tools/steam_frame/make_frame_tile.py makes a Frame one into build/frame-assets, and the frame build // type's assets override the release one with it. Nothing generated is committed, and Quest and Pico // builds don't see this. If the release tile or Python with Pillow and numpy is missing, the Frame // APK still builds with the release tile, and this says so. // Python is -PframePython, $FRAME_PYTHON, tools/steam_frame/.venv, or python3, in that order. def frameAssetsDir = file('build/frame-assets') def makeFrameTile = tasks.register('makeFrameTile') { doLast { delete frameAssetsDir // never ship a stale tile after the source went away def venvPython = file('../../tools/steam_frame/.venv/bin/python') def python = project.findProperty('framePython') ?: System.getenv('FRAME_PYTHON') ?: (venvPython.exists() ? venvPython.absolutePath : 'python3') def status = -1 try { status = exec { commandLine python, file('../../tools/steam_frame/make_frame_tile.py').absolutePath, '--src', file('../../assets/background/800_1_a_loading.tga').absolutePath, '--out', new File(frameAssetsDir, 'background/800_1_a_loading.tga').absolutePath ignoreExitValue = true }.exitValue } catch (Exception e) { logger.warn("Could not run ${python}: ${e.message}") } if (status != 0) { logger.warn('WARNING: no Frame menu tile, the Frame APK gets the release one (with the Quest button help). ' + 'See "Build" in docs/STEAM_FRAME_VR.md.') } } } 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 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", "APP_PLATFORM=android-30" } } } } sourceSets.frame { // generated by makeFrameTile, wins over the release tile in main assets.srcDir frameAssetsDir } 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 in ['preBuild', 'preDebugBuild', 'preReleaseBuild', 'preFrameBuild']) { task.dependsOn fetchKhronosLoader } if (task.name == 'mergeFrameAssets') { task.dependsOn makeFrameTile } } 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") {} }