Files
yenharvey b5a33d7ac8 build(clipboard): 独立安装包与 clipboard-v 发版流程
只要剪贴板的人不必装整个输入法:按用户安装、不要管理员。
推 clipboard-v<版本> 标签在 Windows 上打包,发到 qingjian-clipboard 的 Release,说明取自剪贴板自己的更新日志。
Windows CI 加上剪贴板:它只在 Windows 上有实现,Linux 的 core job 只编到占位的 main。
2026-11-03 10:49:04 +08:00

381 lines
19 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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