首页
/ Flutter 生态包发布指南:自动 CI 发布、批量发布与坏版本恢复全流程

Flutter 生态包发布指南:自动 CI 发布、批量发布与坏版本恢复全流程

2026-09-06 14:47:21作者:劳婵绚Shirley

本文基于 Flutter 仓库中面向生态团队的发布文档 release/README.md(在 生态文档索引 中列为 "Releasing a Plugin or Package")编写,系统讲解 flutter/packages 仓库中插件与包的完整发布链路:自动发布 CI 的触发与判定规则、面向高频变更包的批量发布(Batch release)配置与流程、手动发布的标准操作与检查清单,以及已发布坏版本的修复与撤回策略。读完本文,你将能够理解一个包的版本变更如何最终到达 pub.dev,并掌握作为生态维护者应对发布失败、坏版本事故的完整处置方案。

总则:任何版本变更都应被发布

Flutter 生态的发布基线是一条简单原则:任何修改了包版本的 PR(这应该是绝大多数 PR)都应发布到 pub.dev。这条规则贯穿后文所有发布模式——无论是自动发布、批量发布还是手动发布,目标都是保证 pubspec.yaml 中的版本号与 pub.dev 上可安装的版本严格一致,让包的使用者(包括大量不经常更新传递依赖的客户端)能稳定解析到预期版本。

发布方式的选择取决于包的流量特征:

  • 自动发布(Automatic release):默认模式,master 上每个包含版本更新的提交都会触发发布;
  • 批量发布(Batch release):面向高流量包,把多个提交聚合成一次周期性发布,避免 CHANGELOG.mdpubspec.yaml 频繁冲突、pub.dev 上版本号不断滚动。

自动发布(Automatic release)

flutter/packages 仓库中的包通过一个名为 release 的 GitHub Action 工作流自动发布。其工作机制是:

  1. master 分支的某个提交包含一个或多个包的版本更新时,release CI 会把新版本发布到 pub.dev,并向 GitHub 推送发布标签(tag);
  2. 该 CI 在以下任一情况下视为通过:
    • 发布流程成功;
    • 该提交不包含任何版本更新(无事可做);
    • 新版本此前已经发布过(幂等)。

运行时机与阻塞行为

有几个 CI 行为细节值得注意:

  • release CI 只在 post-submit 阶段运行,并且会等待其他所有 CI 任务全部通过后才开始——这保证了只有整体质量验证通过的提交才会被发布;
  • 与其他 CI 任务一样,release CI 一旦失败会阻塞后续的 PR,因此发布失败必须及时处理(见下文"release CI 失败怎么办");
  • 注意例外:release CI 不会自动发布 flutter_plugin_tools(它本身就是发布工具链的一部分,单独管理)。

新包的首次发布与所有权转移

当一个包首次发布时,它会归属于发布器账户(publisher account),而不是 flutter.dev 认证发布者(verified publisher)。需要拥有发布器账户权限的团队成员登录 pub.dev,通过包页面的 Admin 标签页把包转移给认证发布者。这一步是所有新包接入生态发布体系的必经环节。

release CI 失败了怎么办

原文档给出了清晰的故障处置路径:

  • 抖动(flake)场景(例如网络问题):团队成员可以直接重跑该 CI;
  • 复杂场景:团队成员可以先手动发布相关包(流程见后文"手动发布"),再重跑 CI 使其通过;
  • 最常见的失败原因其实是"其他测试任务先失败了",而不是发布本身出错。如果那次测试失败是抖动导致的,正确顺序是:先重跑失败的测试任务,待其变绿后再重跑 release

批量发布(Batch release)

对于 PR 数量多的包,默认"每个提交发一次版"会带来两个问题:CHANGELOG.mdpubspec.yaml 的合并冲突不断,pub.dev 上版本持续滚动。这类包可以启用批量发布,把多个提交聚合成一次周期性发布。

硬性前提:启用批量发布的包必须处于正式版本号,不能是预发布版本(如 x.y.z-dev),因为批量发布工具链不支持预发布版本。

启用步骤

启用批量发布需要提交一个 PR,包含以下四处改动:

1. 在包根目录添加 ci_config.yaml

release:
  batch: true

这个文件也是判定包发布模式的依据——包根目录存在 ci_config.yaml 且设置了 release: batch: true 即视为批量发布,否则默认走自动发布。

