* ci: optimize CI caching, concurrency, and Tauri security Address multiple CI/CD efficiency and security gaps identified during project optimization analysis. The changes reduce PR build times, prevent wasteful concurrent runs, tighten the Tauri security boundary, and eliminate unnecessary submodule fetches on non-macOS jobs. CI workflow improvements: - Add swatinem/rust-cache@v2 to both ci.yml jobs (android-check and cross-platform), matching the caching already present in release-tauri.yml. This converts PR Rust compilation from full rebuilds to incremental builds, saving an estimated 60-80% of compile time per run. - Add concurrency groups to all three workflows. PR pushes cancel superseded runs to free concurrency slots; tag pushes and manual dispatches are isolated by run_id to avoid mutually canceling release builds. Previously, rapid PR pushes triggered redundant parallel CI runs with no cancellation. - Unify Node version to 22 across release-tauri.yml and android-apk.yml, which were previously pinned to 20. This ensures consistent build environments across all workflows. Submodule and security hardening: - Conditionally fetch the vendor/qwen-asr submodule only on macOS jobs across all three workflows. The submodule is compiled exclusively by build.rs on macOS (build_qwen_asr_macos); Linux, Windows, and Android builds never reference it. Removing the unconditional recursive checkout eliminates a network fetch and a submodule-availability failure point on every non-macOS job. - Set withGlobalTauri to false in tauri.conf.json. The frontend exclusively uses @tauri-apps/api imports and never references the window.__TAURI__ global, so disabling it removes an unnecessary XSS attack surface without functional impact. Dependency hygiene: - Move @types/dompurify from dependencies to devDependencies in package.json. Type declaration packages are build-time only and should not ship in the production bundle. * fix(ci): address review findings * fix(ci): isolate manual CI workflow runs --------- Co-authored-by: Chris233 <h-chris233@outlook.com>
296 lines
12 KiB
YAML
296 lines
12 KiB
YAML
name: CI
|
||
|
||
# 多平台跨语言质量门禁。release-tauri.yml 是发版流水线(仅打包构建),这里负责
|
||
# 在合并前快速验证两件事:
|
||
# 1. 全部前端/契约测试与 vite bundle 通过(捕获行为、合同及跨 locale 类型 drift)
|
||
# 2. Rust 后端在 macOS / Windows / Linux 都能 cargo check 过(捕获 cfg 漏分支 / 平台 API 误用)
|
||
# 跑 build-mac.sh / windows-package-msvc.ps1 / Tauri bundle 太重;只跑轻量 cargo check + vite build。
|
||
|
||
on:
|
||
push:
|
||
branches: [main, beta]
|
||
pull_request:
|
||
branches: [main, beta]
|
||
workflow_dispatch:
|
||
|
||
# 同一 PR 快速重复推送时取消旧运行;workflow_dispatch 用 run_id 隔离。
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }}
|
||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||
|
||
jobs:
|
||
android-check:
|
||
name: Android cargo check
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: openless-all/app
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
# vendor/qwen-asr 仅 macOS 上 build.rs 会编译(build.rs:49 build_qwen_asr_macos);
|
||
# Android/Linux 的 cargo check 不需要,去掉递归拉取省一次网络 fetch + 失败点。
|
||
- name: Setup Android SDK + NDK
|
||
uses: android-actions/setup-android@v3
|
||
with:
|
||
packages: platform-tools
|
||
|
||
- name: Install NDK
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
sdkmanager "ndk;26.1.10909125" "platforms;android-36" "build-tools;36.0.0"
|
||
ndk_dir="$ANDROID_HOME/ndk/26.1.10909125"
|
||
echo "ANDROID_NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
|
||
echo "NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
|
||
set +o pipefail
|
||
yes | sdkmanager --licenses
|
||
set -o pipefail
|
||
|
||
- name: Configure NDK toolchain for cargo
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
prebuilt="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64"
|
||
echo "CC_aarch64_linux_android=$prebuilt/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CXX_aarch64_linux_android=$prebuilt/bin/aarch64-linux-android24-clang++" >> "$GITHUB_ENV"
|
||
echo "AR_aarch64_linux_android=$prebuilt/bin/llvm-ar" >> "$GITHUB_ENV"
|
||
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$prebuilt/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CC_x86_64_linux_android=$prebuilt/bin/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CXX_x86_64_linux_android=$prebuilt/bin/x86_64-linux-android24-clang++" >> "$GITHUB_ENV"
|
||
echo "AR_x86_64_linux_android=$prebuilt/bin/llvm-ar" >> "$GITHUB_ENV"
|
||
echo "CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$prebuilt/bin/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: "22"
|
||
cache: npm
|
||
cache-dependency-path: openless-all/app/package-lock.json
|
||
|
||
- uses: actions/setup-java@v4
|
||
with:
|
||
distribution: zulu
|
||
java-version: "17"
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
with:
|
||
targets: aarch64-linux-android,x86_64-linux-android
|
||
|
||
- uses: swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: 'openless-all/app/src-tauri -> target'
|
||
|
||
- uses: gradle/actions/setup-gradle@v4
|
||
|
||
- name: Install Linux check dependencies
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y build-essential curl file libssl-dev wget
|
||
|
||
- name: Install frontend dependencies
|
||
run: npm ci
|
||
|
||
- name: Build frontend (tsc + vite)
|
||
# generate_context! requires frontendDist (../dist) to exist on Android too.
|
||
run: npm run build
|
||
|
||
- name: Check Android updater pubkey matches tauri.conf.json
|
||
run: npm run check:android-updater-pubkey
|
||
|
||
- name: Check Tauri backend (Android target)
|
||
run: cargo check --manifest-path src-tauri/Cargo.toml --target aarch64-linux-android
|
||
|
||
- name: Initialize generated Android project
|
||
run: npm run tauri -- android init --ci
|
||
|
||
- name: Generate Tauri Gradle dependency scripts
|
||
env:
|
||
TAURI_ANDROID_PROJECT_PATH: ${{ github.workspace }}/openless-all/app/src-tauri/gen/android
|
||
TAURI_ANDROID_PACKAGE_UNESCAPED: com.openless.app
|
||
WRY_ANDROID_LIBRARY: openless_lib
|
||
WRY_ANDROID_KOTLIN_FILES_OUT_DIR: ${{ github.workspace }}/openless-all/app/src-tauri/gen/android/app/src/main/java/com/openless/app/generated
|
||
WRY_ANDROID_PACKAGE: com.openless.app
|
||
run: |
|
||
mkdir -p "$WRY_ANDROID_KOTLIN_FILES_OUT_DIR"
|
||
cargo check --manifest-path src-tauri/Cargo.toml --target x86_64-linux-android
|
||
|
||
- name: Copy Android production and test scaffolding
|
||
run: node scripts/copy-android-scaffolding.mjs
|
||
|
||
- name: Merge Shizuku manifest
|
||
run: node scripts/merge-android-shizuku-manifest.mjs
|
||
|
||
- name: Patch Shizuku Gradle dependencies
|
||
run: node scripts/patch-android-shizuku-deps.mjs
|
||
|
||
- name: Run JVM credential tests and compile instrumentation tests
|
||
# These tests are Kotlin-only. The Rust target is checked above; direct Gradle
|
||
# rustBuild requires the live Tauri CLI RPC server used by `tauri android build`.
|
||
run: >-
|
||
src-tauri/gen/android/gradlew
|
||
--project-dir src-tauri/gen/android
|
||
:app:testX86_64DebugUnitTest
|
||
:app:assembleX86_64DebugAndroidTest
|
||
-x :app:rustBuildX86_64Debug
|
||
--no-daemon
|
||
|
||
- name: Enable KVM for Android instrumentation tests
|
||
run: |
|
||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
|
||
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||
sudo udevadm control --reload-rules
|
||
sudo udevadm trigger --name-match=kvm
|
||
|
||
- name: Run Android Keystore instrumentation tests
|
||
uses: reactivecircus/android-emulator-runner@v2
|
||
with:
|
||
api-level: 35
|
||
target: google_apis
|
||
arch: x86_64
|
||
# The instrumentation suite exercises AndroidKeyStore without launching Tauri.
|
||
script: >-
|
||
cd openless-all/app &&
|
||
src-tauri/gen/android/gradlew
|
||
--project-dir src-tauri/gen/android
|
||
:app:connectedX86_64DebugAndroidTest
|
||
-x :app:rustBuildX86_64Debug
|
||
--no-daemon
|
||
|
||
cross-platform:
|
||
name: ${{ matrix.label }} checks
|
||
strategy:
|
||
# 一个平台挂掉不阻塞其他平台拿到验证结果。
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- os: macos-latest
|
||
label: macOS
|
||
preflight: false
|
||
- os: windows-latest
|
||
label: Windows
|
||
preflight: true
|
||
- os: ubuntu-latest
|
||
label: Linux
|
||
preflight: false
|
||
runs-on: ${{ matrix.os }}
|
||
defaults:
|
||
run:
|
||
working-directory: openless-all/app
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
# vendor/qwen-asr 仅 macOS 上 build.rs 会编译(build.rs:49 build_qwen_asr_macos)。
|
||
# Windows/Linux 的 cargo check 不需要子模块;用矩阵条件避免非 macOS job
|
||
# 多拉一次 git submodule(省网络 fetch + 去掉一个失败点)。
|
||
submodules: ${{ matrix.os == 'macos-latest' && 'recursive' || 'false' }}
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: "22"
|
||
cache: npm
|
||
cache-dependency-path: openless-all/app/package-lock.json
|
||
|
||
# 现有测试继续用 stable;额外安装声明的 MSRV,供下方兼容性门禁显式调用。
|
||
- uses: dtolnay/rust-toolchain@1.88.0
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
|
||
- uses: swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: 'openless-all/app/src-tauri -> target'
|
||
|
||
- name: Install Linux check dependencies
|
||
if: runner.os == 'Linux'
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y \
|
||
build-essential \
|
||
curl \
|
||
file \
|
||
libasound2-dev \
|
||
libayatana-appindicator3-dev \
|
||
librsvg2-dev \
|
||
libssl-dev \
|
||
libwebkit2gtk-4.1-dev \
|
||
libxdo-dev \
|
||
wget
|
||
|
||
- name: Install frontend dependencies
|
||
run: npm ci
|
||
|
||
- name: Check Windows prerequisites
|
||
if: matrix.preflight
|
||
shell: pwsh
|
||
run: ./scripts/windows-preflight.ps1 -Toolchain msvc
|
||
|
||
- name: Check PowerShell scripts
|
||
if: matrix.preflight
|
||
shell: pwsh
|
||
run: |
|
||
foreach ($script in @("./scripts/windows-preflight.ps1", "./scripts/windows-build-gnu.ps1", "./scripts/windows-runtime-smoke.ps1")) {
|
||
$errors = $null
|
||
[System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $script), [ref]$errors) | Out-Null
|
||
if ($errors) {
|
||
$errors | Format-List
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
- name: Build frontend and run frontend/contract tests
|
||
run: npm test
|
||
|
||
- name: Check Tauri backend (cargo check)
|
||
run: cargo check --manifest-path src-tauri/Cargo.toml
|
||
|
||
- name: Run Rust backend unit tests
|
||
if: runner.os != 'Windows'
|
||
run: cargo test --manifest-path src-tauri/Cargo.toml --lib
|
||
|
||
- name: Compile Rust backend unit tests (Windows)
|
||
# Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime
|
||
# DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link
|
||
# 覆盖;Rust-only 后端单测由下一步实际执行。
|
||
if: runner.os == 'Windows'
|
||
run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run
|
||
|
||
- name: Run Rust-only backend unit tests (Windows)
|
||
# 只把纯 Rust 后端模块编进独立 test crate,不链接完整 Tauri app lib。
|
||
# 这样 Windows CI 能实际执行 coordinator/hotkey/recorder/insertion 逻辑单测。
|
||
if: runner.os == 'Windows'
|
||
run: cargo test --manifest-path src-tauri/backend-tests/Cargo.toml
|
||
|
||
- name: Check Tauri backend with Rust 1.88 MSRV
|
||
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml
|
||
|
||
- name: Compile backend tests with Rust 1.88 MSRV
|
||
run: cargo +1.88.0 test --locked --manifest-path src-tauri/backend-tests/Cargo.toml --no-run
|
||
|
||
- name: Verify version sync across all 5 files
|
||
# 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。
|
||
# 一旦版本号 drift 立刻 fail,避免发版时再发现漏改。
|
||
# 校验 5 处:package.json / package-lock.json (root + nested) /
|
||
# tauri.conf.json / Cargo.toml / Cargo.lock 的 [openless] 包。
|
||
shell: bash
|
||
run: |
|
||
PKG=$(node -p "require('./package.json').version")
|
||
LOCK_ROOT=$(node -p "require('./package-lock.json').version")
|
||
LOCK_NESTED=$(node -p "require('./package-lock.json').packages[''].version")
|
||
TAU=$(node -p "require('./src-tauri/tauri.conf.json').version")
|
||
CRG=$(grep -E '^version = ' src-tauri/Cargo.toml | head -1 | sed -E 's/^version = "(.+)"$/\1/')
|
||
# Cargo.lock:找 [openless] 包紧跟的 version 行
|
||
CARGO_LOCK_VER=$(awk 'BEGIN{found=0} /^name = "openless"$/{found=1; next} found && /^version = /{gsub(/"/,""); print $3; exit}' src-tauri/Cargo.lock)
|
||
echo "package.json = $PKG"
|
||
echo "package-lock root = $LOCK_ROOT"
|
||
echo "package-lock nested = $LOCK_NESTED"
|
||
echo "tauri.conf.json = $TAU"
|
||
echo "Cargo.toml = $CRG"
|
||
echo "Cargo.lock openless = $CARGO_LOCK_VER"
|
||
mismatch=0
|
||
for v in "$LOCK_ROOT" "$LOCK_NESTED" "$TAU" "$CRG" "$CARGO_LOCK_VER"; do
|
||
if [ "$v" != "$PKG" ]; then mismatch=1; fi
|
||
done
|
||
if [ "$mismatch" -ne 0 ]; then
|
||
echo "::error::版本号未对齐 — 请用 scripts/bump-version.sh 同步更新"
|
||
exit 1
|
||
fi
|
||
echo "[ok] 全部 5 处版本号一致:$PKG"
|