#!/bin/bash # ============================================================ # OpenDance Studio —— Android 辅助 App 发布打包 # # 产物(文档 §6.3 / §11.2): # opendance-android-vX.Y.Z.zip ← 单个完整包 # SHA256SUMS ← 裸文件名,下载目录内 sha256sum -c 直接可用 # # 包内结构: # opendance-android-vX.Y.Z/ # app-release.apk # THIRD_PARTY.md # README.txt README.md # contracts/ # # 版本号唯一来源是 app/build.gradle.kts 的 versionName;不传参数时自动读取, # 并与 project.yaml 交叉校验(文档 §11.2:产物名 / tag / README / 界面必须一致)。 # # 用法:bash build/package-release.sh [版本号] # ============================================================ set -euo pipefail PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$PROJECT_DIR" # 先定义 log/die:下面的版本号校验就会用到 die,函数必须先于调用定义, # 否则命中的是 "die: 未找到命令"(退出码 127,看不出真实原因)。 log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; } YAML_VERSION="$(sed -n 's/^version:[[:space:]]*//p' project.yaml | head -1)" GRADLE_VERSION="$(sed -n 's/^[[:space:]]*versionName = "\(.*\)"/\1/p' app/build.gradle.kts | head -1)" VERSION="${1:-$GRADLE_VERSION}" VERSION="${VERSION:-0.2.0}" [ -n "$YAML_VERSION" ] && [ "$VERSION" != "$YAML_VERSION" ] \ && die "版本号不一致:参数/versionName=$VERSION,project.yaml=$YAML_VERSION" [ -n "$GRADLE_VERSION" ] && [ "$VERSION" != "$GRADLE_VERSION" ] \ && die "版本号不一致:参数=$VERSION,app/build.gradle.kts 的 versionName=$GRADLE_VERSION" NAME="opendance-android-v${VERSION}" OUT_DIR="$PROJECT_DIR/dist-release" STAGE="$OUT_DIR/$NAME" APK="app/build/outputs/apk/release/app-release.apk" [ -f "$APK" ] || die "未找到 $APK,请先执行 bash build/build-apk.sh release" # 源 APK 的 AndroidManifest 版本号必须是本次发布版本(文档 §11.2): # 安装界面 / 应用信息页读的就是它,与 build.gradle.kts 的配置无关。 bash "$PROJECT_DIR/build/check-apk-version.sh" "$APK" "$VERSION" \ || die "APK 的 versionName/versionCode 不是 v$VERSION;请用 bash build/build-apk.sh release 重新构建" log "源 APK 版本元数据校验通过(=$VERSION)" # APK 是 zip,校验魔数 MAGIC=$(head -c 2 "$APK" | od -An -tx1 | tr -d ' \n') [ "$MAGIC" = "504b" ] || die "$APK 不是合法 APK(魔数 $MAGIC)" log "整理产物到 $STAGE …" rm -rf "$STAGE" mkdir -p "$STAGE" cp "$APK" "$STAGE/app-release.apk" cp THIRD_PARTY.md README.txt "$STAGE/" if [ -f README.md ]; then cp README.md "$STAGE/"; fi cp -r contracts "$STAGE/contracts" # ---- 完整性自检:APK 必须是合法 zip、含 MediaPipe 模型、ABI 只含真机 ABI ---- [ "$(head -c 2 "$APK" | od -An -tx1 | tr -d ' \n')" = "504b" ] || die "$APK 不是合法 APK" unzip -l "$APK" | grep -q "assets/pose_landmarker_lite.task" || die "APK 缺少 MediaPipe 模型资源" ABIS=$(unzip -l "$APK" | grep -oE "lib/[^/]+/" | sed 's#lib/##;s#/##' | sort -u | tr '\n' ' ') log "APK 内 ABI:$ABIS" for abi in arm64-v8a armeabi-v7a; do echo "$ABIS" | grep -q "$abi" || die "APK 缺少 $abi 的 native 库" done if echo "$ABIS" | grep -qE "x86"; then die "release APK 含模拟器 ABI($ABIS);请检查 abiFilters(T-AND-012)" fi unzip -l "$APK" | grep -q "lib/arm64-v8a/.*mediapipe" || die "arm64-v8a 下缺少 MediaPipe native 库" ZIP="$OUT_DIR/$NAME.zip" rm -f "$ZIP" "$OUT_DIR/SHA256SUMS"* log "生成压缩包…" ( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" ) # ---- SHA256SUMS(裸文件名,下载目录内可直接校验) ---- ( cd "$OUT_DIR" && sha256sum "$NAME.zip" > SHA256SUMS ) log "完成:$ZIP($(du -h "$ZIP" | cut -f1))" echo "" echo "产物:" echo " $ZIP" echo " $OUT_DIR/SHA256SUMS" if ( cd "$OUT_DIR" && sha256sum -c SHA256SUMS --status ); then echo " sha256 校验:OK" else die "sha256 自校验失败" fi echo "" unzip -l "$ZIP" | sed -n '4,20p' echo "" echo "分发:上传 $NAME.zip 与 SHA256SUMS 两个资产;用户下载后" echo " sha256sum -c SHA256SUMS && unzip $NAME.zip" echo " 再 adb install -r $NAME/app-release.apk(Android 10+)"