#!/usr/bin/env bash set -euo pipefail PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" ANDROID_SDK_ROOT_DEFAULT="$HOME/Library/Android/sdk" EMULATOR_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/emulator/emulator" ADB_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/platform-tools/adb" AVD_NAME="${1:-auto}" BOOT_TIMEOUT_SECONDS="${BOOT_TIMEOUT_SECONDS:-120}" ADB_CONNECT_TIMEOUT_SECONDS="${ADB_CONNECT_TIMEOUT_SECONDS:-90}" APP_ID_DEFAULT="com.br.pedifoods.app" REQUIRED_BRIDGE_SO="${REQUIRED_BRIDGE_SO:-libPediFoods.so}" SKIP_EXPORT_ARCH="${SKIP_EXPORT_ARCH:-aarch64}" START_EMULATOR_WHEN_NO_DEVICE="${START_EMULATOR_WHEN_NO_DEVICE:-0}" EMULATOR_HEADLESS="${EMULATOR_HEADLESS:-1}" EMULATOR_MEMORY_MB="${EMULATOR_MEMORY_MB:-1536}" if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator" ADB_BIN="$ANDROID_SDK_ROOT/platform-tools/adb" else EMULATOR_BIN="$EMULATOR_BIN_DEFAULT" ADB_BIN="$ADB_BIN_DEFAULT" fi ANDROID_HOME="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$ANDROID_SDK_ROOT_DEFAULT}}" ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" export ANDROID_HOME export ANDROID_SDK_ROOT if [[ ! -x "$EMULATOR_BIN" ]]; then echo "Erro: emulator não encontrado em '$EMULATOR_BIN'" exit 1 fi if [[ ! -x "$ADB_BIN" ]]; then echo "Erro: adb não encontrado em '$ADB_BIN'" exit 1 fi if ! command -v skip >/dev/null 2>&1; then echo "Erro: comando 'skip' não encontrado no PATH" exit 1 fi TARGET_SERIAL="" EMULATOR_PID="" APP_ID="$(sed -nE 's/^[[:space:]]*PRODUCT_BUNDLE_IDENTIFIER[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' "$PROJECT_DIR/Skip.env" | head -n1)" APP_ID="${APP_ID:-$APP_ID_DEFAULT}" REQUIRE_FRESH_APK="${REQUIRE_FRESH_APK:-1}" ALLOW_INSTALLED_FALLBACK="${ALLOW_INSTALLED_FALLBACK:-1}" RUN_STABILITY_SECONDS="${RUN_STABILITY_SECONDS:-8}" repair_skip_packages_links() { local swift_dir="$PROJECT_DIR/.build/plugins/outputs/pedi-foods/PediFoods/destination/skipstone/PediFoods/src/main/swift" local packages_dir="$swift_dir/Packages" if [[ ! -d "$packages_dir" ]]; then return 0 fi for link_path in "$packages_dir"/*; do [[ -e "$link_path" || -L "$link_path" ]] || continue local pkg_name pkg_name="$(basename "$link_path")" local pkg_manifest="$link_path/Package.swift" if [[ -f "$pkg_manifest" ]]; then continue fi local checkout_path="$PROJECT_DIR/.build/checkouts/$pkg_name" if [[ -f "$checkout_path/Package.swift" ]]; then echo "Reparando link de pacote: $pkg_name -> $checkout_path" rm -rf "$link_path" ln -s "$checkout_path" "$link_path" fi done } clean_skip_generated_outputs() { echo "Limpando artefatos gerados do Skip para recuperação..." rm -rf \ "$PROJECT_DIR/.build/plugins/outputs/skip-android-bridge" \ "$PROJECT_DIR/.build/plugins/outputs/skip-bridge" \ "$PROJECT_DIR/.build/plugins/outputs/skip-foundation" \ "$PROJECT_DIR/.build/plugins/outputs/skip-fuse-ui" \ "$PROJECT_DIR/.build/plugins/outputs/skip-lib" \ "$PROJECT_DIR/.build/plugins/outputs/skip-model" \ "$PROJECT_DIR/.build/plugins/outputs/skip-ui" \ "$PROJECT_DIR/.build/plugins/outputs/skip-unit" } run_skip_export() { cd "$PROJECT_DIR" echo "Executando skip export com arquitetura: $SKIP_EXPORT_ARCH" if skip export --debug --no-ios --android --arch "$SKIP_EXPORT_ARCH"; then repair_skip_packages_links return 0 fi echo "Aviso: skip export falhou na primeira tentativa." clean_skip_generated_outputs repair_skip_packages_links echo "Tentando skip export novamente após limpeza de cache..." if skip export --debug --no-ios --android --arch "$SKIP_EXPORT_ARCH"; then repair_skip_packages_links return 0 fi repair_skip_packages_links return 1 } get_avd_abi() { local avd_name="$1" local cfg="$HOME/.android/avd/${avd_name}.avd/config.ini" if [[ ! -f "$cfg" ]]; then return 1 fi sed -nE 's/^[[:space:]]*abi\.type[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' "$cfg" | head -n1 } apk_supports_abi() { local apk="$1" local abi="$2" unzip -l "$apk" 2>/dev/null | grep "lib/${abi}/${REQUIRED_BRIDGE_SO}$" >/dev/null } apk_contains_required_so() { local apk="$1" unzip -l "$apk" 2>/dev/null | grep "/${REQUIRED_BRIDGE_SO}$" >/dev/null } first_connected_device() { "$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1; exit}' } pick_connected_device_for_apk() { local apk="$1" local serial abi while IFS= read -r serial; do [[ -n "$serial" ]] || continue abi="$("$ADB_BIN" -s "$serial" shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r')" if [[ -n "$abi" ]] && apk_supports_abi "$apk" "$abi"; then echo "$serial" return 0 fi done < <("$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1}') return 1 } pick_compatible_avd() { local apk="$1" local preferred="$2" local avd_list avd_list="$("$EMULATOR_BIN" -list-avds)" local host_arch host_arch="$(uname -m 2>/dev/null || echo "")" if [[ "$preferred" != "auto" ]] && echo "$avd_list" | grep -qx "$preferred"; then local preferred_abi preferred_abi="$(get_avd_abi "$preferred" || true)" if [[ -n "$preferred_abi" ]] && apk_supports_abi "$apk" "$preferred_abi"; then echo "$preferred" return 0 fi fi local avd abi while IFS= read -r avd; do [[ -n "$avd" ]] || continue abi="$(get_avd_abi "$avd" || true)" if [[ "$host_arch" == "arm64" || "$host_arch" == "aarch64" ]]; then if [[ "$abi" == "x86_64" || "$abi" == "x86" ]]; then continue fi fi if [[ -n "$abi" ]] && apk_supports_abi "$apk" "$abi"; then echo "$avd" return 0 fi done <<< "$avd_list" return 1 } ensure_android_ready() { local apk_path="${1:-}" local avd_to_boot="$AVD_NAME" if [[ -n "$apk_path" && -f "$apk_path" ]]; then if ! apk_contains_required_so "$apk_path"; then echo "Erro: APK não contém ${REQUIRED_BRIDGE_SO}. Build inválido para app Skip." echo "APK: $apk_path" exit 1 fi local picked picked="$(pick_compatible_avd "$apk_path" "$AVD_NAME" || true)" if [[ -n "$picked" ]]; then avd_to_boot="$picked" fi fi "$ADB_BIN" start-server >/dev/null 2>&1 || true if [[ -n "$apk_path" && -f "$apk_path" ]]; then TARGET_SERIAL="$(pick_connected_device_for_apk "$apk_path" || true)" else TARGET_SERIAL="$(first_connected_device)" fi if [[ -z "$TARGET_SERIAL" ]]; then if [[ "$START_EMULATOR_WHEN_NO_DEVICE" != "1" ]]; then echo "Erro: nenhum dispositivo Android conectado." echo "Para evitar consumo de memória, este script não inicia emulador por padrão." echo "Conecte um celular com USB debugging e rode novamente." echo "Se quiser forçar emulador: START_EMULATOR_WHEN_NO_DEVICE=1 ./scripts/android-run.sh" exit 1 fi if ! "$EMULATOR_BIN" -list-avds | grep -qx "$avd_to_boot"; then echo "Erro: AVD '$avd_to_boot' não existe. Disponíveis:" "$EMULATOR_BIN" -list-avds exit 1 fi echo "Iniciando emulador: $avd_to_boot" local emulator_args=("$EMULATOR_BIN" "-avd" "$avd_to_boot" "-no-snapshot-load") if [[ "$EMULATOR_HEADLESS" == "1" ]]; then emulator_args+=("-no-window" "-gpu" "swiftshader_indirect" "-no-audio") fi if [[ -n "$EMULATOR_MEMORY_MB" ]]; then emulator_args+=("-memory" "$EMULATOR_MEMORY_MB") fi nohup "${emulator_args[@]}" >/tmp/skip-android-emulator.log 2>&1 & EMULATOR_PID="$!" sleep 2 else BOOTED="$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" if [[ "$BOOTED" == "1" ]]; then echo "Dispositivo já pronto." else echo "Dispositivo conectado, aguardando finalização do boot." fi fi if [[ -z "$TARGET_SERIAL" ]]; then echo "Aguardando ADB conectar..." CONNECT_START_TS="$(date +%s)" while true; do if [[ -n "$apk_path" && -f "$apk_path" ]]; then TARGET_SERIAL="$(pick_connected_device_for_apk "$apk_path" || true)" else TARGET_SERIAL="$(first_connected_device)" fi if [[ -n "$TARGET_SERIAL" ]]; then break fi NOW_TS="$(date +%s)" if (( NOW_TS - CONNECT_START_TS >= ADB_CONNECT_TIMEOUT_SECONDS )); then echo "Timeout aguardando ADB conectar (${ADB_CONNECT_TIMEOUT_SECONDS}s)." echo "Estado atual do adb:" "$ADB_BIN" devices || true if [[ -n "$EMULATOR_PID" ]] && ! kill -0 "$EMULATOR_PID" 2>/dev/null; then echo "Processo do emulador encerrou antes da conexão." fi echo "Últimas linhas do log do emulador (/tmp/skip-android-emulator.log):" tail -n 80 /tmp/skip-android-emulator.log || true exit 1 fi if [[ -n "$EMULATOR_PID" ]] && ! kill -0 "$EMULATOR_PID" 2>/dev/null; then echo "Processo do emulador encerrou antes da conexão." echo "Últimas linhas do log do emulador (/tmp/skip-android-emulator.log):" tail -n 80 /tmp/skip-android-emulator.log || true exit 1 fi sleep 2 done fi echo "Aguardando boot do Android..." START_TS="$(date +%s)" until [[ "$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" == "1" ]]; do NOW_TS="$(date +%s)" if (( NOW_TS - START_TS >= BOOT_TIMEOUT_SECONDS )); then echo "Timeout aguardando boot do Android (${BOOT_TIMEOUT_SECONDS}s) no dispositivo $TARGET_SERIAL." "$ADB_BIN" devices || true tail -n 80 /tmp/skip-android-emulator.log || true exit 1 fi sleep 2 done if [[ -n "$apk_path" && -f "$apk_path" ]]; then local device_abi device_abi="$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r')" if [[ -n "$device_abi" ]] && ! apk_supports_abi "$apk_path" "$device_abi"; then echo "Erro: APK não possui ${REQUIRED_BRIDGE_SO} para ABI do dispositivo: $device_abi" echo "Tente outro AVD: x86_64 para APK x86_64, arm64-v8a para APK arm64." exit 1 fi fi } repair_skip_packages_links find_apk_path() { if [[ -f "$PROJECT_DIR/.build/skip-export/PediFoods-debug.apk" ]]; then echo "$PROJECT_DIR/.build/skip-export/PediFoods-debug.apk" return 0 fi local exported_apk exported_apk="$(find "$PROJECT_DIR/.build/skip-export" -type f -name '*-debug.apk' 2>/dev/null | head -n1 || true)" if [[ -n "$exported_apk" ]]; then echo "$exported_apk" return 0 fi if [[ -f "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk" ]]; then echo "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk" return 0 fi if [[ -f "$PROJECT_DIR/Android/app/build/outputs/apk/debug/app-debug.apk" ]]; then echo "$PROJECT_DIR/Android/app/build/outputs/apk/debug/app-debug.apk" return 0 fi find "$PROJECT_DIR/.build/Android" "$PROJECT_DIR/Android/app/build/outputs/apk/debug" -type f -name '*.apk' 2>/dev/null | head -n1 || true } APK_PATH="$(find_apk_path)" SOURCE_MTIME="$(find "$PROJECT_DIR/Sources" -type f -name '*.swift' -exec stat -f '%m' {} \; 2>/dev/null | sort -nr | head -n1 || echo 0)" SOURCE_MTIME="${SOURCE_MTIME:-0}" APK_MTIME=0 if [[ -n "$APK_PATH" && -f "$APK_PATH" ]]; then APK_MTIME="$(stat -f '%m' "$APK_PATH" 2>/dev/null || echo 0)" fi APK_STALE=0 if (( SOURCE_MTIME > APK_MTIME )); then APK_STALE=1 fi if [[ -z "$APK_PATH" || "${FORCE_EXPORT:-0}" == "1" || "$APK_STALE" == "1" ]]; then if [[ "$APK_STALE" == "1" && "${FORCE_EXPORT:-0}" != "1" ]]; then echo "APK desatualizado em relação ao código fonte. Tentando gerar novo APK..." fi echo "Gerando APK (skip export)..." if ! run_skip_export; then echo "Aviso: skip export falhou. Tentando usar último APK gerado..." if [[ -d "$PROJECT_DIR/Android" ]]; then echo "Tentando fallback via Gradle (:app:assembleDebug)..." ( cd "$PROJECT_DIR/Android" ANDROID_SDK_ROOT="$ANDROID_SDK_ROOT" gradle :app:assembleDebug --no-daemon ) || echo "Aviso: fallback Gradle também falhou." fi fi fi APK_PATH="$(find_apk_path)" if [[ -z "$APK_PATH" ]]; then if [[ "$ALLOW_INSTALLED_FALLBACK" == "1" ]]; then ensure_android_ready echo "Aviso: APK não encontrado. Tentando abrir app já instalado: $APP_ID" "$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1 echo "App aberto sem reinstalar (fallback)." exit 0 fi echo "Erro: APK não encontrado." exit 1 fi ensure_android_ready "$APK_PATH" if [[ "$REQUIRE_FRESH_APK" == "1" ]]; then APK_MTIME="$(stat -f '%m' "$APK_PATH" 2>/dev/null || echo 0)" SOURCE_MTIME="$(find "$PROJECT_DIR/Sources" -type f -name '*.swift' -exec stat -f '%m' {} \; 2>/dev/null | sort -nr | head -n1 || echo 0)" if (( SOURCE_MTIME > APK_MTIME )); then if [[ "$ALLOW_INSTALLED_FALLBACK" == "1" ]]; then echo "Aviso: APK continua desatualizado (build novo não gerado)." echo "Abrindo app já instalado para não bloquear o fluxo." "$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1 echo "App aberto sem reinstalar (fallback)." exit 0 fi echo "Erro: APK continua desatualizado (build novo não gerado)." echo "Não vou abrir versão antiga para evitar teste enganoso." echo "Dica: rode com REQUIRE_FRESH_APK=0 para abrir mesmo assim." exit 1 fi fi echo "Instalando APK: $APK_PATH" "$ADB_BIN" -s "$TARGET_SERIAL" install -r "$APK_PATH" LAUNCHABLE="$("$ADB_BIN" -s "$TARGET_SERIAL" shell cmd package resolve-activity --brief "$APP_ID" 2>/dev/null | tr -d '\r' | tail -n1)" if [[ "$LAUNCHABLE" == */* ]]; then echo "Abrindo app: $LAUNCHABLE" "$ADB_BIN" -s "$TARGET_SERIAL" shell am start -n "$LAUNCHABLE" else echo "Abrindo app (fallback monkey): $APP_ID" "$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1 fi echo "Validando execução do app..." sleep 3 APP_PID="$("$ADB_BIN" -s "$TARGET_SERIAL" shell pidof "$APP_ID" 2>/dev/null | tr -d '\r')" if [[ -z "$APP_PID" ]]; then echo "Erro: app não permaneceu em execução após abrir." "$ADB_BIN" -s "$TARGET_SERIAL" logcat -d | tail -n 120 || true exit 1 fi sleep "$RUN_STABILITY_SECONDS" APP_PID_AFTER="$("$ADB_BIN" -s "$TARGET_SERIAL" shell pidof "$APP_ID" 2>/dev/null | tr -d '\r')" if [[ -z "$APP_PID_AFTER" ]]; then echo "Erro: app encerrou durante validação de estabilidade (${RUN_STABILITY_SECONDS}s)." "$ADB_BIN" -s "$TARGET_SERIAL" logcat -d | tail -n 160 || true exit 1 fi echo "App instalado e aberto no Android."