build(android): make release buildable and instrumented testing possible
Four gaps that made "ship it" and "test it on hardware" impossible. RELEASE SIGNING. assembleRelease was silently producing app-release-unsigned.apk because there was no signingConfigs block at all — the failure mode where you only notice at install time. Credentials now resolve from keystore.properties, then local.properties, then WEBTERM_RELEASE_* env; nothing is created or committed, and keystore.properties.example is the template. The degradation is ENFORCED, not documented. The signingConfig is only created when credentials actually resolve — an empty config is precisely what makes AGP emit an unsigned artifact quietly — and a guard on packageRelease throws with an actionable message, distinguishing "not configured" from "credentials found but the keystore file is missing: <path>". It hooks the PACKAGE task on purpose: R8 has already run and reported by then, so a developer without a keystore still gets the full shrink result rather than an early abort. Verified: minifyReleaseWithR8 completes, packageRelease fails loudly, and no unsigned APK is written. VERSIONING. Was still the scaffold 1 / "0.1.0". Now 1 / "0.1.0-alpha01", where the suffix is a factual claim about device verification rather than decoration: alpha means device QA is essentially unstarted, beta means the A34/A35 checklist blocks pass on hardware, and plain 0.1.0 means the whole checklist is ticked. versionCode stays 1 because no artifact has ever left the build machine. MINIFICATION. release had isMinifyEnabled = false and an empty rules file — so nothing had ever exercised R8 on this app, and the first release build would have been the experiment. Now minify + resource shrinking, with a deliberately short rules file: the actual artifacts were checked, and kotlinx.serialization, Tink, Hilt, FCM, OkHttp, Compose and CameraX all ship their own consumer rules, so none are re-declared. The termux packages are kept whole because the JitPack artifacts ship no rules and :terminal-view binds to non-API internals — it writes the public TerminalView.mEmulator field and calls TerminalRenderer.getFontWidth()/getFontLineSpacing(). Getting that wrong yields a build that crashes only in production. lintVitalRelease also passes now, the two pre-existing lint ERRORS having been fixed earlier this session. ANDROID TEST ENABLEMENT. :app had no androidTest source set and no instrumentation runner, which is the mechanical reason A34/A35 have no code at all. The runner (Hilt-aware) and dependencies are wired, and a :macrobenchmark module exists. The tests themselves are a separate change. Verified: ./gradlew test :app:assembleDebug :app:assembleDebugAndroidTest :app:assembleBenchmark :macrobenchmark:assembleBenchmark :app:lintVitalRelease koverVerify -> BUILD SUCCESSFUL; 901 JVM tests, 0 failures on a forced re-execution with test-results deleted and --no-build-cache.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
## Version catalog — single source of truth for plugin & dependency versions.
|
||||
## Pure JVM modules only (this env has NO Android SDK). See android/README.md.
|
||||
## Covers BOTH the pure Kotlin/JVM modules and the Android-framework modules (the
|
||||
## SDK is installed on this machine — see android/README.md "Android SDK setup").
|
||||
## Kotlin pinned to 2.3.21 to match the Kotlin embedded in Gradle 9.6.1 (best
|
||||
## compatibility with the Kotlin Gradle Plugin on Gradle 9.6).
|
||||
|
||||
@@ -52,10 +53,18 @@ datastore = "1.1.1"
|
||||
# listenablefuture conflict dep is unnecessary here).
|
||||
termux = "v0.118.0"
|
||||
# androidx-test — lets androidTest (instrumented) sources COMPILE here; they only
|
||||
# RUN on a device (no emulator installed → deferred to device QA, plan §7).
|
||||
# RUN on a device/emulator (plan §7 — the A34 E2E suite needs a real `npm start` host).
|
||||
androidxTestExtJunit = "1.2.1"
|
||||
androidxTestCore = "1.6.1"
|
||||
androidxTestRunner = "1.6.2"
|
||||
androidxTestRules = "1.6.1"
|
||||
# Espresso + UiAutomator drive the A35 happy path (in-process Compose assertions and
|
||||
# out-of-process device interaction respectively). Pinned to the androidx-test 1.6 line.
|
||||
espresso = "3.6.1"
|
||||
uiautomator = "2.3.0"
|
||||
# Macrobenchmark (A35) — startup/frame timing measured from a SEPARATE `com.android.test`
|
||||
# module against a non-debuggable, minified `benchmark` variant of :app.
|
||||
benchmark = "1.3.4"
|
||||
# QR pairing (A19): CameraX preview/analysis + on-device ML Kit barcode scanning.
|
||||
camerax = "1.4.1"
|
||||
mlkitBarcode = "17.3.0"
|
||||
@@ -127,6 +136,20 @@ termux-terminal-emulator = { module = "com.github.termux.termux-app:terminal-emu
|
||||
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
|
||||
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
|
||||
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
|
||||
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidxTestRules" }
|
||||
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
|
||||
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" }
|
||||
# Compose UI test — BOM-managed (do NOT pin). `ui-test-manifest` is a debugImplementation:
|
||||
# it supplies the empty activity `createComposeRule()` launches into.
|
||||
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||
# Hilt instrumented testing — supplies `HiltAndroidRule` + the generated
|
||||
# `dagger.hilt.android.testing.HiltTestApplication` that :app's custom runner installs.
|
||||
# REQUIRES kspAndroidTest(hilt-compiler); without the processor HiltTestApplication is
|
||||
# never generated and the runner will not compile.
|
||||
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
|
||||
# Macrobenchmark (A35) — used by the :macrobenchmark `com.android.test` module only.
|
||||
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmark" }
|
||||
|
||||
# QR pairing camera (A19)
|
||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" }
|
||||
@@ -148,6 +171,17 @@ unit-test = ["junit-jupiter", "kotlinx-coroutines-test", "turbine", "mockk"]
|
||||
# CameraX (A19 QR pairing) — preview + analysis + PreviewView.
|
||||
camerax = ["androidx-camera-core", "androidx-camera-camera2", "androidx-camera-lifecycle", "androidx-camera-view"]
|
||||
|
||||
# Instrumented (androidTest) foundation — wired into :app's androidTestImplementation.
|
||||
# JUnit4 arrives transitively via androidx.test.ext:junit (AndroidJUnitRunner is JUnit4-only;
|
||||
# the JVM unit tests stay on the JUnit5 platform, which instrumented tasks never touch).
|
||||
android-instrumented-test = [
|
||||
"androidx-test-ext-junit",
|
||||
"androidx-test-core",
|
||||
"androidx-test-runner",
|
||||
"androidx-test-rules",
|
||||
"androidx-test-espresso-core",
|
||||
]
|
||||
|
||||
# Compose UI + Material 3 (+ Adaptive) — wired into :app implementation. All
|
||||
# BOM-managed; add ui-tooling separately as a debugImplementation.
|
||||
compose = [
|
||||
@@ -167,6 +201,12 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
# NOTE: there is deliberately NO `android-test` alias for :macrobenchmark's
|
||||
# `com.android.test` plugin. The two aliases above put AGP on the build classpath with
|
||||
# an "unknown version", so a THIRD versioned alias for the same artifact is rejected
|
||||
# ("plugin is already on the classpath with an unknown version"). :macrobenchmark
|
||||
# therefore applies `id("com.android.test")` bare and inherits the `agp` version above —
|
||||
# exactly the pattern :terminal-view uses for `id("com.android.library")`.
|
||||
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
|
||||
# NOTE: AGP 9+ has BUILT-IN Kotlin support — Android modules apply only the
|
||||
# android plugin, NOT a separate org.jetbrains.kotlin.android (it errors out).
|
||||
|
||||
Reference in New Issue
Block a user