- gitea.com/actions/mise 不存在/需要鉴权, 克隆即 Unauthorized, 导致 job 02:05 秒失败 - 改为 gitea.com/actions/setup-node@v4 + 手动 rustup 安装 - Ensure SDK/NDK 步骤加固: 预装 SDK 复用 + 权限兜底 + 按需安装 - Rust 缓存路径改为 ~/.cargo + ~/.rustup - APK 固定上传到 data/android/wardrobe.apk
223 lines
9.3 KiB
YAML
223 lines
9.3 KiB
YAML
name: Android Build & Publish
|
|
|
|
# 每次提交触发 (所有分支 push; 主分支提交会同时触发 docker-build.yml 自动部署)
|
|
# 构建 release APK (keystore 签名) 并上传到 Cloudflare R2 (S3 兼容 API)
|
|
# R2 凭据从配置注册中心 (bh.vps.honor3.com:8008, CommonExternalService, 分组 cloudflare) 读取
|
|
# 参考: D:\workbench\iboard\.gitea\workflows\android-build.yml
|
|
on:
|
|
push:
|
|
|
|
jobs:
|
|
build-android:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
# 固定工具链版本, 便于 actions/cache 命中
|
|
NDK_VERSION: '27.2.12479018'
|
|
# 配置中心地址与 token (Gitea secrets; URL 未设时回退默认地址)
|
|
CONFIG_CENTER_URL: '${{ secrets.CONFIG_CENTER_URL }}'
|
|
CONFIG_CENTER_TOKEN: '${{ secrets.CONFIG_CENTER_TOKEN }}'
|
|
REGISTRY_URL_FALLBACK: 'http://bh.vps.honor3.com:8008'
|
|
# 配置中心项目 ID: 签名密钥在 service-wardrobe/signing, R2 在 CommonExternalService/cloudflare
|
|
CFG_PROJECT_SIGNING: 'proj-mtwjnvir-k52zvp'
|
|
CFG_PROJECT_R2: 'proj-mts0s5jo-fb3rz7'
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://gitea.com/actions/checkout@v4
|
|
|
|
- name: Setup Node
|
|
uses: https://gitea.com/actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Setup JDK 17
|
|
uses: https://gitea.com/actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
|
|
- name: Setup Rust toolchain
|
|
# 不用 gitea.com/actions/mise (该公共 action 需鉴权), 直接装 rustup
|
|
run: |
|
|
set -euo pipefail
|
|
if ! command -v rustup >/dev/null 2>&1; then
|
|
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
|
fi
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
"$HOME/.cargo/bin/rustup" default stable
|
|
"$HOME/.cargo/bin/rustc" --version
|
|
"$HOME/.cargo/bin/cargo" --version
|
|
|
|
# ---------- 尽量复用 Android SDK ----------
|
|
# 优先复用 runner 预装的 SDK (/usr/local/lib/android/sdk), 其次命中 CI 缓存,
|
|
# 最后才重新下载。NDK 不在预装中, 单独用缓存复用。
|
|
- name: Locate reusable Android SDK
|
|
id: sdk
|
|
run: |
|
|
CANDIDATES=(
|
|
"$ANDROID_HOME"
|
|
"/usr/local/lib/android/sdk" # GitHub ubuntu-latest 预装
|
|
"$HOME/android-sdk"
|
|
)
|
|
for c in "${CANDIDATES[@]}"; do
|
|
if [ -n "$c" ] && [ -d "$c/platforms" ]; then
|
|
echo "ANDROID_ROOT=${c}" >> "$GITHUB_ENV"
|
|
echo "found=${c}" >> "$GITHUB_OUTPUT"
|
|
echo "reusing existing Android SDK: ${c}"
|
|
break
|
|
fi
|
|
done
|
|
echo "found=${found:-}"
|
|
|
|
- name: Cache Android NDK
|
|
id: cache-ndk
|
|
uses: https://gitea.com/actions/cache@v4
|
|
with:
|
|
path: ~/android-sdk/ndk
|
|
key: android-ndk-${{ env.NDK_VERSION }}-${{ runner.os }}
|
|
|
|
- name: Cache Rust toolchain
|
|
id: cache-rust
|
|
uses: https://gitea.com/actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo
|
|
~/.rustup
|
|
key: rustup-stable-android-v2-${{ runner.os }}
|
|
|
|
- name: Cache Tauri cargo target
|
|
uses: https://gitea.com/actions/cache@v4
|
|
with:
|
|
path: src-tauri/target
|
|
key: tauri-target-android-${{ runner.os }}
|
|
|
|
# ---------- 安装缺失组件 (仅未复用/未命中时) ----------
|
|
- name: Ensure Android SDK & NDK
|
|
if: steps.sdk.outputs.found == '' || steps.cache-ndk.outputs.cache-hit != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
BASE="${ANDROID_ROOT:-$HOME/android-sdk}"
|
|
COMMANDLINE_TOOLS="$BASE/cmdline-tools/latest"
|
|
echo "SDK base: $BASE"
|
|
mkdir -p "$BASE/cmdline-tools"
|
|
# 预装 SDK 目录常归 root, 兜底给当前用户写权限
|
|
sudo chown -R "$(whoami)" "$BASE" 2>/dev/null || true
|
|
if [ ! -x "$COMMANDLINE_TOOLS/bin/sdkmanager" ]; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends unzip
|
|
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip
|
|
unzip -q /tmp/cmdline-tools.zip -d /tmp/
|
|
mv /tmp/cmdline-tools "$COMMANDLINE_TOOLS"
|
|
fi
|
|
export PATH="$COMMANDLINE_TOOLS/bin:$BASE/platform-tools:$PATH"
|
|
yes | sdkmanager --licenses >/dev/null || true
|
|
# 复用预装但缺平台时补装; 全新安装时补全组件
|
|
if [ ! -d "$BASE/platforms/android-36" ]; then
|
|
sdkmanager --install "platforms;android-36" "build-tools;36.0.0" "platform-tools"
|
|
fi
|
|
if [ ! -d "$BASE/ndk/${{ env.NDK_VERSION }}" ]; then
|
|
sdkmanager --install "ndk;${{ env.NDK_VERSION }}"
|
|
else
|
|
echo "NDK ${{ env.NDK_VERSION }} already present"
|
|
fi
|
|
|
|
- name: Add Android Rust targets
|
|
run: |
|
|
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
|
|
cargo install cargo-ndk --locked || true
|
|
|
|
- name: Install frontend deps
|
|
run: npm ci --no-audit --no-fund
|
|
|
|
# ---------- 全局 Android 环境 ----------
|
|
- name: Set Android env
|
|
run: |
|
|
echo "ANDROID_HOME=${ANDROID_ROOT:-$HOME/android-sdk}" >> "$GITHUB_ENV"
|
|
echo "ANDROID_NDK_HOME=${ANDROID_ROOT:-$HOME/android-sdk}/ndk/${{ env.NDK_VERSION }}" >> "$GITHUB_ENV"
|
|
echo "${ANDROID_ROOT:-$HOME/android-sdk}/cmdline-tools/latest/bin" >> "$GITHUB_PATH"
|
|
echo "${ANDROID_ROOT:-$HOME/android-sdk}/platform-tools" >> "$GITHUB_PATH"
|
|
|
|
# ---------- 构建 ----------
|
|
- name: Init Android project
|
|
run: |
|
|
cd src-tauri
|
|
npx tauri android init --ci
|
|
|
|
- name: Fetch config center (signing + R2)
|
|
run: |
|
|
set -euo pipefail
|
|
export CONFIG_CENTER_URL="${CONFIG_CENTER_URL:-$REGISTRY_URL_FALLBACK}"
|
|
python3 - <<'PYEOF'
|
|
import json, os, urllib.request
|
|
url = os.environ["CONFIG_CENTER_URL"]
|
|
token = os.environ.get("CONFIG_CENTER_TOKEN", "") or ""
|
|
wanted_keys = {
|
|
"CFG_PROJECT_SIGNING": ("ANDROID_KEYSTORE_B64", "ANDROID_KEY_ALIAS", "ANDROID_KEY_PASSWORD"),
|
|
"CFG_PROJECT_R2": ("CF_API_TOKEN", "CF_R2_PUBLIC_URL"),
|
|
}
|
|
wanted = {}
|
|
# 兼容 key 带分组前缀 (如 CommonExternalService 下是 r2/CF_API_TOKEN)
|
|
def match(cfg_key, token):
|
|
return cfg_key == token or cfg_key.rsplit("/", 1)[-1] == token
|
|
|
|
for pid_env, keys in wanted_keys.items():
|
|
pid = os.environ[pid_env]
|
|
req = urllib.request.Request(f"{url}/api/projects/{pid}/configs")
|
|
if token:
|
|
req.add_header("Authorization", "Bearer " + token)
|
|
with urllib.request.urlopen(req, timeout=20) as r:
|
|
data = json.load(r).get("data", [])
|
|
for cfg in data:
|
|
cfg_key = cfg.get("key", "")
|
|
for wanted_key in keys:
|
|
if match(cfg_key, wanted_key):
|
|
wanted[wanted_key] = cfg.get("value", "")
|
|
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as f:
|
|
for k, v in wanted.items():
|
|
f.write(f"{k}={v}\n")
|
|
missing = [k for k in ("ANDROID_KEYSTORE_B64", "ANDROID_KEY_ALIAS", "ANDROID_KEY_PASSWORD", "CF_API_TOKEN") if k not in wanted]
|
|
if missing:
|
|
raise SystemExit("配置中心缺少密钥: " + ", ".join(missing))
|
|
PYEOF
|
|
|
|
- name: Inject release signing config
|
|
run: bash scripts/ci/android-signing.sh
|
|
|
|
- name: Build release APK
|
|
run: |
|
|
export ANDROID_HOME="${ANDROID_ROOT:-$HOME/android-sdk}"
|
|
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/${{ env.NDK_VERSION }}"
|
|
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH"
|
|
cd src-tauri
|
|
npx tauri android build --apk
|
|
# 断言签名配置仍在且 release 块实际引用了它 (防止 tauri 重新生成 gen/android 或正则未命中)
|
|
if ! grep -q 'create("release")' gen/android/app/build.gradle.kts || ! grep -q 'signingConfig = signingConfigs.getByName("release")' gen/android/app/build.gradle.kts; then
|
|
echo "ERROR: 构建后签名配置丢失/未生效, APK 未签名" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Locate APK
|
|
id: apk
|
|
run: |
|
|
APK=""
|
|
UNIVERSAL=$(find src-tauri/gen/android/app/build/outputs/apk/universal/release -name '*.apk' ! -name '*unsigned*' 2>/dev/null | head -1)
|
|
if [ -n "$UNIVERSAL" ]; then
|
|
APK="$UNIVERSAL"
|
|
else
|
|
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' ! -name '*unsigned*' | head -1)
|
|
fi
|
|
if [ -z "$APK" ]; then
|
|
echo "ERROR: 未找到已签名 APK" >&2
|
|
exit 1
|
|
fi
|
|
echo "path=$APK" >> "$GITHUB_OUTPUT"
|
|
ls -lh "$APK"
|
|
|
|
- name: Upload APK to Cloudflare R2
|
|
env:
|
|
APK_PATH: ${{ steps.apk.outputs.path }}
|
|
run: |
|
|
bash scripts/upload-r2.sh "$APK_PATH" data/android/wardrobe.apk |