mirror of
https://github.com/qingjian-team/qingjian.git
synced 2026-11-06 00:14:40 +00:00
只要剪贴板的人不必装整个输入法:按用户安装、不要管理员。 推 clipboard-v<版本> 标签在 Windows 上打包,发到 qingjian-clipboard 的 Release,说明取自剪贴板自己的更新日志。 Windows CI 加上剪贴板:它只在 Windows 上有实现,Linux 的 core job 只编到占位的 main。
381 lines
19 KiB
YAML
381 lines
19 KiB
YAML
# Linux 跑 fmt / clippy / 全 workspace 测试(排除 IMK 外壳);macOS 与 Windows runner 各编各的壳并跑平台测试,Android 编 debug APK,
|
||
# 平台特有的编译错(Objective-C 调用、TSF 导出、WinUI)在 PR 里就暴露,不等发版。仓库公开,Actions 不计费。
|
||
# actions 一律钉到 commit(注释里是对应的标签),Dependabot 每周提 PR 更新;cargo 全加 --locked,Cargo.lock 没提交就报错。
|
||
name: ci
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
pull_request:
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
concurrency:
|
||
group: ci-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
|
||
jobs:
|
||
core:
|
||
runs-on: ubuntu-26.04
|
||
timeout-minutes: 20
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
components: rustfmt, clippy
|
||
|
||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||
|
||
# runner 的 apt 镜像偶尔只有 100 kB/s,平时 20 秒的这一步会拖到 18 分钟撞上超时。
|
||
# 下载的 .deb 放进缓存,按周换键,镜像慢时也只下这周新出的版本。
|
||
- name: Week for apt cache key
|
||
id: apt-week
|
||
run: echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT"
|
||
|
||
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
with:
|
||
path: ~/apt-archives
|
||
key: apt-ubuntu-26.04-${{ steps.apt-week.outputs.week }}
|
||
restore-keys: apt-ubuntu-26.04-
|
||
|
||
- name: Install Fcitx5 build and test dependencies
|
||
run: |
|
||
mkdir -p ~/apt-archives/partial
|
||
sudo apt-get update
|
||
sudo apt-get install -y -o Dir::Cache::Archives="$HOME/apt-archives" cmake g++ libfcitx5core-dev libfcitx5config-dev libfcitx5utils-dev nlohmann-json3-dev fcitx5
|
||
|
||
- name: Format
|
||
run: cargo fmt --all --check
|
||
|
||
# 依赖的许可证、来源、通配版本、RustSec 漏洞。audit.yml 也在查漏洞,
|
||
# 但那是每周一次的定时监控(发现新披露的漏洞就开 issue),这里是合并门禁:
|
||
# 任何 PR 把带漏洞或被撤回(yanked)的版本带进来,这一步当场红。
|
||
# 同一份豁免清单被两边消费(deny.toml 的 [advisories] 与 .cargo/audit.toml),
|
||
# 所以下面紧跟一步机械校验两者等价——人记不住「改一处要改两处」。
|
||
# 装 cargo-deny 而不是用 cargo-deny-action:少一个要钉 commit 的第三方 action,
|
||
# 且版本由这一行钉死,与 workflow 里别的 cargo 命令一致。
|
||
# cargo-deny 自己读仓库根的 Cargo.lock(不吃 --locked,也没有 manifest-path 参数),
|
||
# 所以「锁与 Cargo.toml 不一致」这一类由别的 --locked 步骤负责,这里只管内容。
|
||
- name: Dependency licenses, sources, bans and advisories
|
||
run: |
|
||
cargo install cargo-deny --version 0.20.2 --locked
|
||
cargo deny check
|
||
|
||
# `ignore` 里每一条都是一次「知道有风险但暂时不能升」的决定。两处清单一旦分叉,
|
||
# 就会出现「定时那条说没问题、PR 这条说有问题」或者反过来的分裂——
|
||
# 而两种都不会有人立刻发现,只会在某次合并后才暴露。所以让分叉本身失败。
|
||
# diff 的方向:`<` 是 audit.toml 独有的,`>` 是 deny.toml 独有的。
|
||
- name: Advisory ignores in sync
|
||
shell: bash
|
||
run: |
|
||
audit=$(grep -oE 'RUSTSEC-[0-9]{4}-[0-9]{4}' .cargo/audit.toml | sort -u)
|
||
deny=$(grep -oE 'RUSTSEC-[0-9]{4}-[0-9]{4}' deny.toml | sort -u)
|
||
if [ "$audit" != "$deny" ]; then
|
||
echo "两份 RustSec 豁免清单不一致(< 只在 audit.toml,> 只在 deny.toml):" >&2
|
||
diff <(echo "$audit") <(echo "$deny") >&2 || true
|
||
exit 1
|
||
fi
|
||
echo "两份豁免清单一致,共 $(echo "$deny" | grep -c . ) 条"
|
||
|
||
- name: Clippy
|
||
run: cargo clippy --workspace --exclude qingjian-macos --all-targets --locked -- -D warnings
|
||
|
||
# 云联想的 TLS 有两套:桌面默认 native-tls(系统栈),安卓没有可链的系统 TLS,
|
||
# 走 rustls(见 qingjian-predict 的 Cargo.toml 与 apps/android/core)。上面那条
|
||
# clippy 跑的是默认特性,rustls 那条 `#[cfg(all(feature = "rustls",
|
||
# not(feature = "native-tls")))]` 分支在 Linux 上从来编不到——只有 android job
|
||
# 的 assembleDebug 会顺带编一次,而它不跑测试也不 lint。TLS 栈是安全边界的
|
||
# 一部分,没有 lint 没有测试说不过去,所以在这里单独编一遍、测一遍。
|
||
- name: Clippy (rustls TLS, as Android builds it)
|
||
run: cargo clippy -p qingjian-predict --no-default-features --features rustls --all-targets --locked -- -D warnings
|
||
|
||
- name: Test (rustls TLS, as Android builds it)
|
||
run: cargo test -p qingjian-predict --no-default-features --features rustls --locked
|
||
|
||
# 文档断链、裸 HTML 标签(<TAB> 这类)平时编译看不出来,只有 rustdoc 会报;文档是给用户直接看的,
|
||
# 坏了不会红,只能靠这条门禁兜住。RUSTDOCFLAGS 只设在这一步,不污染后面的构建与测试。
|
||
- name: Rustdoc
|
||
env:
|
||
RUSTDOCFLAGS: -D warnings
|
||
run: cargo doc --no-deps --workspace --exclude qingjian-macos --locked
|
||
|
||
- name: Test
|
||
run: cargo test --workspace --exclude qingjian-macos --locked
|
||
|
||
- name: Build Linux server
|
||
run: cargo build -p qingjian-linux-server --locked
|
||
|
||
- name: Configure Fcitx5 default panel
|
||
run: cmake -S apps/linux/fcitx5 -B target/fcitx5-stage1 -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
|
||
|
||
- name: Build Fcitx5 default panel
|
||
run: cmake --build target/fcitx5-stage1 --parallel 2
|
||
|
||
# 日志用 tee 落盘,失败时由下面的 Upload Linux diagnostics 打包上传,便于排障。
|
||
# 必须显式 shell: bash:默认的 bash -e 不带 pipefail,`| tee` 会返回 tee 的退出码、把测试失败吞掉,
|
||
# if: failure() 的诊断上传也不会触发
|
||
- name: Test Fcitx5 default panel
|
||
shell: bash
|
||
run: |
|
||
mkdir -p target/ci-logs
|
||
env -u DBUS_SESSION_BUS_ADDRESS ctest --test-dir target/fcitx5-stage1 --output-on-failure 2>&1 | tee target/ci-logs/fcitx5-ctest.log
|
||
|
||
# Fcitx4 壳(Ubuntu 22.04 那条线,#351 引入)。上面三步只覆盖 Fcitx5,
|
||
# 而 apps/linux/fcitx4 下的 C++ 插件(qingjian.cpp 415 行 + key + primary_selection,
|
||
# 还与 fcitx5 共用 ../fcitx5/src/ipc/connection.cpp)此前没有任何检查碰过:
|
||
# 改共用的 connection.cpp 时 Fcitx4 那侧编不过,只有装到 22.04 上才会发现。
|
||
# 它自带一个 CTest(test-surrounding,同样共用 ../tests/surrounding.cpp),一起跑。
|
||
# 26.04 上装 fcitx-libs-dev 会把 fcitx5 卸掉,所以等 Fcitx5 的测试跑完再装
|
||
# 缓存在 job 结束时打包,apt 建的 partial/ 归 _apt 所有、runner 读不了,装完交回 runner
|
||
- name: Install Fcitx4 build dependencies
|
||
run: |
|
||
sudo apt-get install -y -o Dir::Cache::Archives="$HOME/apt-archives" fcitx-libs-dev libx11-dev libxres-dev
|
||
sudo chown -R "$USER" ~/apt-archives
|
||
|
||
- name: Configure Fcitx4 shell
|
||
run: cmake -S apps/linux/fcitx4 -B target/fcitx4-stage1 -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
|
||
|
||
- name: Build Fcitx4 shell
|
||
run: cmake --build target/fcitx4-stage1 --parallel 2
|
||
|
||
- name: Test Fcitx4 shell
|
||
shell: bash
|
||
run: |
|
||
mkdir -p target/ci-logs
|
||
env -u DBUS_SESSION_BUS_ADDRESS ctest --test-dir target/fcitx4-stage1 --output-on-failure 2>&1 | tee target/ci-logs/fcitx4-ctest.log
|
||
|
||
- name: Validate Linux install scripts
|
||
run: bash -n apps/linux/scripts/install.sh apps/linux/scripts/uninstall.sh apps/linux/scripts/package.sh apps/linux/scripts/package-install.sh
|
||
|
||
- name: Test Linux installation
|
||
shell: bash
|
||
run: |
|
||
mkdir -p target/ci-logs
|
||
python3 apps/linux/tests/installation.py 2>&1 | tee target/ci-logs/linux-installation.log
|
||
|
||
- name: Upload Linux diagnostics
|
||
if: failure()
|
||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||
with:
|
||
name: linux-ci-diagnostics
|
||
if-no-files-found: ignore
|
||
path: |
|
||
target/ci-logs/**
|
||
target/fcitx5-stage1/Testing/**
|
||
target/fcitx5-stage1/CMakeFiles/CMakeOutput.log
|
||
target/fcitx5-stage1/CMakeFiles/CMakeError.log
|
||
target/fcitx4-stage1/Testing/**
|
||
target/fcitx4-stage1/CMakeFiles/CMakeOutput.log
|
||
target/fcitx4-stage1/CMakeFiles/CMakeError.log
|
||
|
||
# 上面所有 Rust 检查都跑在 debug 下,而发出去的东西是 release profile 编的。
|
||
# 两者不是同一份代码:
|
||
# - `debug_assert!` 在 release 下整个消失,它引用的变量随之变成未使用变量;
|
||
# - `#[cfg(debug_assertions)]` 包住的 import / 函数 / 模块在 release 下不存在,
|
||
# debug 下不报的「未使用导入」「死代码」只在 release 下冒出来;
|
||
# - `cfg!(debug_assertions)` 为假的那侧分支可能整段是空的。
|
||
# 这类问题此前的暴露时机是 release.yml,也就是打 tag 正在发版的时候——
|
||
# 修不了也得发,或者撤回 tag。放到这里,它在 PR 上就该红。
|
||
# 用 clippy 而不是 check:目的就是拿 release 视角下的 lint。
|
||
release:
|
||
runs-on: ubuntu-26.04
|
||
timeout-minutes: 20
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
components: clippy
|
||
|
||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||
|
||
# 与 linux-22_04 job 同一份最小依赖:native-tls 走系统 OpenSSL(libssl-dev),
|
||
# cc 编 C 依赖要 pkg-config 与 build-essential。这里不装 fcitx5,因为
|
||
# C++ 壳不在本 job 范围内。
|
||
- name: Install build dependencies
|
||
run: |
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y -qq pkg-config libssl-dev
|
||
|
||
- name: Clippy (release profile)
|
||
run: cargo clippy --release --workspace --exclude qingjian-macos --all-targets --locked -- -D warnings
|
||
|
||
shellcheck:
|
||
runs-on: ubuntu-26.04
|
||
timeout-minutes: 5
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
# 静态检查所有 shell 脚本:安装脚本、发版脚本、图标渲染脚本都在里面。
|
||
# ubuntu runner 自带 shellcheck,无需额外安装步骤。
|
||
# 只报 warning 及以上——style/info 级噪音太多,会淹掉真问题。
|
||
# 用 git ls-files 而不是 find:只管纳入版本控制的文件。
|
||
- name: Shellcheck
|
||
run: shellcheck -S warning $(git ls-files "*.sh")
|
||
|
||
macos:
|
||
# 与 release.yml 的发版 job 用同一个 label(那边是 macos-26)。同一个仓库里 macOS 侧
|
||
# 只应有一个版本:`macos-latest` 会随 GitHub 的 runner 策略漂移,CI 跑在一个版本、
|
||
# 发版构建在另一个版本上,差异只在发版时才暴露——系统库版本、Xcode 命令行工具版本
|
||
# 都会让「CI 全绿、发版挂」成为可能,而那是最难查的一类。
|
||
runs-on: macos-26
|
||
timeout-minutes: 15
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
components: clippy
|
||
|
||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||
|
||
- name: Clippy (IMK shell)
|
||
run: cargo clippy -p qingjian-macos --all-targets --locked -- -D warnings
|
||
|
||
- name: Test (IMK shell)
|
||
run: cargo test -p qingjian-macos --locked
|
||
|
||
# 最老的受支持发行版。docs/user/platforms/linux.md 写着「Fcitx4 提供 Ubuntu 22.04
|
||
# 源码安装」并记了「已验证 Ubuntu 22.04 的 GTK3 文本框输入」,而上面 core job 跑在
|
||
# ubuntu-26.04。两边差四年:22.04 是 glibc 2.35 / GCC 11,26.04 是 glibc 2.39+ / GCC 15。
|
||
# 依赖或代码只要用一个 2.35 之后才有的符号,26.04 上全绿、22.04 上一装就崩——
|
||
# 而崩溃点在用户机器上,本地没有那个环境复现不了。
|
||
#
|
||
# 用容器而不是 `ubuntu-22.04` 的 runner label:那个 label 会随 GitHub 的镜像策略退休,
|
||
# 容器里的 22.04 是实打实的那个用户态,与 runner 上装的是什么无关。
|
||
linux-22_04:
|
||
runs-on: ubuntu-26.04
|
||
container: ubuntu:22.04
|
||
timeout-minutes: 25
|
||
steps:
|
||
# 容器里是裸系统:checkout 要 git、rustup 要 curl 与证书。
|
||
# 依赖按 install.sh 的清单装(cmake / g++ / pkg-config / python3 / libssl-dev)——
|
||
# 第一版漏了 libssl-dev,openssl-sys 当场编不过,这个 job 也就抓到了它。
|
||
# 那正是它该干的事:对着用户实际走的 install.sh 路径验,漏一个包就暴露。
|
||
#
|
||
# 字体是**额外**的一项,install.sh 的清单里没有:bamboo-slip 的自绘 UI 要一个
|
||
# 系统 UI 字体(fonts/mod.rs 里 load_first 失败直接返回 NoUiFont,没有回退),
|
||
# 于是 17 个 UI 测试在没装字体的裸容器里全红。
|
||
# ubuntu-26.04 runner 上它们是镜像预装的,所以 core job 一直是绿的——
|
||
# 也就是说 CI 的绿有一部分来自 runner 镜像而不是显式声明的依赖。
|
||
# 这里显式装上,让这个 job 反映「用户装完依赖后的状态」。
|
||
# install.sh 自己漏了字体依赖是另一个问题(生产代码同样会硬失败),不在这改。
|
||
- name: Install base tooling
|
||
run: |
|
||
apt-get update
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||
git curl ca-certificates build-essential pkg-config python3 \
|
||
cmake libssl-dev \
|
||
fonts-dejavu-core
|
||
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
|
||
# install.sh 在 22.04 上装的就是这两个包(见它的 cargo_args),所以先按原样编一遍:
|
||
# 这是「用户装得上」那道关,比跑测试更接近发版路径。
|
||
- name: Build (what install.sh builds)
|
||
run: cargo build --locked -p qingjian-linux-server -p qingjian-settings
|
||
|
||
# 再跑一遍 workspace 测试:老 glibc 上某个依赖编不过或行为不同,在这里暴露。
|
||
- name: Test
|
||
run: cargo test --workspace --exclude qingjian-macos --locked
|
||
|
||
# Kotlin 与 Gradle 只有这里编;data/generated/ 不在仓库里,带的是随包样例数据
|
||
android:
|
||
runs-on: ubuntu-26.04
|
||
timeout-minutes: 30
|
||
env:
|
||
NDK_VERSION: 29.0.13599879
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
targets: aarch64-linux-android
|
||
|
||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||
|
||
# Gradle 8.14 只认到 JDK 24,runner 缺省的是 25
|
||
- name: Use JDK 21
|
||
run: |
|
||
if [[ ! -d "${JAVA_HOME_21_X64:-}" ]]; then
|
||
echo "runner 上没有 JDK 21(JAVA_HOME_21_X64):" >&2
|
||
ls /usr/lib/jvm >&2
|
||
exit 1
|
||
fi
|
||
echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV"
|
||
|
||
# NDK 版本要与 app/build.gradle.kts 的 ndkVersion 一致
|
||
- name: Install NDK and cargo-ndk
|
||
run: |
|
||
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" "platforms;android-36" > /dev/null
|
||
cargo install cargo-ndk --version 4.1.2 --locked
|
||
echo "sdk.dir=$ANDROID_HOME" > apps/android/local.properties
|
||
|
||
- name: Build debug APK
|
||
working-directory: apps/android
|
||
run: ./gradlew assembleDebug --no-daemon --console=plain
|
||
|
||
windows:
|
||
runs-on: windows-latest
|
||
timeout-minutes: 15
|
||
env:
|
||
# Server 的 build.rs 嵌 uiAccess manifest,没签名的 exe(含测试二进制)起不来(os error 740);与 release.yml 一样关掉
|
||
QINGJIAN_UIACCESS: '0'
|
||
steps:
|
||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1
|
||
with:
|
||
toolchain: 1.96.0
|
||
components: clippy
|
||
targets: i686-pc-windows-msvc
|
||
|
||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||
|
||
- name: Clippy (Server, TSF DLL, Settings)
|
||
run: cargo clippy -p qingjian-windows-server -p qingjian-windows-tsf -p qingjian-windows-settings -p qingjian-settings -p qingjian-clipboard-app --all-targets --locked -- -D warnings
|
||
|
||
# core job 在 Linux 上文档化不到 #[cfg(windows)] 的模块,那一半的文档错误只有这里看得见。
|
||
- name: Rustdoc (Server, TSF DLL, Settings)
|
||
env:
|
||
RUSTDOCFLAGS: -D warnings
|
||
run: cargo doc -p qingjian-windows-server -p qingjian-windows-tsf -p qingjian-windows-settings -p qingjian-settings -p qingjian-clipboard-app --no-deps --locked
|
||
|
||
- name: Test (Server, TSF DLL, Settings)
|
||
run: cargo test -p qingjian-windows-server -p qingjian-windows-tsf -p qingjian-windows-settings -p qingjian-settings -p qingjian-clipboard-app --locked
|
||
|
||
# 安装包另带一份 32 位 DLL(企业微信 / WPS 这类 32 位应用只能加载它)。
|
||
# 这里原来只跑 `cargo check`:能编就算过,一条 lint 都不看。而 32 位恰恰是
|
||
# lint 最该看一眼的地方——usize 只有 32 位、指针宽度不同、`as u32` 的截断
|
||
# 在 64 位上永远试不出来。改成 clippy,范围与原来一致(不 --all-targets:
|
||
# examples 是按 64 位宿主写的,不在这条分发路径上)。
|
||
- name: Clippy (TSF DLL, 32-bit)
|
||
run: cargo clippy -p qingjian-windows-tsf --target i686-pc-windows-msvc --locked -- -D warnings
|