chore(watch): add run.sh helper for build/install/run
Inner-loop tooling so the dev cycle does not require Android Studio. Subcommands: build, install, start, stop, logcat, shot, clean (default = install + start). Auto-detects JAVA_HOME (bundled JBR) and adb. Boots an emulator on demand when no device is attached: interactive menu of available AVDs, with WEAR_AVD=<name> env-var bypass for non-interactive use. Waits for sys.boot_completed (180s timeout) before installing.
This commit is contained in:
Executable
+226
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build / install / run helper for the Wear OS app.
|
||||
#
|
||||
# Subcommands:
|
||||
# (none) install + start (default inner loop)
|
||||
# build ./gradlew :app:assembleDebug
|
||||
# install ./gradlew :app:installDebug
|
||||
# start adb shell am start ...
|
||||
# stop adb shell am force-stop ...
|
||||
# logcat adb logcat, filtered to the app's PID
|
||||
# shot [path] screencap to PNG (default /tmp/wear_screen.png)
|
||||
# clean ./gradlew clean
|
||||
# help show this help
|
||||
#
|
||||
# Env vars:
|
||||
# WEAR_AVD AVD name to auto-boot when no device is attached
|
||||
# (skips the interactive menu; useful for CI / scripted runs)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Config ----------------------------------------------------------------
|
||||
PACKAGE="com.doctate.watch"
|
||||
ACTIVITY=".presentation.MainActivity"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# --- Output helpers --------------------------------------------------------
|
||||
if [[ -t 1 ]]; then
|
||||
C_INFO=$'\e[1;34m'; C_OK=$'\e[1;32m'; C_ERR=$'\e[1;31m'; C_OFF=$'\e[0m'
|
||||
else
|
||||
C_INFO=""; C_OK=""; C_ERR=""; C_OFF=""
|
||||
fi
|
||||
info() { printf "%s==>%s %s\n" "$C_INFO" "$C_OFF" "$*"; }
|
||||
ok() { printf "%s OK%s %s\n" "$C_OK" "$C_OFF" "$*"; }
|
||||
die() { printf "%sERR%s %s\n" "$C_ERR" "$C_OFF" "$*" >&2; exit 1; }
|
||||
|
||||
# --- Toolchain detection ---------------------------------------------------
|
||||
detect_java() {
|
||||
if [[ -n "${JAVA_HOME:-}" && -x "$JAVA_HOME/bin/java" ]]; then
|
||||
return
|
||||
fi
|
||||
for candidate in /opt/android-studio/jbr /usr/lib/jvm/java-21-openjdk; do
|
||||
if [[ -x "$candidate/bin/java" ]]; then
|
||||
export JAVA_HOME="$candidate"
|
||||
return
|
||||
fi
|
||||
done
|
||||
die "No JDK found. Set JAVA_HOME or install jdk21-openjdk."
|
||||
}
|
||||
|
||||
detect_adb() {
|
||||
if [[ -n "${ADB:-}" && -x "$ADB" ]]; then return; fi
|
||||
if [[ -x "$HOME/Android/Sdk/platform-tools/adb" ]]; then
|
||||
ADB="$HOME/Android/Sdk/platform-tools/adb"
|
||||
elif command -v adb >/dev/null 2>&1; then
|
||||
ADB="$(command -v adb)"
|
||||
else
|
||||
die "adb not found. Expected ~/Android/Sdk/platform-tools/adb."
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_device() {
|
||||
detect_adb
|
||||
local count
|
||||
count=$("$ADB" devices | awk 'NR>1 && $2=="device"' | wc -l)
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
if [[ "$count" -gt 1 ]]; then
|
||||
info "Multiple devices attached; ADB will pick the first."
|
||||
fi
|
||||
return
|
||||
fi
|
||||
boot_emulator_or_die
|
||||
}
|
||||
|
||||
boot_emulator_or_die() {
|
||||
local emulator_bin="$HOME/Android/Sdk/emulator/emulator"
|
||||
if [[ ! -x "$emulator_bin" ]]; then
|
||||
die "No device attached and emulator not found at $emulator_bin."
|
||||
fi
|
||||
|
||||
local -a avds
|
||||
mapfile -t avds < <("$emulator_bin" -list-avds 2>/dev/null)
|
||||
if [[ "${#avds[@]}" -eq 0 ]]; then
|
||||
die "No device attached and no AVDs configured. Create one in Android Studio."
|
||||
fi
|
||||
|
||||
local chosen=""
|
||||
|
||||
# Env-var override: skip the menu (CI / scripted runs).
|
||||
if [[ -n "${WEAR_AVD:-}" ]]; then
|
||||
local a
|
||||
for a in "${avds[@]}"; do
|
||||
if [[ "$a" == "$WEAR_AVD" ]]; then
|
||||
chosen="$WEAR_AVD"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$chosen" ]]; then
|
||||
die "WEAR_AVD='$WEAR_AVD' not in available AVDs: ${avds[*]}"
|
||||
fi
|
||||
elif [[ -t 0 ]]; then
|
||||
info "No device attached. Available AVDs:"
|
||||
local i
|
||||
for i in "${!avds[@]}"; do
|
||||
printf " %d) %s\n" "$((i+1))" "${avds[$i]}"
|
||||
done
|
||||
printf " 0) abort\n"
|
||||
local choice
|
||||
read -r -p "Start which? " choice
|
||||
if [[ ! "$choice" =~ ^[0-9]+$ ]]; then
|
||||
die "Invalid choice: '$choice' (expected a number)."
|
||||
fi
|
||||
if [[ "$choice" -eq 0 ]]; then
|
||||
die "Aborted by user."
|
||||
fi
|
||||
if [[ "$choice" -lt 1 || "$choice" -gt "${#avds[@]}" ]]; then
|
||||
die "Out of range: $choice (have ${#avds[@]} AVDs)."
|
||||
fi
|
||||
chosen="${avds[$((choice-1))]}"
|
||||
else
|
||||
die "No device attached. Set WEAR_AVD=<name> or run interactively. Available: ${avds[*]}"
|
||||
fi
|
||||
|
||||
start_emulator_and_wait "$emulator_bin" "$chosen"
|
||||
}
|
||||
|
||||
start_emulator_and_wait() {
|
||||
local emulator_bin="$1"
|
||||
local avd="$2"
|
||||
|
||||
info "Starting AVD '$avd' (background)..."
|
||||
nohup "$emulator_bin" -avd "$avd" >/dev/null 2>&1 &
|
||||
disown
|
||||
|
||||
info "Waiting for ADB to see the device..."
|
||||
"$ADB" wait-for-device
|
||||
|
||||
info "Waiting for boot to complete (up to 180s)..."
|
||||
local boot=""
|
||||
local elapsed=0
|
||||
local timeout=180
|
||||
until [[ "$boot" == "1" ]]; do
|
||||
if [[ "$elapsed" -ge "$timeout" ]]; then
|
||||
die "Boot timeout after ${timeout}s (AVD '$avd')."
|
||||
fi
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
boot=$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r' || true)
|
||||
done
|
||||
ok "AVD '$avd' is up (boot took ${elapsed}s)."
|
||||
}
|
||||
|
||||
# --- Subcommands -----------------------------------------------------------
|
||||
cmd_build() {
|
||||
detect_java
|
||||
info "Building debug APK..."
|
||||
(cd "$SCRIPT_DIR" && ./gradlew :app:assembleDebug)
|
||||
ok "Built."
|
||||
}
|
||||
|
||||
cmd_install() {
|
||||
detect_java
|
||||
ensure_device
|
||||
info "Installing on device..."
|
||||
(cd "$SCRIPT_DIR" && ./gradlew :app:installDebug)
|
||||
ok "Installed."
|
||||
}
|
||||
|
||||
cmd_start() {
|
||||
ensure_device
|
||||
info "Starting $PACKAGE/$ACTIVITY..."
|
||||
"$ADB" shell am start -n "$PACKAGE/$ACTIVITY" >/dev/null
|
||||
ok "Started."
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
ensure_device
|
||||
info "Stopping $PACKAGE..."
|
||||
"$ADB" shell am force-stop "$PACKAGE"
|
||||
ok "Stopped."
|
||||
}
|
||||
|
||||
cmd_logcat() {
|
||||
ensure_device
|
||||
local pid
|
||||
pid=$("$ADB" shell pidof "$PACKAGE" | tr -d '\r' | awk '{print $1}')
|
||||
if [[ -z "$pid" ]]; then
|
||||
die "App $PACKAGE is not running. Use './run.sh start' first."
|
||||
fi
|
||||
info "Logcat for $PACKAGE (PID $pid). Ctrl-C to exit."
|
||||
exec "$ADB" logcat --pid="$pid"
|
||||
}
|
||||
|
||||
cmd_shot() {
|
||||
ensure_device
|
||||
local out="${1:-/tmp/wear_screen.png}"
|
||||
info "Screenshot -> $out"
|
||||
"$ADB" exec-out screencap -p > "$out"
|
||||
ok "Saved ($(wc -c < "$out") bytes)."
|
||||
}
|
||||
|
||||
cmd_clean() {
|
||||
detect_java
|
||||
info "Cleaning..."
|
||||
(cd "$SCRIPT_DIR" && ./gradlew clean)
|
||||
ok "Clean."
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
# Print the leading comment block (lines 2..first non-comment line).
|
||||
awk 'NR==1 { next } /^[^[:space:]#]/ { exit } { print }' "$0" \
|
||||
| sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
# --- Dispatch --------------------------------------------------------------
|
||||
case "${1:-}" in
|
||||
"") cmd_install; cmd_start ;;
|
||||
build) cmd_build ;;
|
||||
install) cmd_install ;;
|
||||
start) cmd_start ;;
|
||||
stop) cmd_stop ;;
|
||||
logcat) cmd_logcat ;;
|
||||
shot) shift; cmd_shot "$@" ;;
|
||||
clean) cmd_clean ;;
|
||||
-h|--help|help) cmd_help ;;
|
||||
*) die "Unknown command: $1 (try './run.sh help')" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user