chore(android): streamline local dev loop
This commit is contained in:
+32
-7
@@ -17,17 +17,41 @@ That's it — no extra setup or credentials required for a debug build.
|
||||
Helper scripts for common development tasks:
|
||||
|
||||
```bash
|
||||
scripts/dev.bat build # Build debug APK
|
||||
scripts/dev.bat build # Build the sideload debug APK
|
||||
scripts/dev.bat compile # Compile sideload Kotlin only
|
||||
scripts/dev.bat test-one "com.hermesandroid.relay.SomeTest" # Run one test class
|
||||
scripts/dev.bat install-fast # Build arm64 only + install + launch
|
||||
scripts/dev.bat release # Build signed release APK
|
||||
scripts/dev.bat bundle # Build release AAB for Google Play
|
||||
scripts/dev.bat run # Build + install + launch + logcat
|
||||
scripts/dev.bat test # Run unit tests
|
||||
scripts/dev.bat run # Build sideload + install + launch + logcat
|
||||
scripts/dev.bat test # Run sideload debug unit tests
|
||||
scripts/dev.bat version # Show current version
|
||||
scripts/dev.bat relay # Start relay server (dev, no TLS)
|
||||
```
|
||||
|
||||
Linux/macOS equivalent lives at `scripts/dev.sh`.
|
||||
|
||||
### Fast Android iteration
|
||||
|
||||
Gradle's daemon, local build cache, configuration cache, and parallel task
|
||||
execution are enabled for repeat local builds. Keep the same Gradle JVM
|
||||
configuration between invocations and do not add `--no-daemon` to normal dev
|
||||
commands; a different heap or Java home starts a separate daemon and discards
|
||||
the warm-process benefit.
|
||||
|
||||
Use the narrowest command that proves the change:
|
||||
|
||||
1. `scripts/dev.bat compile` for a Kotlin compile check.
|
||||
2. `scripts/dev.bat test-one "<fully-qualified-class-or-pattern>"` for a focused regression.
|
||||
3. `scripts/dev.bat install-fast` when the result must run on the connected
|
||||
arm64 phone. This passes `-Phermes.devAbi=arm64-v8a`, avoiding the x86,
|
||||
x86_64, and armeabi-v7a native libraries in the local APK.
|
||||
4. `scripts/dev.bat prepush` before pushing Android work.
|
||||
|
||||
`install-fast` is intentionally phone-specific. Use `install` for a universal
|
||||
sideload debug APK or when the target ABI is not arm64. Release builds remain
|
||||
universal and are unaffected unless `-Phermes.devAbi` is explicitly supplied.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
@@ -51,12 +75,12 @@ The legacy `relay_server/` directory is a thin compatibility shim around `plugin
|
||||
|
||||
| Component | Stack |
|
||||
|-----------|-------|
|
||||
| **Android App** | Kotlin 2.0, Jetpack Compose, Material 3, OkHttp |
|
||||
| **Android App** | Kotlin 2.4, Jetpack Compose, Material 3, OkHttp |
|
||||
| **Relay Server** | Python 3.11+, aiohttp |
|
||||
| **Serialization** | kotlinx.serialization |
|
||||
| **Build** | AGP 9, Gradle 8.13, JVM toolchain 17 |
|
||||
| **Build** | AGP 9.3.1, Gradle 9.6.1, JVM toolchain 17 |
|
||||
| **CI/CD** | GitHub Actions (lint, build, test, signed APK artifacts) |
|
||||
| **Min SDK** | 26 (Android 8.0) / Target SDK 35 |
|
||||
| **Min SDK** | 26 (Android 8.0) / Target SDK 36 |
|
||||
|
||||
## Running the Relay Locally
|
||||
|
||||
@@ -174,7 +198,8 @@ Release notes (`RELEASE_NOTES.md`, `app/src/main/assets/whats_new.txt`, `docs/pl
|
||||
CI in one cached Gradle invocation. Run it before pushing Android PR updates
|
||||
to catch common hosted failures without waiting for another full Actions
|
||||
cycle; hosted CI remains the exhaustive all-variant gate.
|
||||
- **Android unit tests:** `scripts/dev.bat test` (runs JUnit + MockK + Compose testing)
|
||||
- **Focused Android unit test:** `scripts/dev.bat test-one "<fully-qualified-class-or-pattern>"`
|
||||
- **Android unit tests:** `scripts/dev.bat test` (runs the sideload debug JUnit + MockK + Compose suite)
|
||||
- **Python tests:** `python -m unittest plugin.tests.test_<name>` from the repo root with the hermes-agent venv active. `pytest` works too but the pre-existing `conftest.py` imports a module that isn't always installed — `unittest` avoids that entirely.
|
||||
|
||||
CI is split into path-filtered workflows: `.github/workflows/ci-android.yml` (lint + build + test on app/Gradle changes), `.github/workflows/ci-server.yml` (syntax check + focused server tests on plugin/Python changes), and `.github/workflows/ci-desktop.yml` (desktop type/build/smoke checks). They run on pushes to `main` and `dev` and on PRs targeting either when their paths are touched.
|
||||
|
||||
@@ -267,11 +267,14 @@ Already installed? The same recipe is auto-loaded as a Hermes skill — invoke `
|
||||
|
||||
```bash
|
||||
# Android: open the repo root in Android Studio, wait for Gradle sync, Run (Shift+F10).
|
||||
scripts/dev.bat build # Build debug APK
|
||||
scripts/dev.bat build # Build sideload debug APK
|
||||
scripts/dev.bat compile # Compile sideload Kotlin only
|
||||
scripts/dev.bat test-one "com.hermesandroid.relay.SomeTest" # Focused unit test
|
||||
scripts/dev.bat install-fast # arm64 phone build + install + launch
|
||||
scripts/dev.bat release # Build signed release APK
|
||||
scripts/dev.bat bundle # Build release AAB for Google Play
|
||||
scripts/dev.bat run # Build + install + launch + logcat
|
||||
scripts/dev.bat test # Run unit tests
|
||||
scripts/dev.bat run # Build sideload + install + launch + logcat
|
||||
scripts/dev.bat test # Run sideload debug unit tests
|
||||
scripts/dev.bat version # Show current version
|
||||
scripts/dev.bat relay # Start the relay server (dev, no TLS)
|
||||
```
|
||||
@@ -280,13 +283,13 @@ scripts/dev.bat relay # Start the relay server (dev, no TLS)
|
||||
|
||||
| Component | Stack |
|
||||
|-----------|-------|
|
||||
| **Android app** | Kotlin 2.0, Jetpack Compose, Material 3, OkHttp |
|
||||
| **Android app** | Kotlin 2.4, Jetpack Compose, Material 3, OkHttp |
|
||||
| **Hermes-Relay CLI** | TypeScript, Bun-compiled native binary, Node ≥21 (source/dev), zero runtime deps |
|
||||
| **Server / plugin** | Python 3.11+, aiohttp |
|
||||
| **Serialization** | kotlinx.serialization (Android) |
|
||||
| **Build** | AGP 9, Gradle 8.13, JVM toolchain 17 (Android); `tsc` + `bun build --compile` (CLI) |
|
||||
| **Build** | AGP 9.3.1, Gradle 9.6.1, JVM toolchain 17 (Android); `tsc` + `bun build --compile` (CLI) |
|
||||
| **CI/CD** | GitHub Actions — lint, build, test, APK artifact, CLI binaries per platform |
|
||||
| **Min SDK** | 26 (Android 8.0) · Target SDK 35 |
|
||||
| **Min SDK** | 26 (Android 8.0) · Target SDK 36 |
|
||||
|
||||
<details>
|
||||
<summary><b>Repository structure</b></summary>
|
||||
|
||||
@@ -24,6 +24,22 @@ multi-session protocol before treating `lifecycle=session` as an isolation claim
|
||||
|
||||
---
|
||||
|
||||
## Split fast Android unit tests from resource and screenshot tests
|
||||
|
||||
The quick-loop commands now narrow execution to the sideload debug variant and
|
||||
support one-class filtering, but all `:app` unit tests still share one Android
|
||||
test variant. That variant includes merged Android resources, gives every test
|
||||
worker a 2 GiB heap, runs on JDK 21, and enables Roborazzi recording because a
|
||||
small subset of Robolectric/screenshot tests requires those settings.
|
||||
|
||||
Create a separate resource/screenshot test lane so pure state, parser, routing,
|
||||
and formatting tests can run as ordinary JVM tests without Android resource
|
||||
packaging. Keep golden-image recording explicit rather than applying it to all
|
||||
unit tests, preserve a CI task that runs both lanes, and benchmark cold plus
|
||||
warm focused-test latency before adopting the split.
|
||||
|
||||
---
|
||||
|
||||
## Verify Android native dashboard sign-in on device
|
||||
|
||||
Android now selects Custom Tab + PKCE for HTTPS gateways that advertise
|
||||
|
||||
@@ -7,6 +7,15 @@ plugins {
|
||||
alias(libs.plugins.play.publisher)
|
||||
}
|
||||
|
||||
val supportedHermesDevAbis = setOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
val hermesDevAbi = providers.gradleProperty("hermes.devAbi").orNull
|
||||
hermesDevAbi?.let { requestedAbi ->
|
||||
require(requestedAbi in supportedHermesDevAbis) {
|
||||
"Unsupported hermes.devAbi '$requestedAbi'. Expected one of: " +
|
||||
supportedHermesDevAbis.sorted().joinToString()
|
||||
}
|
||||
}
|
||||
|
||||
// Rename output artifacts to include the app version. AGP respects
|
||||
// `archivesName` for both APK (assemble*) and AAB (bundle*) outputs, so
|
||||
// this single line produces `hermes-relay-<version>-<flavor>-<buildType>`
|
||||
@@ -43,6 +52,17 @@ android {
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Optional local-only fast path for device iteration. Native voice/VAD
|
||||
// dependencies make the universal sideload APK very large, while a
|
||||
// connected phone needs only its own ABI. Release and normal debug
|
||||
// builds remain universal unless the developer explicitly supplies
|
||||
// -Phermes.devAbi=<abi>.
|
||||
hermesDevAbi?.let { requestedAbi ->
|
||||
ndk {
|
||||
abiFilters += requestedAbi
|
||||
}
|
||||
}
|
||||
|
||||
// Feature flags — DEV_MODE enables all experimental features in debug builds
|
||||
buildConfigField("boolean", "DEV_MODE", "false")
|
||||
}
|
||||
|
||||
@@ -83,8 +83,15 @@ git worktree remove <path> # delete a worktree (must be clean, or pass --force)
|
||||
(`../hermes-feat-x`), not under the repo root, or it gets swept into globs and
|
||||
IDE indexing.
|
||||
- **Build outputs are per-folder.** That's the point (warm caches), but it also
|
||||
means three worktrees ≈ three `build/` trees on disk. Prune merged worktrees so
|
||||
they don't accumulate.
|
||||
means three worktrees ≈ three `build/` trees on disk. Gradle's local build
|
||||
cache is shared through the Gradle user home and can reuse compatible task
|
||||
outputs across worktrees. Prune merged worktrees so build trees don't
|
||||
accumulate.
|
||||
- **Serialize heavy Gradle invocations on one host.** Worktrees isolate source
|
||||
and output directories, but concurrent Android compiles, lint, tests, and APK
|
||||
packaging still compete for the same CPU, memory, daemon pool, and local
|
||||
cache. Keep one heavy Gradle lane active at a time; parallelism inside that
|
||||
invocation remains enabled.
|
||||
|
||||
## How this maps to releasing
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
|
||||
org.gradle.daemon=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.parallel=true
|
||||
android.useAndroidX=true
|
||||
android.suppressUnsupportedCompileSdk=37
|
||||
kotlin.code.style=official
|
||||
|
||||
@@ -88,7 +88,6 @@ def main() -> int:
|
||||
str(wrapper),
|
||||
"--console=plain",
|
||||
"--configuration-cache",
|
||||
"-Dorg.gradle.jvmargs=-Xmx1536m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8",
|
||||
*tasks,
|
||||
]
|
||||
if not args.skip_tests:
|
||||
|
||||
+48
-18
@@ -10,6 +10,9 @@ if "%1"=="release" goto release
|
||||
if "%1"=="bundle" goto bundle
|
||||
if "%1"=="install" goto install
|
||||
if "%1"=="run" goto run
|
||||
if "%1"=="compile" goto compile
|
||||
if "%1"=="test-one" goto testone
|
||||
if "%1"=="install-fast" goto installfast
|
||||
if "%1"=="test" goto test
|
||||
if "%1"=="lint" goto lint
|
||||
if "%1"=="prepush" goto prepush
|
||||
@@ -23,9 +26,9 @@ if "%1"=="help" goto help
|
||||
goto help
|
||||
|
||||
:build
|
||||
echo Building debug APK...
|
||||
call gradlew.bat assembleDebug
|
||||
echo APK: app\build\outputs\apk\debug\app-debug.apk
|
||||
echo Building sideload debug APK...
|
||||
call gradlew.bat :app:assembleSideloadDebug --console=plain
|
||||
echo APK: app\build\outputs\apk\sideload\debug\
|
||||
goto end
|
||||
|
||||
:release
|
||||
@@ -49,33 +52,57 @@ echo Location: app\build\outputs\bundle\release\
|
||||
goto end
|
||||
|
||||
:install
|
||||
echo Building and installing to connected device...
|
||||
call gradlew.bat installDebug
|
||||
echo Building and installing sideload debug to connected device...
|
||||
call gradlew.bat :app:installSideloadDebug --console=plain
|
||||
if errorlevel 1 goto end
|
||||
echo Launching app...
|
||||
REM Explicit FQCN: applicationId is com.axiomlabs.hermesrelay but the
|
||||
REM Explicit FQCN: the sideload applicationId includes the flavor suffix but the
|
||||
REM namespace (and thus the real class FQCN) is still com.hermesandroid.relay,
|
||||
REM so the `.MainActivity` shorthand no longer resolves correctly.
|
||||
adb shell am start -n com.axiomlabs.hermesrelay/com.hermesandroid.relay.MainActivity
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
goto end
|
||||
|
||||
:run
|
||||
echo Building, installing, and launching...
|
||||
call gradlew.bat installDebug
|
||||
REM Explicit FQCN: applicationId is com.axiomlabs.hermesrelay but the
|
||||
echo Building, installing, and launching sideload debug...
|
||||
call gradlew.bat :app:installSideloadDebug --console=plain
|
||||
if errorlevel 1 goto end
|
||||
REM Explicit FQCN: the sideload applicationId includes the flavor suffix but the
|
||||
REM namespace (and thus the real class FQCN) is still com.hermesandroid.relay,
|
||||
REM so the `.MainActivity` shorthand no longer resolves correctly.
|
||||
adb shell am start -n com.axiomlabs.hermesrelay/com.hermesandroid.relay.MainActivity
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
adb logcat -s HermesRelay:* --format=brief
|
||||
goto end
|
||||
|
||||
:compile
|
||||
echo Compiling sideload debug Kotlin...
|
||||
call gradlew.bat :app:compileSideloadDebugKotlin --console=plain
|
||||
goto end
|
||||
|
||||
:testone
|
||||
if "%~2"=="" (
|
||||
echo Usage: scripts\dev.bat test-one ^<fully-qualified-test-class-or-pattern^>
|
||||
exit /b 2
|
||||
)
|
||||
echo Running focused sideload test: %~2
|
||||
call gradlew.bat :app:testSideloadDebugUnitTest --tests "%~2" --console=plain
|
||||
goto end
|
||||
|
||||
:installfast
|
||||
echo Building arm64 sideload debug and installing to connected phone...
|
||||
call gradlew.bat :app:installSideloadDebug -Phermes.devAbi=arm64-v8a --console=plain
|
||||
if errorlevel 1 goto end
|
||||
echo Launching app...
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
goto end
|
||||
|
||||
:test
|
||||
echo Running unit tests...
|
||||
call gradlew.bat test
|
||||
echo Running sideload debug unit tests...
|
||||
call gradlew.bat :app:testSideloadDebugUnitTest --console=plain
|
||||
goto end
|
||||
|
||||
:lint
|
||||
echo Running lint...
|
||||
call gradlew.bat lint
|
||||
echo Running sideload debug lint...
|
||||
call gradlew.bat :app:lintSideloadDebug --console=plain
|
||||
goto end
|
||||
|
||||
:prepush
|
||||
@@ -125,12 +152,15 @@ goto end
|
||||
:help
|
||||
echo Hermes-Relay Dev Scripts
|
||||
echo.
|
||||
echo build Build debug APK
|
||||
echo build Build sideload debug APK
|
||||
echo release Build signed release APK
|
||||
echo bundle Build release AAB (for Google Play upload)
|
||||
echo install Build + install to connected device
|
||||
echo install Build universal sideload + install
|
||||
echo run Build + install + launch + logcat
|
||||
echo test Run unit tests
|
||||
echo compile Compile sideload debug Kotlin only
|
||||
echo test-one Run one test class or wildcard pattern
|
||||
echo install-fast Build arm64 only + install + launch
|
||||
echo test Run sideload debug unit tests
|
||||
echo lint Run lint checks
|
||||
echo prepush Run Android repository checks, lint, and focused CI tests
|
||||
echo clean Clean build outputs
|
||||
|
||||
+39
-18
@@ -7,35 +7,53 @@ cd "$(dirname "$0")/.."
|
||||
|
||||
case "${1:-help}" in
|
||||
build)
|
||||
echo "Building debug APK..."
|
||||
./gradlew assembleDebug
|
||||
echo "APK: app/build/outputs/apk/debug/app-debug.apk"
|
||||
echo "Building sideload debug APK..."
|
||||
./gradlew :app:assembleSideloadDebug --console=plain
|
||||
echo "APK: app/build/outputs/apk/sideload/debug/"
|
||||
;;
|
||||
install)
|
||||
echo "Building and installing to connected device..."
|
||||
./gradlew installDebug
|
||||
echo "Building and installing sideload debug to connected device..."
|
||||
./gradlew :app:installSideloadDebug --console=plain
|
||||
echo "Launching app..."
|
||||
# Explicit FQCN: applicationId is com.axiomlabs.hermesrelay but the
|
||||
# Explicit FQCN: the sideload applicationId includes the flavor suffix but the
|
||||
# namespace (and thus the real class FQCN) is still com.hermesandroid.relay,
|
||||
# so the `.MainActivity` shorthand no longer resolves correctly.
|
||||
adb shell am start -n com.axiomlabs.hermesrelay/com.hermesandroid.relay.MainActivity
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
;;
|
||||
run)
|
||||
echo "Building, installing, and launching..."
|
||||
./gradlew installDebug
|
||||
# Explicit FQCN: applicationId is com.axiomlabs.hermesrelay but the
|
||||
echo "Building, installing, and launching sideload debug..."
|
||||
./gradlew :app:installSideloadDebug --console=plain
|
||||
# Explicit FQCN: the sideload applicationId includes the flavor suffix but the
|
||||
# namespace (and thus the real class FQCN) is still com.hermesandroid.relay,
|
||||
# so the `.MainActivity` shorthand no longer resolves correctly.
|
||||
adb shell am start -n com.axiomlabs.hermesrelay/com.hermesandroid.relay.MainActivity
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
adb logcat -s HermesRelay:* --format=brief
|
||||
;;
|
||||
compile)
|
||||
echo "Compiling sideload debug Kotlin..."
|
||||
./gradlew :app:compileSideloadDebugKotlin --console=plain
|
||||
;;
|
||||
test-one)
|
||||
if [ -z "${2:-}" ]; then
|
||||
echo "Usage: ./scripts/dev.sh test-one <fully-qualified-test-class-or-pattern>"
|
||||
exit 2
|
||||
fi
|
||||
echo "Running focused sideload test: $2"
|
||||
./gradlew :app:testSideloadDebugUnitTest --tests "$2" --console=plain
|
||||
;;
|
||||
install-fast)
|
||||
echo "Building arm64 sideload debug and installing to connected phone..."
|
||||
./gradlew :app:installSideloadDebug -Phermes.devAbi=arm64-v8a --console=plain
|
||||
echo "Launching app..."
|
||||
adb shell am start -n com.axiomlabs.hermesrelay.sideload/com.hermesandroid.relay.MainActivity
|
||||
;;
|
||||
test)
|
||||
echo "Running unit tests..."
|
||||
./gradlew test
|
||||
echo "Running sideload debug unit tests..."
|
||||
./gradlew :app:testSideloadDebugUnitTest --console=plain
|
||||
;;
|
||||
lint)
|
||||
echo "Running lint..."
|
||||
./gradlew lint
|
||||
echo "Running sideload debug lint..."
|
||||
./gradlew :app:lintSideloadDebug --console=plain
|
||||
;;
|
||||
prepush)
|
||||
echo "Running Android pre-push checks..."
|
||||
@@ -77,10 +95,13 @@ case "${1:-help}" in
|
||||
help|*)
|
||||
echo "Hermes-Relay Dev Scripts"
|
||||
echo ""
|
||||
echo " build Build debug APK"
|
||||
echo " install Build + install to connected device"
|
||||
echo " build Build sideload debug APK"
|
||||
echo " install Build universal sideload + install"
|
||||
echo " run Build + install + launch + logcat"
|
||||
echo " test Run unit tests"
|
||||
echo " compile Compile sideload debug Kotlin only"
|
||||
echo " test-one Run one test class or wildcard pattern"
|
||||
echo " install-fast Build arm64 only + install + launch"
|
||||
echo " test Run sideload debug unit tests"
|
||||
echo " lint Run lint checks"
|
||||
echo " prepush Run Android repository checks, lint, and focused CI tests"
|
||||
echo " clean Clean build outputs"
|
||||
|
||||
Reference in New Issue
Block a user