Files
shorebird-workspace/scripts/android_runtime_patch_smoke.sh
T
2026-06-25 20:08:23 +08:00

250 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APP_DIR="${APP_DIR:-$ROOT/testapps/license_flavor_patch_test}"
FLUTTER_BIN="${FLUTTER_BIN:-$ROOT/flutter/bin/flutter}"
ADB_BIN="${ADB_BIN:-adb}"
PACKAGE="${ANDROID_PACKAGE:-com.example.licenseflavorpatchtest.license_flavor_patch_test}"
ACTIVITY="${ANDROID_ACTIVITY:-com.example.licenseflavorpatchtest.license_flavor_patch_test.MainActivity}"
RELEASE_VERSION="${ANDROID_RELEASE_VERSION:-1.0+1}"
LOCAL_ENGINE_SRC_PATH="${LOCAL_ENGINE_SRC_PATH:-$ROOT/flutter/engine/src}"
LOCAL_ENGINE="${LOCAL_ENGINE:-android_release_arm64}"
LOCAL_ENGINE_HOST="${LOCAL_ENGINE_HOST:-host_release_arm64}"
WORK_DIR="${ANDROID_RUNTIME_SMOKE_WORK_DIR:-$(mktemp -d "${TMPDIR:-/tmp}/open-shorebird-android-runtime.XXXXXX")}"
SEED_REMOTE_DIR="${ANDROID_SEED_REMOTE_DIR:-/data/local/tmp/open-shorebird-android-runtime-seed}"
TARGET_PLATFORM="${ANDROID_TARGET_PLATFORM:-android-arm64}"
if [[ "${KEEP_ANDROID_RUNTIME_SMOKE_ARTIFACTS:-0}" != "1" ]]; then
trap 'rm -rf "$WORK_DIR"' EXIT
fi
ADB=("$ADB_BIN")
if [[ -n "${ANDROID_SERIAL:-}" ]]; then
ADB+=("-s" "$ANDROID_SERIAL")
fi
adb_cmd() {
"${ADB[@]}" "$@"
}
adb_shell() {
adb_cmd shell "$@"
}
python_bin() {
if command -v python3 >/dev/null 2>&1; then
printf '%s\n' python3
else
printf '%s\n' python
fi
}
require_tool() {
local tool="$1"
if ! command -v "$tool" >/dev/null 2>&1; then
echo "$tool is required" >&2
exit 127
fi
}
build_apk() {
local license="$1"
local output="$2"
if [[ "${SKIP_ANDROID_BUILDS:-0}" == "1" ]]; then
local env_name
case "$license" in
free) env_name=ANDROID_FREE_APK ;;
pro) env_name=ANDROID_PRO_APK ;;
*)
echo "Unsupported license variant: $license" >&2
exit 64
;;
esac
local existing="${!env_name:-}"
if [[ -z "$existing" || ! -f "$existing" ]]; then
echo "SKIP_ANDROID_BUILDS=1 requires $env_name to point at an APK" >&2
exit 66
fi
cp "$existing" "$output"
return
fi
(
cd "$APP_DIR"
"$FLUTTER_BIN" build apk --release \
--target-platform "$TARGET_PLATFORM" \
--local-engine-src-path="$LOCAL_ENGINE_SRC_PATH" \
--local-engine="$LOCAL_ENGINE" \
--local-engine-host="$LOCAL_ENGINE_HOST" \
--dart-define="LICENSE_TYPE=$license"
)
cp "$APP_DIR/build/app/outputs/flutter-apk/app-release.apk" "$output"
}
extract_libapp() {
local apk="$1"
local output="$2"
local py
py="$(python_bin)"
"$py" - "$apk" "$output" <<'PY'
import pathlib
import sys
import zipfile
apk_path = pathlib.Path(sys.argv[1])
output_path = pathlib.Path(sys.argv[2])
with zipfile.ZipFile(apk_path) as archive:
candidates = [
name for name in archive.namelist()
if name.endswith("/libapp.so") and "arm64-v8a/" in name
]
if not candidates:
raise SystemExit(f"missing arm64 libapp.so in {apk_path}")
output_path.write_bytes(archive.read(candidates[0]))
PY
}
wait_for_text() {
local expected="$1"
local dump="$WORK_DIR/window.xml"
for _ in $(seq 1 "${ANDROID_UI_WAIT_ATTEMPTS:-80}"); do
adb_shell uiautomator dump /sdcard/open_shorebird_window.xml >/dev/null 2>&1 || true
adb_cmd exec-out cat /sdcard/open_shorebird_window.xml >"$dump" 2>/dev/null || true
if grep -q "$expected" "$dump"; then
return 0
fi
sleep 0.25
done
echo "Timed out waiting for Android UI text: $expected" >&2
echo "Last uiautomator dump:" >&2
sed -n '1,120p' "$dump" >&2 || true
return 70
}
start_app() {
adb_shell am force-stop "$PACKAGE" >/dev/null 2>&1 || true
adb_shell am start -W -n "$PACKAGE/$ACTIVITY" >/dev/null
}
can_seed_with_run_as() {
adb_shell run-as "$PACKAGE" sh -c 'test -d files' >/dev/null 2>&1
}
can_seed_with_root() {
if adb_shell sh -c 'test "$(id -u)" = "0"' >/dev/null 2>&1; then
return 0
fi
adb_cmd root >/dev/null 2>&1 || true
adb_cmd wait-for-device >/dev/null 2>&1 || true
adb_shell sh -c 'test "$(id -u)" = "0"' >/dev/null 2>&1
}
seed_with_run_as() {
adb_shell run-as "$PACKAGE" sh -c \
"mkdir -p files && rm -rf files/shorebird_updater && cp -R '$SEED_REMOTE_DIR/shorebird_updater' files/"
}
seed_with_root() {
local target="/data/data/$PACKAGE/files"
local owner
owner="$(adb_shell sh -c "stat -c '%u:%g' '$target'" | tr -d '\r')"
adb_shell sh -c \
"rm -rf '$target/shorebird_updater' && cp -R '$SEED_REMOTE_DIR/shorebird_updater' '$target/' && chown -R '$owner' '$target/shorebird_updater'"
}
prepare_seed() {
local libapp="$1"
local seed_root="$WORK_DIR/seed/shorebird_updater"
local size
size="$(wc -c <"$libapp" | tr -d ' ')"
rm -rf "$WORK_DIR/seed"
mkdir -p "$seed_root/patches/1"
cp "$libapp" "$seed_root/patches/1/dlc.vmcode"
cat >"$seed_root/state.json" <<EOF
{
"client_id": "android-runtime-smoke",
"release_version": "$RELEASE_VERSION",
"queued_events": []
}
EOF
cat >"$seed_root/pointers.json" <<'EOF'
{
"next_boot_patch": 1,
"last_booted_patch": null,
"currently_booting_patch": null,
"boot_started_at": null
}
EOF
cat >"$seed_root/patches/1/state.json" <<EOF
{
"kind": "Installed",
"signature": null,
"size": $size
}
EOF
adb_shell rm -rf "$SEED_REMOTE_DIR" >/dev/null 2>&1 || true
adb_shell mkdir -p "$SEED_REMOTE_DIR" >/dev/null
adb_cmd push "$seed_root" "$SEED_REMOTE_DIR/" >/dev/null
echo "seeded_patch_size=$size"
}
require_tool "$ADB_BIN"
require_tool "$FLUTTER_BIN"
require_tool java
adb_cmd start-server >/dev/null
device_count="$(adb_cmd devices | awk 'NR > 1 && $2 == "device" { count++ } END { print count + 0 }')"
if [[ "$device_count" -lt 1 ]]; then
echo "No Android device or emulator is connected." >&2
echo "Start an emulator/device, then rerun this script. Use ANDROID_SERIAL to pick a device." >&2
exit 69
fi
if [[ "$device_count" -gt 1 && -z "${ANDROID_SERIAL:-}" ]]; then
echo "Multiple Android devices are connected; set ANDROID_SERIAL." >&2
adb_cmd devices -l >&2
exit 64
fi
free_apk="$WORK_DIR/free.apk"
pro_apk="$WORK_DIR/pro.apk"
pro_libapp="$WORK_DIR/pro-libapp.so"
build_apk free "$free_apk"
build_apk pro "$pro_apk"
extract_libapp "$pro_apk" "$pro_libapp"
adb_cmd uninstall "$PACKAGE" >/dev/null 2>&1 || true
adb_cmd install -r "$free_apk" >/dev/null
start_app
wait_for_text 'license:free'
wait_for_text 'pro-feature:off'
echo "android_base_status=license:free/pro-feature:off"
prepare_seed "$pro_libapp"
if can_seed_with_run_as; then
seed_with_run_as
echo "android_seed_mode=run-as"
elif can_seed_with_root; then
seed_with_root
echo "android_seed_mode=root"
else
cat >&2 <<EOF
Unable to seed the Android app-private updater directory.
Use a debuggable build/device where 'adb shell run-as $PACKAGE' works, or use
a rooted emulator/device where 'adb root' works. The smoke seed target is:
/data/data/$PACKAGE/files/shorebird_updater
EOF
exit 77
fi
start_app
wait_for_text 'license:pro'
wait_for_text 'pro-feature:enabled'
echo "android_patch_status=license:pro/pro-feature:enabled"
adb_shell am force-stop "$PACKAGE" >/dev/null 2>&1 || true
echo "android_runtime_patch_smoke=passed"