Pure Live 构建与发布实战指南:从质量门禁到签名交付的完整流水线

原创2026-10-03 12:25:25173 阅读
文章标签:音视频直播移动开发

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." 也就是说,它只服务构建/交付类任务,不做常规源码阅读或纯文档修改。

技能文档给出三条核心纪律,也是本文展开的总纲:

  1. 先读策略、再选路由:执行任何构建前必须阅读 BUILD_POLICY.md(资源与交付不变量),并按 AGENT_WORKFLOW.md 选择适用的执行路径,不得加载无关的发布或设备流程。
  2. 入口唯一、互斥内建:质量门禁统一走 tool/local_ci.ps1,打包统一走 tool/build_local_release.ps1(一次只处理一个目标/配置)。两个入口自身已持有重型资源租约,调用方不得再嵌套一层锁;确需直接调用重型命令时,才显式使用 tool/build_resource_guard.ps1。
  3. 证据分层、失败续跑:构建、打包、签名、上传、发布是相互独立的证据层;某一阶段失败时,从失败阶段继续,不重复已通过阶段。

二、先定路由:按改动类型选择验证深度

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 的真实执行顺序为:

  1. 参数校验:Focused 必须带 -TestPath 或 -Analyze;Full 禁止 -SkipPubGet;每个 -TestPath 必须真实存在。
  2. 租约与监控:调用 Enter-PureLiveHeavyTaskSlot 获取共享重型任务槽位,并启动资源监控 Job。
  3. 仓库预检(仅 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。
  4. 依赖解析:默认 flutter pub get --enforce-lockfile(锁文件强制);-SkipPubGet 时校验依赖清单未变。
  5. 仓库审计(仅 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。
  6. Native 依赖预取:prefetch_android_native.ps1 -SkipAndroidMedia。
  7. 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/ 下文件排除在格式范围之外。
  8. 测试与 Analyze 顺序:Focused 先跑更短的受影响测试(红灯立即结束),转绿后再执行本轮唯一一次 Analyze;Full 则 Analyze 先行。
  9. Full 收尾:完整 Flutter 测试套件 + interface_probe.py 接口探测。
  10. 记录落盘: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):

  1. 先运行 normalize_flutter_generated_paths.ps1,并要求 .dart_tool/package_config.json 已由前置质量/依赖阶段生成(正式打包使用 --no-pub,避免为 Android 重建其它平台插件链接)。
  2. 注入固定 GRADLE_OPTS:daemon、parallel、build cache、configuration cache、VFS watch 全开,workers.max 按 16/20 档位设定;不传 --no-daemon,不主动停 daemon。
  3. 非官方仓库模式下把 gradle-cn-mirrors.init.gradle 复制到 %USERPROFILE%\.gradle\init.d\pure-live-cn-mirrors.gradle 并设置 PURE_LIVE_USE_CN_MIRRORS=1;稳定路径保证 configuration cache 条目可复用。
  4. 执行 prefetch_android_native.ps1 预取 Native Assets。
  5. 构建命令: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。
  6. FFmpeg Kit 固定工件核验:校验 Native Assets hook 实际消费的 AAR 的 SHA-256 必须等于固定的 c6c9b1ff7be756b0fb587f98e05972ca4dae97e8275c22961b443b4fd5f49bf7(对应固定解析的 1.0.4-purelive16k Maven 工件 / n9.0.2 原生包),详见 plugins/flv_lzc/android/libs/README.md。
  7. 调用 verify_android_apk.ps1 做内容完整性核验(见下一节)。
  8. 产物复制到 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 提供,核验点包括:

  1. 必需清单条目: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)。每一项都有最小字节阈值。
  2. FFmpeg 库版本内嵌校验:直接读取 libffmpegkit.so 的 ZIP 条目内容,必须包含 n9.0.2 标记。
  3. Flutter 资源规模下限:assets/flutter_assets/ 文件数 ≥ 1000 且总字节 ≥ 10 MB。
  4. ABI 唯一性:APK 内只允许唯一 ABI arm64-v8a(与 --split-per-abi 一致)。
  5. zipalign -c -P 16 4:校验 APK ZIP 内部对齐兼容 16 KB。
  6. ELF LOAD 段对齐:调用 tool/verify_android_elf_alignment.ps1,要求每个目标 ABI ELF 的全部 LOAD 段对齐 ≥ 0x4000(16 KB)——这正是为了兼容 Android 15+ 的 16 KB 页大小要求。
  7. Manifest 元数据:用 aapt2 dump badging 读取包名、minSdkVersion(必须为 26)、versionName;versionCode 必须等于 pubspec 基础 build + ABI 偏移 2000(--split-per-abi 会为 arm64 Manifest versionCode 增加 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、版本递增与原生构建收敛到统一收敛点执行。完成修复列车后按固定顺序执行:

  1. 版本递增:语义版本补丁位与数字 build 各递增一次,同步 pubspec.yaml、assets/version.json、工作流默认标签、MSIX 版本、README、Release Notes 与阶段文档。
  2. 完整质量门禁:在干净提交上执行一次完整门禁;若同一业务源码已通过而后续只改发布脚本/文档,可引用该证据并用 -SkipQuality 重建最终提交。
  3. 本机串行构建 Android arm64 Release:执行 APK 内容、包名、版本、ABI、关键原生库、文件大小与 SHA-256 核验。
  4. 推送 master 并创建草稿 Release:创建与源码提交一致的版本 tag 和草稿,上传本机暂存 APK、构建元数据与校验文件。
  5. 短时正式签名:仅调用 sign-staged-android 使用 GitHub Secrets 完成签名;核对固定证书指纹与最终 APK 哈希后发布 Release。
  6. 刷新发布索引:更新 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

相关资源

登录后查看全文
pure_live