2. 在包根目录创建 pending_changelogs 目录,并放入 template.yaml 模板文件:

# Use this file as a template to draft an unreleased changelog file.
# Make a copy of this file in the same directory, give it an appropriate name, and fill in the details.
changelog: |
  - Can include a list of changes.
  - with markdown supported.
version: <major|minor|patch|skip>

贡献者后续为每个 PR 复制该模板、重命名并填写变更说明;version 字段声明该变更期望的版本级别(major / minor / patch),skip 表示本次不升版本。

3. 在工作流目录添加名为 <package_name>_batch.yml 的工作流文件package_name 为包名):

name: "Creates Batch Release for <package_name>"

on:
  workflow_dispatch:
  schedule:
    # Run every Monday at 8:00 AM. Update cron as needed.
    - cron: "0 8 * * 1"

jobs:
  dispatch_release_pr:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Repository Dispatch
        uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f
        with:
          event-type: batch-release-pr
          client-payload: '{"package": "<package_name>"}'

该工作流支持手动触发(workflow_dispatch)和定时触发(示例 cron 为每周一 8:00),通过 repository dispatch 事件 batch-release-pr 通知后续流程。

4. 为两个既有工作流添加分支触发条件——release_from_branches.ymlsync_release_pr.ymlon.push.branches 中都要加上:

on:
  push:
    branches:
      - 'release-<package_name>-*'

合并该 PR 后,批量发布即配置完成。

批量发布的日常工作流

启用后,贡献者不应再直接修改 CHANGELOG.mdpubspec.yaml,而是为每个 PR 在 pending_changelogs 目录新增一个变更文件。之后的发布过程自动进行:

  1. <package_name>_batch.yml 中的定时任务触发一个指向 release-<package_name>-<version> 分支的新发布 PR
  2. 该 PR 把 pending_changelogs 中的所有文件聚合成对 CHANGELOG.mdpubspec.yaml 的一次更新(版本号按各条目声明的级别推进);
  3. 包属主(package owner)评审并合并该 PR;
  4. 合并动作触发 release_from_branches.yml把包发布到 pub.dev
  5. 同时 sync_release_pr.yml 创建一个指向 main 分支的"同步 PR",把发布分支上的变更同步回主干;
  6. 包属主评审并合并同步 PR,周期结束。

这一设计的关键在于:变更在周期内以"独立文件"形式存在,几乎不可能产生冲突;版本号与 CHANGELOG 的写入被收敛到一次性的聚合 PR 中,实现了"高流量、低摩擦"的发布节奏。

手动发布(Manual release)

手动发布是兜底手段,只在自动发布被阻断时使用。典型触发场景是:带外破坏(out-of-band breakage)导致 post-submit 测试因与被发布 PR 无关的原因失败,从而卡住了本应自动发布的版本。文档同时指出,对于影响面较广(涉及很多插件)的 PR,revert 后重新落地是手动发布之外的一个值得优先考虑的替代方案。

发布前的三个检查

文档要求发布前逐条确认:

  1. post-submit CI 是否为绿? 如果因带外破坏而未全绿,也要确认后续存在一个"未改动任何与该插件相关内容"的绿色 post-submit。发布前必须检查 post-submit CI 状态;
  2. "发布即永久(Publishing is forever)":pub.dev 上的版本不可覆盖。虽然 bug 或破坏性变更理应已在 PR 评审中被捕获,但这是上线前最后一次的 revert 机会;
  3. "不要在周五发布":发布前考虑是否有较长的不可用时段即将到来。新版本可能存在 bug 或引发使用者提问,如果此时无法跟进,问题的发现与解决周期会被显著拉长。

标准操作步骤

使用仓库工具链 flutter_plugin_tools 完成发布:

  1. git checkout <commit_hash_to_publish>——这应当就是你要发布的那个 PR 的提交,除非有非常充分的理由使用其他版本;
  2. 确认 git status 干净,且本地仓库没有多余文件(例如通过 git clean -xfd 清理);
  3. 运行 flutter_plugin_toolspublish 命令。该命令会检查上一步的清理状态,把新版本发布到 pub.dev,并按 <package_name>-v<package_version> 格式为提交打标签,再把标签推送到上游仓库。

完全手动备选方案

