build.gradle

11.6 KB · raw

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}