Repository navigation
261 lines (242 loc) · 12.6 KB
/
Copy pathci.yml
File metadata and controls
261 lines (242 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# The gates every change has to pass.
#
# This is a Tauri app, so there are two toolchains: the frontend
# (typecheck + build) and the Rust shell (fmt + clippy + tests). Both
# run, because a green Rust side with a broken `tsc` is an app that
# won't start.
#
# No `.app` is built here. That is `release.yml`'s job, and it only runs on
# a tag — bundling on every push would add minutes to every PR to produce a
# file nobody downloads.
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
# **同一个分支、同一个 PR 上新的一次推送,把还在跑的旧一次取消掉。**结果只看最新的那次,
# 旧的跑完也没人看,只会占着并发名额(免费组织同时最多 20 个任务、其中 5 个 macOS)。
# main 和 dev 上的不取消:那里每一笔都要有自己的结果。
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}
env:
CARGO_TERM_COLOR: always
jobs:
frontend:
name: Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 版本号写在三个文件里,漂了之后谁也不报错 —— 直到打 tag 的那一
# 刻,而那时候要收回一个已经推上去的 tag。在改它的那个 PR 上拦。
- name: One version, three files
run: bash scripts/version.sh
# 发版流水线只在推 tag 和 `rehearse/**` 时才跑,文件写坏了,PR 上什么都
# 看不出来 —— GitHub 只会在之后每次推送时记一条「workflow file issue」,
# 演练也不会触发。lite#169 就这样把一行重复的 `- name:` 带进了 dev。
# 在改它的那个 PR 上拦。脚本里的 shell 不在这里查,只查工作流本身
- name: Workflow files are valid
run: |
bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash) 1.7.12
./actionlint -color -shellcheck=
# Linux 用户的一行安装脚本在别人的机器上、用别人的 sh 跑(Ubuntu 上是
# dash),写出 bash 的语法就是装不上。runner 自带 shellcheck
- name: The install script is POSIX sh
run: shellcheck -s sh scripts/install.sh
# 发布页的正文只在推 tag 时才写(release.yml 的 `release-body`)。脚本和
# `release-notes/` 下的说明在改它们的那个 PR 上就核对:链接对不对得上
# 发出去的文件、说明里有没有混进中文
- name: Release page text
run: python3 scripts/release_notes_test.py
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
# 前端单元测试原来只在本机跑。其中有守规矩的测试(比如不许绕过
# `src/lib/tauriEvent.ts` 直接订阅 Tauri 事件),不进 CI 就形同虚设。
- name: Unit tests
run: pnpm test
- name: Build
run: pnpm build
# Formatting doesn't depend on the platform, so it is checked once, here,
# and the three platform jobs below only compile and test. It needs no
# build and no cache: rustfmt only reads the source.
format:
name: Rust format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --manifest-path src-tauri/Cargo.toml --all --check
rust:
name: Rust (macOS)
# macOS, Windows and Linux each get their own job: every platform has
# `cfg` branches (menu bar, notifications, theme, login-shell env) that
# only its own compiler ever sees.
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
# **只在 dev 上存。**一个 PR 读得到的缓存只有三处:它自己的、目标分支(dev)
# 的、默认分支(main)的,读不到别的 PR 存的。PR 上存的那份只有同一个 PR
# 再推一次才用得上,却一样大,挤占仓库 10 GB 的缓存额度、把 dev 的挤掉。
# main 只是快进,它的推送和 dev 上同一笔一样,存了也没人读
save-if: ${{ github.ref == 'refs/heads/dev' }}
# **一个只检查代码的任务为什么要下载一个发布产物。**
#
# `tauri.conf.json` 声明 `.app` 里装着 twcore,而 Tauri 的
# build script 在**编译期**就校验那个文件在不在 —— 所以连
# `cargo clippy` 都需要它。
#
# 换个写法可以绕开(打包完再手工拷一份进去),代价是「没有网关
# 的包」又能被悄悄造出来 —— 那正是引入这套东西要修的 bug。宁可
# 让 CI 多下十四兆。校验和对得上就不会重复下载。
- name: Fetch the core binary the bundle declares
run: bash src-tauri/scripts/fetch-core.sh
# Warnings are errors. Relaxing this on CI is the same as removing
# it — a warning nobody is forced to read is a warning nobody
# reads.
- name: Clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
- name: Test
run: cargo test --manifest-path src-tauri/Cargo.toml
# **这台机器上没有 unix socket。**控制面在那里落在回环端口上,凭据是唯一的
# 门,而在这个任务出现之前,那一半从来没有被编译过 —— 一个 `#[cfg(windows)]`
# 分支「检查通过」和「根本没人看过它」长得一模一样。
#
# **只编、只测,不打包。**安装包在 release.yml 里打;改了打包流程,推一个
# `rehearse/` 开头的分支先演练。这个任务钉住的是「编得过、测得过、连得上」。
rust-windows:
name: Rust (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
# **失败也存。**这个 action 默认只在任务成功时保存缓存,而一个刚
# 立起来、还在逐条修的任务恰恰一次都不会成功 —— 于是「红 → 不存
# 缓存 → 从零编译 → 还是红」自己维持住了。PR 上不存(见 macOS 那一档),
# 所以这条只对 dev 起作用
cache-on-failure: true
save-if: ${{ github.ref == 'refs/heads/dev' }}
# 理由同 macOS 那一档:`tauri.conf.json` 声明包里装着 twcore,而 Tauri
# 的 build script 在**编译期**就校验那个文件在不在。
#
# 取回来的是 Windows 那一份,名字带 `.exe` —— 见 `CORE_EXE`。
- name: Fetch the core binary the bundle declares
shell: bash
run: bash src-tauri/scripts/fetch-core.sh
- name: Clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
# 这里的测试**包含那条接缝测试**:起一份真的 twcore.exe,让桌面端这一侧
# 的客户端在回环端口上真连上去。那条路在这个平台上没有替代品,而它是否
# 成立,只有这台机器答得出来。
- name: Test
run: cargo test --manifest-path src-tauri/Cargo.toml
# **绿色版会被放在 U 盘和网络共享上。**它的自更新靠「正在运行的 exe 能改名、
# 腾出来的名字上能写新的」(`portable::swap_in`),上一步只在 NTFS 上证明过。
# 这里建两块小虚拟盘、格式化成 FAT32 和 exFAT,再开一个本机的 SMB 共享,把
# portable 那一组测试(含改名正在运行的 exe 那一条)在这三处各跑一遍:
# `TW_PORTABLE_TEST_DIR` 指定测试文件夹建在哪。
#
# diskpart、New-SmbShare 要管理员身份。GitHub 的 Windows 机器上步骤本来就以
# 管理员身份运行(UAC 关着),先核实一遍,不是的话直接报错,不让它悄悄跳过。
# 测试程序上一步已经编好,这里不重编
- name: Portable tests on FAT32, exFAT and an SMB share
shell: pwsh
run: |
$me = [Security.Principal.WindowsIdentity]::GetCurrent()
if (-not ([Security.Principal.WindowsPrincipal]$me).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "This step needs an elevated shell"
}
$dirs = @()
foreach ($v in @(@{ fs = 'fat32'; letter = 'R' }, @{ fs = 'exfat'; letter = 'S' })) {
$vhd = Join-Path $env:RUNNER_TEMP "tw-$($v.fs).vhdx"
$script = Join-Path $env:RUNNER_TEMP "tw-$($v.fs).diskpart"
@(
"create vdisk file=""$vhd"" maximum=2048 type=expandable"
"select vdisk file=""$vhd"""
"attach vdisk"
"convert mbr"
"create partition primary"
"format fs=$($v.fs) quick label=TW$($v.fs.ToUpper())"
"assign letter=$($v.letter)"
) | Set-Content -Encoding ascii $script
diskpart /s $script
if ($LASTEXITCODE -ne 0) { throw "diskpart could not make the $($v.fs) volume" }
$vol = Get-Volume -DriveLetter $v.letter
if ($vol.FileSystem -ne $v.fs) { throw "$($v.letter): is $($vol.FileSystem), not $($v.fs)" }
$dirs += "$($v.letter):\"
}
$share = Join-Path $env:RUNNER_TEMP 'tw-share'
New-Item -ItemType Directory $share | Out-Null
New-SmbShare -Name tw-share -Path $share -FullAccess $me.Name | Out-Null
$dirs += '\\localhost\tw-share'
Get-Volume -DriveLetter R, S | Format-Table DriveLetter, FileSystem, FileSystemLabel, Size
Get-SmbShare -Name tw-share | Format-Table Name, Path
$failed = @()
foreach ($dir in $dirs) {
Write-Host "::group::portable tests in $dir"
$env:TW_PORTABLE_TEST_DIR = $dir
cargo test --manifest-path src-tauri/Cargo.toml --lib -- portable::tests --nocapture
if ($LASTEXITCODE -ne 0) { $failed += $dir }
Write-Host "::endgroup::"
}
if ($failed) { throw "portable tests failed in: $($failed -join ', ')" }
# 理由同 Windows 那一档:一个 `#[cfg(target_os = "linux")]` 分支「检查通过」
# 和「根本没人编过它」长得一模一样。跟随系统的深浅色(portal)、登录 shell
# 的环境、系统通知,这些只在这里编、只在这里测。
#
# **ubuntu-22.04,不是 latest。**发出去的包在 22.04 上编(glibc 2.35 是支持
# 的底线),检查也在同一个系统上做。
rust-linux:
name: Rust (Linux)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
# Tauri v2 在 Debian/Ubuntu 上的系统依赖,照官方的前置清单
# (https://v2.tauri.app/start/prerequisites/#linux);`dbus` 给下面的
# `dbus-run-session`
- name: System libraries
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev \
libssl-dev libayatana-appindicator3-dev librsvg2-dev dbus
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-on-failure: true
# 见 macOS 那一档
save-if: ${{ github.ref == 'refs/heads/dev' }}
# 理由同 macOS 那一档:`tauri.conf.json` 声明包里装着 twcore,而 Tauri
# 的 build script 在**编译期**就校验那个文件在不在;接缝测试还要真的
# 起它。
- name: Fetch the core binary the bundle declares
run: bash src-tauri/scripts/fetch-core.sh
- name: Clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
# **套一层会话总线。**这台机器没有桌面,而有几条测试要真的连 D-Bus
# (系统通知的往返、深浅色的 portal);不套的话通知那条会悄悄跳过自己。
# `dbus-run-session` 起一个只活到命令结束的总线,把地址交给它
- name: Test
run: dbus-run-session -- cargo test --manifest-path src-tauri/Cargo.toml