Krolyxon
Commit 744d2be

Branches

Refs →

budctlandroidappbuild.gradle.kts

100 lines · 3.7 KB · Kotlin

1import com.android.build.api.artifact.SingleArtifact
2
3plugins {
4 id("com.android.application")
5 id("org.jetbrains.kotlin.plugin.compose")
6}
7
8// One version for everything (CLAUDE.md, releases): the cargo workspace's.
9val workspace: File = rootDir.parentFile
10val version: String = workspace.resolve("Cargo.toml").readLines()
11 .first { it.startsWith("version = ") }
12 .substringAfter('"').substringBefore('"')
13
14android {
15 namespace = "io.github.krolyxon.budctl"
16 compileSdk = 37
17 // For stripping the Rust library's debug symbols from the APK, and for cargo-ndk.
18 ndkVersion = "30.0.16248370"
19
20 defaultConfig {
21 applicationId = "io.github.krolyxon.budctl"
22 // Android 12: BLUETOOTH_CONNECT begins there (docs/android.md#bluetooth).
23 minSdk = 31
24 targetSdk = 37
25 versionName = version
26 versionCode = version.split('.').fold(0) { code, part -> code * 100 + part.toInt() }
27 }
28
29 buildTypes {
30 // Only where the Rust library is built for: the phone, and the emulator in debug builds.
31 debug {
32 ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
33 }
34 release {
35 ndk { abiFilters += listOf("arm64-v8a") }
36 // The Rust library calls Platform's methods by name, so nothing may rename them.
37 isMinifyEnabled = false
38 }
39 }
40
41 buildFeatures {
42 compose = true
43 }
44}
45
46dependencies {
47 implementation(platform("androidx.compose:compose-bom:2026.09.00"))
48 implementation("androidx.activity:activity-compose:1.13.0")
49 implementation("androidx.compose.foundation:foundation")
50 implementation("androidx.compose.ui:ui")
51 testImplementation("junit:junit:4.13.2")
52}
53
54/** The Rust library (crates/budctl-android), built by cargo-ndk into [output]/<abi>/. */
55abstract class CargoNdk : Exec() {
56 @get:OutputDirectory
57 abstract val output: DirectoryProperty
58}
59
60/** Rule 1 (CLAUDE.md): the app never gets network access, not even from a library's manifest. */
61abstract class CheckNoInternet : DefaultTask() {
62 @get:InputFile
63 abstract val manifest: RegularFileProperty
64
65 @TaskAction
66 fun check() {
67 if ("android.permission.INTERNET" in manifest.get().asFile.readText()) {
68 throw GradleException("The merged manifest asks for INTERNET; budctl must not (CLAUDE.md rule 1).")
69 }
70 }
71}
72
73androidComponents {
74 onVariants { variant ->
75 val name = variant.name.replaceFirstChar { it.uppercase() }
76 val release = variant.buildType == "release"
77 // Debug builds also run on the emulator (x86_64); releases only on the phone.
78 val abis = if (release) listOf("arm64-v8a") else listOf("arm64-v8a", "x86_64")
79 val ndk = sdkComponents.ndkDirectory
80
81 val cargo = tasks.register<CargoNdk>("cargoNdk$name") {
82 workingDir = workspace
83 executable = "cargo"
84 // Cargo knows what to rebuild, so it always runs.
85 outputs.upToDateWhen { false }
86 argumentProviders.add {
87 listOf("ndk", "--platform", "31") + abis.flatMap { listOf("--target", it) } +
88 listOf("--output-dir", output.get().asFile.path, "build", "-p", "budctl-android") +
89 if (release) listOf("--release") else emptyList()
90 }
91 doFirst { environment("ANDROID_NDK_HOME", ndk.get().asFile.path) }
92 }
93 variant.sources.jniLibs?.addGeneratedSourceDirectory(cargo, CargoNdk::output)
94
95 val check = tasks.register<CheckNoInternet>("check${name}NoInternet") {
96 manifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST))
97 }
98 tasks.matching { it.name == "package$name" }.configureEach { dependsOn(check) }
99 }
100}