Skip to content

Merge branch 'fix/ui-adjustments' #23

Merge branch 'fix/ui-adjustments'

Merge branch 'fix/ui-adjustments' #23

Workflow file for this run

name: Android Release
# 打 Tag(如 v1.0.0)时触发:构建三个签名 Release APK
# - universal : arm64-v8a + x86_64,含 arm + x86 两套容器镜像(通用,体积最大)
# - armsolo : 仅 arm64-v8a,仅含 arm 镜像(真机首选,体积约为 universal 的一半)
# - x86solo : 仅 x86_64,仅含 x86 镜像(x86 模拟器/Chromebook,体积约为 universal 的一半)
# 创建 GitHub Release 并把三个 APK 作为资产发布。
on:
push:
tags:
- 'v*'
# 允许在 Actions 页面手动触发,便于验证构建链路。
workflow_dispatch:
jobs:
build:
name: Build & Release
runs-on: ubuntu-latest
permissions:
contents: write # 创建 Release 与上传资产需要写权限
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# versionCode 由 build.gradle.kts 的 gitCommitCount() 取 git rev-list --count HEAD,
# 浅克隆(默认 depth=1)会得到 1,必须拉完整历史,否则 Release APK 版本号错乱。
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
# 复用 Gradle 构建缓存,加速后续构建。
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
# 在构建前校验:tag 名的版本部分必须与 app/build.gradle.kts 的 versionName 一致。
# 防止"改了发版 tag 但忘记改 versionName"导致 Release 版本号与 APK 实际版本对不上。
# 仅打 tag 触发时校验;workflow_dispatch(手动,无 tag)跳过。
# 兼容 rc 后缀:v1.0.0 与 v1.0.0-rc2 都要求 versionName=1.0.0。
- name: Verify tag matches versionName
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -e
ver_in_code=$(grep -E '^\s*versionName\s*=' app/build.gradle.kts | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
tag_ver="${GITHUB_REF_NAME#v}" # 1.0.0 / 1.0.0-rc2 / ...
tag_base="${tag_ver%%-*}" # 1.0.0(去掉 -rc2 后缀)
echo "build.gradle.kts versionName = $ver_in_code"
echo "tag version part (without v) = $tag_ver"
if [[ -z "$ver_in_code" ]]; then
echo "::error::未能从 app/build.gradle.kts 解析出 versionName,检查格式是否为 versionName = \"x.y.z\""
exit 1
fi
if [[ "$tag_base" != "$ver_in_code" ]]; then
echo "::error::tag 版本部分 ($tag_base) 与 versionName ($ver_in_code) 不一致。请先在 app/build.gradle.kts 改好 versionName,再重新打 tag 或用正确的 tag。"
exit 1
fi
echo "✓ tag 与 versionName 一致: $ver_in_code(tag 后缀: ${tag_ver#$tag_base})"
# 校验 versionCode 单调递增:versionCode = git commit count,rebase/squash 改写历史
# 可能让 commit count 变小,导致新版本 versionCode < 旧版本、升级判定失效。
# 取按提交时间倒序最近的非当前 tag,算其 commit count,要求当前 HEAD 不小于上个 Release。
# 允许相等:rc 转正式版时常在同一提交上打 tag,versionCode 相等是正常的。
# 仅打 tag 触发时校验;workflow_dispatch(手动,无 tag)跳过。
- name: Verify versionCode monotonic
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -e
cur_vc=$(git rev-list --count HEAD)
prev_tag=$(git tag --sort=-creatordate | grep -vE "^${GITHUB_REF_NAME}$" | head -1)
if [[ -z "$prev_tag" ]]; then
echo "无历史 tag,跳过 versionCode 单调校验(首次发版)"
echo "当前 versionCode(commit count) = $cur_vc"
exit 0
fi
prev_vc=$(git rev-list --count "$prev_tag")
echo "上个 Release($prev_tag) versionCode = $prev_vc"
echo "当前(${GITHUB_REF_NAME}) versionCode = $cur_vc"
if [[ "$cur_vc" -lt "$prev_vc" ]]; then
echo "::error::versionCode 回退: 当前 $cur_vc < 上个 Release($prev_tag) $prev_vc。通常因 rebase/squash 改写历史使 commit count 变小。请确保新版本在旧 tag 之上线性推进。"
exit 1
fi
echo "✓ versionCode 单调递增: $prev_vc -> $cur_vc"
- name: Restore release keystore
env:
# keystore 文件以 base64 存入 Secret,避免二进制文件被破坏。
# 还原到 app/aicode.jks,与 build.gradle.kts 期望的位置一致。
KEYSTORE_BASE64: ${{ secrets.AICODE_KEYSTORE_BASE64 }}
run: |
echo "$KEYSTORE_BASE64" | base64 -d > app/aicode.jks
echo "keystore restored ($(stat -c%s app/aicode.jks) bytes)"
- name: Generate keystore.properties
env:
KEYSTORE_PASSWORD: ${{ secrets.AICODE_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.AICODE_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.AICODE_KEY_PASSWORD }}
run: |
cat > app/keystore.properties <<EOF
storeFile=aicode.jks
storePassword=$KEYSTORE_PASSWORD
keyAlias=$KEY_ALIAS
keyPassword=$KEY_PASSWORD
EOF
echo "app/keystore.properties generated"
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# 发版质量门禁:跑单元测试,失败即中止,避免发出回归包。
# 只跑 universal debug 变体:单测逻辑与 flavor 无关,跑全 flavor 6 变体是重复编译。
- name: Run unit tests
run: ./gradlew :app:testUniversalDebugUnitTest
# 一次性构建全部 flavor 的 release APK(universal/armsolo/x86solo)
# --stacktrace:构建失败时输出完整调用栈,便于在 CI 定位 OOM/内存类根因。
- name: Build all flavor Release APKs
run: ./gradlew assembleRelease --stacktrace
# 给三个 APK 重命名为「包名-架构-版本」格式,架构用真实 ABI 名便于用户识别:
# aicode-arm64-v1.0.0-rc1.apk (armsolo)
# aicode-x86_64-v1.0.0-rc1.apk (x86solo)
# aicode-universal-v1.0.0-rc1.apk (universal,含两套镜像)
- name: Rename APKs by flavor
run: |
mkdir -p dist
ver="${GITHUB_REF_NAME:-manual}" # tag 如 v1.0.0-rc1;手动触发时为 manual-N
declare -A abi=( [armsolo]=arm64 [x86solo]=x86_64 [universal]=universal )
for f in universal armsolo x86solo; do
src="app/build/outputs/apk/$f/release/app-$f-release.apk"
if [[ -f "$src" ]]; then
out="dist/aicode-${abi[$f]}-${ver}.apk"
cp "$src" "$out"
echo "packaged $out"
else
echo "::warning::missing $src"
fi
done
ls -lh dist/
- name: Determine release name
id: release
run: |
# 手动触发(无 tag)时用 run id 作为临时名,避免破坏 tag 发布。
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
else
echo "tag=manual-${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
fi
- name: Create GitHub Release & Upload assets
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.release.outputs.tag }}
name: Release ${{ steps.release.outputs.tag }}
generate_release_notes: true
# 产物名约定 aicode-<abi>-<版本>.apk(见 Rename 步骤),故 glob 把 ABI 段放 aicode- 紧后、版本段用 * 兜底。
# 不要写成 aicode-*-<abi>.apk——那会把 abi 误放末段,与 aicode-<abi>-<ver> 永远匹配不上(rc3 翻车原因)。
files: |
dist/aicode-universal-*.apk
dist/aicode-arm64-*.apk
dist/aicode-x86_64-*.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}