Dart SDK 分支管理与发布周期全解:从 main 到 stable 的四通道工作流
【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址: https://gitcode.com/gh_mirrors/sdk1/sdk
Dart SDK 采用"单主干 + 三个发布通道"的简洁分支模型:日常开发集中在main分支,功能在验证充分后通过 flag 门控逐步开放,最终经dev→beta→stable三个通道逐级发布。本文以仓库文档 docs/Branches-and-releases.md 为核心骨架,结合 tools/VERSION、tools/utils.py 及 docs/Cherry-picks-to-a-release-channel.md 等源码级证据,完整讲解分支职责、约两个月一轮的发布节奏、cherry-pick 季节的操作流程,以及实验性 flag 如何与发布通道协同工作。读完你既能看清整个 SDK 从提交到发布的完整链路,也能直接上手执行"把修复带到 beta/stable"的完整操作。
四分支模型:一条主干,三条发布通道
Dart SDK 的分支结构非常简单直接:所有日常开发都在main上完成,尚未准备就绪的特性统一藏在实验 flag 之后,待稳定性充分验证后才默认开启;偶尔也会为大型破坏性改动创建临时特性分支。除此之外,还有dev、beta、stable三个发布分支,负责把代码逐级推向用户。
四类分支的职责与约束如下:
| 分支 | 来源 | 刷新频率 | 发布产物 | 开发者注意事项 |
|---|---|---|---|---|
main | 日常开发主干 | 持续 | 每日构建 | 你的 CL 应该落在 main |
dev | 从 main 全量推送 | 通常每周两次 | dev 通道构建 | 不要在 dev 上直接落 CL |
beta | 从 dev 全量推送,或 cherry-pick | 通常每月一次 | beta 通道构建 | 不要在 beta 上直接落 CL |
stable | 从 beta 全量推送,或 cherry-pick | 有发布时 | stable 通道构建 | 不要在 stable 上直接落 CL |
- main:日常开发分支,"everyday" 开发都在这里进行,所有普通 CL 都应落在 main。可以理解为开发者唯一真正打交道的分支。
- dev:由 main 通过全量推送(full push)填充,通常每周两次。除非紧急情况,否则不会在 dev 上落 cherry-pick。dev 通道构建由此分支发布。
- beta:由 dev 全量推送填充,通常每月一次,或通过 cherry-pick 更新。beta 通道构建由此分支发布。
- stable:主发布分支。当某个版本准备好发布时,从 beta 全量推送而来,或通过 cherry-pick 更新。stable 通道构建由此分支发布。
一个需要反复强调的纪律是:dev、beta、stable三个分支都不接受直接提交的 CL,发布分支的内容只能通过全量推送或 cherry-pick 两条路径进入,这从流程上保证了发布内容的可追溯性。
发布周期:约两个月一轮的稳定化流程
Dart SDK 的正常发布周期约为2 个月,但这个数字并非承诺——团队会根据稳定性提前或推迟发布。团队并不遵循特性驱动的发布节奏,但大型语言特性可能例外:为了让所有工具链(VM、编译器、分析器等)保持同步,可能推迟某个版本。
完整周期可以拆解为以下阶段:
- 日常阶段(周期的大部分时间):团队持续在
main上工作,并定期把 main 的绿构建(green build)全量合并到dev,约每周两次。如果在 dev 上快速发现了 bug,会再做一次额外的全量推送。 - 稳定化阶段(周期最后约 2 周):把最新的 dev 版本全量推送到
beta,进入 beta 通道稳定期。此阶段只允许对 beta cherry-pick 关键修复(critical fixes)。main 上的开发与 dev 通道的发布照常继续。这一阶段被称为cherry-pick season(cherry-pick 季节),每周会做多批 cherry-pick 并在 beta 通道发布。 - 发布阶段:当 beta 通道状态良好("looks good")时,合并到
stable并正式发布。 - 补丁维护阶段:在 2 个月的周期中,基于最新的 stable 版本,持续为安全(security)、崩溃(crash)、关键 bug(critical bug)发布补丁版本。
cherry-pick season 中把你的改动带到 beta
在 cherry-pick 季节,如果你的修复需要进入 beta 通道,完整操作流程见 Cherry-picks-to-a-release-channel.md,核心步骤概括为:先在 main 上落地修复与测试 → 识别 beta/stable 是否受影响 → 在新分支上执行git cherry-pick --edit→ 更新提交信息(添加[beta]或[stable]hashtag,改写Reviewed-on为Cherry-pick,移除不适用于新 CL 的字段)→ 填写 Issue description / What is the fix / Why cherry-pick / Risk / Issue link(s) 五段式说明 →git cl upload提交 Gerrit 审批 → 由领域专家与 Dart 团队 lead 评审后提交 commit queue,tryjobs 会与 beta/stable 分支上一提交对比测试结果,引入回归则失败。
版本号如何随周期演进
版本号并不是随手改的,而是由 tools/VERSION 文件与 tools/utils.py 中的Version类、ReadVersionFile、GetVersion等逻辑共同驱动。当前仓库的 VERSION 文件内容为:
CHANNEL main MAJOR 3 MINOR 14 PATCH 0 PRERELEASE 0 PRERELEASE_PATCH 0VERSION 文件头部注释完整定义了数字的变化规则:
- 新发布周期开始(stable 刚发布):
MINOR+1,PATCH/PRERELEASE/PRERELEASE_PATCH归 0; - push-to-trunk(周期内首次会同时把
PRERELEASE归 0):PRERELEASE+1,PRERELEASE_PATCH归 0; - cherry-pick 到 trunk:
PRERELEASE_PATCH+1; - 发布 stable:
PRERELEASE与PRERELEASE_PATCH归 0(新的 stable 版本号会排在所有 prerelease 之上); - stable 通道的 cherry-pick:
PATCH+1。
tools/utils.py 中的GetVersion(tools/utils.py#L369-L384)展示了这些字段如何拼装成最终版本字符串:
main/be通道 →{major}.{minor}.{patch}-edge.{git_hash}(如3.14.0-edge.abcdef1234);beta/dev通道 →{major}.{minor}.{patch}-{prerelease}.{prerelease_patch}.{channel};stable通道 → 纯{major}.{minor}.{patch}。
而Version.__str__(tools/utils.py#L177-L182)则定义了语义化版本串的拼接规则。这套机制保证了四个分支上的版本号始终可比较、可排序,这正是"dev 版本号永远低于未来 stable 版本号"这一保证的来源。
实验 flag 与分支模型的协同:大特性如何安全落地
原文档特别强调:"尚未准备好上线的特性隐藏在 flag 之后,待稳定性充分验证后启用。"这正是 tools/experimental_features.yaml 的职责所在。从该文件的结构看(其help、enabledIn、experimentalReleaseVersion、expired、validation等字段,详见文件头部注释与 docs/process/experimental-flags.md),一个实验特性会经历如下状态机:
- Disabled:刚加入文件时,省略
enabledIn与expired字段,默认关闭,可通过--enable-experiment=xxx命令行开启; - Experimental release:加入
sdk/lib/_internal/allowed_experiments.json白名单并提升experimentalReleaseVersion,允许特定库/包默认开启,其他用户仍需显式传 flag; - Shipped:添加
enabledIn字段标记正式发布的 SDK 版本,此时 flag 默认开启且无法关闭; - Retired / Rejected:添加
expired: true,命令行传 flag 会收到警告但工具继续运行,条目等待最终从文件中移除。
原文档给出了一个典型时序:特性在版本 n-1 的 beta 1 公开为实验特性,在版本 n 的 beta 1 默认启用,在版本 n+1 的 beta 1 退役。从当前文件可以看出,records、patterns、class-modifiers等已在 3.0.0 默认启用并标记expired: true,dot-shorthands(3.10.0 启用)、native-assets(3.10.0 启用)、primary-constructors(3.13.0 启用)等也已走完这条生命周期,而macros、augmentations、enhanced-parts等仍处于实验阶段。这个机制正是"main 上可以安全地长期保留未完成代码"的根基——未完成特性对用户不可见,而dev/beta/stable通道的每次构建因此都能保持"功能完整且受支持"的高质量。
实践操作:按分支定位版本与频道
查看当前分支的版本通道
VERSION 文件中的CHANNEL字段即当前分支的发布通道标识。在仓库根目录执行:
$ git branch --show-current main $ cat tools/VERSION | grep CHANNEL CHANNEL main每个发布分支(dev/beta/stable)上的 VERSION 文件会各自携带对应的通道标识,这正与 tools/utils.py 中GetChannel(tools/utils.py#L387-L389)读取CHANNEL字段的逻辑对应。
把修复带到 stable 的完整流程(cherry-pick)
以 docs/Cherry-picks-to-a-release-channel.md 的操作步骤为基准:
$ git fetch $ git new-branch --upstream origin/stable cherry # 或 origin/beta $ git cherry-pick --edit $commit $ $EDITOR CHANGELOG.md # stable 必须更新 CHANGELOG,beta 不需要提交信息按以下规则改写(示例见原文档):
[stable] Fix foo crash. Issue description: When attempting to use foo under certain conditions, users are unable to compile. What is the fix: foo is now evaluated at runtime. Why cherry-pick: Users of foo are no longer able to compile to bar. Risk: Low, this fix has landed on the main channel and is tested on the same infrastructure. Issue link(s): https://github.com/dart-lang/sdk/issues/12345678 Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/12345678要点包括:第一行以[beta]或[stable]Gerrit hashtag 开头;Reviewed-on改名为Cherry-pick并指向原始 CL;删除不再成立的Change-id、Commit-queue、Reviewed-by字段;说明必须包含 Issue description / What is the fix / Why cherry-pick / Risk / Issue link(s) 五个要素。
CHANGELOG 要求:stable 通道的 cherry-pick 必须携带 CHANGELOG.md 条目(相关硬性要求见 docs/Gerrit-Submit-Requirements.md),beta 通道则不需要。若下一个 stable 补丁版本尚未在 CHANGELOG 中建立小节,需要新建小节并把补丁号 +1(如3.0.4→3.0.5),且不加发布日期——日期在正式发布时才补上;已有发布日期的小节表示已经发布,不得再改动。纯基础设施、对用户不可见的改动可通过Changelog-Exempt: ...脚注豁免。
随后上传并提交:
$ git cl uploadCL 需经领域主题专家(area subject matter expert)与 Dart 团队 lead 双重评审,之后作者将其提交到 commit queue。关键保护机制:tryjobs 会把测试结果与 beta/stable 分支上的前一提交对比,一旦引入回归即失败;若必须引入回归、或 try builders 无法在较老的 beta/stable 代码上运行,可在 Gerrit 中强制提交(... -> Submit)绕过 commit queue。
cherry-pick 依赖仓库中的提交
当修复涉及第三方依赖(如third_party/pkg/pub)时,流程变为两步。先在 SDK 检出中定位发布分支上的依赖修订号:
dart-sdk/sdk/ > git checkout beta && git pull dart-sdk/sdk/ > gclient getdep -r sdk/third_party/pkg/pub a3f8b2fd36ec432450caf907474a02023ef3e44e然后在依赖仓库的克隆中做 cherry-pick 并推送,等待镜像同步后,通过合并提交把该提交并入依赖的受保护分支(防止被 GC),最后回到 SDK 检出用tools/manage_deps.dart创建 bump 提交:
dart-sdk/sdk/ > tools/manage_deps.dart bump third_party/pkg/pub --target=6d1857c84cfb8a014aefedaf2d453214bf5ddb96 dart-sdk/sdk/ > git branch --set-upstream-to=origin/beta dart-sdk/sdk/ > git cl upload总结:一套面向稳定与速度的工程纪律
回顾整个模型,可以提炼出 Dart SDK 发布工程的核心设计:
- 单一主干,多通道发布:所有开发集中在
main,dev/beta/stable只通过全量推送与 cherry-pick 两种受控路径更新,杜绝了发布分支的不可控提交。 - 稳定化节奏前置:每个周期最后两周专门用于 beta 通道稳定化(cherry-pick season),把风险控制在发布之前。
- flag 门控替代特性分支:未完成特性以实验 flag 形式长期存在于 main,既保证 dev 通道构建的功能完整性,又允许大特性跨多个版本持续演进(如 null safety、records、macros)。
- 版本号机器可读:tools/VERSION 与 tools/utils.py 的组合让每个通道的版本号保持单调可排序,为发布判断和依赖解析提供了可靠依据。
这套机制同时服务于三类人群:普通开发者只需知道"CL 落在 main,实验特性用 flag 尝鲜";贡献者需要掌握 cherry-pick 的完整操作与提交信息规范;而需要预发布版本的团队则可以根据 dev/beta 通道的构建选择合适的时间窗口。相关细节还可进一步阅读 Cherry-picks-to-a-release-channel.md、Gerrit-Submit-Requirements.md 与 Experimental-Flags.md。
【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址: https://gitcode.com/gh_mirrors/sdk1/sdk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考