Files
DroidDeck/tools/agent/dd-lab-build
T

112 lines
5.3 KiB
Bash
Executable File

#!/bin/bash
# Builds this checkout as a Lab build of the installed DroidDeck and restarts the app on it.
# Usage: dd-lab-build [--no-restart]
set -euo pipefail
pkg=com.droiddeck.launcher
serial=${ADB_SERIAL:-127.0.0.1:5555}
restart=1
[ "${1:-}" = --no-restart ] && restart=0
started=$(date +%s)
progress_file=$HOME/.cache/dd-lab-build.json
mkdir -p "$HOME/.cache"
# The Agent tab and the agent read this file to show how far the build has got.
progress() { # stage [done total task]
printf '{"stage":"%s","done":%d,"total":%d,"task":"%s","elapsed":%d}\n' \
"$1" "${2:-0}" "${3:-0}" "${4:-}" $(( $(date +%s) - started )) > "$progress_file.part"
mv "$progress_file.part" "$progress_file"
}
fail() { progress failed; echo "dd-lab-build: $*" >&2; exit 1; }
device() { adb -s "$serial" shell "$@" | tr -d '\r'; }
progress checking
repo=$(git rev-parse --show-toplevel) || fail "run it inside the DroidDeck checkout"
cd "$repo"
adb connect "$serial" >/dev/null 2>&1 || true
git diff --quiet && git diff --cached --quiet || fail "commit your changes first"
state=$(python3 tools/droiddeckctl state 2>/dev/null | sed -n '/^{/,$p')
# A Lab build reports its own label, so the APK's commit comes from apkCommit when the app has it.
label=$(python3 -c 'import json,sys; s=json.load(sys.stdin); print(s.get("apkCommit") or s["build"].rsplit(" at ", 1)[-1])' <<<"$state")
base=$(git rev-parse --verify --quiet "$label^{commit}") || fail "the installed build ($label) is not in this checkout; fetch it first"
git merge-base --is-ancestor "$base" HEAD || fail "HEAD does not build on the installed commit ${base:0:9}; branch from it"
blocked=$(git diff --name-only "$base" HEAD | grep -E 'AndroidManifest\.xml$|/cpp/|/jniLibs/|\.rs$|(^|/)build\.gradle$|/App\.kt$|/lab/LabOverlay\.kt$' || true)
[ -z "$blocked" ] || fail "these need a new APK, not a Lab build: $(echo $blocked)"
apk=$(device pm path $pkg | sed -n 's/^package://p' | grep 'base\.apk$' | head -1)
[ -n "$apk" ] || fail "DroidDeck is not installed"
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64 ANDROID_HOME=$HOME/android-sdk
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties
if [ ! -d "$ANDROID_HOME/platforms/android-34" ]; then
progress sdk
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "platforms;android-34" "build-tools;35.0.0" >/dev/null
fi
# A full Kotlin compile of the app needs about 3 GB of heap; smaller devices get what they can spare.
total_mb=$(awk '/^MemTotal:/ {print int($2 / 1024)}' /proc/meminfo)
heap=$(( total_mb * 2 / 5 )); [ $heap -gt 3072 ] && heap=3072; [ $heap -lt 1536 ] && heap=1536
heap=${DD_LAB_HEAP_MB:-$heap}
# Google publishes aapt2 for x86-64 Linux only; setup wraps it in qemu-user at ~/aapt2/aapt2.
gradle=(./gradlew :app:processReleaseResources :app:mergeDexRelease --max-workers=2 -PskipRust=true -x buildVkBridgeServer
-Pandroid.aapt2FromMavenOverride=$HOME/aapt2/aapt2 -Pkotlin.compiler.execution.strategy=in-process
-Dorg.gradle.jvmargs=-Xmx${heap}m --console=plain)
progress planning
total=$("${gradle[@]}" --dry-run -q 2>/dev/null | grep -c '^:' || true)
current=$(mktemp)
echo "0 starting" > "$current"
(
while sleep 60; do
read -r n task < "$current"
echo "[build $n/$total] still on $task, $(( ($(date +%s) - started) / 60 ))m in"
progress building "$n" "$total" "$task"
done
) &
heartbeat=$!
trap 'kill $heartbeat 2>/dev/null || true' EXIT
set +o pipefail
"${gradle[@]}" 2>&1 | {
n=0
while IFS= read -r line; do
printf '%s\n' "$line"
case $line in
"> Task "*)
n=$((n + 1)); task=${line#> Task }; task=${task%% *}
echo "$n $task" > "$current"
echo "[build $n/$total] $task"
progress building "$n" "$total" "$task" ;;
esac
done
}
status=${PIPESTATUS[0]}
set -o pipefail
kill $heartbeat 2>/dev/null || true
[ "$status" = 0 ] || fail "Gradle failed (exit $status); the reason is above"
progress packaging
linked=$(ls app/build/intermediates/linked_resources_binary_format/release/processReleaseResources/*.ap_ | head -1)
work=$(mktemp -d)
cp "$linked" "$work/overlay.apk"
(cd app/build/intermediates/dex/release/mergeDexRelease && zip -q "$work/overlay.apk" classes*.dex)
name="$(git branch --show-current) @ $(git rev-parse --short HEAD)"
python3 -c 'import json,sys; print(json.dumps({"label": sys.argv[1], "baseCommit": sys.argv[2], "baseApk": sys.argv[3]}))' \
"$name" "$base" "$apk" > "$work/overlay.json"
lab=/data/user/0/$pkg/files/lab
mkdir -p "$lab"
rm -rf "$lab/next.part" "$lab/next"
mv "$work" "$lab/next.part"
mv "$lab/next.part" "$lab/next"
progress ready
echo "Lab build ready: $name ($(du -h "$lab/next/overlay.apk" | cut -f1)) in $(( ($(date +%s) - started) / 60 ))m"
[ $restart = 1 ] || exit 0
# Classes in an app image load before the Lab build can, so the app is recompiled without one.
device cmd package compile -m verify -f $pkg >/dev/null
progress restarting
echo "Restarting DroidDeck on the Lab build"
# The app's own agent ends with the app, so it is told to carry on once the app is back.
if [ -n "${DD_AGENT:-}" ]; then mkdir -p "/data/user/0/$pkg/files/agent" && : > "/data/user/0/$pkg/files/agent/resume"; fi
device "nohup sh -c 'sleep 3; monkey -p $pkg -c android.intent.category.LAUNCHER 1' >/dev/null 2>&1 &"
for pid in $(device pidof $pkg); do kill "$pid" 2>/dev/null || true; done