build: stabilize Android particle validation APK pipeline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
68
docs/00_项目规范/07_Android粒子Validation_APK构建规范_v1.md
Normal file
68
docs/00_项目规范/07_Android粒子Validation_APK构建规范_v1.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Android 粒子 Validation APK 构建规范 v1
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
固定 `MapLibreAndroidTestApp` 粒子验证 APK 的制作流程,避免每次重新调查 Gradle 参数、Java/Android SDK 环境、native strip 与 APK 体积异常。
|
||||||
|
|
||||||
|
权威入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tools/particle_android_validation/build_validation_apk.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
除排错外,不再手工拼接 Validation APK 的 Gradle 命令。
|
||||||
|
|
||||||
|
## 固定构建契约
|
||||||
|
|
||||||
|
```text
|
||||||
|
App: MapLibreAndroidTestApp
|
||||||
|
Renderer: OpenGL
|
||||||
|
BuildType: Debug
|
||||||
|
ABI: x86_64
|
||||||
|
Native debug symbols: strip
|
||||||
|
Gradle console: plain
|
||||||
|
```
|
||||||
|
|
||||||
|
核心参数固定为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
:MapLibreAndroidTestApp:assembleOpenglDebug
|
||||||
|
-Pmaplibre.abis=x86_64
|
||||||
|
-Pmaplibre.validation.stripNativeDebugSymbols=true
|
||||||
|
--console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
## 必须执行的防错逻辑
|
||||||
|
|
||||||
|
1. 构建前删除目标 APK 文件,但不做全量 Gradle clean。
|
||||||
|
2. Java 必须为 17;Android SDK 必须可解析。
|
||||||
|
3. 构建失败时不得复用旧 APK。
|
||||||
|
4. 构建成功后必须校验 APK ZIP 完整性。
|
||||||
|
5. APK 内 native ABI 必须只有 `x86_64`。
|
||||||
|
6. 必须包含 `lib/x86_64/libmaplibre.so`。
|
||||||
|
7. 默认 `libmaplibre.so` 不得超过 80 MiB。
|
||||||
|
8. 默认 APK 不得超过 100 MiB。
|
||||||
|
9. 必须记录 SHA-256、commit、branch、dirty 状态和完整 Gradle 日志。
|
||||||
|
|
||||||
|
STEP-090/091 的正常 APK 参考体积约 57 MB。数百 MB 通常表示未 strip native debug symbols;约 95 MB 且 ZIP 逻辑内容仍约 60 MB,通常表示同一路径增量打包残留旧 ZIP 尾部数据。
|
||||||
|
|
||||||
|
## 标准产物目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
artifacts/maplibre-particle/android-validation/
|
||||||
|
├── MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
|
||||||
|
├── build-info.txt
|
||||||
|
└── build.log
|
||||||
|
```
|
||||||
|
|
||||||
|
该目录为本地验证产物目录,不进入 Git。
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
本 Pipeline 只负责构建和 APK 自验,不负责 ADB 安装、Activity 启动、设备交互或 STEP 业务场景验收。
|
||||||
|
|
||||||
|
Android 上机验证仍由后续独立步骤执行。构建成功不等于 STEP 场景已通过。
|
||||||
|
|
||||||
|
## 变更规则
|
||||||
|
|
||||||
|
若未来测试 App、ABI、Renderer、strip 策略或产物门槛发生正式变化,应先修改 Pipeline 脚本与本规范,再使用新流程;不得只在聊天或临时命令中形成新的事实基线。
|
||||||
113
tools/particle_android_validation/README.md
Normal file
113
tools/particle_android_validation/README.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Android Particle Validation APK Pipeline
|
||||||
|
|
||||||
|
这是 `MapLibreAndroidTestApp` 粒子验证 APK 的唯一推荐构建入口。
|
||||||
|
|
||||||
|
以后不要再手工回忆 Gradle 参数,也不要直接拿 `build/outputs` 目录中的旧 APK 做验证。
|
||||||
|
|
||||||
|
## 一键构建
|
||||||
|
|
||||||
|
在 Mac 的仓库根目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tools/particle_android_validation/build_validation_apk.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline 固定执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapLibreAndroidTestApp
|
||||||
|
OpenGL Debug
|
||||||
|
ABI = x86_64
|
||||||
|
stripNativeDebugSymbols = true
|
||||||
|
```
|
||||||
|
|
||||||
|
对应 Gradle 核心参数为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
:MapLibreAndroidTestApp:assembleOpenglDebug
|
||||||
|
-Pmaplibre.abis=x86_64
|
||||||
|
-Pmaplibre.validation.stripNativeDebugSymbols=true
|
||||||
|
--console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
## 稳定产物
|
||||||
|
|
||||||
|
成功后统一输出到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
artifacts/maplibre-particle/android-validation/
|
||||||
|
├── MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
|
||||||
|
├── build-info.txt
|
||||||
|
└── build.log
|
||||||
|
```
|
||||||
|
|
||||||
|
`artifacts/maplibre-particle/` 已在 `.gitignore` 中,不进入 Git。
|
||||||
|
|
||||||
|
`build-info.txt` 会记录 commit、branch、dirty 状态、构建时间、ABI、APK 大小、`libmaplibre.so` 大小和 SHA-256。
|
||||||
|
|
||||||
|
## Pipeline 为什么必须存在
|
||||||
|
|
||||||
|
历史上有两个容易重复踩中的问题:
|
||||||
|
|
||||||
|
1. 少传 `-Pmaplibre.validation.stripNativeDebugSymbols=true` 时,Debug `libmaplibre.so` 会包含大量 native debug symbols,APK 可膨胀到数百 MB。
|
||||||
|
2. 在同一路径先生成超大 APK、再切换 strip 参数增量打包时,APK 文件可能保留旧 ZIP 尾部数据,出现约 95 MB 的物理文件;因此 Pipeline 在每次构建前都会先删除最终 APK,只重新 package,不执行全量 Gradle clean。
|
||||||
|
|
||||||
|
STEP-090/091 的正常 x86_64 Validation APK 基线约为 57 MB。
|
||||||
|
|
||||||
|
## 自动环境处理
|
||||||
|
|
||||||
|
Pipeline 会优先使用已有 `JAVA_HOME` / `ANDROID_HOME`。
|
||||||
|
|
||||||
|
若未设置,会在当前 Mac 上自动尝试:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Java 17:
|
||||||
|
/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
|
||||||
|
/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
|
||||||
|
/usr/libexec/java_home -v 17
|
||||||
|
|
||||||
|
Android SDK:
|
||||||
|
~/Library/Android/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
Java 不是 17、Android SDK 不存在或 Gradle 失败时直接停止,不会把旧 APK 当成成功产物。
|
||||||
|
|
||||||
|
## 构建后自动验收
|
||||||
|
|
||||||
|
Pipeline 会检查:
|
||||||
|
|
||||||
|
- APK ZIP 完整性;
|
||||||
|
- native ABI 必须只有 `x86_64`;
|
||||||
|
- 必须存在 `lib/x86_64/libmaplibre.so`;
|
||||||
|
- `libmaplibre.so` 默认不得超过 80 MiB;
|
||||||
|
- APK 默认不得超过 100 MiB;
|
||||||
|
- 生成 SHA-256;
|
||||||
|
- 保存完整 Gradle 日志。
|
||||||
|
|
||||||
|
超过体积门槛默认视为 pipeline 失败,以防再次交付未 strip 或带旧 ZIP 尾部的 APK。
|
||||||
|
|
||||||
|
如未来 Validation App 因真实资源增长需要调整门槛,可显式覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MAPLIBRE_VALIDATION_MAX_APK_BYTES=134217728 \
|
||||||
|
MAPLIBRE_VALIDATION_MAX_LIBMAPLIBRE_BYTES=94371840 \
|
||||||
|
./tools/particle_android_validation/build_validation_apk.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以修改产物目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MAPLIBRE_VALIDATION_OUT_DIR=/tmp/my-validation \
|
||||||
|
./tools/particle_android_validation/build_validation_apk.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
这个 Pipeline 只负责**制作和验收 APK**,不自动执行:
|
||||||
|
|
||||||
|
- ADB 安装;
|
||||||
|
- 启动 Activity;
|
||||||
|
- 真机/模拟器交互;
|
||||||
|
- STEP 场景验收。
|
||||||
|
|
||||||
|
这样构建产物与上机验证保持分离,避免脚本意外操作设备。
|
||||||
141
tools/particle_android_validation/build_validation_apk.sh
Executable file
141
tools/particle_android_validation/build_validation_apk.sh
Executable file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
ANDROID_DIR="$REPO_ROOT/platform/android"
|
||||||
|
SOURCE_APK="$ANDROID_DIR/MapLibreAndroidTestApp/build/outputs/apk/opengl/debug/MapLibreAndroidTestApp-opengl-debug.apk"
|
||||||
|
OUT_DIR="${MAPLIBRE_VALIDATION_OUT_DIR:-$REPO_ROOT/artifacts/maplibre-particle/android-validation}"
|
||||||
|
OUT_APK="$OUT_DIR/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk"
|
||||||
|
OUT_INFO="$OUT_DIR/build-info.txt"
|
||||||
|
OUT_LOG="$OUT_DIR/build.log"
|
||||||
|
|
||||||
|
MAX_APK_BYTES="${MAPLIBRE_VALIDATION_MAX_APK_BYTES:-104857600}"
|
||||||
|
MAX_LIB_BYTES="${MAPLIBRE_VALIDATION_MAX_LIBMAPLIBRE_BYTES:-83886080}"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[validation-apk] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_java_home() {
|
||||||
|
if [[ -n "${JAVA_HOME:-}" && -x "$JAVA_HOME/bin/java" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local candidate
|
||||||
|
for candidate in /usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home; do
|
||||||
|
if [[ -x "$candidate/bin/java" ]]; then
|
||||||
|
export JAVA_HOME="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -x /usr/libexec/java_home ]]; then
|
||||||
|
candidate="$(/usr/libexec/java_home -v 17 2>/dev/null || true)"
|
||||||
|
if [[ -n "$candidate" && -x "$candidate/bin/java" ]]; then
|
||||||
|
export JAVA_HOME="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail "Java 17 not found. Set JAVA_HOME to a Java 17 JDK."
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_android_home() {
|
||||||
|
local candidate="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
||||||
|
if [[ -n "$candidate" && -d "$candidate" ]]; then
|
||||||
|
export ANDROID_HOME="$candidate"
|
||||||
|
export ANDROID_SDK_ROOT="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
candidate="$HOME/Library/Android/sdk"
|
||||||
|
if [[ -d "$candidate" ]]; then
|
||||||
|
export ANDROID_HOME="$candidate"
|
||||||
|
export ANDROID_SDK_ROOT="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail "Android SDK not found. Set ANDROID_HOME or ANDROID_SDK_ROOT."
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_java_home
|
||||||
|
resolve_android_home
|
||||||
|
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$PATH"
|
||||||
|
|
||||||
|
JAVA_VERSION="$("$JAVA_HOME/bin/java" -version 2>&1 | head -n 1)"
|
||||||
|
[[ "$JAVA_VERSION" == *'"17.'* ]] || fail "Java 17 required; got: $JAVA_VERSION"
|
||||||
|
[[ -x "$ANDROID_DIR/gradlew" ]] || fail "Gradle wrapper not found: $ANDROID_DIR/gradlew"
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
rm -f "$SOURCE_APK" "$OUT_APK"
|
||||||
|
|
||||||
|
COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD)"
|
||||||
|
BRANCH="$(git -C "$REPO_ROOT" branch --show-current)"
|
||||||
|
DIRTY_COUNT="$(git -C "$REPO_ROOT" status --porcelain | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
echo "[validation-apk] commit=$COMMIT branch=$BRANCH"
|
||||||
|
echo "[validation-apk] java=$JAVA_VERSION"
|
||||||
|
echo "[validation-apk] android_sdk=$ANDROID_HOME"
|
||||||
|
echo "[validation-apk] output=$OUT_APK"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
(
|
||||||
|
cd "$ANDROID_DIR"
|
||||||
|
./gradlew :MapLibreAndroidTestApp:assembleOpenglDebug -Pmaplibre.abis=x86_64 -Pmaplibre.validation.stripNativeDebugSymbols=true --console=plain
|
||||||
|
) >"$OUT_LOG" 2>&1
|
||||||
|
GRADLE_RC=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ "$GRADLE_RC" -ne 0 ]]; then
|
||||||
|
tail -n 120 "$OUT_LOG" >&2 || true
|
||||||
|
fail "Gradle build failed with exit code $GRADLE_RC. Full log: $OUT_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$SOURCE_APK" ]] || fail "Build succeeded but APK was not produced."
|
||||||
|
unzip -tq "$SOURCE_APK" >/dev/null || fail "APK ZIP integrity check failed."
|
||||||
|
|
||||||
|
ABI_LIST="$(unzip -Z1 "$SOURCE_APK" | sed -n 's#^lib/\([^/]*\)/.*#\1#p' | sort -u | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
[[ "$ABI_LIST" == "x86_64" ]] || fail "Unexpected APK ABI set: [$ABI_LIST]"
|
||||||
|
|
||||||
|
LIB_BYTES="$(unzip -l "$SOURCE_APK" 'lib/x86_64/libmaplibre.so' | awk '/lib\/x86_64\/libmaplibre\.so$/ {print $1; exit}')"
|
||||||
|
[[ -n "$LIB_BYTES" ]] || fail "lib/x86_64/libmaplibre.so not found in APK."
|
||||||
|
|
||||||
|
APK_BYTES="$(wc -c < "$SOURCE_APK" | tr -d ' ')"
|
||||||
|
if (( LIB_BYTES > MAX_LIB_BYTES )); then
|
||||||
|
fail "libmaplibre.so is too large ($LIB_BYTES bytes). Native debug symbols may not be stripped."
|
||||||
|
fi
|
||||||
|
if (( APK_BYTES > MAX_APK_BYTES )); then
|
||||||
|
fail "APK is too large ($APK_BYTES bytes). Refuse to publish a likely stale/unstripped package."
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -f "$SOURCE_APK" "$OUT_APK"
|
||||||
|
SHA256="$(shasum -a 256 "$OUT_APK" | awk '{print $1}')"
|
||||||
|
BUILT_AT_UTC="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||||
|
GIT_DIRTY="false"
|
||||||
|
[[ "$DIRTY_COUNT" -gt 0 ]] && GIT_DIRTY="true"
|
||||||
|
|
||||||
|
cat >"$OUT_INFO" <<EOF
|
||||||
|
pipeline=particle-android-validation-v1
|
||||||
|
commit=$COMMIT
|
||||||
|
branch=$BRANCH
|
||||||
|
git_dirty=$GIT_DIRTY
|
||||||
|
built_at_utc=$BUILT_AT_UTC
|
||||||
|
variant=OpenglDebug
|
||||||
|
abi=x86_64
|
||||||
|
strip_native_debug_symbols=true
|
||||||
|
apk=$OUT_APK
|
||||||
|
apk_bytes=$APK_BYTES
|
||||||
|
libmaplibre_bytes=$LIB_BYTES
|
||||||
|
sha256=$SHA256
|
||||||
|
gradle_log=$OUT_LOG
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "[validation-apk] BUILD SUCCESS"
|
||||||
|
echo "[validation-apk] apk=$OUT_APK"
|
||||||
|
echo "[validation-apk] apk_bytes=$APK_BYTES"
|
||||||
|
echo "[validation-apk] libmaplibre_bytes=$LIB_BYTES"
|
||||||
|
echo "[validation-apk] sha256=$SHA256"
|
||||||
|
echo "[validation-apk] info=$OUT_INFO"
|
||||||
|
echo "[validation-apk] log=$OUT_LOG"
|
||||||
Reference in New Issue
Block a user