1plugins {2 id "com.android.application"3 id "org.jetbrains.kotlin.android"4 id "org.jetbrains.kotlin.plugin.compose"5 id "org.jetbrains.kotlin.plugin.parcelize"6 id "org.jetbrains.kotlin.plugin.serialization"7 id "com.google.devtools.ksp"8 id "com.google.dagger.hilt.android"9}1011dependencyLocking {12 lockAllConfigurations()13}1415android {16 namespace = "com.peachpasswords"17 compileSdk = 351819 // VERSION is the single semantic-version authority. versionCode remains20 // Android/Gradle metadata because Kepr's manifest does not carry it; the21 // updater enforces exact VERSION plus a strictly newer code.22 def versionAuthorityFile = rootProject.file("VERSION")23 if (!versionAuthorityFile.isFile()) {24 throw new GradleException("VERSION is missing; it is the semantic-version authority")25 }26 def semanticVersion = versionAuthorityFile.getText("UTF-8").trim()27 if (!(semanticVersion ==~ /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/)) {28 throw new GradleException("VERSION must contain exactly one strict major.minor.patch value")29 }30 def versionParts = semanticVersion.split("\\.")31 def vMajor = versionParts[0].toInteger()32 def vMinor = versionParts[1].toInteger()33 def vPatch = versionParts[2].toInteger()34 if (vMajor > 99 || vMinor > 99 || vPatch > 999) {35 throw new GradleException("VERSION components exceed versionCode encoding range (major/minor <= 99, patch <= 999)")36 }37 // Derive a strictly-monotonic versionCode from semver so it can never38 // accidentally stay flat across releases (which breaks the updater's39 // versionCode > currentVersionCode identity gate).40 def derivedVersionCode = vMajor * 1000000 + vMinor * 10000 + vPatch * 104142 defaultConfig {43 applicationId = "com.peachpasswords"44 minSdk = 2645 targetSdk = 3546 versionCode = derivedVersionCode47 versionName = semanticVersion4849 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"50 vectorDrawables {51 useSupportLibrary = true52 }53 }5455 splits {56 abi {57 enable = true58 reset()59 include "arm64-v8a", "x86_64"60 universalApk = false61 }62 }6364 signingConfigs {65 release {66 // PB-26-001: signing passwords are injected via environment67 // variables (sourced from ~/.secrets), never stored in68 // repository-adjacent files. The keystore path and alias are69 // non-secret and remain in local.properties.70 def props = new Properties()71 def localPropertiesFile = rootProject.file("local.properties")72 if (localPropertiesFile.isFile()) {73 localPropertiesFile.withInputStream { props.load(it) }74 }75 def storeFilePath = props.getProperty("peachStoreFile")76 def alias = props.getProperty("peachKeyAlias")77 def storePass = System.getenv("PEACH_STORE_PASSWORD")78 def keyPass = System.getenv("PEACH_KEY_PASSWORD")79 if (gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") }) {80 def missing = []81 if (!storeFilePath) missing << "peachStoreFile"82 if (!storePass) missing << "PEACH_STORE_PASSWORD"83 if (!alias) missing << "peachKeyAlias"84 if (!keyPass) missing << "PEACH_KEY_PASSWORD"85 if (!missing.empty) {86 throw new GradleException(87 "Missing release signing inputs: ${missing.join(", ")}. " +88 "Passwords come from PEACH_STORE_PASSWORD / PEACH_KEY_PASSWORD env vars.",89 )90 }91 }92 if (storeFilePath != null) {93 storeFile file(storeFilePath)94 storePassword = storePass95 keyAlias = alias96 keyPassword = keyPass97 }98 enableV2Signing = true99 enableV3Signing = true100 }101 }102103 buildTypes {104 debug {105 applicationIdSuffix ".debug"106 versionNameSuffix "-debug"107 debuggable = true108 }109 release {110 minifyEnabled = true111 shrinkResources = true112 proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"113 signingConfig = signingConfigs.release114 }115 }116117 // Kepr Beta APKs use the established certificate so installed users can update.118119 compileOptions {120 sourceCompatibility JavaVersion.VERSION_17121 targetCompatibility JavaVersion.VERSION_17122 }123124 kotlinOptions {125 jvmTarget = "17"126 }127128 buildFeatures {129 compose = true130 buildConfig = true131 }132133 composeOptions {134 kotlinCompilerExtensionVersion = "1.5.15"135 }136137 packaging {138 resources {139 excludes += [140 "/META-INF/{AL2.0,LGPL2.1}",141 "META-INF/DEPENDENCIES",142 "META-INF/LICENSE*",143 "META-INF/NOTICE*",144 ]145 }146 }147148 testOptions {149 unitTests.returnDefaultValues = true150 unitTests.all {151 def harnessNonce = System.getenv("PEACH_SYNC_HARNESS_NONCE")152 if (harnessNonce != null) inputs.property("peachSyncHarnessNonce", harnessNonce)153 def coreDir = rootProject.file("../peach-browser/crypto-core")154 def nativeLibrary = new File(155 coreDir,156 "target/release/libpeach_noise_core.dylib",157 )158 if (coreDir.exists()) {159 dependsOn rootProject.tasks.named(name.contains("Test") ? "buildRustNoiseHostTest" : "buildRustNoiseHost")160 systemProperty "java.library.path", nativeLibrary.parentFile.absolutePath161 }162 }163 }164}165166dependencies {167 implementation platform("androidx.compose:compose-bom:2024.06.00")168 androidTestImplementation platform("androidx.compose:compose-bom:2024.06.00")169170 implementation "androidx.activity:activity-compose:1.9.2"171 implementation "androidx.autofill:autofill:1.1.0"172 implementation "androidx.biometric:biometric:1.1.0"173 implementation "androidx.core:core-splashscreen:1.0.1"174 implementation "androidx.appcompat:appcompat:1.7.0"175 implementation "androidx.credentials:credentials:1.5.0"176 implementation "androidx.credentials:credentials-play-services-auth:1.5.0"177 implementation "androidx.glance:glance-appwidget:1.1.0"178 implementation "androidx.glance:glance-material3:1.1.0"179 implementation "androidx.hilt:hilt-navigation-compose:1.2.0"180 // In-app updates: a periodic release check that survives Doze without a181 // service. hilt-work lets the worker take UpdateRepository by injection182 // rather than reaching for a global.183 implementation "androidx.hilt:hilt-work:1.2.0"184 ksp "androidx.hilt:hilt-compiler:1.2.0"185 implementation "androidx.work:work-runtime-ktx:2.9.1"186 implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.4"187 implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4"188 implementation "androidx.lifecycle:lifecycle-process:2.8.4"189 implementation "androidx.navigation:navigation-compose:2.8.0"190 // Deliberately pinned to 1.1.0-alpha06 (audit M-12). 1.1.0 stable exists191 // now and GradleDependency lint flags this coordinate; a bump is a policy192 // choice (Tink runtime vs. baseline AES, master-key rotation semantics),193 // not an oversight, and is tracked separately. Exact alpha (never a floating194 // range) so the resolved artifact is reproducible. Used only for the195 // duress/license EncryptedSharedPreferences; the vault slots are sealed by196 // VaultCrypto, not this.197 implementation "androidx.security:security-crypto:1.1.0-alpha06"198 implementation "androidx.compose.material3:material3"199 implementation "androidx.compose.ui:ui"200 implementation "androidx.compose.ui:ui-tooling-preview"201 implementation "androidx.compose.foundation:foundation"202 implementation "androidx.compose.material:material-icons-extended"203 implementation "androidx.core:core-ktx:1.13.1"204 implementation "com.google.android.material:material:1.12.0"205206 implementation "com.google.dagger:hilt-android:2.51.1"207 ksp "com.google.dagger:hilt-compiler:2.51.1"208 implementation "com.google.zxing:core:3.5.3"209 implementation "androidx.camera:camera-core:1.3.4"210 implementation "androidx.camera:camera-camera2:1.3.4"211 implementation "androidx.camera:camera-lifecycle:1.3.4"212 implementation "androidx.camera:camera-view:1.3.4"213 implementation "com.squareup.okhttp3:okhttp:4.12.0"214 implementation "org.bouncycastle:bcprov-jdk18on:1.84"215 // zstd-jni (reference libzstd) — per-section compression for the PeachScript216 // PCHS binary format (MF-2). Required for cross-platform interop with the217 // browser's zstd sections. The @aar variant ships the Android jniLibs218 // (lib/<abi>/libzstd-jni.so); the plain jar bundles only desktop natives and219 // would fail with UnsatisfiedLinkError on-device.220 implementation "com.github.luben:zstd-jni:1.5.6-3@aar"221 // The @aar ships only Android .so; the plain jar carries the desktop natives222 // the host JVM unit tests need.223 testImplementation "com.github.luben:zstd-jni:1.5.6-3"224 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"225 implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1"226227 debugImplementation "androidx.compose.ui:ui-tooling"228 debugImplementation "androidx.compose.ui:ui-test-manifest"229230 androidTestImplementation "androidx.test.ext:junit:1.2.1"231 androidTestImplementation "androidx.test:core:1.6.1"232 androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1"233 androidTestImplementation "androidx.test.uiautomator:uiautomator:2.3.0"234 androidTestImplementation "androidx.compose.ui:ui-test-junit4"235236 testImplementation "junit:junit:4.13.2"237 // Real org.json on the unit-test classpath; android.jar's is a throwing stub,238 // so the release-manifest parser would otherwise be untestable off-device.239 testImplementation "org.json:json:20240303"240 testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1"241}242// PB-26-144: the alpha-train Play-Services identity-credentials artifact is243// reviewed by decision rather than removed, pending non-Play device evidence.244// A silent bump must fail with the review requirement named. The check is245// deferred to release-task execution: resolving the release runtime classpath246// at configuration time fails the variant model and would slow every build.247def pinnedCredentialsIdentityVersion = "16.0.0-alpha02"248249tasks.matching { it.name in ["assembleRelease", "bundleRelease", "packageRelease"] }.configureEach {250 doFirst {251 def resolved = configurations.getByName("releaseRuntimeClasspath")252 .resolvedConfiguration.resolvedArtifacts.find {253 it.moduleVersion.id.group == "com.google.android.gms" &&254 it.moduleVersion.id.name == "play-services-identity-credentials"255 }256 if (resolved == null || resolved.moduleVersion.id.version != pinnedCredentialsIdentityVersion) {257 def resolvedVersion = resolved?.moduleVersion?.id?.version ?: "missing"258 throw new GradleException(259 "play-services-identity-credentials must remain pinned to ${pinnedCredentialsIdentityVersion}; " +260 "resolved ${resolvedVersion}. Review the alpha artifact's manifest components and GMS " +261 "metadata, and update docs/ANDROID-RELEASE-MANIFEST-INVENTORY.md, before bumping it.",262 )263 }264 }265}