如果第 3 步中无法使用 flutter_plugin_tools,可以退化为纯手动三步:

  1. dart pub publish 把包更新推送到 pub.dev;
  2. git tag<package_name>-v<package_version> 格式为提交打标签;
  3. git push upstream <tagname> 把标签推送到上游 master 分支。

恢复坏版本(Recovering from a bad release)

尽管有重重防护,破坏性问题仍可能在包发布之后才被发现。这里有一个与 flutter/engine、flutter/flutter 仓库根本不同的约束:已发布过破坏性变更的 PR 不能直接 revert——revert 会把包带回一个更早的版本号,而那个版本同样已经发布过,pub.dev 不允许重复发布同一版本号。

正确的修复路径是:

  1. 以一个新版本落地修复:视具体情况选择"revert 并带上版本号和 CHANGELOG 更新"或"正向修复(fix-forward)";
  2. 可选:撤回坏版本。如果坏版本发布在最近七天内flutter.dev 发布者组成员可以通过包 pub.dev 页面的 Admin 标签页撤回(retract)该版本。

撤回这一步在两种情况下尤其有价值:

  • 坏版本的问题与错误的 Flutter/Dart 版本约束有关(例如包依赖了新版 Flutter/Dart 的功能,却没有设置对应的最低版本约束):即使后续发布了修正约束的新版本,仍在使用旧版 Flutter 的客户端可能继续解析到坏版本,撤回可以杜绝这种解析结果;
  • 作为修复版本推广期间的即时止损,防止在修复版本铺开前更多用户被波及。

文档特别强调:撤回应当与发布修复版本同时进行,而不是替代它——这样已经被破坏的用户才能顺畅地到达一个可用状态。

相关联的生态发布流程

理解发布链路后,以下同仓库文档可作为延伸阅读,它们与发布过程直接衔接:

  • Updating-Packages-repo-for-a-stable-release.md:描述每个 stable Flutter 版本发布后如何更新 flutter/packages 仓库的 stable 版本钉扎、Flutter-Dart 版本映射、N-1/N-2 遗留分析测试与最低 Flutter 版本约束——这是"版本发布"在 SDK 维度的配套动作,与包维度的 pub.dev 发布互为表里。文档给出了具体命令示例,例如用仓库工具批量抬升最低 SDK 版本:
    dart run script/tool/bin/flutter_plugin_tools.dart update-min-sdk --flutter-min=3.44.0
    
    以及用 update-release-info --version=next 只更新受影响包的发布说明;
  • contributing/README.md:定义进入发布流水线的上游规则——版本与 CHANGELOG 更新规范、## NEXT 段的使用、批量发布包 pending_changelogs 变更文件的写入要求,以及破坏性变更的"分批落地"策略(先临时加 publish_to: none 隔离不可发布状态,再集中落地破坏性变更,最后统一升版);
  • Package-migration-to-1.0.0.md:解释包跨越 1.0.0 里程碑时的生态摩擦——由于 pub 对 0.x 版本采用语义偏移,从 0.x.y1.0.0 属于主版本跃迁,文档建议依赖方在过渡期使用 >=0.x.y+z <2.0.0 的宽约束而非 ^1.0.0,以减少生态碎片化。这直接影响发布时约束字段的写法决策。

从本仓库结构看,Flutter 还有另一条独立的"发布"通道:dev/bots/prepare_package.dart 负责把 Flutter git 仓库打包成 SDK 分发包(要求完整 40 位 --revision--branch 等参数),dev/bots/unpublish_package.dart 则负责从云存储移除已发布的 SDK 归档并回滚各 channel 的元数据——两者面向的是 Flutter SDK 本身的 dev/beta/stable 通道分发,与本文讨论的包发布(pub.dev)是两套机制,阅读时应注意区分。

小结

Flutter 生态的包发布体系可以概括为三层:默认层是 master 提交触发的自动发布 CI,保证"改版本即发布"且发布前置所有 CI 验证;效率层是批量发布,用 ci_config.yamlpending_changelogs 与三个协作工作流把高频变更收敛为周期性聚合发布;兜底层是手动发布与坏版本恢复,前者以"post-submit 全绿、发布即永久、避免周五发布"为检查清单,后者以"新版本修复 + 七天内撤回"双管齐下。三者共同保障了 pub.dev 上版本、仓库 tag 与 CHANGELOG 三者的一致性。

登录后查看全文
热门项目推荐
相关项目推荐