Skip to content

fix(linux-egui): 修复设置崩溃与选区助手发送和收起 #2311

fix(linux-egui): 修复设置崩溃与选区助手发送和收起

fix(linux-egui): 修复设置崩溃与选区助手发送和收起 #2311

Workflow file for this run

name: CI
# 多平台跨语言质量门禁。release-tauri.yml 是发版流水线(仅打包构建),这里负责
# 在合并前快速验证两件事:
# 1. 全部前端/契约测试与 vite bundle 通过(捕获行为、合同及跨 locale 类型 drift)
# 2. Tauri 后端在 macOS / Windows、移动端在 Android 编译;Linux Core、
# remote_tls、egui 和 deb/rpm 统一使用发版构建链检查(PR 不上传 Release)。
# macOS/Windows/Android 不跑完整 Tauri bundle;Linux 则运行完整 deb/rpm 发版构建校验。
on:
push:
branches: [main, beta]
pull_request:
branches: [main, beta]
workflow_dispatch:
inputs:
platform:
description: Platforms to check (macos runs only macOS gates)
type: choice
options: [all, macos]
default: all
# Cancel old runs when the same PR is pushed repeatedly; workflow_dispatch is isolated by 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:
# 判定这次改动能不能影响到各平台门禁。只有“改动物理上够不到”的变更才会
# 跳过对应 job:判定脚本对任何未知情况(没有基线、git 失败、空 diff)都返回
# true。push / tag / 手动运行不带基线,因此始终全量验证。
changes:
name: Changed areas
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
tauri: ${{ steps.detect.outputs.tauri }}
msrv: ${{ steps.detect.outputs.msrv }}
linux: ${{ steps.detect.outputs.linux }}
steps:
- uses: actions/checkout@v4
with:
# 需要基线提交才能算出差异,客户端不再重复 fetch。
fetch-depth: 0
- name: Detect changed areas
id: detect
working-directory: openless-all/app
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
for area in tauri msrv linux; do
verdict=$(bash scripts/ci-changed-areas.sh "$area" "$BASE_SHA")
# 失败即运行:脚本读不到差异时返回 true,这里也只接受明确的 false。
echo "$area=$( [ "$verdict" = 'false' ] && echo false || echo true )" >> "$GITHUB_OUTPUT"
done
ui-motion:
name: UI motion (Chromium + WebKit)
needs: [changes]
if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.tauri != 'false'
runs-on: ubuntu-24.04
timeout-minutes: 20
defaults:
run:
working-directory: openless-all/app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
- run: npm ci
- run: npm run build
- run: npx playwright install --with-deps chromium webkit
- run: npm run test:ui-motion
- uses: actions/upload-artifact@v4
if: failure()
with:
name: ui-motion-failure
path: |
openless-all/app/test-results/
openless-all/app/playwright-report/
android-check:
needs: [changes]
if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.tauri != 'false'
name: Android cargo check
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: openless-all/app
steps:
- uses: actions/checkout@v4
with:
# Android uses no desktop local-ASR submodules; the macOS MLX dependency stays out of
# this job.
submodules: false
# Android compiles no local-ASR C code: build.rs excludes the Android triple by TARGET
# (does not call build_qwen_asr). qwen3-asr-rs is a path dependency, and the Cargo
# resolver must read its manifest for all targets — so the ci-disable-macos-qwen3.mjs
# step below removes that dependency line, otherwise cargo check hard-fails.
# Dropping recursive fetch saves one network fetch and one failure point.
- 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'
# 仓库缓存上限 10 GB,超出后按 LRU 淘汰。过去每个 PR 都会各存一份
# ~1.6 GB 的依赖树,把其他 job 的缓存挤掉,导致每次运行都从零编译。
# 现在 PR 只从基线分支恢复,只有分支推送和 tag 才写入缓存。
save-if: ${{ github.event_name != 'pull_request' }}
- 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: Disable macOS-only Qwen3 MLX dependency
run: node scripts/ci-disable-macos-qwen3.mjs
- name: Check Tauri backend (Android target)
run: cargo check --locked --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 --locked --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
# The Linux core/egui/deb+rpm checks live in a reusable workflow, which runs
# the same `cargo test -p openless-core` as upstream's `linux-core-contract`
# plus the Linux host, fcitx5 and package contracts.
linux-egui-package:
needs: [changes]
if: github.event_name != 'workflow_dispatch' || inputs.platform != 'macos'
name: Linux Core, egui and deb/rpm checks
permissions:
contents: read
uses: ./.github/workflows/check-linux-egui.yml
with:
# 散文改动跳过整条 Linux 构建与打包链;输出缺失/未知时退回 full。
scope: ${{ needs.changes.outputs.linux == 'false' && 'none' || 'full' }}
cross-platform:
needs: [changes]
# 失败即运行:只有判定脚本明确说“够不到”才跳过。
if: needs.changes.outputs.tauri != 'false'
name: ${{ matrix.label }} checks
strategy:
# One platform failing must not block other platforms from getting verification results.
fail-fast: false
matrix:
include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.platform == 'macos' && '[{"os":"macos-latest","label":"macOS","preflight":false}]' || '[{"os":"macos-latest","label":"macOS","preflight":false},{"os":"windows-latest","label":"Windows","preflight":true}]') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
env:
# With the shared Core in place, macOS's first build compiles the MLX C++ dependencies and
# the full Tauri test binary together; limit concurrency so the arm64 runner is not OOM-killed
# at peak memory.
CARGO_BUILD_JOBS: 2
CARGO_PROFILE_TEST_DEBUG: 0
CMAKE_BUILD_PARALLEL_LEVEL: 2
defaults:
run:
working-directory: openless-all/app
steps:
- uses: actions/checkout@v4
with:
# Initialize the MLX submodule on macOS only; Windows does not resolve that dependency.
submodules: ${{ matrix.os == 'macos-latest' && 'recursive' || 'false' }}
- name: Disable macOS-only Qwen3 MLX dependency
if: runner.os != 'macOS'
run: node scripts/ci-disable-macos-qwen3.mjs
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
# macOS MSRV is checked in a separate parallel job; Windows keeps the original check order.
- uses: dtolnay/rust-toolchain@1.88.0
if: runner.os == 'Windows'
- uses: dtolnay/rust-toolchain@stable
- name: Configure macOS check profile
if: runner.os == 'macOS'
run: echo "CARGO_PROFILE_DEV_DEBUG=0" >> "$GITHUB_ENV"
- uses: swatinem/rust-cache@v2
if: runner.os == 'Windows'
with:
workspaces: 'openless-all/app/src-tauri -> target'
save-if: ${{ github.event_name != 'pull_request' }}
- name: Cache macOS stable dependencies
if: runner.os == 'macOS'
uses: swatinem/rust-cache@v2
with:
key: macos-stable-v1
save-if: ${{ github.event_name != 'pull_request' }}
workspaces: |
openless-all/app -> target
openless-all/app/src-tauri -> target
- name: Cache macOS MLX native build
if: runner.os == 'macOS' && runner.arch == 'ARM64'
uses: ./.github/actions/cache-macos-mlx
with:
profile: debug
- name: Prepare Windows Sherpa static libraries
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/prepare-windows-sherpa.ps1
- 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", "./scripts/prepare-windows-sherpa.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: Install pinned Codex CLI for sandbox contract
if: runner.os == 'macOS'
run: npm install --global @openai/codex@0.146.0
- name: Configure isolated Codex home
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
codex_home="$RUNNER_TEMP/openless-codex-home"
mkdir -p "$codex_home"
echo "CODEX_HOME=$codex_home" >> "$GITHUB_ENV"
- name: Run Codex writable-roots sandbox contract
if: runner.os == 'macOS'
env:
OPENLESS_CODEX_TEST_EXE: codex
run: cargo test --locked -p openless-core hardening_actually_narrows_the_writable_roots -- --ignored --nocapture --test-threads=1
- name: Check Tauri backend (cargo check)
if: runner.os == 'Windows'
run: cargo check --locked --manifest-path src-tauri/Cargo.toml
# test compiles and runs lib/bin, covering the production binary paths that plain cargo
# check misses. Avoids a metadata-only check followed by another codegen pass over the same
# dependency graph.
- name: Run Rust backend unit tests
if: runner.os != 'Windows'
run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --bins --timings
- name: Compile Rust backend unit tests (Windows)
# A Windows runner can link the lib test binary, but on a clean image lacking an optional
# native runtime DLL entrypoint, the process exits before the test harness starts. Keep
# the cfg/link coverage here; the shared Core's public compatibility contract is actually
# executed in the next step.
if: runner.os == 'Windows'
run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --no-run
- name: Run Rust-only backend unit tests (Windows)
# The standalone test crate does not link the full Tauri app lib; it verifies the public
# Core contract only. Windows-specific Tauri tests on this runner only get the cfg/link
# compile coverage from the step above.
if: runner.os == 'Windows'
run: cargo test --locked --manifest-path src-tauri/backend-tests/Cargo.toml
- name: Check Tauri backend with Rust 1.88 MSRV
if: runner.os == 'Windows'
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml
- name: Compile backend tests with Rust 1.88 MSRV
if: runner.os == 'Windows'
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
# Runs on both platforms: the Windows runner ships git-bash, giving consistent behavior
# across shells. Fail immediately on version drift instead of discovering it at release.
# Checks 5 places: package.json / package-lock.json (root + nested) /
# tauri.conf.json / Cargo.toml / the [openless] package in Cargo.lock.
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: find the version line right after the [openless] package name
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"
macos-msrv:
needs: [changes]
# 失败即运行:只有判定脚本明确说“够不到”才跳过。
if: needs.changes.outputs.msrv != 'false'
name: macOS Rust 1.88 MSRV
runs-on: macos-latest
timeout-minutes: 60
env:
CARGO_BUILD_JOBS: 2
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
CMAKE_BUILD_PARALLEL_LEVEL: 2
defaults:
run:
working-directory: openless-all/app
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
- uses: dtolnay/rust-toolchain@1.88.0
- uses: swatinem/rust-cache@v2
with:
key: macos-msrv-v1
save-if: ${{ github.event_name != 'pull_request' }}
workspaces: |
openless-all/app/src-tauri -> target
openless-all/app/src-tauri/backend-tests -> target
- name: Cache macOS MLX native build
if: runner.arch == 'ARM64'
uses: ./.github/actions/cache-macos-mlx
with:
profile: debug
- run: npm ci
- run: npm run build
- name: Check Tauri backend with Rust 1.88 MSRV
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml --timings
- 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 --timings