163 lines
6.3 KiB
Bash
Executable File
163 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
set -euo pipefail
|
||
|
||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
MOBILE_DIR="$PROJECT_ROOT/apps/mobile"
|
||
ANDROID_DIR="$MOBILE_DIR/android"
|
||
MODE="${1:-fast}"
|
||
|
||
if [[ "$MODE" != "fast" && "$MODE" != "apk" && "$MODE" != "play" ]]; then
|
||
echo "用法: $0 [fast|apk|play]" >&2
|
||
exit 2
|
||
fi
|
||
|
||
if [[ -z "${JAVA_HOME:-}" && -d /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home ]]; then
|
||
export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
|
||
fi
|
||
|
||
if [[ -n "${JAVA_HOME:-}" ]]; then
|
||
export PATH="$JAVA_HOME/bin:$PATH"
|
||
fi
|
||
|
||
if ! command -v java >/dev/null 2>&1; then
|
||
echo "未找到 Java 17。请先执行: brew install openjdk@17" >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [[ -z "${ANDROID_HOME:-}" && -d /opt/homebrew/share/android-commandlinetools ]]; then
|
||
export ANDROID_HOME=/opt/homebrew/share/android-commandlinetools
|
||
fi
|
||
export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}"
|
||
export NODE_ENV="${NODE_ENV:-production}"
|
||
|
||
run_timed() {
|
||
local label="$1"
|
||
shift
|
||
local started_at=$SECONDS
|
||
|
||
echo "[$(date '+%H:%M:%S')] 开始:${label}"
|
||
"$@"
|
||
echo "[$(date '+%H:%M:%S')] 完成:${label}($((SECONDS - started_at)) 秒)"
|
||
}
|
||
|
||
if [[ -z "$ANDROID_SDK_ROOT" ]]; then
|
||
echo "未找到 Android SDK,请设置 ANDROID_HOME 或 ANDROID_SDK_ROOT。" >&2
|
||
exit 1
|
||
fi
|
||
|
||
cd "$PROJECT_ROOT"
|
||
run_timed "安装 JavaScript 依赖" bun install --frozen-lockfile
|
||
run_timed "准备移动端图标" bun run prepare:mobile:icons
|
||
|
||
PREBUILD_FINGERPRINT="$({
|
||
shasum \
|
||
apps/mobile/app.json \
|
||
apps/mobile/assets/adaptive-icon-transparent.svg \
|
||
apps/web/public/pwa-512x512.png \
|
||
bun.lock
|
||
} | shasum | awk '{print $1}')"
|
||
PREBUILD_STAMP="$ANDROID_DIR/.edgeever-prebuild-fingerprint"
|
||
PREVIOUS_FINGERPRINT="$(test -f "$PREBUILD_STAMP" && cat "$PREBUILD_STAMP" || true)"
|
||
|
||
if [[ ! -x "$ANDROID_DIR/gradlew" || "$PREBUILD_FINGERPRINT" != "$PREVIOUS_FINGERPRINT" ]]; then
|
||
echo "更新 Android 原生工程(保留已有编译缓存)..."
|
||
cd "$MOBILE_DIR"
|
||
run_timed "生成 Android 原生工程" bunx expo prebuild --platform android
|
||
printf '%s' "$PREBUILD_FINGERPRINT" > "$PREBUILD_STAMP"
|
||
else
|
||
echo "跳过 Android prebuild:原生配置未变化。"
|
||
fi
|
||
|
||
cd "$ANDROID_DIR"
|
||
COMMON_ARGS=(
|
||
--build-cache
|
||
--parallel
|
||
--daemon
|
||
-Dorg.gradle.jvmargs=-Xmx6g\ -XX:MaxMetaspaceSize=1g\ -Dfile.encoding=UTF-8
|
||
)
|
||
|
||
: "${ANDROID_KEYSTORE_FILE:?请设置 ANDROID_KEYSTORE_FILE(本地上传密钥路径)}"
|
||
: "${ANDROID_KEYSTORE_PASSWORD:?请设置 ANDROID_KEYSTORE_PASSWORD}"
|
||
: "${ANDROID_KEY_ALIAS:?请设置 ANDROID_KEY_ALIAS}"
|
||
: "${ANDROID_KEY_PASSWORD:?请设置 ANDROID_KEY_PASSWORD}"
|
||
|
||
PLAY_ARCHS="${EDGE_EVER_ANDROID_PLAY_ARCHS:-arm64-v8a}"
|
||
APK_ARCHS="${EDGE_EVER_ANDROID_APK_ARCHS:-arm64-v8a}"
|
||
KEYSTORE_FILE="$(cd "$(dirname "$ANDROID_KEYSTORE_FILE")" && pwd)/$(basename "$ANDROID_KEYSTORE_FILE")"
|
||
APK_PATH="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
||
APKSIGNER_PATH="$ANDROID_SDK_ROOT/build-tools/36.0.0/apksigner"
|
||
|
||
if [[ "$MODE" == "fast" ]]; then
|
||
echo "构建生产签名 arm64 快速 APK(关闭 R8、资源压缩和 PNG crunch)..."
|
||
run_timed "Gradle 快速 APK 构建" ./gradlew assembleRelease \
|
||
"${COMMON_ARGS[@]}" \
|
||
-PreactNativeArchitectures=arm64-v8a \
|
||
-Pandroid.enableMinifyInReleaseBuilds=false \
|
||
-Pandroid.enableShrinkResourcesInReleaseBuilds=false \
|
||
-Pandroid.enablePngCrunchInReleaseBuilds=false \
|
||
-Pandroid.injected.signing.store.file="$KEYSTORE_FILE" \
|
||
-Pandroid.injected.signing.store.password="$ANDROID_KEYSTORE_PASSWORD" \
|
||
-Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \
|
||
-Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD" \
|
||
-Pandroid.injected.signing.store.type=PKCS12
|
||
test -s "$APK_PATH"
|
||
test -x "$APKSIGNER_PATH"
|
||
run_timed "校验 APK 固定签名" \
|
||
node "$PROJECT_ROOT/scripts/verify-android-apk-signature.mjs" \
|
||
"$APK_PATH" "$APKSIGNER_PATH"
|
||
echo "完成: $APK_PATH"
|
||
exit 0
|
||
fi
|
||
|
||
if [[ "$MODE" == "apk" ]]; then
|
||
echo "构建生产签名 APK(${APK_ARCHS})..."
|
||
run_timed "Gradle 生产 APK 构建" ./gradlew assembleRelease \
|
||
"${COMMON_ARGS[@]}" \
|
||
-PreactNativeArchitectures="$APK_ARCHS" \
|
||
-Pandroid.injected.signing.store.file="$KEYSTORE_FILE" \
|
||
-Pandroid.injected.signing.store.password="$ANDROID_KEYSTORE_PASSWORD" \
|
||
-Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \
|
||
-Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD" \
|
||
-Pandroid.injected.signing.store.type=PKCS12
|
||
AAPT2_PATH="$ANDROID_SDK_ROOT/build-tools/36.0.0/aapt2"
|
||
test -s "$APK_PATH"
|
||
test -x "$APKSIGNER_PATH"
|
||
test -x "$AAPT2_PATH"
|
||
run_timed "校验 APK 固定签名" \
|
||
node "$PROJECT_ROOT/scripts/verify-android-apk-signature.mjs" \
|
||
"$APK_PATH" "$APKSIGNER_PATH"
|
||
run_timed "读取 APK 信息" bash -c '"$1" dump badging "$2" | sed -n "1p"' _ "$AAPT2_PATH" "$APK_PATH"
|
||
run_timed "计算 APK SHA-256" shasum -a 256 "$APK_PATH"
|
||
echo "完成: $APK_PATH"
|
||
exit 0
|
||
fi
|
||
|
||
echo "构建 Play 签名 AAB(${PLAY_ARCHS})..."
|
||
run_timed "Gradle 生产 AAB 构建" ./gradlew bundleRelease \
|
||
"${COMMON_ARGS[@]}" \
|
||
-PreactNativeArchitectures="$PLAY_ARCHS" \
|
||
-Pandroid.injected.signing.store.file="$KEYSTORE_FILE" \
|
||
-Pandroid.injected.signing.store.password="$ANDROID_KEYSTORE_PASSWORD" \
|
||
-Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \
|
||
-Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD" \
|
||
-Pandroid.injected.signing.store.type=PKCS12
|
||
|
||
AAB_PATH="app/build/outputs/bundle/release/app-release.aab"
|
||
PACKAGED_MANIFEST="app/build/intermediates/packaged_manifests/release/processReleaseManifestForPackage/AndroidManifest.xml"
|
||
test -s "$PACKAGED_MANIFEST"
|
||
if grep -q "android.permission.REQUEST_INSTALL_PACKAGES" "$PACKAGED_MANIFEST"; then
|
||
echo "Play AAB 的最终合并清单仍包含 REQUEST_INSTALL_PACKAGES,停止发布。" >&2
|
||
exit 1
|
||
fi
|
||
ACTUAL_PLAY_ARCHS="$(unzip -Z1 "$AAB_PATH" | sed -n 's#^base/lib/\([^/]*\)/.*#\1#p' | sort -u | paste -sd, -)"
|
||
if [[ "$ACTUAL_PLAY_ARCHS" != "$PLAY_ARCHS" ]]; then
|
||
echo "AAB 架构不符合预期:期望 ${PLAY_ARCHS},实际 ${ACTUAL_PLAY_ARCHS:-无原生库}。" >&2
|
||
exit 1
|
||
fi
|
||
|
||
run_timed "校验 AAB 签名" jarsigner -verify "$AAB_PATH"
|
||
test -s app/build/outputs/mapping/release/mapping.txt
|
||
echo "完成: $ANDROID_DIR/$AAB_PATH"
|
||
echo "反混淆文件: $ANDROID_DIR/app/build/outputs/mapping/release/mapping.txt"
|