Build the phone's APK as a signed release, and say so in the render report
The phone has been running the debug build: build-apk.sh assembled it, and nothing in the app or its report said which build a frame time came from. A debuggable build runs Compose at a fraction of release speed, so the tuning so far was measured against the wrong number. On the emulator, the same fixture and gestures: measure 1.4ms mean / 14.7ms worst on debug, 0.8ms / 6.6ms on release. build-apk.sh now assembles the release variant, signed with a key it generates once under ~/.config/ai-app (beside the pinned CA, outside any checkout). The report's header names the build. The one native library is declared kept-with-symbols so packaging stops warning about an NDK the build does not need. Also: ui-sandbox.sh keep now keeps the config too. The server appends spawned sessions and enrolled tokens to it, so regenerating it left the transcripts on disk and the registry empty. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
486842e4aa
commit
8750ff90cb
5 files changed
+97
-10
No files matched your search
@@ -103,8 +103,35 @@ android {
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||
buildTypes { getByName("release") { isMinifyEnabled = false } }
|
||||
packaging {
|
||||
resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" }
|
||||
// The one native library here is AndroidX's, a few hundred kilobytes with its symbols.
|
||||
// Stripping them needs an NDK the release build would otherwise not use; keeping them
|
||||
// is declared so AGP stops warning that it could not.
|
||||
jniLibs { keepDebugSymbols += "**/libandroidx.graphics.path.so" }
|
||||
}
|
||||
// A release build must be signed, and the key is per machine rather than per repo: it is
|
||||
// what the phone recognises the app by, and a secret never lives in a checkout (the mount is
|
||||
// shared with an untrusted VM). build-apk.sh keeps it beside the pinned CA and points here
|
||||
// through the environment; without it the release build is unsigned, which is fine for
|
||||
// everything except installing.
|
||||
val keystore = System.getenv("AI_APP_KEYSTORE")
|
||||
signingConfigs {
|
||||
if (keystore != null) {
|
||||
create("release") {
|
||||
storeFile = file(keystore)
|
||||
storePassword = System.getenv("AI_APP_KEYSTORE_PASSWORD")
|
||||
keyAlias = "ai-app"
|
||||
keyPassword = storePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
if (keystore != null) signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.aiapp
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
@@ -1195,7 +1197,11 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
debugReport(
|
||||
device =
|
||||
"device: ${Build.MODEL} (${Build.MANUFACTURER})," +
|
||||
" Android ${Build.VERSION.RELEASE}",
|
||||
" Android ${Build.VERSION.RELEASE}\n" +
|
||||
// A debuggable build runs Compose at a fraction of
|
||||
// release speed, so a report that did not say which
|
||||
// it came from was read as the app's own cost.
|
||||
"build: ${if (debuggable(context)) "debug" else "release"}",
|
||||
transcript =
|
||||
listOf(
|
||||
" ${items.size} events, ${rows.size} rows," +
|
||||
@@ -2312,3 +2318,6 @@ private fun PickerButton(current: String, options: List<String>, onPick: (String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun debuggable(context: Context) =
|
||||
context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
|
||||
+39
-6
@@ -55,6 +55,37 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The phone runs the release build. A debuggable build runs Compose at a
|
||||
# fraction of the speed -- ART keeps the process debugger-friendly and the
|
||||
# compiler leaves its inspection hooks in -- so a frame time measured on one
|
||||
# says little about the app; that cost a day of tuning against the wrong
|
||||
# number. A release build must be signed, and the key is what the phone
|
||||
# recognises the app by, so it lives beside the CA, outside any checkout,
|
||||
# and is generated once here. Switching from an installed debug build means
|
||||
# uninstalling it first: the signatures differ, and Android refuses to
|
||||
# update across them.
|
||||
KEYSTORE="${AI_APP_KEYSTORE:-${XDG_CONFIG_HOME:-$HOME/.config}/ai-app/release.jks}"
|
||||
if [ ! -f "$KEYSTORE" ]; then
|
||||
KEYTOOL="${JAVA_HOME:+$JAVA_HOME/bin/keytool}"
|
||||
KEYTOOL="${KEYTOOL:-keytool}"
|
||||
if ! command -v "$KEYTOOL" >/dev/null 2>&1; then
|
||||
echo "No signing key at $KEYSTORE and no keytool to make one -- set" >&2
|
||||
echo "JAVA_HOME to the JDK Gradle uses, or AI_APP_KEYSTORE to an existing key." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> No signing key at $KEYSTORE -- generating one"
|
||||
mkdir -p "$(dirname "$KEYSTORE")"
|
||||
PASSWORD=$(head -c 24 /dev/urandom | base64 | tr -d '/+=')
|
||||
(umask 077 && printf '%s\n' "$PASSWORD" > "$KEYSTORE.password")
|
||||
(umask 077 && "$KEYTOOL" -genkeypair -keystore "$KEYSTORE" -alias ai-app \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 \
|
||||
-storepass "$PASSWORD" -keypass "$PASSWORD" -dname "CN=ai-app" >/dev/null 2>&1)
|
||||
fi
|
||||
AI_APP_KEYSTORE="$KEYSTORE"
|
||||
AI_APP_KEYSTORE_PASSWORD=$(cat "$KEYSTORE.password")
|
||||
export AI_APP_KEYSTORE AI_APP_KEYSTORE_PASSWORD
|
||||
echo "==> Signing with $KEYSTORE"
|
||||
|
||||
# Dev Updater draws a real progress bar from "@@progress done/total" lines,
|
||||
# and ignores anything that isn't exactly that shape. Gradle can't be asked
|
||||
# for this directly: an init script using taskGraph.afterTask is rejected
|
||||
@@ -64,17 +95,17 @@ fi
|
||||
# the total. The build then prints one "> Task :x" line per task as it
|
||||
# goes, so counting those against it is the whole mechanism.
|
||||
#
|
||||
# Task count is not time -- compileDebugKotlin and dexBuilder are most of
|
||||
# Task count is not time -- compileReleaseKotlin and dexBuilder are most of
|
||||
# the wall clock -- so the bar moves unevenly. It is still counted work
|
||||
# rather than a guess at how long last time took.
|
||||
TASKS=$(./gradlew :androidApp:assembleDebug --dry-run --console=plain 2>/dev/null \
|
||||
TASKS=$(./gradlew :androidApp:assembleRelease --dry-run --console=plain 2>/dev/null \
|
||||
| grep -c '^:[A-Za-z:]* SKIPPED' || true)
|
||||
|
||||
echo "==> Building"
|
||||
if [ "${TASKS:-0}" -gt 0 ]; then
|
||||
echo "@@progress 0/$TASKS"
|
||||
DONE=0
|
||||
./gradlew :androidApp:assembleDebug --console=plain 2>&1 | while IFS= read -r line; do
|
||||
./gradlew :androidApp:assembleRelease --console=plain 2>&1 | while IFS= read -r line; do
|
||||
echo "$line"
|
||||
case "$line" in
|
||||
"> Task "*)
|
||||
@@ -86,17 +117,19 @@ if [ "${TASKS:-0}" -gt 0 ]; then
|
||||
# The pipeline's exit status is the shell's, not gradle's, so ask
|
||||
# gradle again rather than reporting a failed build as a success. It is
|
||||
# up to date by now, so this is a second or two.
|
||||
./gradlew :androidApp:assembleDebug --console=plain >/dev/null
|
||||
./gradlew :androidApp:assembleRelease --console=plain >/dev/null
|
||||
else
|
||||
./gradlew :androidApp:assembleDebug
|
||||
./gradlew :androidApp:assembleRelease
|
||||
fi
|
||||
|
||||
APK="$SCRIPT_DIR/androidApp/build/outputs/apk/debug/androidApp-debug.apk"
|
||||
APK="$SCRIPT_DIR/androidApp/build/outputs/apk/release/androidApp-release.apk"
|
||||
echo
|
||||
echo "==> Built $APK"
|
||||
[ -f "$APK" ] && ls -lh "$APK" | awk '{print " " $5}'
|
||||
echo
|
||||
echo "To get it onto the phone: add this project to Dev Updater (or hit"
|
||||
echo "Update on it if it's already there) and install from there."
|
||||
echo "If the phone still has the old debug build, uninstall that first:"
|
||||
echo "it is signed with a different key, so Android will refuse the update."
|
||||
echo "Then start the backend and scan the enrollment QR it prints:"
|
||||
echo " ./server/target/release/ai-server --rotate-token"
|
||||
+11
-1
@@ -162,7 +162,15 @@ PROJECTS=$ROOT/home/.claude/projects/-home-bob-repos-sandbox
|
||||
if [ -z "${KEEP:-}" ]; then
|
||||
rm -rf "$ROOT/home" "$ROOT/sessions"
|
||||
fi
|
||||
rm -f "$ROOT/config.ron"
|
||||
# The server appends to this file: the tokens of phones enrolled against it
|
||||
# and every session spawned. Regenerating it under `keep` was what un-kept
|
||||
# the sessions -- their transcripts survived on disk while the registry that
|
||||
# lists them went back to empty -- so a kept sandbox keeps its config too.
|
||||
regen_config=1
|
||||
if [ -n "${KEEP:-}" ] && [ -f "$ROOT/config.ron" ]; then
|
||||
regen_config=""
|
||||
fi
|
||||
[ -n "$regen_config" ] && rm -f "$ROOT/config.ron"
|
||||
mkdir -p "$PROJECTS" "$ROOT/sessions"
|
||||
if [ -z "${KEEP:-}" ]; then
|
||||
|
||||
@@ -231,6 +239,7 @@ awk -v mb="$BIG_MB" 'BEGIN {
|
||||
}' > "$big"
|
||||
fi
|
||||
|
||||
if [ -n "$regen_config" ]; then
|
||||
cat >"$ROOT/config.ron" <<RON
|
||||
tokens: [
|
||||
(
|
||||
@@ -260,6 +269,7 @@ setups: [
|
||||
],
|
||||
sessions: [],
|
||||
RON
|
||||
fi
|
||||
|
||||
echo "sandbox: building"
|
||||
(cd "$SERVER_DIR" && cargo build --quiet)
|
||||
|
||||
Reference in new issue
Block a user