Pure Live 构建与发布实战指南:从质量门禁到签名交付的完整流水线
Pure Live 构建与发布实战指南:从质量门禁到签名交付的完整流水线
导读
Pure Live 是一个跨 Android / Windows / Linux / macOS / iOS 的多平台直播客户端。本文围绕仓库内置的构建技能 pure-live-build(位于 .agents/skills/pure-live-build/SKILL.md)展开,完整讲解如何为该项目执行"验证 → 本机构建 → 打包 → 签名 → GitHub Release"的端到端交付流程。读完本文,你将掌握 local_ci.ps1 的聚焦/全量质量门禁、build_local_release.ps1 的单平台打包、重型任务互斥机制、Android APK 内容完整性核验、Windows 便携包/安装器打包要点,以及 Bug 修复默认交付闭环(bugfix-android-release-default)的固定执行顺序。
一、技能定位:谁负责"构建与交付"这件事
pure-live-build 是仓库中负责验证、本机构建、打包与发布的专用技能,其 frontmatter 明确限定适用范围:"Select and run Pure Live validation, local platform builds, packaging and verified GitHub releases. Use for build or delivery work; not for routine source reading or documentation-only edits." 也就是说,它只服务构建/交付类任务,不做常规源码阅读或纯文档修改。
技能文档给出三条核心纪律,也是本文展开的总纲:
- 先读策略、再选路由:执行任何构建前必须阅读 BUILD_POLICY.md(资源与交付不变量),并按 AGENT_WORKFLOW.md 选择适用的执行路径,不得加载无关的发布或设备流程。
- 入口唯一、互斥内建:质量门禁统一走
tool/local_ci.ps1,打包统一走tool/build_local_release.ps1(一次只处理一个目标/配置)。两个入口自身已持有重型资源租约,调用方不得再嵌套一层锁;确需直接调用重型命令时,才显式使用tool/build_resource_guard.ps1。 - 证据分层、失败续跑:构建、打包、签名、上传、发布是相互独立的证据层;某一阶段失败时,从失败阶段继续,不重复已通过阶段。
二、先定路由:按改动类型选择验证深度
pure-live-build 技能要求先依据 AGENT_WORKFLOW.md 选择路径。该文档把改动类型映射到"起点"与"完成证据",摘录关键几类:
| 改动类型 | 起始动作 | 完成证据 |
|---|---|---|
| 指令/文案/链接 | 改动文件与直接引用 | 链接与 frontmatter 检查、git diff --check |
| 工作流/配置/脚本 | 改动配置与其调用方 | YAML/PowerShell/Python 语法 + 相关静态策略检查 |
| 解析器/API 契约 | 真实或脱敏响应 + 解析器调用方 | 正/负用例夹具测试 |
| 播放器/生命周期/录制 | 状态/事件序列、所有权与释放 | 复现 + 相邻暂停/退出/切源回归 |
| 正式发布 | 干净源码提交 + 完成的修复批次 | 完整质量门禁 + 平台产物/签名/发布核验 |
AGENT_WORKFLOW 同时强调一条可达性门禁(reachability gate):在动手修改前,必须先证明被报告/扫描到的表面能从当前产品到达(找到生产调用点、路由、注册或运行时契约),未引用的 vendored 框架辅助代码不构成当前 UI 回归——这一规则避免了为程序从未执行过的代码写测试和修复。
三、质量门禁:local_ci.ps1 的两种 Scope 与全部参数
质量验证统一由 tool/local_ci.ps1 承载,支持 Focused 与 Full 两种范围:
# 聚焦验证:修复列车中的单项红绿循环
PowerShell -ExecutionPolicy Bypass -File .\tool\local_ci.ps1 `
-Scope Focused -TestPath test/example_test.dart -SkipPubGet
# 聚焦验证 + 全仓一次 Analyze
PowerShell -ExecutionPolicy Bypass -File .\tool\local_ci.ps1 `
-Scope Focused -Analyze -SkipPubGet
# 正式交付质量门禁:完整回归
PowerShell -ExecutionPolicy Bypass -File .\tool\local_ci.ps1 -Scope Full
3.1 参数全景
| 参数 | 合法值/类型 | 行为说明 |
|---|---|---|
-Scope |
Focused / Full(默认 Focused) |
Focused 只跑受影响测试与可选的 Analyze;Full 为正式交付门禁,Analyze 在完整测试之前执行(以较短静态门禁先行失败) |
-TestPath |
string[] | 聚焦范围的目标测试文件,可一次传入多个;Focused 下为空且未传 -Analyze 时会直接抛错 |
-Analyze |
switch | 在计划内 Dart 修改收敛后只执行一次全仓 flutter analyze,避免每个小提交都重启分析服务器 |
-SkipPubGet |
switch | 跳过依赖解析,前提是根/本地插件 pubspec.yaml、pubspec.lock 均未变化且 .dart_tool/package_config.json 已存在;脚本会自行核对,Full 下禁用 |
-OfflinePub |
switch | 依赖解析追加 --offline |
-SkipTestAssets |
switch | 测试时追加 --no-test-assets |
-SkipInterfaces |
switch | Full 下跳过 interface_probe.py 公共接口探测 |
-IncludeRepositoryChecks |
switch | 把全仓策略、设备夹具、仓库完整性审计纳入本次验证(默认只由 Full 承担) |
-TestConcurrency |
int(1–20,默认 12) | Flutter 测试并发数 |
3.2 执行管线细节(以源码为准)
从源码结构看,local_ci.ps1 的真实执行顺序为:
- 参数校验:
Focused必须带-TestPath或-Analyze;Full禁止-SkipPubGet;每个-TestPath必须真实存在。 - 租约与监控:调用
Enter-PureLiveHeavyTaskSlot获取共享重型任务槽位,并启动资源监控 Job。 - 仓库预检(仅 Full/
-IncludeRepositoryChecks):依次执行validate_build_policy.ps1、test_subst_path.ps1、test_android_recording_platforms.ps1、test_android_recording_guard.ps1、test_android_proxy_session.ps1、test_android_activity_state.ps1、test_android_surfaceflinger_timestats.ps1、test_android_process_resource_metrics.ps1、test_android_room_tag_assignment_smoke.ps1、test_android_share_intake_smoke.ps1以及validate_device_ui_map.py。 - 依赖解析:默认
flutter pub get --enforce-lockfile(锁文件强制);-SkipPubGet时校验依赖清单未变。 - 仓库审计(仅 Full):
test_repository_secret_audit.py、test_cc_interface_probe.py、test_assemble_ffmpeg_android_aar.py、test_verify_ffmpeg_native.py、test_acceptance_status_alignment.py、audit_repository.py(输出 JSON 到local-artifacts/repository-audits/)、audit_built_in_kotlin.py。 - Native 依赖预取:
prefetch_android_native.ps1 -SkipAndroidMedia。 - Dart 格式化:
Focused为 apply 模式(直接格式化改动文件并校验成功),Full为 check 模式(--set-exit-if-changed,只检查不改源码);lib/core/scripts/douyin_sign.dart与plugins/built_in_kotlin/、plugins/flv_lzc/、third_party/media_kit_video/下文件排除在格式范围之外。 - 测试与 Analyze 顺序:
Focused先跑更短的受影响测试(红灯立即结束),转绿后再执行本轮唯一一次 Analyze;Full则 Analyze 先行。 - Full 收尾:完整 Flutter 测试套件 +
interface_probe.py接口探测。 - 记录落盘:
finally块中释放租约、等待后台 CPU 回落,并把包含源码提交、耗时、各阶段秒数、缓存命中观察、峰值资源、失败阶段、活跃重型进程数的 JSON 记录写入local-artifacts/build-records/。
四、单平台打包:build_local_release.ps1
打包统一入口为 tool/build_local_release.ps1,强制要求显式传入 -Target 与 -Configuration,每次调用只生成该目标产物:
# Android 交互 Debug(一次只构建这一目标)
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target AndroidArm64 -Configuration Debug -SkipQuality
# Android 正式交付:一次完整回归后构建正式目标并校验签名环境
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target AndroidArm64 -Configuration Release -FullRegression -RequireReleaseSigning
# 同一提交已由上一阶段完成完整回归时,Windows 正式阶段复用该证据
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target WindowsX64 -Configuration Release -SkipQuality
4.1 参数契约
-Target:仅AndroidArm64或WindowsX64(ValidateSet强制)。-Configuration:仅Debug或Release。-FullRegression与-SkipQuality必须二选一(二者同真/同假即抛错):-FullRegression在本调用内先执行完整质量门禁;-SkipQuality只允许在同一应用源码提交已通过完整回归、本次失败阶段仅涉及构建脚本/Gradle 兼容配置/发布流程时使用,且交付报告必须引用此前通过的完整回归记录。-RequireReleaseSigning:仅适用于AndroidArm64 Release,会强制校验android/key.properties四项密钥(storeFile/storePassword/keyPassword/keyAlias)及 store 文件存在,并向 Gradle 注入-Dorg.gradle.project.pureLiveRequireReleaseSigning=true。-DedicatedBuild:Gradle workers 从交互默认 16 提升到 20。-CandidateLabel:命名产物目录(local-artifacts/<version>-<label>),正则约束为小写字母数字与连字符、最长 40 字符。-SkipInstaller:跳过 Windows Inno Setup 安装器打包(仅留便携 ZIP)。-UseOfficialRepositories:跳过 CN 镜像 Gradle init 脚本注入。
4.2 Android 打包流水线细节
Android 分支的关键动作(对应源码行号 tool/build_local_release.ps1#L165-L230):
- 先运行
normalize_flutter_generated_paths.ps1,并要求.dart_tool/package_config.json已由前置质量/依赖阶段生成(正式打包使用--no-pub,避免为 Android 重建其它平台插件链接)。 - 注入固定
GRADLE_OPTS:daemon、parallel、build cache、configuration cache、VFS watch 全开,workers.max按 16/20 档位设定;不传--no-daemon,不主动停 daemon。 - 非官方仓库模式下把 gradle-cn-mirrors.init.gradle 复制到
%USERPROFILE%\.gradle\init.d\pure-live-cn-mirrors.gradle并设置PURE_LIVE_USE_CN_MIRRORS=1;稳定路径保证 configuration cache 条目可复用。 - 执行
prefetch_android_native.ps1预取 Native Assets。 - 构建命令:
flutter build apk --release --split-per-abi --target-platform android-arm64 --build-name=<version> --build-number=<build> --no-pub --dart-define=PURELIVE_BUILD_SOURCE=local。 - FFmpeg Kit 固定工件核验:校验 Native Assets hook 实际消费的 AAR 的 SHA-256 必须等于固定的
c6c9b1ff7be756b0fb587f98e05972ca4dae97e8275c22961b443b4fd5f49bf7(对应固定解析的1.0.4-purelive16kMaven 工件 / n9.0.2 原生包),详见 plugins/flv_lzc/android/libs/README.md。 - 调用
verify_android_apk.ps1做内容完整性核验(见下一节)。 - 产物复制到
local-artifacts/<version>/,命名如PureLive-<version>-android-arm64-v8a-release.apk。
4.3 Windows 打包流水线细节
Windows 分支(tool/build_local_release.ps1#L231-L437)有几个易踩的坑,脚本都做了硬校验:
- 版本号来源不同:Windows 不从
pubspec.yaml取版本,而是读取 assets/version.json 的platforms.windows条目,避免把更新的 Android/pubspec 版本误盖到 EXE 上。 - 剔除 stale debug kernel blob:
build/flutter_assets/kernel_blob.bin是 Debug/JIT 运行残留,Release 打包前强制删除,避免 180 MB 调试文件混入发布产物。 - 按 install_manifest 精确打包:只按
build/windows/x64/install_manifest.txt与经过审查的 runner 运行时小白名单(pure_live.exe、WebView2Loader.dll,Release 再加msvcp140.dll、vcruntime140.dll、vcruntime140_1.dll)建立独立打包目录,禁止直接复制整个runner/Release目录(否则会带入已移除插件的旧 DLL/资源)。 - 白名单逐项核对:
.exp/.ilk/.lib/.pdb开发文件、已退役的 QuickJS 运行时(dart_quickjs.dll、flutter_js_plugin.dll、quickjs_c_bridge.dll)一旦出现即判失败。 - FFmpeg DLL 固定哈希核验:
libffmpegkit.dll与 Windows ZIP 均校验 SHA-256 固定值。 - 运行时状态隔离:EXE 旁的
AppData、IPTV_CACHE目录是便携运行时状态而非构建输入,会被移到local-artifacts/test-runtime/审计性隔离区,绝不进入分发包。 - 安装器:Release 且未传
-SkipInstaller时,用 Inno Setup 6 编译 windows/packaging/exe/local_release.iss,找不到 ISCC 时降级为仅生成便携 ZIP 并告警。
产物目录会同时生成 BUILD_METADATA.json(Android)或 WINDOWS_BUILD_METADATA.json(Windows)与 SHA256SUMS.txt / WINDOWS_SHA256SUMS.txt(只哈希本次调用产生的文件,绝不吸收同版本目录下其它平台的旧产物)。
五、重型任务互斥:build_resource_guard.ps1 的机制
在启动 Gradle、Java、Dart、Flutter 或大范围搜索之前,都应经过 tool/build_resource_guard.ps1。它提供四个核心能力(对应源码函数):
Enter-PureLiveHeavyTaskSlot:获取当前 Windows 会话共享的命名互斥锁Local\Codex.HeavyBuildSlot(超时默认 180 分钟),取得锁后再做两次连续安静采样——用 CPU 增量区分真实负载与常驻 daemon,检测到活跃的gradle/java/javaw/dart/dartaotruntime/flutter/rg就排队等待,确认连续安静后才放行。- daemon 与构建客户端的区分:
GradleDaemon、KotlinCompileDaemon、analysis_server.dart.snapshot属长驻缓存进程,单次 CPU 增量 < 1.25 秒不视为重型任务;而GradleWrapperMain、flutter_tools.snapshot ... build|test|analyze|pub、dart ... test|analyze|compile|run等显式构建客户端立即判为活跃。rg若无显式路径而等待 stdin,同样按"无实际 CPU 工作"处理,避免一个闲置搜索管道永久阻塞后续构建。 Start/Stop-PureLiveResourceMonitor:后台 Job 每秒采样重型进程的 CPU 百分比、WorkingSet 与进程数,供记录峰值资源。Wait-PureLiveBackgroundCpuSettle:构建结束后最多等待 20 秒让后台 CPU 回落,确认无残留活跃重型进程。
同时有配套的共享 Android 实机互斥:同一部手机由 bilibili、小红书与 Pure Live 三个任务轮转使用(biliroaming → xhs → purelive → biliroaming),Pure Live 的实机操作必须通过 tool/run_android_device_test_turn.ps1 取得 purelive lane,遵守 docs/ANDROID_DEVICE_TEST_ROTATION.md;设备租约与构建锁相互独立。
六、Android APK 内容完整性核验
构建门禁不能只看包名、版本、ABI 与签名——完整交付证据由 tool/verify_android_apk.ps1 提供,核验点包括:
- 必需清单条目:
AssetManifest.bin、NOTICES.Z、assets/version.json、assets/releases.json、assets/translations/zh.json、assets/images/banner.png、assets/emo/json/bilibili.json等 Flutter 资源;以及lib/arm64-v8a/下的libflutter.so、libmpv.so、libijkffmpeg.so、libffmpegkit.so、libsqlite3.so(Release 还要求 AOT 的libapp.so,Debug 则要求kernel_blob.bin/isolate_snapshot_data)。每一项都有最小字节阈值。 - FFmpeg 库版本内嵌校验:直接读取
libffmpegkit.so的 ZIP 条目内容,必须包含n9.0.2标记。 - Flutter 资源规模下限:
assets/flutter_assets/文件数 ≥ 1000 且总字节 ≥ 10 MB。 - ABI 唯一性:APK 内只允许唯一 ABI
arm64-v8a(与--split-per-abi一致)。 zipalign -c -P 16 4:校验 APK ZIP 内部对齐兼容 16 KB。- ELF LOAD 段对齐:调用 tool/verify_android_elf_alignment.ps1,要求每个目标 ABI ELF 的全部
LOAD段对齐 ≥0x4000(16 KB)——这正是为了兼容 Android 15+ 的 16 KB 页大小要求。 - Manifest 元数据:用
aapt2 dump badging读取包名、minSdkVersion(必须为 26)、versionName;versionCode必须等于 pubspec 基础 build + ABI 偏移 2000(--split-per-abi会为 arm64 ManifestversionCode增加 2000,禁止只凭文件名判断升级顺序)。
最终输出 package_name / version_name / base_version_code / abi_version_code_offset / manifest_version_code / manifest_min_sdk / abi / native_library_count / minimum_elf_load_alignment / size_bytes / sha256 元数据,随 BUILD_METADATA.json 一起交付。
七、Bug 修复默认交付闭环(bugfix-android-release-default)
BUILD_POLICY.md 规定:一个持续审查批次中的多个独立修复组成"修复列车(repair train)",每项修复通过受影响测试后立即独立提交并同步源码,但在仍有计划内 Dart 修改时,把全仓 Analyze、Full、版本递增与原生构建收敛到统一收敛点执行。完成修复列车后按固定顺序执行:
- 版本递增:语义版本补丁位与数字 build 各递增一次,同步
pubspec.yaml、assets/version.json、工作流默认标签、MSIX 版本、README、Release Notes 与阶段文档。 - 完整质量门禁:在干净提交上执行一次完整门禁;若同一业务源码已通过而后续只改发布脚本/文档,可引用该证据并用
-SkipQuality重建最终提交。 - 本机串行构建 Android arm64 Release:执行 APK 内容、包名、版本、ABI、关键原生库、文件大小与 SHA-256 核验。
- 推送 master 并创建草稿 Release:创建与源码提交一致的版本 tag 和草稿,上传本机暂存 APK、构建元数据与校验文件。
- 短时正式签名:仅调用
sign-staged-android使用 GitHub Secrets 完成签名;核对固定证书指纹与最终 APK 哈希后发布 Release。 - 刷新发布索引:更新
assets/releases.json,以独立的[skip ci]索引提交同步 GitHub。
签名或发布阶段出错时,保留已通过的本机构建证据,修正发布层后从失败阶段继续,不重复远端编译。纯文档/策略/注释变更本身不单独触发 APK 版本;它们若属于同一修复交付批次则随该版本一并提交。
八、GitHub 发布路由与签名基础设施
按 AGENT_WORKFLOW.md 的 workflow 路由表,本地构建优先,GitHub Actions 是显式回退/签名基础设施(各工作流文件位于 .github/workflows):
| 路由 | 用途 |
|---|---|
audit-upstream.yml |
只读入站变更盘点,不执行 merge |
feature-build.yml |
主要托管回退构建,所选平台串行执行;是 stage-linux-* / stage-macos-* / stage-ios-* 标签触发器的唯一所有者 |
build_pure_live_release.yml |
手动 legacy/全 ABI 兼容入口,无 stage 标签触发 |
build-ios-unsigned.yml |
显式手动独立 iOS 构建 |
local-signed-android.yml |
显式自托管 Android 回退 |
sign-staged-android.yml |
用仓库 Secrets 对已本地构建的 APK 签名/校验 |
publish-signed-android.yml |
核验并发布既有签名产物 |
stage-hosted-artifacts.yml |
校验源码身份并把托管产物附加到草稿 |
publish-staged-release.yml |
核验协调好的全平台发布与选定 Windows 源码 |
update_releases.yml |
在 master 上手动更新发布索引 |
关键纪律:同一个交付物不得同时运行 primary 与 legacy 两套构建器;发布变更阶段必须串行,且前一个运行的终态未检查前不得派发下一个;所选平台失败/取消会阻断下游构建与发布;并发组不是持久 FIFO 队列,调用方必须在两次运行之间等待终态结果。workflow 的自由输入与发布文本通过环境变量或文件传递,严禁以 GitHub expression 插值拼进 run 脚本。
九、资源档位、Gradle 缓存与工具链约定
- 本机资源档位(目标机器 Core Ultra 9 275HX / 192 GB RAM):交互开发默认 Gradle workers 16、Flutter 测试并发 12 起步;专门构建(
-DedicatedBuild)workers 提升至 20,但上限 20,不使用全部 24 核,始终为桌面与系统交互保留资源;GPU 主要留给桌面与播放器验证。 - Gradle 基线(固化于 android/gradle.properties):daemon、parallel、build cache、configuration cache、VFS watch 全开;Configuration Cache 保持严格失败模式,不兼容的 Flutter 聚合任务用
notCompatibleWithConfigurationCache精确标注;Gradle JVM Heap 6 GiB / Metaspace 1 GiB / Parallel GC;Kotlin daemon Heap 4 GiB。 - 缓存保留策略:不传
--no-daemon,保留.gradle、.dart_tool、build与原生依赖缓存;flutter clean与递归删除整个构建目录不属于常规步骤,仅在缓存损坏、生成物与源码明显不一致或工具链迁移确有必要时做针对性清理。 - 长路径处理:Android 构建由 tool/flutterw.ps1 把工程映射到
%LOCALAPPDATA%\Codex\workspaces下的稳定同盘目录联接,保证与 Pub 缓存同盘、避免 Kotlin 增量缓存跨盘回退;Windows/MSBuild 与 Flutter 测试使用稳定SUBST盘符,规避 Visual Studio 在目录联接下写ZERO_CHECK.tlog解析失败的问题。 - 工具链解析:
flutterw.ps1按.fvmrc锁定 Flutter 版本,从PURE_LIVE_FLUTTER/.fvm/LOCALAPPDATA候选链解析;自动补ANDROID_HOME、JAVA_HOME(Temurin 17 或 Android Studio JBR)、可用 Git(保证锁依赖可解析),并注入--enable-native-access=ALL-UNNAMED。
十、记录与收尾:让每个构建都可审计
每次重型任务都会在 local-artifacts/build-records/ 写入 JSON 记录,至少包含:实际命令、源码提交、运行开始时的未提交路径、目标平台与变体、开始时间、总耗时、各阶段耗时、失败阶段与结果(运行中源码状态改变必须单独标记)、Gradle/配置缓存启用状态与 UP-TO-DATE 数量、重型进程峰值 CPU/内存/进程数、产物绝对路径或验证范围(Android 同时记录包名、versionName、pubspec 基础 build、ABI 偏移、Manifest 实际 versionCode、文件大小与 SHA-256)、任务结束后的活跃重型进程数。
监控任务、临时脚本与互斥锁在 finally 中释放;构建结束后确认后台 CPU 回落。Bug 修复批次只继续既定 Android 签名、GitHub 发布与索引同步,不追加下一轮完整回归或其它平台打包——这是文档化证据复用原则的落地:已通过的输入不复测,新改动只使输入 SHA 变化的收敛证据失效。
十一、推荐调用速查
# 1) 修复列车单项红绿循环:先只运行受影响测试,成功后即可同步源码
PowerShell -ExecutionPolicy Bypass -File .\tool\local_ci.ps1 `
-Scope Focused -TestPath test/example_test.dart -SkipPubGet
# 2) 计划内 Dart 修改收敛后只运行一次全仓 Analyze
PowerShell -ExecutionPolicy Bypass -File .\tool\local_ci.ps1 `
-Scope Focused -Analyze -SkipPubGet
# 3) Android 交互 Debug(一次只构建这一目标)
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target AndroidArm64 -Configuration Debug -SkipQuality
# 4) Android 正式交付:一次完整回归后构建正式目标
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target AndroidArm64 -Configuration Release -FullRegression -RequireReleaseSigning
# 5) 同一提交已由上一阶段完成完整回归时,Windows 正式阶段复用该证据
PowerShell -ExecutionPolicy Bypass -File .\tool\build_local_release.ps1 `
-Target WindowsX64 -Configuration Release -SkipQuality