-
Notifications
You must be signed in to change notification settings - Fork 0
321 lines (298 loc) · 14.1 KB
/
Copy pathrelease.yml
File metadata and controls
321 lines (298 loc) · 14.1 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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
name: Release
# 触发:本地 `pnpm version <major|minor|patch>` 自动改 package.json + Cargo.toml + commit + tag;
# 之后 `git push && git push --tags`,tag `v*` 推上来这条 workflow 被触发。
#
# 工作流被拆成两阶段,满足 "全部成功才发布 / 任一失败整体中止":
# 1. `build` 矩阵(6 平台并行,fail-fast: true)—— 直接调 `pnpm tauri build`,产物 upload-artifact
# 到 Actions 私有缓存。**不动 Release**。任一 platform 失败立即取消其他 jobs,避免"半成品 draft"
# 被部分 upload 出去。
# 2. `release` job(needs: build, if: success())—— 下载所有 6 个 artifact,给 installer 加平台
# 前缀,自己合成 `latest.json`,最后用 softprops/action-gh-release 一次性创建 draft Release +
# 上传所有 asset。
#
# 为什么不用 tauri-apps/tauri-action:它把 build + 创建 release + upload + 合并 latest.json 全打包
# 到一个 step,无法在多 matrix job 之间做 "全部成功才上传" 的原子切片。直接调 Tauri CLI 损失的只是
# keychain import 的便利——Tauri 2 CLI 自身已经认 `APPLE_CERTIFICATE` 等 env 自动处理 keychain。
#
# 首次启用前必做(一次性):
# 1. `pnpm tauri signer generate -w ~/.tauri/openspeech.key`(已完成)
# 2. 把输出 pubkey 贴到 src-tauri/tauri.conf.json → plugins.updater.pubkey(已完成)
# 3. GitHub Secrets:
# - TAURI_SIGNING_PRIVATE_KEY (整个 key 文件内容)
# - TAURI_SIGNING_PRIVATE_KEY_PASSWORD (生成时输入的密码,无密码留空)
# 4. **组织级权限**:OpenLoaf org → Settings → Actions → General → Workflow permissions 必须设
# "Read and write permissions"。否则 release job 创建 draft Release 时返回 "Resource not
# accessible by integration"。这是组织级配置,repo 改不了,也无法用 yml `permissions:` 提权。
#
# macOS 代码签名 + 公证(Secrets 命名跟 OpenLoaf 仓库对齐,便于跨项目复用一套 Apple 证书):
# MAC_CER_BASE64 = Developer ID Application 证书 .p12 的 base64
# MAC_CER_PASSWORD = 上述 .p12 的导出密码
# APPLE_SIGNING_IDENTITY = "Developer ID Application: 你的名字 (TEAMID)"
# APPLE_ID = Apple ID 邮箱
# APPLE_APP_SPECIFIC_PASSWORD= App-Specific Password(appleid.apple.com 生成)
# APPLE_TEAM_ID = 10 位 Team ID
# 全部为空时 Tauri CLI 自动跳过签名,回退到未签名包。
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
strategy:
# 任一 platform 失败立即取消其他正在跑的 jobs,避免:
# 1. 部分 platform 跑一两个小时再因为已知失败而被人工 cancel,浪费配额;
# 2. release job 因为 needs: build 失败永远不会进,所以让 build 早死早超生。
fail-fast: true
matrix:
include:
# ---- macOS ----
- name: macOS ARM64
platform: macos-latest
rust-target: aarch64-apple-darwin
args: '--target aarch64-apple-darwin'
artifact: macOS-arm64
arch-tag: aarch64
# Intel 也跑在 Apple Silicon runner 上交叉编译 x86_64 —— GitHub 已把 macos-13
# (Intel)从免费 runner 池退役,留下来的 Intel runner 走付费 Larger Runner 标签,
# 现状 v0.2.0/v0.2.1 的 Intel job 永远排不到队。dtolnay/rust-toolchain 里声明
# target,cargo 自动交叉编译;OpenSpeech 的 Rust 依赖(cpal/tauri/reqwest 等)
# 没有需要 brew 的 native 依赖,纯 cross OK。OpenLoaf 仓库 publish-desktop.yml
# 同样的处理。
- name: macOS Intel
platform: macos-latest
rust-target: x86_64-apple-darwin
args: '--target x86_64-apple-darwin'
artifact: macOS-intel
arch-tag: x64
# ---- Linux ----
- name: Linux x86_64
platform: ubuntu-22.04
rust-target: ''
args: ''
artifact: Linux-x86_64
arch-tag: ''
- name: Linux ARM64
platform: ubuntu-22.04-arm
rust-target: ''
args: ''
artifact: Linux-arm64
arch-tag: ''
# ---- Windows ----
- name: Windows x86_64
platform: windows-latest
rust-target: ''
args: ''
artifact: Windows-x86_64
arch-tag: ''
- name: Windows ARM64
platform: windows-11-arm
rust-target: ''
args: ''
artifact: Windows-arm64
arch-tag: ''
name: Build · ${{ matrix.name }}
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust-target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
key: ${{ matrix.rust-target || matrix.platform }}
# Linux 打包 deb + rpm + AppImage 三件套需要的全套工具链:
# - 运行时/编译:webkit2gtk-4.1(不是 4.0)、gtk-3、ayatana appindicator、librsvg2、openssl
# - 音频:libasound2-dev(cpal/alsa-sys 找 alsa.pc)
# - 打包:patchelf / rpm / fakeroot / file / wget / libfuse2
# - **xdg-utils**:tauri-plugin-opener 在 Linux 打包阶段会校验 `/usr/bin/xdg-open` 存在,
# 缺则直接 "failed to bundle project xdg-open binary not found"。
- name: Install Linux dependencies
if: startsWith(matrix.platform, 'ubuntu-')
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libssl-dev \
libasound2-dev \
patchelf \
rpm \
fakeroot \
file \
wget \
libfuse2 \
xdg-utils
- name: Install frontend deps
run: pnpm install --frozen-lockfile
# 直接调 Tauri CLI;macOS 签名 / 公证 / updater 签名都靠 env 透传给 CLI 自己处理。
# tauri-action 替你做的 keychain import 是 Tauri CLI 2.x 已经内置的能力,env 配齐即可。
- name: Tauri build
shell: bash
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# macOS 签名+公证 env(其它平台无害;CLI 看到没填的 env 自动跳过对应步骤)
APPLE_CERTIFICATE: ${{ secrets.MAC_CER_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CER_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: pnpm tauri build ${{ matrix.args }}
# macOS .app.tar.gz 默认命名是 `OpenSpeech.app.tar.gz` —— 不带 arch!ARM64 + Intel 两个 job
# 上传同名 artifact 互不影响(按 artifact name 分组),但下载到 release job 的 staging 目录
# 时会同名覆盖。因此在 upload 之前就给它加上 arch 后缀,跟 dmg/exe/AppImage 那种"自带 arch
# 后缀"的命名风格对齐。`.sig` 同步重命名(updater 拉 .app.tar.gz 时按同名找 sig)。
- name: Tag macOS updater artifact with arch
if: startsWith(matrix.platform, 'macos')
shell: bash
env:
ARCH_TAG: ${{ matrix.arch-tag }}
RUST_TARGET: ${{ matrix.rust-target }}
run: |
set -euo pipefail
version=$(node -p "require('./package.json').version")
dir="src-tauri/target/${RUST_TARGET}/release/bundle/macos"
if [[ -f "$dir/OpenSpeech.app.tar.gz" ]]; then
mv "$dir/OpenSpeech.app.tar.gz" "$dir/OpenSpeech_${version}_${ARCH_TAG}.app.tar.gz"
mv "$dir/OpenSpeech.app.tar.gz.sig" "$dir/OpenSpeech_${version}_${ARCH_TAG}.app.tar.gz.sig"
echo "[mv] OpenSpeech.app.tar.gz -> OpenSpeech_${version}_${ARCH_TAG}.app.tar.gz"
fi
- name: Upload bundle artifacts
uses: actions/upload-artifact@v4
with:
name: bundle-${{ matrix.artifact }}
if-no-files-found: error
retention-days: 3
# 各平台只取实际产出的文件类型;nullglob 行为由 actions/upload-artifact 自己处理。
path: |
src-tauri/target/**/release/bundle/dmg/*.dmg
src-tauri/target/**/release/bundle/macos/*.app.tar.gz
src-tauri/target/**/release/bundle/macos/*.app.tar.gz.sig
src-tauri/target/**/release/bundle/nsis/*-setup.exe
src-tauri/target/**/release/bundle/nsis/*-setup.nsis.zip
src-tauri/target/**/release/bundle/nsis/*-setup.nsis.zip.sig
src-tauri/target/**/release/bundle/deb/*.deb
src-tauri/target/**/release/bundle/rpm/*.rpm
src-tauri/target/**/release/bundle/appimage/*.AppImage
src-tauri/target/**/release/bundle/appimage/*.AppImage.tar.gz
src-tauri/target/**/release/bundle/appimage/*.AppImage.tar.gz.sig
release:
name: Publish draft release
needs: build
if: success()
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all bundles
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: bundle-*
# staging/ 是即将上传到 Release 的扁平目录:
# - installer(dmg/exe/deb/rpm/AppImage)加平台前缀,下载页一目了然
# - updater 链路(.app.tar.gz / .nsis.zip / .AppImage.tar.gz + 各自 .sig)保持原名 —— 因为
# latest.json 里 url 字段写死,改名 = 自动更新断链。
- name: Stage release files
shell: bash
run: |
set -euo pipefail
mkdir -p staging
for art in artifacts/bundle-*; do
[[ -d "$art" ]] || continue
prefix="${art##*/bundle-}"
while IFS= read -r f; do
[[ -f "$f" ]] || continue
base="$(basename "$f")"
# 注意 `*.tar.gz` / `*.tar.gz.sig` 的匹配要放在 `*.gz` 之前;这里用具体扩展规避歧义
case "$base" in
*.app.tar.gz|*.app.tar.gz.sig|*.nsis.zip|*.nsis.zip.sig|*.AppImage.tar.gz|*.AppImage.tar.gz.sig)
cp "$f" "staging/${base}"
;;
*.dmg|*.exe|*.deb|*.rpm|*.AppImage)
cp "$f" "staging/${prefix}-${base}"
;;
*)
echo "::warning::unrecognized artifact, skipping: $f"
;;
esac
done < <(find "$art" -type f)
done
echo "--- staging/ ---"
ls -la staging/
# latest.json 是 Tauri updater 客户端 fetch 的入口(plugins.updater.endpoints 指向它)。
# 字段:version / pub_date / platforms[<platform-arch>].{signature, url}。
# signature 取 .sig 文件全文;url 指向 Release 上的对应 asset 直链(保留原名)。
- name: Generate latest.json
shell: bash
env:
TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
cd staging
pub_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
platforms='{}'
for sig in *.sig; do
[[ -f "$sig" ]] || continue
base="${sig%.sig}"
sig_content="$(cat "$sig")"
url="https://github.com/${REPO}/releases/download/${TAG}/${base}"
case "$base" in
*aarch64*.app.tar.gz) key="darwin-aarch64" ;;
*_x64*.app.tar.gz|*x86_64*.app.tar.gz) key="darwin-x86_64" ;;
*_amd64.AppImage.tar.gz) key="linux-x86_64" ;;
*_arm64.AppImage.tar.gz|*_aarch64.AppImage.tar.gz) key="linux-aarch64" ;;
*_x64-setup.nsis.zip|*_x86_64-setup.nsis.zip) key="windows-x86_64" ;;
*_arm64-setup.nsis.zip|*_aarch64-setup.nsis.zip) key="windows-aarch64" ;;
*)
echo "::warning::unknown sig pattern, skipping: $base"
continue
;;
esac
platforms="$(jq -n --argjson p "$platforms" --arg key "$key" --arg sig "$sig_content" --arg url "$url" \
'$p + {($key): {signature: $sig, url: $url}}')"
done
jq -n \
--arg version "$TAG" \
--arg pub_date "$pub_date" \
--argjson platforms "$platforms" \
'{version: $version, notes: "Auto-generated release", pub_date: $pub_date, platforms: $platforms}' \
> ../latest.json
echo "--- latest.json ---"
cat ../latest.json
- name: Publish draft release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: 'OpenSpeech ${{ github.ref_name }}'
draft: true
prerelease: false
fail_on_unmatched_files: true
body: |
Auto-generated release for **${{ github.ref_name }}**.
6 targets: macOS ARM64 / macOS Intel / Linux x86_64 / Linux ARM64 / Windows x86_64 / Windows ARM64.
Installer 文件名带 `macOS-arm64- / Windows-x86_64- / Linux-arm64-` 等平台前缀,方便手动下载。
Updater 链路(`*.app.tar.gz` / `*.nsis.zip` / `*.AppImage.tar.gz` + `.sig` + `latest.json`)
保持原名,应用内自动更新通过 `latest.json` 索引到对应平台。
files: |
staging/*
latest.json