perf(macos): 加速 Rust CI 与桌面打包

This commit is contained in:
sim committed 2026-09-25 13:11:14 +08:00
1 parent d9113e0b03
commit b268dcf978
7 files changed
+317 -38

No files matched your search

+65 -9
View File
@@ -12,6 +12,12 @@ on:
pull_request:
branches: [main, beta]
workflow_dispatch:
inputs:
platform:
description: Platforms to check (macos runs only macOS gates)
type: choice
options: [all, macos]
default: all
# 同一 PR 快速重复推送时取消旧运行;workflow_dispatch 用 run_id 隔离。
concurrency:
@@ -20,6 +26,7 @@ concurrency:
jobs:
android-check:
if: github.event_name != 'workflow_dispatch' || inputs.platform != 'macos'
name: Android cargo check
runs-on: ubuntu-latest
defaults:
@@ -166,6 +173,7 @@ jobs:
--no-daemon
linux-core-contract:
if: github.event_name != 'workflow_dispatch' || inputs.platform != 'macos'
name: Linux core tests
runs-on: ubuntu-22.04
defaults:
@@ -194,13 +202,7 @@ jobs:
# 一个平台挂掉不阻塞其他平台拿到验证结果。
fail-fast: false
matrix:
include:
- os: macos-latest
label: macOS
preflight: false
- os: windows-latest
label: Windows
preflight: true
include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.platform == 'macos' && '[{"os":"macos-latest","label":"macOS","preflight":false}]' || '[{"os":"macos-latest","label":"macOS","preflight":false},{"os":"windows-latest","label":"Windows","preflight":true}]') }}
runs-on: ${{ matrix.os }}
env:
# 新增 shared Core 后,macOS 首次编译同时构建 MLX C++ 依赖和完整
@@ -226,15 +228,30 @@ jobs:
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
# 现有测试继续用 stable;额外安装声明的 MSRV,供下方兼容性门禁显式调用。
# macOS MSRV 在独立 job 并行检查;Windows 保留原有检查顺序。
- uses: dtolnay/rust-toolchain@1.88.0
if: runner.os == 'Windows'
- uses: dtolnay/rust-toolchain@stable
- name: Configure macOS check profile
if: runner.os == 'macOS'
run: echo "CARGO_PROFILE_DEV_DEBUG=0" >> "$GITHUB_ENV"
- uses: swatinem/rust-cache@v2
if: runner.os == 'Windows'
with:
workspaces: 'openless-all/app/src-tauri -> target'
- name: Cache macOS stable dependencies
if: runner.os == 'macOS'
uses: swatinem/rust-cache@v2
with:
key: macos-stable-v1
workspaces: |
openless-all/app -> target
openless-all/app/src-tauri -> target
- name: Prepare Windows Sherpa static libraries
if: runner.os == 'Windows'
shell: pwsh
@@ -284,11 +301,14 @@ jobs:
run: cargo test --locked -p openless-core hardening_actually_narrows_the_writable_roots -- --ignored --nocapture --test-threads=1
- name: Check Tauri backend (cargo check)
if: runner.os == 'Windows'
run: cargo check --locked --manifest-path src-tauri/Cargo.toml
# test 编译并运行 lib/bin,覆盖原 cargo check 的生产 binary 路径。
# 避免先 metadata-only check、再为同一依赖图做一次 codegen。
- name: Run Rust backend unit tests
if: runner.os != 'Windows'
run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib
run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --bins --timings
- name: Compile Rust backend unit tests (Windows)
# Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime
@@ -304,9 +324,11 @@ jobs:
run: cargo test --locked --manifest-path src-tauri/backend-tests/Cargo.toml
- name: Check Tauri backend with Rust 1.88 MSRV
if: runner.os == 'Windows'
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml
- name: Compile backend tests with Rust 1.88 MSRV
if: runner.os == 'Windows'
run: cargo +1.88.0 test --locked --manifest-path src-tauri/backend-tests/Cargo.toml --no-run
- name: Verify version sync across all 5 files
@@ -338,3 +360,37 @@ jobs:
exit 1
fi
echo "[ok] 全部 5 处版本号一致:$PKG"
macos-msrv:
name: macOS Rust 1.88 MSRV
runs-on: macos-latest
env:
CARGO_BUILD_JOBS: 2
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
CMAKE_BUILD_PARALLEL_LEVEL: 2
defaults:
run:
working-directory: openless-all/app
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
- uses: dtolnay/rust-toolchain@1.88.0
- uses: swatinem/rust-cache@v2
with:
key: macos-msrv-v1
workspaces: |
openless-all/app/src-tauri -> target
openless-all/app/src-tauri/backend-tests -> target
- run: npm ci
- run: npm run build
- name: Check Tauri backend with Rust 1.88 MSRV
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml --timings
- name: Compile backend tests with Rust 1.88 MSRV
run: cargo +1.88.0 test --locked --manifest-path src-tauri/backend-tests/Cargo.toml --no-run --timings
+31 -13
View File
@@ -21,6 +21,12 @@ on:
tags:
- 'v*-tauri'
workflow_dispatch:
inputs:
platform:
description: Desktop platforms to build (manual builds do not publish a release)
type: choice
options: [all, macos]
default: all
# 同一 tag 重复推送只跑最新一次;workflow_dispatch 用 run_id 隔离避免互相取消。
concurrency:
@@ -34,19 +40,7 @@ jobs:
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
include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.platform == 'macos' && '[{"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":"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"}]') }}
runs-on: ${{ matrix.platform }}
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
@@ -79,11 +73,25 @@ jobs:
with:
targets: ${{ matrix.rust-target }}
# 在恢复缓存前设置实际编译环境,让 cache key 包含 macOS profile。
- name: Configure macOS build environment
if: startsWith(matrix.platform, 'macos')
working-directory: openless-all/app
run: bash scripts/macos-build-env.sh
- name: Cache Cargo
if: matrix.platform == 'windows-latest'
uses: swatinem/rust-cache@v2
with:
workspaces: 'openless-all/app/src-tauri -> target'
- name: Cache macOS release dependencies
if: startsWith(matrix.platform, 'macos')
uses: swatinem/rust-cache@v2
with:
key: macos-release-v1
workspaces: 'openless-all/app/src-tauri -> target'
- name: Prepare Windows Sherpa static libraries
if: matrix.platform == 'windows-latest'
working-directory: 'openless-all/app'
@@ -192,6 +200,14 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bash scripts/build-mac.sh
- name: Upload macOS Cargo timings
if: always() && startsWith(matrix.platform, 'macos')
uses: actions/upload-artifact@v4
with:
name: macos-cargo-timings-${{ matrix.updater-arch }}
path: openless-all/app/src-tauri/target/cargo-timings/*.html
if-no-files-found: ignore
# ── 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 不固定)。
@@ -427,6 +443,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: openless-macos-${{ matrix.updater-arch }}
compression-level: 0
path: |
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
if-no-files-found: error
@@ -436,6 +453,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: openless-macos-${{ matrix.updater-arch }}-updater
compression-level: 0
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
+1
View File
@@ -23,6 +23,7 @@
## 平台与运营
- [macOS CI 与打包耗时](macos-build-performance.md):基线日志、Rust 编译优化、缓存边界与仅 macOS 验证入口。
- [Android APK / 悬浮窗计划](android-mobile-apk-overlay-plan.md)(实施中)
- [火山引擎 ASR 配置](volcengine-setup.md)
- [讯飞(iflytek)ASR 配置](xfyun-asr.md)
+53
View File
@@ -0,0 +1,53 @@
# macOS CI 与打包耗时
状态:实现说明;更新:2026-09-25。范围仅包含 macOS 检查和桌面打包。Windows、Android、Linux 的编译参数与发布行为由原有工作流维护。
## 调研依据
对照 `beta` 的 `d9113e0b`、[CI 35984434219](https://github.com/Open-Less/openless/actions/runs/35984434219) 与 [桌面构建 35989822601](https://github.com/Open-Less/openless/actions/runs/35989822601) 的完整日志:
| 基线步骤 | 时间 | 日志证据 |
| --- | --- | --- |
| macOS 检查 job | 24 分 59 秒 | stable 检查、测试、MSRV 串行执行 |
| stable `cargo check` | 7 分 54 秒 | 含 MLX,本轮 dev profile 带 debug info |
| stable Tauri 库测试编译 | 7 分 27 秒 | 再次编译 qwen3、Core、Host,test profile 不带 debug info |
| Rust 1.88 Host 检查 | 5 分 03 秒 | 第三次编译 qwen3 与 Host |
| Rust 1.88 独立 backend-tests 编译 | 58.51 秒 | 独立 target 未配置缓存 |
| Apple Silicon release Rust 编译 | 14 分 16 秒 | 依赖缓存命中约 731 MB,仍重新编译 qwen3、Core、Host |
| Intel release Rust 编译 | 13 分 57 秒 | 依赖缓存命中约 700 MB,仍重新编译 Core、Host |
ARM/Intel 的整个打包步骤分别约 15 分 17 秒、15 分 08 秒。主要等待发生在 Rust,而不是 npm 安装、DMG 或 artifact 上传。时间是该次运行的观测值,不是不同 runner、缓存和提交之间的性能保证。
源码中的相关因素:
- `ci.yml` 的 macOS job 先 metadata-only check,再生成测试机器码;dev/test 的 debug 配置也不同。MSRV 与 stable 共用 job 和缓存,Core workspace、独立 backend-tests 的 target 没有全部纳入缓存。
- Tauri release profile 使用 `opt-level=3`、thin LTO 和 `codegen-units=1`。最后一个设置限制单个大 crate 的 LLVM 并行能力;命中第三方依赖缓存仍无法避免 Core/Host 的代码生成成本。
- `build-mac.sh` 曾将所有 `qwen3-asr-rs-*` 目录当成重复输出。Cargo 实际分别保存 build-script 可执行文件和 `OUT_DIR`;只留一个目录会破坏下一轮缓存。
- `rust-cache` 默认只保留依赖产物,不应把命中缓存等同于整个应用无须重编译。参见 [rust-cache 缓存行为](https://github.com/Swatinem/rust-cache#cache-details)。没有添加 sccache:该工具不能缓存调用系统 linker 的 bin/cdylib/proc-macro,参见 [官方限制](https://github.com/mozilla/sccache/blob/main/docs/Rust.md)。
## 当前流程
`ci.yml` 的 macOS stable job 用 `cargo test --lib --bins` 编译并运行库及二进制目标,覆盖库的生产构建与测试构建。MSRV 单独并行执行,继续检查完整 Tauri Host 并编译独立 backend-tests。两个 job 均保留 `--locked`、MLX 子模块与两路编译并发限制;stable 保留全部前端/合同测试和 Codex sandbox 实测。
macOS 检查统一关闭 dev/test debug info,分别缓存 Core、Host、backend-tests 的实际 target 目录。stable、MSRV、release 缓存分开,避免不同工具链和 profile 的产物互相挤占。
`scripts/macos-build-env.sh` 为 macOS 打包默认设置 `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16`,保留 `opt-level=3`、thin LTO 和 unwind;环境变量可以显式覆盖为其他值。参数取舍依据 [Cargo profiles](https://doc.rust-lang.org/cargo/reference/profiles.html#codegen-units):更多 codegen units 允许更快的并行代码生成,可能影响最终体积或优化效果。共享 `Cargo.toml` 不修改。CI 在恢复缓存前加载同一环境,本地 `build-mac.sh` 也加载它。
MLX 清理只比较含非空 metallib 的输出目录,保留 build-script 可执行文件。构建前删除本次架构的旧 app、DMG 和 updater,保留 Cargo 编译缓存;Tauri 非零退出直接失败。因此热构建复用旧时间戳二进制时仍能正确打包,失败时也不会接受旧安装包。现有用途声明、签名、公证和 MLX 包内容校验继续执行。
## 仅 macOS 的验证入口
从待验证分支手动触发已有工作流:
```sh
gh workflow run ci.yml --repo Open-Less/openless --ref <branch> -f platform=macos
gh workflow run release-tauri.yml --repo Open-Less/openless --ref <branch> -f platform=macos
```
前者只运行 macOS stable/MSRV,后者并行生成 Apple Silicon 与 Intel 的桌面包。使用分支 ref,不创建 tag 或 GitHub Release。省略 input 的既有手动运行以及 tag 发布仍使用原有全部平台矩阵。
桌面工作流上传两个架构的 Cargo HTML timings;失败时若已生成计时文件也会上传。DMG/updater 本身已压缩,artifact 使用 `compression-level: 0`。
本地在 `openless-all/app` 运行 `npm test`、`cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --bins` 与 `INSTALL=0 bash scripts/build-mac.sh`。`macos-build-cache.test.mjs` 实际执行隔离的 shell 构建流程,验证缓存目录保留、重复热打包、旧产物清理以及失败传播。
性能验收应记录一次新缓存运行及相同提交的再次运行,分别报告 Rust 编译、完整 job 和产物大小。不同机器的本地构建时间不与 GitHub runner 直接比较;构建通过不等于真实设备 ASR 性能已经验证。
+20 -16
View File
@@ -27,25 +27,28 @@ fi
echo "▶ 检查 Apple Silicon MLX 构建依赖"
npm run check:macos-metal-toolchain
# Homebrew rustc 在 macOS 上对 `strip=symbols` 生成的 proc-macro dylib
# 可能报 "mis-aligned LINKEDIT string pool"。仅官方 macOS 发布脚本降级
# 为 debuginfo;Cargo.toml 的全局 profile 仍让 Linux/Windows/Android 使用 symbols。
export CARGO_PROFILE_RELEASE_STRIP=debuginfo
export RUSTC_WRAPPER="$PWD/scripts/rustc-macos-proc-macro-wrapper.sh"
source scripts/macos-build-env.sh
echo "▶ Cargo release codegen units: ${CARGO_PROFILE_RELEASE_CODEGEN_UNITS} (macOS only)"
echo "▶ Cargo release strip: ${CARGO_PROFILE_RELEASE_STRIP} (macOS only)"
echo "▶ Rust proc-macro host wrapper: ${RUSTC_WRAPPER}"
# 只保留最新一份 qwen3-asr-rs 构建目录:本地混跑 cargo check/test/build 会按不同
# feature 上下文生成多份,各自带一份 metallib,会让暂存脚本拒绝猜测。
KEEP_QWEN_DIR="$(ls -dt src-tauri/target/release/build/qwen3-asr-rs-* 2>/dev/null | head -1 || true)"
# Cargo 为 build-script 可执行文件和 OUT_DIR 创建不同目录。只在多个
# metallib 输出之间清理,保留所有 build-script 缓存,避免每次重新编译。
KEEP_QWEN_DIR=""
for d in src-tauri/target/release/build/qwen3-asr-rs-*; do
[ -s "$d/out/lib/mlx.metallib" ] || continue
if [ -z "$KEEP_QWEN_DIR" ] || [ "$d/out/lib/mlx.metallib" -nt "$KEEP_QWEN_DIR/out/lib/mlx.metallib" ]; then
KEEP_QWEN_DIR="$d"
fi
done
if [ -n "$KEEP_QWEN_DIR" ]; then
for d in src-tauri/target/release/build/qwen3-asr-rs-*; do
[ -s "$d/out/lib/mlx.metallib" ] || continue
[ "$d" = "$KEEP_QWEN_DIR" ] || rm -rf "$d"
done
fi
echo "▶ tauri build"
BUILD_START_TS="$(date +%s)"
TAURI_BUILD_ARGS=(build --ci)
case "$(uname -m)" in
arm64)
@@ -63,16 +66,17 @@ esac
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ] || [ -n "${TAURI_SIGNING_PRIVATE_KEY_PATH:-}" ]; then
TAURI_BUILD_ARGS+=(--config '{"bundle":{"createUpdaterArtifacts":true}}')
fi
# bundle_dmg(AppleScript)在 Xcode beta 上可能退出非零;.app 与 DMG 是否真实
# 产出交给下方的新鲜度/存在性校验判定,不在这一步盲 abort。
npm run tauri -- "${TAURI_BUILD_ARGS[@]}" || echo "⚠ tauri build 退出码非零,继续校验产物"
APP_VERSION="$(node -p "require('./package.json').version")"
DMG_PATH="$DMG_DIR/OpenLess_${APP_VERSION}_${MAC_BUNDLE_ARCH}.dmg"
# bundle 必须是本次构建产出的(与构建开始时间比;打包后原始二进制还会被签名
# 触碰,不能拿它当基准)。
if [ ! -d "$APP" ] || [ "$(stat -f %m "$APP/Contents/MacOS/openless")" -lt "$BUILD_START_TS" ]; then
# 清掉交付产物,保留 Cargo 缓存。热构建可复用旧时间戳的二进制,不能用
# 编译文件的 mtime 判断 bundle 新鲜度;Tauri 失败时也不能接受上轮安装包。
rm -rf "$APP"
rm -f "$DMG_PATH" "${APP}.tar.gz" "${APP}.tar.gz.sig"
TAURI_BUILD_ARGS+=(-- --locked --timings)
npm run tauri -- "${TAURI_BUILD_ARGS[@]}"
if [ ! -f "$APP/Contents/MacOS/openless" ]; then
echo "✗ $APP 缺失或不是本次构建的产物(打包未完成),中止"
exit 1
fi
@@ -0,0 +1,127 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import {
chmodSync,
copyFileSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
utimesSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
if (process.platform !== 'darwin') {
console.log('macOS build cache tests skipped on other platforms');
process.exit(0);
}
const scripts = dirname(fileURLToPath(import.meta.url));
const root = mkdtempSync(join(tmpdir(), 'openless-macos-build-'));
const app = join(root, 'src-tauri/target/release/bundle/macos/OpenLess.app');
const dmg = join(root, 'src-tauri/target/release/bundle/dmg/OpenLess_1.2.3_x64.dmg');
const builds = join(root, 'src-tauri/target/release/build');
function put(path, content) {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, content);
}
function executable(name, content) {
const path = join(root, 'bin', name);
put(path, `#!/usr/bin/env bash\nset -euo pipefail\n${content}\n`);
chmodSync(path, 0o755);
}
function run(mode = 'success') {
const env = { ...process.env };
for (const name of Object.keys(env)) {
if (/^(APPLE_|TAURI_SIGNING_|CARGO_PROFILE_RELEASE_|GITHUB_ENV$)/.test(name)) delete env[name];
}
return spawnSync('bash', ['scripts/build-mac.sh'], {
cwd: root,
encoding: 'utf8',
env: { ...env, INSTALL: '0', BUILD_FIXTURE_MODE: mode, PATH: `${root}/bin:${env.PATH}` },
});
}
try {
mkdirSync(join(root, 'scripts'), { recursive: true });
for (const name of [
'build-mac.sh',
'macos-build-env.sh',
'check-macos-speech-usage-description.sh',
]) {
copyFileSync(join(scripts, name), join(root, 'scripts', name));
}
put(join(root, 'package.json'), '{"version":"1.2.3"}');
put(
join(root, 'fixture.plist'),
`<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0"><dict>
<key>NSMicrophoneUsageDescription</key><string>Microphone</string>
<key>NSSpeechRecognitionUsageDescription</key><string>Speech</string>
</dict></plist>`,
);
executable('uname', 'echo x86_64');
executable('codesign', 'echo com.apple.security.device.audio-input');
executable('xattr', 'exit 1');
executable(
'npm',
`
if [ "$*" = "run check:macos-metal-toolchain" ]; then exit 0; fi
[[ "$*" == *"-- --locked --timings"* ]]
[[ "$CARGO_PROFILE_RELEASE_CODEGEN_UNITS" == 16 ]]
[[ "$CARGO_PROFILE_RELEASE_STRIP" == debuginfo ]]
app=src-tauri/target/release/bundle/macos/OpenLess.app
dmg=src-tauri/target/release/bundle/dmg/OpenLess_1.2.3_x64.dmg
# Old bundles must be gone before compilation starts, while Cargo stays warm.
[[ ! -e "$app" && ! -e "$dmg" && ! -e "$app.tar.gz" && ! -e "$app.tar.gz.sig" ]]
[[ -f src-tauri/target/release/build/qwen3-asr-rs-helper/build-script-build ]]
[[ -f src-tauri/target/release/build/qwen3-asr-rs-new/out/lib/mlx.metallib ]]
[[ ! -e src-tauri/target/release/build/qwen3-asr-rs-old ]]
if [ "$BUILD_FIXTURE_MODE" = missing ]; then exit 0; fi
mkdir -p "$app/Contents/MacOS" "$(dirname "$dmg")"
cp fixture.plist "$app/Contents/Info.plist"
echo binary > "$app/Contents/MacOS/openless"
# Cargo may reuse a binary compiled before this packaging invocation.
touch -t 200001010000 "$app/Contents/MacOS/openless"
echo dmg > "$dmg"
if [ "$BUILD_FIXTURE_MODE" = failure ]; then exit 23; fi
`,
);
put(join(builds, 'qwen3-asr-rs-helper/build-script-build'), 'cached executable');
put(join(builds, 'qwen3-asr-rs-new/out/lib/mlx.metallib'), 'new shader');
put(join(builds, 'qwen3-asr-rs-old/out/lib/mlx.metallib'), 'old shader');
utimesSync(join(builds, 'qwen3-asr-rs-old/out/lib/mlx.metallib'), 1, 1);
put(join(app, 'Contents/MacOS/openless'), 'stale binary');
put(dmg, 'stale dmg');
put(`${app}.tar.gz`, 'stale updater');
put(`${app}.tar.gz.sig`, 'stale signature');
for (let repeat = 0; repeat < 2; repeat += 1) {
const result = run();
assert.equal(result.status, 0, result.stdout + result.stderr);
assert.equal(readFileSync(dmg, 'utf8').trim(), 'dmg');
assert.equal(
readFileSync(join(builds, 'qwen3-asr-rs-helper/build-script-build'), 'utf8'),
'cached executable',
);
}
const failure = run('failure');
assert.equal(failure.status, 23, failure.stdout + failure.stderr);
const missing = run('missing');
assert.notEqual(missing.status, 0, missing.stdout + missing.stderr);
assert.equal(existsSync(app), false);
assert.equal(existsSync(dmg), false);
} finally {
rmSync(root, { recursive: true, force: true });
}
console.log('macOS warm build and packaging failure tests passed');
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Source locally; execute before rust-cache in GitHub Actions.
set -euo pipefail
MACOS_BUILD_APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Keep opt-level=3 and thin LTO, while allowing LLVM to compile large crates
# in parallel. The shared Cargo profile continues to govern other platforms.
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS="${CARGO_PROFILE_RELEASE_CODEGEN_UNITS:-16}"
# Avoid malformed proc-macro dylibs with the macOS strip tool.
export CARGO_PROFILE_RELEASE_STRIP=debuginfo
export RUSTC_WRAPPER="$MACOS_BUILD_APP_ROOT/scripts/rustc-macos-proc-macro-wrapper.sh"
if [ -n "${GITHUB_ENV:-}" ]; then
{
echo "CARGO_PROFILE_RELEASE_CODEGEN_UNITS=$CARGO_PROFILE_RELEASE_CODEGEN_UNITS"
echo "CARGO_PROFILE_RELEASE_STRIP=$CARGO_PROFILE_RELEASE_STRIP"
echo "RUSTC_WRAPPER=$RUSTC_WRAPPER"
} >> "$GITHUB_ENV"
fi