11ca7531a3
Deploy Client / build-android (push) Successful in 1m46s
Deploy Client / build-windows (push) Successful in 1m55s
Deploy Client / build-macos (push) Successful in 3m40s
Deploy Client / build-ios (push) Successful in 3m28s
Deploy Client / release-deploy (push) Successful in 2m14s
iOS build 曾因 flutter build 内隐式 pub get 拉 google_fonts 时 pub.flutter-io.cn socket error(exit 69)而失败。_env.sh 加 flutter_pub_get_retry(5 次重试),四个 compile 脚本在 flutter build 前显式预取,成功后 build 命中缓存不再拉网,减少偶发 网络失败。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
158 lines
8.3 KiB
Bash
Executable File
158 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# compile-android.sh <tag> — build+embed libbox.aar, build a signed Flutter
|
|
# Android APK, package into dist/.
|
|
#
|
|
# Mirrors ~/code/jiu/scripts/ci/compile-android.sh, adapted for pangolin:
|
|
# - version derived via pangolin's ver_from_tag (client-v* tag; _env.sh),
|
|
# not jiu's ad-hoc `${TAG#client-v}` strip.
|
|
# - pangolin embeds sing-box as a native library (io.nekohasekai.libbox,
|
|
# gomobile bind) — jiu's Flutter app has no embedded core at all. This
|
|
# script MUST build+deploy libbox.aar (scripts/build-libbox.sh android)
|
|
# before `flutter build apk`, or the APK links no VPN kernel at all.
|
|
# Needs JDK 17 (gomobile bind javac step) — see CLAUDE.md "client/ 移动端"
|
|
# and "已知坑" for why: JDK 21 is rejected outright, NDK 27 fails link.
|
|
# - signing secrets: RELEASE_KEYSTORE (base64) + KEY_PASSWORD are the
|
|
# secret names given for this task ("actual configured" names). jiu uses
|
|
# 4 separate secrets (ANDROID_KEYSTORE_BASE64/ANDROID_KEYSTORE_PASSWORD/
|
|
# ANDROID_KEY_ALIAS/ANDROID_KEY_PASSWORD), and pangolin's OWN prior CI
|
|
# design docs (docs/cicd-design.html, docs/superpowers/plans/2026-07-05-
|
|
# cicd.md, docs/superpowers/specs/2026-07-05-cicd-design.md) planned the
|
|
# same 4-secret jiu-style naming. That is a real conflict with this
|
|
# task's brief — see the TODO(controller) block below and the draft
|
|
# report. This script follows the 2-secret brief as instructed, assuming
|
|
# storePassword == keyPassword == KEY_PASSWORD and a derivable keyAlias
|
|
# (default "pangolin", overridable via optional RELEASE_KEY_ALIAS).
|
|
# - dart-define is PANGOLIN_API_URL (client/lib/services/api_config.dart),
|
|
# not jiu's BASE_URL/PUBLIC_URL/APP_VERSION trio.
|
|
# - build.gradle previously signed release builds with signingConfigs.debug
|
|
# (no release signing config existed at all) — this task's draft also
|
|
# wires client/android/app/build.gradle to add a `release` signingConfig
|
|
# that reads client/android/key.properties (falls back to debug when
|
|
# key.properties is absent, so local/no-secret builds still work). See
|
|
# report for the exact diff.
|
|
# - universal APK (`flutter build apk --release`, no --split-per-abi) — see
|
|
# report for the size-vs-single-stable-URL tradeoff discussion; this
|
|
# deviates from docs/superpowers/plans/2026-07-05-cicd.md Task 7 which
|
|
# had planned --split-per-abi.
|
|
# - output dist/pangolin-android.apk (stable name; deploy-client.sh keeps
|
|
# only the latest one under this name on pangolin1).
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=scripts/ci/_env.sh
|
|
. "${SCRIPT_DIR}/_env.sh"
|
|
|
|
TAG="${1:?usage: compile-android.sh <tag>}"
|
|
|
|
# No $() anywhere (repo convention): ver_from_tag prints to stdout, captured
|
|
# via a temp file + `read`, same pattern as release-server.sh.
|
|
ver_file="/tmp/compile_android_ver.$$"
|
|
ver_from_tag client "$TAG" > "$ver_file"
|
|
VER=""
|
|
# `|| true`: ver_from_tag uses printf '%s' (no trailing newline); `read`
|
|
# hitting EOF without a newline returns 1 even though VER was assigned —
|
|
# under `set -e` that would abort here. Same rationale as release-server.sh.
|
|
read -r VER < "$ver_file" || true
|
|
rm -f "$ver_file"
|
|
|
|
# versionCode 必须单调递增,否则 Android 无法覆盖升级安装(同 jiu 注释)。
|
|
# 由版本号推导:major*10000 + minor*100 + patch(如 1.0.48 -> 10048)。
|
|
# Pure parameter expansion (no `cut`/`$()`), and strip any `-suffix` off the
|
|
# patch component so the arithmetic below stays numeric (e.g. "48-rc1"->48).
|
|
MAJOR="${VER%%.*}"
|
|
_REST="${VER#*.}"
|
|
MINOR="${_REST%%.*}"
|
|
PATCH="${_REST#*.}"
|
|
PATCH="${PATCH%%-*}"
|
|
BUILD=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
|
|
|
echo "==> compile-android: tag=${TAG} version=${VER} build=${BUILD}"
|
|
|
|
API_URL="${PANGOLIN_API_URL:-https://api.yanmeiai.com}"
|
|
|
|
# ── [1/3] build+embed libbox.aar (io.nekohasekai.libbox) ────────────────────
|
|
# Must run BEFORE `flutter build apk` — build.gradle's dependency block reads
|
|
# app/kernel/dist/android/libbox.aar and just logs a warning (does not fail
|
|
# the build!) if it's missing, which would silently ship an APK with no VPN
|
|
# kernel. Fail loudly here instead.
|
|
JAVA_HOME_17="${JAVA_HOME_17:-/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home}"
|
|
if [ ! -d "$JAVA_HOME_17" ]; then
|
|
echo "ERROR: JDK 17 not found at ${JAVA_HOME_17} (required for gomobile Android bind)." >&2
|
|
echo " See CLAUDE.md 'libbox 构建' — install via 'brew install openjdk@17' or set JAVA_HOME_17." >&2
|
|
exit 1
|
|
fi
|
|
echo "==> compile-android: building libbox.aar (JAVA_HOME=${JAVA_HOME_17}, NDK auto-detected >=28)"
|
|
JAVA_HOME="$JAVA_HOME_17" bash scripts/build-libbox.sh android
|
|
|
|
LIBBOX_AAR="app/kernel/dist/android/libbox.aar"
|
|
if [ ! -f "$LIBBOX_AAR" ]; then
|
|
echo "ERROR: ${LIBBOX_AAR} not found after build-libbox.sh — aborting." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── [2/3] sync pubspec version + release signing ────────────────────────────
|
|
# BSD sed (macOS runner — mac-pangolin-2, see docs/ci-runner.md), same as jiu.
|
|
sed -i '' "s/^version:.*/version: ${VER}+${BUILD}/" client/pubspec.yaml
|
|
|
|
# 必须 release 签名。缺任一签名 secret 直接失败,绝不回退 debug:debug 包用公开
|
|
# 调试密钥签名,不能正式分发,也无法与正式版互相覆盖升级(同 jiu 注释)。
|
|
: "${RELEASE_KEYSTORE:?缺少 RELEASE_KEYSTORE(base64 编码的 release keystore):未配置签名,构建中止(不回退 debug)}"
|
|
: "${KEY_PASSWORD:?缺少 KEY_PASSWORD:未配置签名,构建中止}"
|
|
|
|
# TODO(controller): confirm this 2-secret assumption before relying on it.
|
|
# - Assumes storePassword == keyPassword == KEY_PASSWORD (true only if the
|
|
# keystore was generated with the same password for both).
|
|
# - keyAlias is NOT among the secrets this task named, so it defaults to
|
|
# "pangolin" below — override with RELEASE_KEY_ALIAS if the real alias
|
|
# differs (e.g. keytool default "androiddebugkey" is NOT this — that
|
|
# alias belongs to the debug keystore, never use it for release signing).
|
|
# - pangolin's OWN prior CI design docs (docs/cicd-design.html §secrets,
|
|
# docs/superpowers/plans/2026-07-05-cicd.md Task 6/7,
|
|
# docs/superpowers/specs/2026-07-05-cicd-design.md) planned 4 separate
|
|
# secrets instead: ANDROID_KEYSTORE_BASE64 / ANDROID_KEYSTORE_PASSWORD /
|
|
# ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD. If THAT naming is what is
|
|
# actually configured in Forgejo (not RELEASE_KEYSTORE/KEY_PASSWORD),
|
|
# this script needs updating before first real run.
|
|
RELEASE_KEY_ALIAS="${RELEASE_KEY_ALIAS:-pangolin}"
|
|
|
|
echo "==> compile-android: configuring release signing (keyAlias=${RELEASE_KEY_ALIAS})"
|
|
KEYSTORE_PATH="${PWD}/client/android/pangolin-release.jks"
|
|
printf '%s' "$RELEASE_KEYSTORE" | base64 --decode > "$KEYSTORE_PATH"
|
|
cat > client/android/key.properties <<EOF
|
|
storeFile=${KEYSTORE_PATH}
|
|
storePassword=${KEY_PASSWORD}
|
|
keyAlias=${RELEASE_KEY_ALIAS}
|
|
keyPassword=${KEY_PASSWORD}
|
|
EOF
|
|
|
|
# ── [3/3] flutter build apk (--split-per-abi) ───────────────────────────────
|
|
# split-per-abi 而非 universal:嵌入的 libbox.so(gomobile 编的 sing-box)每 ABI
|
|
# ~60MB,universal 三合一直接 232MB,做下载链接太大。拆分后取 **arm64-v8a**(覆盖
|
|
# 现代绝大多数 Android 机)作为唯一下载,约 ~80MB;32 位老机(armeabi-v7a)/模拟器
|
|
# (x86_64)本轮不分发(需要再加)。这样官网 + deploy-client 仍是单一稳定 URL。
|
|
cd client
|
|
flutter_pub_get_retry # 预取包 + 重试,防 pub.flutter-io.cn 被 GFW 抖断(见 _env.sh)
|
|
flutter build apk --release --split-per-abi \
|
|
"--dart-define=PANGOLIN_API_URL=${API_URL}"
|
|
cd ..
|
|
|
|
# Locate the arm64-v8a APK (path differs across Flutter versions) and copy to dist/
|
|
mkdir -p dist
|
|
APK=""
|
|
for cand in \
|
|
client/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
|
|
client/build/app/outputs/apk/release/app-arm64-v8a-release.apk; do
|
|
if [ -f "$cand" ]; then APK="$cand"; break; fi
|
|
done
|
|
if [ -z "$APK" ]; then
|
|
echo "ERROR: built arm64-v8a APK not found" >&2
|
|
exit 1
|
|
fi
|
|
cp "$APK" dist/pangolin-android.apk
|
|
|
|
# Clean up signing material so it never lingers on the runner workspace.
|
|
rm -f client/android/key.properties "$KEYSTORE_PATH" 2>/dev/null || true
|
|
|
|
echo "==> compile-android: done — dist/ contents:"
|
|
ls -lh dist/
|