build.gradle.kts
219 lines · 8.8 KB
Changed in: 0.5.0
| 1 | import java.util.Properties |
| 2 | |
| 3 | plugins { |
| 4 | alias(libs.plugins.android.application) |
| 5 | alias(libs.plugins.kotlin.android) |
| 6 | alias(libs.plugins.kotlin.compose) |
| 7 | alias(libs.plugins.kotlin.serialization) |
| 8 | alias(libs.plugins.ksp) |
| 9 | alias(libs.plugins.hilt) |
| 10 | } |
| 11 | |
| 12 | // Private, per-machine values, read from earhorn-private.properties next to this |
| 13 | // file. That file is for your own builds only: never commit or publish it. |
| 14 | // Public builds: run Gradle with -Pearhorn.public, which ignores the file and |
| 15 | // leaves both values empty (those two features are then off). Keys: |
| 16 | // earhorn.musicnowStatusUrl=http://<host>:8642/status |
| 17 | // earhorn.domEthaDefaultUrl=http://<host>/mediacontrol.html |
| 18 | val earhornLocal = Properties().also { props -> |
| 19 | if (!project.hasProperty("earhorn.public")) { |
| 20 | project.file("earhorn-private.properties").takeIf { it.isFile }?.inputStream()?.use { props.load(it) } |
| 21 | } |
| 22 | } |
| 23 | fun earhornLocalString(key: String): String = |
| 24 | "\"" + (earhornLocal.getProperty(key) ?: "").trim().replace("\\", "\\\\").replace("\"", "\\\"") + "\"" |
| 25 | |
| 26 | android { |
| 27 | namespace = "dev.stave" |
| 28 | compileSdk = libs.versions.compileSdk.get().toInt() |
| 29 | installation { |
| 30 | // The kukui Chromebase (MT8183, network ADB) can take minutes to |
| 31 | // complete an install — the ddmlib default gives up long before |
| 32 | // the ChromeOS ARC container finishes. 10 minutes of headroom. |
| 33 | timeOutInMs = 600_000 |
| 34 | // -r reinstall keeping data, -d allow version downgrade while |
| 35 | // iterating between zips/builds on multiple test devices. |
| 36 | installOptions("-r", "-d") |
| 37 | } |
| 38 | defaultConfig { |
| 39 | applicationId = "dev.stave" |
| 40 | minSdk = libs.versions.minSdk.get().toInt() |
| 41 | targetSdk = libs.versions.targetSdk.get().toInt() |
| 42 | // 5 = the deployed 0.2.0 build. Never reuse a versionCode: devices |
| 43 | // refuse an update that does not increase it. |
| 44 | versionCode = 51 |
| 45 | versionName = "0.5.0" |
| 46 | vectorDrawables { useSupportLibrary = true } |
| 47 | buildConfigField("String", "MUSICNOW_STATUS_URL", earhornLocalString("earhorn.musicnowStatusUrl")) |
| 48 | buildConfigField("String", "DOM_ETHA_DEFAULT_URL", earhornLocalString("earhorn.domEthaDefaultUrl")) |
| 49 | } |
| 50 | |
| 51 | flavorDimensions += "distribution" |
| 52 | productFlavors { |
| 53 | create("play") { |
| 54 | dimension = "distribution" |
| 55 | // With Google Cast. Shipped to Play Store for devices with GPS. |
| 56 | } |
| 57 | create("libre") { |
| 58 | dimension = "distribution" |
| 59 | applicationIdSuffix = ".libre" |
| 60 | versionNameSuffix = "-libre" |
| 61 | // No Cast. For Fire TV, GrapheneOS, F-Droid. |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | buildTypes { |
| 66 | debug { |
| 67 | isMinifyEnabled = false |
| 68 | } |
| 69 | release { |
| 70 | isMinifyEnabled = true |
| 71 | // Resource shrinking is OFF — even with res/raw/keep.xml listing the |
| 72 | // launcher icons, AGP's resource shrinker mangles manifest-only icon |
| 73 | // references in release builds, replacing the launcher PNG with a |
| 74 | // surviving drawable (in our case, empty_clef → APK shows clef |
| 75 | // icon instead of the EARHORN badge). Code shrinking via R8 (minify) |
| 76 | // remains on for size; only the resource pass is disabled. |
| 77 | isShrinkResources = false |
| 78 | // Sign release builds with the debug keystore so Android Studio's |
| 79 | // run/install flow works for local testing. Without this, the |
| 80 | // release APK comes out as `app-play-release-unsigned.apk` and |
| 81 | // can't be installed — the installer fails (sometimes surfacing |
| 82 | // as INSTALL_BASELINE_PROFILE_FAILED because the install never |
| 83 | // landed and the post-install baseline-profile step then can't |
| 84 | // find the package). |
| 85 | // |
| 86 | // Production / Play Store releases SHOULD override this with a |
| 87 | // real release keystore via an environment-driven config — but |
| 88 | // until we set that up, the debug key keeps local iteration |
| 89 | // unblocked. The debug keystore is auto-generated by Android |
| 90 | // Studio at ~/.android/debug.keystore on first build. |
| 91 | signingConfig = signingConfigs.getByName("debug") |
| 92 | proguardFiles( |
| 93 | getDefaultProguardFile("proguard-android-optimize.txt"), |
| 94 | "proguard-rules.pro" |
| 95 | ) |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | compileOptions { |
| 100 | sourceCompatibility = JavaVersion.VERSION_17 |
| 101 | targetCompatibility = JavaVersion.VERSION_17 |
| 102 | } |
| 103 | |
| 104 | kotlinOptions { |
| 105 | jvmTarget = "17" |
| 106 | freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn") |
| 107 | } |
| 108 | |
| 109 | buildFeatures { |
| 110 | compose = true |
| 111 | // For the two private values above (see earhorn-private.properties). |
| 112 | buildConfig = true |
| 113 | } |
| 114 | |
| 115 | packaging { |
| 116 | resources { |
| 117 | excludes += setOf( |
| 118 | "/META-INF/{AL2.0,LGPL2.1}", |
| 119 | "/META-INF/DEPENDENCIES", |
| 120 | "/META-INF/LICENSE*", |
| 121 | "/META-INF/NOTICE*", |
| 122 | "/META-INF/*.kotlin_module" |
| 123 | ) |
| 124 | pickFirsts += setOf( |
| 125 | "/META-INF/INDEX.LIST" |
| 126 | ) |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | dependencies { |
| 132 | // AndroidX core |
| 133 | implementation(libs.androidx.core.ktx) |
| 134 | implementation(libs.androidx.appcompat) |
| 135 | implementation(libs.androidx.lifecycle.runtime.ktx) |
| 136 | implementation(libs.androidx.lifecycle.viewmodel.compose) |
| 137 | implementation(libs.androidx.activity.compose) |
| 138 | |
| 139 | // Compose BOM governs versions of all compose libs below |
| 140 | implementation(platform(libs.compose.bom)) |
| 141 | implementation(libs.compose.ui) |
| 142 | implementation(libs.compose.ui.graphics) |
| 143 | implementation(libs.compose.ui.tooling.preview) |
| 144 | debugImplementation(libs.compose.ui.tooling) |
| 145 | implementation(libs.compose.material3) |
| 146 | implementation(libs.compose.material.icons.extended) |
| 147 | |
| 148 | // Compose for TV |
| 149 | implementation(libs.androidx.tv.foundation) |
| 150 | implementation(libs.androidx.tv.material) |
| 151 | |
| 152 | // Navigation + Hilt |
| 153 | implementation(libs.androidx.navigation.compose) |
| 154 | implementation(libs.androidx.hilt.navigation.compose) |
| 155 | implementation(libs.hilt.android) |
| 156 | ksp(libs.hilt.compiler) |
| 157 | |
| 158 | // Media3 |
| 159 | implementation(libs.media3.exoplayer) |
| 160 | implementation(libs.media3.session) |
| 161 | implementation(libs.media3.ui) |
| 162 | implementation(libs.media3.datasource.okhttp) |
| 163 | |
| 164 | // Room (for saved servers + preferences that need relations later) |
| 165 | implementation(libs.room.runtime) |
| 166 | implementation(libs.room.ktx) |
| 167 | ksp(libs.room.compiler) |
| 168 | |
| 169 | // DataStore (metadata display prefs) |
| 170 | implementation(libs.datastore.preferences) |
| 171 | |
| 172 | // UPnP — jUPnP 3.0.3 (maintained fork of abandoned Cling). Published to Maven Central. |
| 173 | // jupnp.android provides AndroidUpnpServiceImpl + AndroidUpnpServiceConfiguration which wires |
| 174 | // up the Jetty-backed transport and Recovering* XML processors jUPnP needs on Android. |
| 175 | implementation(libs.jupnp.core) |
| 176 | implementation(libs.jupnp.support) |
| 177 | implementation(libs.jupnp.android) |
| 178 | |
| 179 | // Jetty 9.4 is jUPnP's required transport on Android. The javax.servlet dep bundled with |
| 180 | // jetty-servlet ships an empty package that Android's runtime already partially claims; we |
| 181 | // drop it here and let jetty pull the real javax.servlet-api through jetty-server instead. |
| 182 | implementation(libs.jetty.server) |
| 183 | implementation(libs.jetty.servlet) { |
| 184 | exclude(group = "org.eclipse.jetty.orbit", module = "javax.servlet") |
| 185 | } |
| 186 | implementation(libs.jetty.client) |
| 187 | |
| 188 | // jUPnP logs via SLF4J. jdk14 binding routes to java.util.logging → logcat. |
| 189 | implementation(libs.slf4j.api) |
| 190 | implementation(libs.slf4j.jdk14) |
| 191 | |
| 192 | // Image loading for album art |
| 193 | implementation(libs.coil.compose) |
| 194 | implementation(libs.coil.network.okhttp) |
| 195 | implementation(libs.coil.gif) |
| 196 | |
| 197 | // HTTP |
| 198 | implementation(libs.okhttp) |
| 199 | |
| 200 | // Kotlinx |
| 201 | implementation(libs.kotlinx.coroutines.android) |
| 202 | implementation(libs.kotlinx.serialization.json) |
| 203 | |
| 204 | // Android Auto (cartemplate module) |
| 205 | implementation(libs.androidx.car.app) |
| 206 | // Legacy MediaSessionCompat — required by Car App Library |
| 207 | // MediaPlaybackManager.registerMediaPlaybackToken(). Media3 |
| 208 | // MediaLibrarySession.sessionCompatToken returns this type, but |
| 209 | // the class itself lives in this support library and isn't |
| 210 | // pulled in transitively. |
| 211 | implementation(libs.androidx.media) |
| 212 | |
| 213 | // Cast — only for the play flavor |
| 214 | "playImplementation"(libs.play.services.cast.framework) |
| 215 | // MediaRouter — AppCompat-based chooser dialog used by the Cast sender UI. |
| 216 | // Cast SDK normally pulls this transitively but pinning explicitly avoids |
| 217 | // version drift and makes the dependency obvious. |
| 218 | "playImplementation"(libs.androidx.mediarouter) |
| 219 | } |


