Krolyxon
Commit c2b9d4b

Branches

Refs →

budctlandroidappbuild.gradle.kts

118 lines · 4.6 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 // The release key stays with the owner: its file and password come from the environment (a CI
30 // secret, or ~/.local/share/budctl-release on the owner's machine), never from the repo.
31 val keystore = System.getenv("BUDCTL_KEYSTORE")
32 signingConfigs {
33 if (keystore != null) {
34 create("release") {
35 storeFile = file(keystore)
36 storePassword = System.getenv("BUDCTL_KEYSTORE_PASSWORD")
37 keyAlias = "budctl"
38 keyPassword = System.getenv("BUDCTL_KEYSTORE_PASSWORD")
39 }
40 }
41 }
42
43 buildTypes {
44 // Only where the Rust library is built for: the phone, and the emulator in debug builds.
45 debug {
46 ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
47 }
48 release {
49 ndk { abiFilters += listOf("arm64-v8a") }
50 // The Rust library calls Platform's methods by name, so nothing may rename them.
51 isMinifyEnabled = false
52 // Without the key, the release build comes out unsigned.
53 if (keystore != null) signingConfig = signingConfigs.getByName("release")
54 }
55 }
56
57 buildFeatures {
58 compose = true
59 }
60}
61
62dependencies {
63 implementation(platform("androidx.compose:compose-bom:2026.09.00"))
64 implementation("androidx.activity:activity-compose:1.13.0")
65 implementation("androidx.compose.foundation:foundation")
66 implementation("androidx.compose.ui:ui")
67 testImplementation("junit:junit:4.13.2")
68}
69
70/** The Rust library (crates/budctl-android), built by cargo-ndk into [output]/<abi>/. */
71abstract class CargoNdk : Exec() {
72 @get:OutputDirectory
73 abstract val output: DirectoryProperty
74}
75
76/** Rule 1 (CLAUDE.md): the app never gets network access, not even from a library's manifest. */
77abstract class CheckNoInternet : DefaultTask() {
78 @get:InputFile
79 abstract val manifest: RegularFileProperty
80
81 @TaskAction
82 fun check() {
83 if ("android.permission.INTERNET" in manifest.get().asFile.readText()) {
84 throw GradleException("The merged manifest asks for INTERNET; budctl must not (CLAUDE.md rule 1).")
85 }
86 }
87}
88
89androidComponents {
90 onVariants { variant ->
91 val name = variant.name.replaceFirstChar { it.uppercase() }
92 val release = variant.buildType == "release"
93 // Debug builds also run on the emulator (x86_64); releases only on the phone.
94 val abis = if (release) listOf("arm64-v8a") else listOf("arm64-v8a", "x86_64")
95 val ndk = sdkComponents.ndkDirectory
96
97 val cargo = tasks.register<CargoNdk>("cargoNdk$name") {
98 workingDir = workspace
99 executable = "cargo"
100 // Cargo knows what to rebuild, so it always runs.
101 outputs.upToDateWhen { false }
102 argumentProviders.add {
103 listOf("ndk", "--platform", "31") + abis.flatMap { listOf("--target", it) } +
104 listOf("--output-dir", output.get().asFile.path, "build", "-p", "budctl-android") +
105 if (release) listOf("--release") else emptyList()
106 }
107 doFirst { environment("ANDROID_NDK_HOME", ndk.get().asFile.path) }
108 }
109 variant.sources.jniLibs?.addGeneratedSourceDirectory(cargo, CargoNdk::output)
110 // The desktop app's fonts and their license (OFL 1.1), shared rather than copied.
111 variant.sources.assets?.addStaticSourceDirectory(workspace.resolve("crates/budctl-gui/fonts").path)
112
113 val check = tasks.register<CheckNoInternet>("check${name}NoInternet") {
114 manifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST))
115 }
116 tasks.matching { it.name == "package$name" }.configureEach { dependsOn(check) }
117 }
118}