diff --git a/Cargo.toml b/Cargo.toml index 13b7054a..01d65fb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -260,3 +260,11 @@ strip = "symbols" # packages (docs/mobile-rn.md §6). [profile.release.package.voltip-mobile-rn] strip = false + +# voltip-server as one codegen unit is one LLVM module, and LLVM spends about 14 minutes optimising +# it (2026-11-10, -Z time-passes: LLVM_passes 862.7 s, every other pass under a second), plus 5 in +# the ThinLTO link; the release candidate's Linux leg waited 19–34 minutes for it. In 16 units, at +# -j4 as on the runner, the library takes 2 s and the binary 26 s. ThinLTO still optimises across +# the units. Every other package keeps the single unit above. +[profile.release.package.voltip-server] +codegen-units = 16 diff --git a/docs/runbook.md b/docs/runbook.md index 1bddd793..8415a804 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -113,7 +113,7 @@ Relay 无状态(内存里只有活动会话 / 频道),可水平扩展前 工作流都跑在 GitHub 托管的 runner 上: - `ci.yml`:push `main` / PR → `changes`(`.github/scripts/changed-code.sh`:只改了 `docs/**`、`*.md`、`LICENSE*` 时 `code=false`;新分支、API 失败或一次推送 300 个以上文件都算改了代码)→ 并行的 `verify-rust`(`VERIFY_GATES=rust`:格式、features、clippy、交叉检查、覆盖率下跑一遍测试、cargo-deny、IPC e2e)与 `verify-web`(`VERIFY_GATES=web`:web 门禁、台账、主机名守卫、发布脚本测试,外加带 `VOLTIP_PRODUCTION_HOSTS` 的守卫;fork 与 Dependabot 的 PR 没有 secrets,跳过这一步)、`codecov`(上传覆盖率,只做报告,不阻塞)、`windows-cross`(`make windows-x64`,无内置服务)→ `windows-native`(Windows Server 上无头运行便携包与安装后的 exe)、`hooks-windows`(Windows 桌面上真实的单键钩子)、`smoke-desktop`(Xvfb 与纯 Wayland 冒烟、X11 真实钩子、无头识别)、`android`(对 Android 目标跑 clippy;用 `scripts/build-android-rn.sh --unsigned` 构建 React Native 版未签名的 release APK 与 AAB,用当次生成的临时密钥签名,再跑与候选相同的包检查)→ `android-device`(把这个 APK 装进 Android 15 模拟器并启动:首屏出现、20 秒内不退出、日志里没有致命错误,`.github/scripts/android-device-smoke.sh`)、`ci-success` 聚合(CI 的总结论);另有 `candidate-status` 给不是发布 PR 的 PR head 写 `Release candidate = success`(见下面的 ruleset)。只改文档时 `verify-web` 照常跑,其余构建、测试、平台 job 跳过;`ci-success` 只在 `changes` 判定为只改文档时接受跳过,其他任何跳过、失败或取消都判失败。CI 不注入任何内置引擎值。 -- CI 的速度(2026-09-28 实测后调整):Rust 测试只在覆盖率门禁里跑一遍(原先 `cargo test` 与 `cargo llvm-cov` 各编一遍、各跑一遍,各约 170 秒);`verify` 拆成可并行的两半;Cargo 缓存只从 `main` 保存(PR 的缓存只有同一个 PR 能用,而仓库 10 GB 的上限会把 `main` 的挤掉),不缓存 `~/.cargo/bin`;dev / test 构建只留行号表(`CARGO_PROFILE_DEV_DEBUG=line-tables-only`);清理预装 SDK 只在剩余空间不足 40 GB 时做(`.github/scripts/free-disk-space.sh`,托管 runner 实测开跑时有 86 GB);CI 的 Windows 包复用按全部输入(锁文件、清单、`about.toml`、生成脚本与许可证文本)缓存的第三方声明,省掉约两分钟的 cargo-about(`VOLTIP_NOTICES_CACHED=1`,发布总是重新生成);下载失败重试(`CARGO_NET_RETRY`、`RUSTUP_MAX_RETRIES`);桌面壳的 lib 只编成 `rlib`(2026-10-05):Tauri 模板为手机准备的 `staticlib` 和 `cdylib` 让 release 构建的每个依赖都同时生成机器码和 bitcode,去掉后依赖只生成 thin LTO 用的 bitcode(`-C linker-plugin-lto`),本机 32 核冷构建 224 → 156 秒,target/ 3.3 → 2.4 GB;macOS 腿的钥匙串测试程序是带 `keychain-harness` feature 的 bin 而不是 example(example 会带上 dev-dependencies,它们的 feature 让应用构建的 60 多个 crate 重编一遍,Intel Mac 冷跑时 11 分钟),应用构建之后只需再编这个包本身。`verify-*.log` 里每道门都记了耗时(`secs=`)。 +- CI 的速度(2026-09-28 实测后调整):Rust 测试只在覆盖率门禁里跑一遍(原先 `cargo test` 与 `cargo llvm-cov` 各编一遍、各跑一遍,各约 170 秒);`verify` 拆成可并行的两半;Cargo 缓存只从 `main` 保存(PR 的缓存只有同一个 PR 能用,而仓库 10 GB 的上限会把 `main` 的挤掉),不缓存 `~/.cargo/bin`;dev / test 构建只留行号表(`CARGO_PROFILE_DEV_DEBUG=line-tables-only`);清理预装 SDK 只在剩余空间不足 40 GB 时做(`.github/scripts/free-disk-space.sh`,托管 runner 实测开跑时有 86 GB);CI 的 Windows 包复用按全部输入(锁文件、清单、`about.toml`、生成脚本与许可证文本)缓存的第三方声明,省掉约两分钟的 cargo-about(`VOLTIP_NOTICES_CACHED=1`,发布总是重新生成);下载失败重试(`CARGO_NET_RETRY`、`RUSTUP_MAX_RETRIES`);桌面壳的 lib 只编成 `rlib`(2026-10-05):Tauri 模板为手机准备的 `staticlib` 和 `cdylib` 让 release 构建的每个依赖都同时生成机器码和 bitcode,去掉后依赖只生成 thin LTO 用的 bitcode(`-C linker-plugin-lto`),本机 32 核冷构建 224 → 156 秒,target/ 3.3 → 2.4 GB;macOS 腿的钥匙串测试程序是带 `keychain-harness` feature 的 bin 而不是 example(example 会带上 dev-dependencies,它们的 feature 让应用构建的 60 多个 crate 重编一遍,Intel Mac 冷跑时 11 分钟),应用构建之后只需再编这个包本身;voltip-server 的 release 构建单独用 16 个代码生成单元(2026-11-10,根 `Cargo.toml`):只有一个单元时整个 crate 是一个 LLVM 模块,LLVM 优化要 862 秒、ThinLTO 链接 303 秒,候选的 Linux 腿为此等 19–34 分钟;拆开后 4 个并行任务下分别是 2 秒和 26 秒,其余包仍是一个单元。`verify-*.log` 里每道门都记了耗时(`secs=`)。 - `ci.yml` 的 `macos` job(2026-09-28 从 `macos.yml` 并入):推送 `main` 或手动运行时在 `macos-15`(Apple 芯片)与 `macos-15-intel`(Intel)上构建 ad-hoc 签名的 `.app` 和 `.dmg`(发布包用固定证书,见下面「macOS 签名」),用 `.github/scripts/check-macos-bundle.sh` 检查,并确认发布用的检查会拒绝这个 ad-hoc 包,跑 macOS 单元测试、真实的事件 tap(仅 Apple 芯片,先在 TCC 里给测试程序授予辅助功能)、无头识别,以及桌面上的 `scripts/smoke-tray-macos.sh`(标题栏左端的内容离红绿灯至少 12 pt,菜单栏图标、菜单与各项操作,截图随产物保存);不跑在 pull request 上。在 push 和手动运行时 `CI Success` 等它(规则在 `.github/scripts/ci-success.sh`,只有 pull request 上允许它被跳过),所以 `main` 上任何一台 Mac 失败,下一个候选都过不了 `source-gate`;Cargo 缓存从 `main` 保存。 - release-please 的 PR(作者 `github-actions[bot]`、分支 `release-please--branches--main--*`、标签 `autorelease: pending`)和它合并到 `main` 的发布提交只改版本号和 CHANGELOG,只跑 `verify-web`:候选在同一个 head 上构建并检查每个安装包,而 head 的基点已经通过 CI(`.github/scripts/changed-code.sh`)。同样的改动换个作者或没有标签,照常跑完整 CI。 - 发布是「构建一次再晋升」(2026-09-28 用户选定):安装包在发布 PR 上构建、签名、证明并封存,合并后只校验和发布,不再编译。 diff --git a/scripts/release/test_release_workflows.py b/scripts/release/test_release_workflows.py index 27070b34..ab602b97 100644 --- a/scripts/release/test_release_workflows.py +++ b/scripts/release/test_release_workflows.py @@ -9,6 +9,7 @@ import json import re +import tomllib import unittest from pathlib import Path @@ -623,6 +624,21 @@ def test_regression_the_appimage_carries_an_apprun_every_user_can_run(self) -> N self.assertRegex(code, r"(?m)^for tool in [^\n]*\bunsquashfs\b") +class ServerPackageBuild(unittest.TestCase): + """The release build of voltip-server (scripts/build-server-linux-x64.sh, the candidate's Linux + leg).""" + + def test_regression_voltip_server_builds_in_more_than_one_codegen_unit(self) -> None: + # Regression (0.3.1 candidate 42733161564, 2026-11-10): the Linux leg spent 19.3 minutes in + # "Build and package voltip-server", 27.7–34.3 in earlier candidates. As one codegen unit + # the crate is one LLVM module: `-Z time-passes` measured LLVM_passes 862.717 s for the + # library (every other pass under a second) and the ThinLTO link took 302.7 s. With 16 + # units, at -j4 as on the runner: 2.0 s and 25.6 s. Every other package keeps one unit. + profile = tomllib.loads((ROOT / "Cargo.toml").read_text(encoding="utf-8"))["profile"]["release"] + self.assertEqual(profile["codegen-units"], 1) + self.assertEqual(profile.get("package", {}).get("voltip-server", {}).get("codegen-units"), 16) + + class WindowsPackageScript(unittest.TestCase): """scripts/build-windows-x64.sh (`make windows-x64`), which ci.yml's windows-cross job and the candidate's Windows leg run, bundles NSIS with the bundler's tools already in place."""