- Accept upstream's show_capsule_window_for_recording wrapper + CapsuleShowStrategy (our Linux fallback logic is subsumed by the upstream strategy pattern) - Keep Linux on NoActivate strategy; show_capsule_window_no_activate returns true on Linux to suppress capsule window (no popup on Wayland/fcitx5) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
586 lines
29 KiB
YAML
586 lines
29 KiB
YAML
name: Release Tauri (cross-platform)
|
||
# 触发条件:
|
||
# - 推 v*.*.*-tauri 形式的 tag(与老 Swift 版的 vX.Y.Z 区分开,不冲突)
|
||
# - 手动 dispatch(用于测试构建,不发版)
|
||
#
|
||
# 输出:
|
||
# macOS arm64/x64 .dmg + Windows x64 .msi/.exe + Linux x64 .deb/.rpm/.AppImage,自动作为 GitHub Release 资产上传。
|
||
#
|
||
# macOS 分发:
|
||
# - 配好 APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD / APPLE_ID /
|
||
# APPLE_PASSWORD / APPLE_TEAM_ID 后,Tauri 会做 Developer ID 签名和公证。
|
||
# 用户从浏览器下载后不需要手工 xattr。
|
||
# - 未配置 Apple secrets 时自动回退 ad-hoc 签名,GitHub Actions 会打印 warning。
|
||
# - Windows 没签名(无证书),Win 11 SmartScreen 会警告 "未识别的发布者",用户点"仍要运行"。
|
||
# - 任意一个 platform 失败不影响另一个继续构建(fail-fast: false)。
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
- 'v*-tauri'
|
||
workflow_dispatch:
|
||
|
||
jobs:
|
||
build:
|
||
permissions:
|
||
contents: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- platform: macos-latest
|
||
rust-target: aarch64-apple-darwin
|
||
updater-target: darwin
|
||
updater-arch: aarch64
|
||
- platform: macos-15-intel
|
||
rust-target: x86_64-apple-darwin
|
||
updater-target: darwin
|
||
updater-arch: x86_64
|
||
- platform: windows-latest
|
||
rust-target: x86_64-pc-windows-msvc
|
||
updater-target: windows
|
||
updater-arch: x86_64
|
||
- platform: ubuntu-22.04
|
||
rust-target: x86_64-unknown-linux-gnu
|
||
updater-target: linux
|
||
updater-arch: x86_64
|
||
runs-on: ${{ matrix.platform }}
|
||
env:
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
# 渠道由 tag 后缀决定:
|
||
# v<v>-beta-tauri → beta 渠道(GitHub Release 标 prerelease,manifest 文件名带 -beta 后缀,
|
||
# 正式版用户的 endpoint 拿不到)
|
||
# v<v>-tauri → stable 渠道(正式版,文件名沿用旧约定,向后兼容)
|
||
# workflow_dispatch / 非 tag 触发时 github.ref_name 不是 tag 字符串,
|
||
# endsWith 返回 false,回退为 stable,不改变现有 dispatch 行为。
|
||
OPENLESS_RELEASE_CHANNEL: ${{ endsWith(github.ref_name, '-beta-tauri') && 'beta' || 'stable' }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
# vendor/qwen-asr 是 macOS 上 build.rs 必须的 git submodule(cc-rs 编译
|
||
# Open-Less/qwen-asr fork 的 C 源),不拉就会在 mac 端 cargo build 阶段挂掉。
|
||
submodules: recursive
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 20
|
||
cache: npm
|
||
cache-dependency-path: 'openless-all/app/package-lock.json'
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
with:
|
||
targets: ${{ matrix.rust-target }}
|
||
|
||
- name: Cache Cargo
|
||
uses: swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: 'openless-all/app/src-tauri -> target'
|
||
|
||
- name: Install Linux bundle deps
|
||
if: matrix.platform == 'ubuntu-22.04'
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y \
|
||
build-essential \
|
||
curl \
|
||
file \
|
||
libasound2-dev \
|
||
libayatana-appindicator3-dev \
|
||
libfuse2 \
|
||
librsvg2-dev \
|
||
libssl-dev \
|
||
libwebkit2gtk-4.1-dev \
|
||
libxdo-dev \
|
||
patchelf \
|
||
rpm \
|
||
wget
|
||
|
||
- name: Install npm deps
|
||
working-directory: 'openless-all/app'
|
||
run: npm ci
|
||
|
||
- name: Check updater signing availability
|
||
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||
shell: bash
|
||
env:
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
run: |
|
||
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||
echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for signed auto-update artifacts."
|
||
exit 1
|
||
fi
|
||
|
||
- name: Check Apple signing availability
|
||
if: startsWith(matrix.platform, 'macos') && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||
shell: bash
|
||
env:
|
||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
run: |
|
||
missing=()
|
||
for name in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do
|
||
if [ -z "${!name:-}" ]; then
|
||
missing+=("$name")
|
||
fi
|
||
done
|
||
if [ "${#missing[@]}" -gt 0 ]; then
|
||
echo "::warning::macOS release will use ad-hoc signing because Apple signing/notarization secrets are missing: ${missing[*]}"
|
||
fi
|
||
|
||
- name: Import Apple Developer ID certificate
|
||
if: startsWith(matrix.platform, 'macos')
|
||
shell: bash
|
||
env:
|
||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||
run: |
|
||
if [ -z "${APPLE_CERTIFICATE:-}" ] || [ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]; then
|
||
echo "No Apple certificate secrets configured; macOS build will use ad-hoc signing."
|
||
exit 0
|
||
fi
|
||
|
||
KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD:-$(openssl rand -base64 32)}"
|
||
CERT_PATH="$RUNNER_TEMP/openless-certificate.p12"
|
||
KEYCHAIN_PATH="$RUNNER_TEMP/openless-build.keychain-db"
|
||
|
||
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
|
||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
security default-keychain -s "$KEYCHAIN_PATH"
|
||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
security set-keychain-settings -t 3600 -u "$KEYCHAIN_PATH"
|
||
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
|
||
DEVELOPER_ID_INFO="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep 'Developer ID Application' | head -n 1)"
|
||
if [ -n "$DEVELOPER_ID_INFO" ]; then
|
||
CERT_INFO="$DEVELOPER_ID_INFO"
|
||
else
|
||
CERT_INFO="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep -E 'Apple Distribution|Apple Development' | head -n 1)"
|
||
fi
|
||
if [ -z "$CERT_INFO" ]; then
|
||
echo "Apple certificate imported, but no usable code-signing identity was found."
|
||
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||
exit 1
|
||
fi
|
||
|
||
CERT_ID="$(echo "$CERT_INFO" | awk -F'"' '{print $2}')"
|
||
echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> "$GITHUB_ENV"
|
||
echo "Imported Apple signing identity: $CERT_ID"
|
||
|
||
- name: Configure Apple notarization
|
||
if: startsWith(matrix.platform, 'macos')
|
||
shell: bash
|
||
env:
|
||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
APPLE_PROVIDER_SHORT_NAME: ${{ secrets.APPLE_PROVIDER_SHORT_NAME }}
|
||
run: |
|
||
for name in APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID APPLE_PROVIDER_SHORT_NAME; do
|
||
value="${!name:-}"
|
||
if [ -n "$value" ]; then
|
||
echo "$name=$value" >> "$GITHUB_ENV"
|
||
fi
|
||
done
|
||
|
||
# ── macOS:用我们自己的 build-mac.sh,统一处理签名、公证和 artifact 清理 ──
|
||
- name: Build (macOS)
|
||
if: startsWith(matrix.platform, 'macos')
|
||
working-directory: 'openless-all/app'
|
||
env:
|
||
INSTALL: '0' # CI 不要装到 /Applications,也不要 reset TCC
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
run: bash scripts/build-mac.sh
|
||
|
||
# ── Windows:先 build OpenLessIme.dll(x64+x86),再跑 tauri bundle。
|
||
# openless-ime.wxs 用 $(env.OPENLESS_IME_DLL_X64) / _X86 拿绝对路径,
|
||
# 跨 candle/light cwd 都能 resolve(Tauri wix bundler cwd 不固定)。
|
||
- name: Build Windows IME native DLLs
|
||
if: matrix.platform == 'windows-latest'
|
||
shell: pwsh
|
||
working-directory: 'openless-all/app'
|
||
run: |
|
||
$appRoot = (Resolve-Path .).Path
|
||
foreach ($t in @(
|
||
@{ Platform = 'x64'; Folder = 'x64'; EnvName = 'OPENLESS_IME_DLL_X64' },
|
||
@{ Platform = 'Win32'; Folder = 'x86'; EnvName = 'OPENLESS_IME_DLL_X86' }
|
||
)) {
|
||
$out = Join-Path $appRoot "src-tauri\target\windows-ime-msvc\$($t.Folder)\Release"
|
||
$obj = Join-Path $appRoot "src-tauri\target\windows-ime-msvc\obj\$($t.Folder)\Release"
|
||
./scripts/windows-ime-build.ps1 -Configuration Release -Platform $t.Platform -OutputDirectory $out -IntermediateDirectory $obj
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "OpenLessIme $($t.Platform) build failed with exit $LASTEXITCODE"
|
||
}
|
||
$dll = (Resolve-Path (Join-Path $out 'OpenLessIme.dll')).Path
|
||
if (-not (Test-Path $dll)) {
|
||
throw "OpenLessIme.dll not produced at $dll"
|
||
}
|
||
"$($t.EnvName)=$dll" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||
Write-Host "[ok] built $dll (exported $($t.EnvName))"
|
||
|
||
# bundle.resources 引用的是 src-tauri/openless-ime-payload/{x64,x86}/OpenLessIme.dll
|
||
# (仓库里 commit 的是 0 字节占位让 mac 本地 build 也能 resolve)。
|
||
# 这里用真 dll 覆盖占位,让 NSIS / MSI 都装上真文件;NSIS hook 的 regsvr32
|
||
# 同时会把 64 / 32 位 COM 注册到 HKLM\Software\Classes\CLSID 的
|
||
# KEY_WOW64_64KEY / KEY_WOW64_32KEY 两侧(windows_ime_profile.rs 都会查)。
|
||
$payloadDir = Join-Path $appRoot "src-tauri\openless-ime-payload\$($t.Folder)"
|
||
New-Item -ItemType Directory -Force -Path $payloadDir | Out-Null
|
||
Copy-Item -Force -Path $dll -Destination (Join-Path $payloadDir 'OpenLessIme.dll')
|
||
Write-Host "[ok] copied real $($t.Folder) dll into bundle.resources payload path"
|
||
}
|
||
|
||
# ── Windows tauri build:保持 bash shell,因为 PowerShell 调外部命令
|
||
# 会把 '{"bundle":...}' 的内部双引号吃掉、让 tauri 收到无效 JSON。
|
||
# 用 set +e + GITHUB_ENV 把 exit code 传到下一步给 Repair 判断。
|
||
- name: Build (Windows)
|
||
if: matrix.platform == 'windows-latest'
|
||
shell: bash
|
||
working-directory: 'openless-all/app'
|
||
env:
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
run: |
|
||
set +e
|
||
# 拆两轮跑:Tauri 的签名 / updater artifact 阶段是 post-bundle 钩子,
|
||
# 任意 bundler 失败会让 *所有* bundle 的 .sig 跳过。MSI 必踩 ICE80,
|
||
# 所以单一 `tauri build` 永远拿不到 NSIS 的 .exe.sig。
|
||
# Pass 1:NSIS 独立跑——必须成功,产出 *_x64-setup.exe(.sig) 给 updater。
|
||
# Pass 2:MSI 独立跑——允许失败,Repair 步骤兜底 light.exe 重链。
|
||
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||
npm run tauri -- build --bundles nsis --config '{"bundle":{"createUpdaterArtifacts":true}}'
|
||
nsis_exit=$?
|
||
npm run tauri -- build --bundles msi --config '{"bundle":{"createUpdaterArtifacts":true}}'
|
||
msi_exit=$?
|
||
else
|
||
npm run tauri -- build --bundles nsis
|
||
nsis_exit=$?
|
||
npm run tauri -- build --bundles msi
|
||
msi_exit=$?
|
||
fi
|
||
echo "TAURI_BUILD_EXIT=$msi_exit" >> "$GITHUB_ENV"
|
||
# NSIS 是 updater 的硬依赖,挂了就直接 fail step。
|
||
if [ "$nsis_exit" -ne 0 ]; then
|
||
echo "::error::NSIS bundle failed (exit $nsis_exit) — updater artifact unavailable."
|
||
exit 1
|
||
fi
|
||
# MSI 失败不挡——下一步 Repair 用 light.exe 重链(带 -sice:ICE80)。
|
||
exit 0
|
||
|
||
# ── 如果 tauri 在 wix link 阶段失败(candle 出了 wixobj,但 light 因 cwd
|
||
# 解析 wxs Source 找不到 IME DLL),从 appRoot 手动跑 light 兜底重链。
|
||
# 这是本地 windows-package-msvc.ps1::Repair-TauriMsiBundle 的同款手术。
|
||
- name: Repair Windows MSI if Tauri failed at WiX link
|
||
if: matrix.platform == 'windows-latest'
|
||
shell: pwsh
|
||
working-directory: 'openless-all/app'
|
||
run: |
|
||
$exitCode = $env:TAURI_BUILD_EXIT
|
||
$appRoot = (Resolve-Path .).Path
|
||
$msi = Get-ChildItem "src-tauri\target\release\bundle\msi\*.msi" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||
if ($msi) {
|
||
Write-Host "[ok] MSI already produced by tauri: $($msi.FullName); no repair needed."
|
||
return
|
||
}
|
||
if (-not $exitCode -or $exitCode -eq '0') {
|
||
throw "Tauri exited 0 but no MSI found at src-tauri\target\release\bundle\msi\"
|
||
}
|
||
Write-Warning "Tauri MSI failed (exit $exitCode). Attempting manual light.exe relink from app root."
|
||
$wixRoot = Join-Path $appRoot 'src-tauri\target\release\wix\x64'
|
||
$mainObj = Join-Path $wixRoot 'main.wixobj'
|
||
$imeObj = Join-Path $wixRoot 'openless-ime.wixobj'
|
||
$locale = Join-Path $wixRoot 'locale.wxl'
|
||
foreach ($p in @($mainObj, $imeObj, $locale)) {
|
||
if (-not (Test-Path $p)) {
|
||
throw "Required WiX object missing: $p — tauri build aborted before candle ran. Check the Build (Windows) step log."
|
||
}
|
||
}
|
||
$light = Get-ChildItem "$env:LOCALAPPDATA\tauri\WixTools*\light.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object FullName |
|
||
Select-Object -Last 1 -ExpandProperty FullName
|
||
if (-not $light) { throw "WiX light.exe not found under $env:LOCALAPPDATA\tauri\WixTools*" }
|
||
$version = (Get-Content src-tauri\tauri.conf.json -Raw | ConvertFrom-Json).version
|
||
$bundleDir = Join-Path $appRoot 'src-tauri\target\release\bundle\msi'
|
||
New-Item -ItemType Directory -Force -Path $bundleDir | Out-Null
|
||
$msiPath = Join-Path $bundleDir "OpenLess_${version}_x64_en-US.msi"
|
||
Push-Location $appRoot
|
||
try {
|
||
# -sice:ICE80:x86 IME DLL 与 x64 一同打进 INSTALLDIR\windows-ime\,
|
||
# 这是 32 位组件落在 64 位 Directory 下的合法场景(DLL 路径绝对指向,
|
||
# 不依赖 SysWOW64 重定向)。Tauri 自身没有暴露 light 透传参数,所以
|
||
# 必须在这里抑制,否则 LGHT0204 必失败。
|
||
& $light -nologo -sice:ICE80 -ext WixUIExtension -ext WixUtilExtension -loc $locale -out $msiPath $mainObj $imeObj
|
||
if ($LASTEXITCODE -ne 0) { throw "light.exe relink failed with exit $LASTEXITCODE" }
|
||
} finally {
|
||
Pop-Location
|
||
}
|
||
Write-Host "[ok] MSI rebuilt at $msiPath"
|
||
|
||
- name: Verify Windows installers register TSF IME
|
||
if: matrix.platform == 'windows-latest'
|
||
shell: pwsh
|
||
working-directory: 'openless-all/app'
|
||
run: |
|
||
$nsis = Get-ChildItem "src-tauri\target\release\bundle\nsis\*.exe" -ErrorAction Stop | Select-Object -First 1
|
||
$msi = Get-ChildItem "src-tauri\target\release\bundle\msi\*.msi" -ErrorAction Stop | Select-Object -First 1
|
||
if (-not $nsis) { throw "NSIS installer not found." }
|
||
if (-not $msi) { throw "MSI installer not found." }
|
||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\windows-ime-install-smoke.ps1 -InstallerPath $nsis.FullName -InstallerKind nsis
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "NSIS installer smoke failed with exit $LASTEXITCODE"
|
||
}
|
||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\windows-ime-install-smoke.ps1 -InstallerPath $msi.FullName -InstallerKind msi
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "MSI installer smoke failed with exit $LASTEXITCODE"
|
||
}
|
||
|
||
# ── Linux:先编译 fcitx5 插件,再产 deb / rpm / AppImage ──
|
||
- name: Build fcitx5 plugin
|
||
if: matrix.platform == 'ubuntu-22.04'
|
||
shell: bash
|
||
working-directory: 'openless-all/scripts/linux-fcitx5-plugin'
|
||
run: |
|
||
sudo apt-get install -y cmake fcitx5-dev
|
||
mkdir -p build && cd build
|
||
cmake .. 2>&1
|
||
make
|
||
# 从 cmake 缓存中读出 distro 实际的插件安装路径(multiarch 感知)。
|
||
FCITX_ADDON_DIR=$(cmake -LA . 2>/dev/null \
|
||
| grep "^FCITX_INSTALL_ADDONDIR:" \
|
||
| cut -d= -f2)
|
||
FCITX_PKGDATA_DIR=$(cmake -LA . 2>/dev/null \
|
||
| grep "^FCITX_INSTALL_PKGDATADIR:" \
|
||
| cut -d= -f2)
|
||
echo "Detected: addon=$FCITX_ADDON_DIR pkgdata=$FCITX_PKGDATA_DIR"
|
||
echo "FCITX_ADDON_DIR=$FCITX_ADDON_DIR" >> "$GITHUB_ENV"
|
||
echo "FCITX_ADDON_CONF_DIR=${FCITX_PKGDATA_DIR}/addon" >> "$GITHUB_ENV"
|
||
# 对 RPM 目标映射路径:Debian multiarch(如 /usr/lib/x86_64-linux-gnu)
|
||
# -> /usr/lib64(RPM 标准)。conf 路径跨发行版一致。
|
||
RPM_ADDON_DIR=$(echo "$FCITX_ADDON_DIR" \
|
||
| sed 's|/usr/lib/[^/]*/fcitx5|/usr/lib64/fcitx5|;s|/usr/lib/x86_64-linux-gnu/fcitx5|/usr/lib64/fcitx5|')
|
||
echo "FCITX_RPM_ADDON_DIR=$RPM_ADDON_DIR" >> "$GITHUB_ENV"
|
||
# 把插件 .so + .conf 复制到 src-tauri/linux-fcitx5-plugin/ 下面,
|
||
# 供 tauri deb/rpm bundler 的 files 配置使用。
|
||
mkdir -p "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin"
|
||
cp libopenless.so "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/"
|
||
cp openless.conf "$GITHUB_WORKSPACE/openless-all/app/src-tauri/linux-fcitx5-plugin/"
|
||
|
||
- name: Build (Linux)
|
||
if: matrix.platform == 'ubuntu-22.04'
|
||
shell: bash
|
||
working-directory: 'openless-all/app'
|
||
env:
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
run: |
|
||
# 构建带 fcitx5 插件的 deb/rpm。AppImage 不含插件(无法安装系统路径),
|
||
# 用户需手动安装脚本 scripts/linux-fcitx5-plugin/build.sh 输出的 .so 和 .conf。
|
||
# 插件 .so + .conf 由上一步 Build fcitx5 plugin 生成并复制到
|
||
# src-tauri/linux-fcitx5-plugin/ 下。
|
||
cat > /tmp/tauri-linux-config.json << CONFIG_EOF
|
||
{
|
||
"bundle": {
|
||
"resources": {},
|
||
"linux": {
|
||
"deb": {
|
||
"depends": ["fcitx5", "fcitx5-module-dbus", "libdbus-1-3"],
|
||
"files": {
|
||
"${FCITX_ADDON_DIR}/libopenless.so": "linux-fcitx5-plugin/libopenless.so",
|
||
"${FCITX_ADDON_CONF_DIR}/openless.conf": "linux-fcitx5-plugin/openless.conf"
|
||
}
|
||
},
|
||
"rpm": {
|
||
"depends": ["fcitx5", "fcitx5-module-dbus"],
|
||
"files": {
|
||
"${FCITX_RPM_ADDON_DIR}/libopenless.so": "linux-fcitx5-plugin/libopenless.so",
|
||
"${FCITX_ADDON_CONF_DIR}/openless.conf": "linux-fcitx5-plugin/openless.conf"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
CONFIG_EOF
|
||
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||
jq '.bundle.createUpdaterArtifacts = true' /tmp/tauri-linux-config.json > /tmp/tauri-linux-config-signed.json
|
||
CONFIG_FILE=/tmp/tauri-linux-config-signed.json
|
||
else
|
||
CONFIG_FILE=/tmp/tauri-linux-config.json
|
||
fi
|
||
npm run tauri -- build --bundles deb,rpm,appimage --config "$CONFIG_FILE"
|
||
|
||
- name: Disambiguate macOS updater bundle filename
|
||
if: startsWith(matrix.platform, 'macos') && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||
shell: bash
|
||
working-directory: 'openless-all/app/src-tauri/target/release/bundle/macos'
|
||
run: |
|
||
if [ -f OpenLess.app.tar.gz ]; then
|
||
mv OpenLess.app.tar.gz "OpenLess_${{ matrix.updater-arch }}.app.tar.gz"
|
||
fi
|
||
if [ -f OpenLess.app.tar.gz.sig ]; then
|
||
mv OpenLess.app.tar.gz.sig "OpenLess_${{ matrix.updater-arch }}.app.tar.gz.sig"
|
||
fi
|
||
|
||
- name: Write updater manifest
|
||
if: env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||
shell: bash
|
||
working-directory: 'openless-all/app'
|
||
env:
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
OPENLESS_UPDATE_TARGET: ${{ matrix.updater-target }}
|
||
OPENLESS_UPDATE_ARCH: ${{ matrix.updater-arch }}
|
||
OPENLESS_UPDATE_REPO: appergb/openless
|
||
OPENLESS_UPDATE_MIRROR_BASE_URL: https://fastgit.cc/https://github.com
|
||
# beta 渠道时输出 latest-{tgt}-{arch}-beta.json,stable 沿用旧文件名。
|
||
OPENLESS_RELEASE_CHANNEL: ${{ env.OPENLESS_RELEASE_CHANNEL }}
|
||
# Beta 渠道里脚本要把 manifest.url 写成 releases/download/<tag>/...
|
||
# 而不是 releases/latest(后者永远 = Stable,Beta 用户按 url 下载会拉到错文件)。
|
||
# workflow_dispatch 时 github.ref_name 不是 tag——但那种情况下 channel=stable,
|
||
# 脚本不会读这个字段,安全。
|
||
OPENLESS_RELEASE_TAG: ${{ github.ref_name }}
|
||
run: node scripts/write-updater-manifest.mjs
|
||
|
||
# ── 收集产物 ──
|
||
- name: List artifacts (debug)
|
||
shell: bash
|
||
working-directory: 'openless-all/app/src-tauri/target/release/bundle'
|
||
run: ls -la macos/ dmg/ nsis/ msi/ deb/ rpm/ appimage/ 2>/dev/null || true
|
||
|
||
# 防御性步骤:剥掉 macOS 产物上任何残留扩展属性 / quarantine。
|
||
# 理论上 GitHub Actions 输出的 .app/.dmg 不会带 com.apple.quarantine
|
||
# (xattr 也不会通过 actions/upload-artifact 跨机器持久化),但保留这一步
|
||
# 让"云端 artifact 一定干净"成为可验证的承诺。用户下载后再被本地浏览器
|
||
# 加 quarantine 时,按 release notes 的 `xattr -cr` 一行即可消除。
|
||
- name: Strip xattr / quarantine on macOS bundles
|
||
if: startsWith(matrix.platform, 'macos')
|
||
shell: bash
|
||
working-directory: 'openless-all/app/src-tauri/target/release/bundle'
|
||
run: |
|
||
for path in macos/*.app dmg/*.dmg; do
|
||
if [ -e "$path" ]; then
|
||
echo "▶ stripping xattr: $path"
|
||
xattr -cr "$path" || true
|
||
xattr -lr "$path" || true
|
||
fi
|
||
done
|
||
|
||
- name: Upload macOS artifacts
|
||
if: startsWith(matrix.platform, 'macos')
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-macos-${{ matrix.updater-arch }}
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
||
if-no-files-found: error
|
||
|
||
- name: Upload macOS updater artifacts
|
||
if: startsWith(matrix.platform, 'macos') && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-macos-${{ matrix.updater-arch }}-updater
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz
|
||
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
|
||
openless-all/app/src-tauri/target/release/bundle/latest-darwin-${{ matrix.updater-arch }}*.json
|
||
if-no-files-found: error
|
||
|
||
- name: Upload Windows artifacts
|
||
if: matrix.platform == 'windows-latest'
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-windows-x64
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe
|
||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
||
if-no-files-found: error
|
||
|
||
- name: Upload Windows updater artifacts
|
||
if: matrix.platform == 'windows-latest' && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-windows-x64-updater
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig
|
||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig
|
||
openless-all/app/src-tauri/target/release/bundle/latest-windows-x86_64*.json
|
||
if-no-files-found: error
|
||
|
||
- name: Upload Linux artifacts
|
||
if: matrix.platform == 'ubuntu-22.04'
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-linux-x64
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/deb/*.deb
|
||
openless-all/app/src-tauri/target/release/bundle/rpm/*.rpm
|
||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
||
if-no-files-found: error
|
||
|
||
- name: Upload Linux updater artifacts
|
||
if: matrix.platform == 'ubuntu-22.04' && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openless-linux-x64-updater
|
||
path: |
|
||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||
openless-all/app/src-tauri/target/release/bundle/latest-linux-x86_64*.json
|
||
if-no-files-found: error
|
||
|
||
# ── tag 推送时,同步上传到 GitHub Release ──
|
||
- name: Create / update release
|
||
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||
uses: softprops/action-gh-release@v2
|
||
with:
|
||
tag_name: ${{ github.ref_name }}
|
||
name: 'OpenLess ${{ github.ref_name }}'
|
||
draft: false
|
||
# beta 渠道的 release 必须标 prerelease=true:GitHub UI 会折叠它,
|
||
# 普通用户看不到;同时只上传 latest-*-beta.json,正式版用户的
|
||
# endpoint(latest-*.json)永远不会被覆盖,保证 Beta 不溢出正式版。
|
||
prerelease: ${{ env.OPENLESS_RELEASE_CHANNEL == 'beta' }}
|
||
# body 在 generated notes 之前 prepend——所有 matrix job 设同一份字符串,
|
||
# 多次调用 action 时内容一致、idempotent。提示 macOS Gatekeeper 绕过指令 +
|
||
# 渠道说明,每个 release 自动带上,用户不用回项目 README 翻就能看到。
|
||
body: |
|
||
### macOS 用户首次安装提示
|
||
|
||
下载 DMG 拖入 `/Applications` 后,**必须**在终端运行:
|
||
|
||
```bash
|
||
xattr -cr /Applications/OpenLess.app
|
||
```
|
||
|
||
否则 Gatekeeper 会提示「OpenLess 已损坏」——这是因为当前 build 用 ad-hoc 签名、没做 Apple 公证。
|
||
|
||
### 渠道说明
|
||
|
||
- 以 `-tauri` 结尾的 tag 是**正式版**(自动推送给所有 in-app 检查更新的用户)。
|
||
- 以 `-beta-tauri` 结尾的 tag 是 **Beta 版**(GitHub 标 pre-release,**不**通过 in-app updater 推送给正式版用户;只对在「设置 → 关于 → 加入 Beta 渠道」开关切到 Beta 的用户可见,且需要手动从此页面下载安装)。
|
||
|
||
### 行为变更提示
|
||
|
||
- 流式输入默认开启;不兼容场景会自动回落到一次性插入。可在「设置 → 高级」关闭。
|
||
- 流式输入成功后默认把最终文本同步到剪贴板,方便再次粘贴;可在「设置 → 高级」关闭。
|
||
|
||
append_body: true
|
||
# Matrix jobs all upload assets to the same release. Generate notes once
|
||
# so macOS, Windows, and Linux jobs do not duplicate the release body.
|
||
generate_release_notes: ${{ matrix.updater-target == 'darwin' && matrix.updater-arch == 'aarch64' }}
|
||
files: |
|
||
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
||
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz
|
||
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
|
||
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe
|
||
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig
|
||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig
|
||
openless-all/app/src-tauri/target/release/bundle/deb/*.deb
|
||
openless-all/app/src-tauri/target/release/bundle/rpm/*.rpm
|
||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||
openless-all/app/src-tauri/target/release/bundle/latest-*.json
|