首页
/ Flutter 仓库 Dart SDK 最低版本约束升级指南:基于 flutter/flutter 源码的完整流程

Flutter 仓库 Dart SDK 最低版本约束升级指南:基于 flutter/flutter 源码的完整流程

2026-09-06 18:15:22作者:何将鹤

本指南系统讲解在 Flutter 主仓库(monorepo)中如何将 pubspec.yaml 里声明的 Dart SDK 最低版本约束(minimum SDK constraint)从旧版本提升到新版本,涵盖约束写法规范、升级策略与节奏、稳定的版本上限策略、以及分四步完成的完整实操流程。读完本文,你将掌握 -0 预发布后缀的作用、为什么约束不能超过 Dart stable 版本、如何用 flutter update-packages 统一重新求解并刷新 # PUBSPEC CHECKSUM 校验和,以及如何通过仓库级静态分析与测试验收一次约束升级。

说明:本仓库即 flutter/flutter 官方仓库镜像,源码、工具与配置均可直接查阅。滚动 Engine 中 Dart SDK 二进制/编译器本身不属于本文范围,相关流程见 Rolling the Dart SDK


概述:为什么每个 pubspec 都要声明 Dart SDK 约束

flutter/flutter 仓库是一个庞大的 Dart 工作区(workspace),包括 flutter framework、flutter_tools、各类 dev 工具、测试与示例在内,每个 Dart 包都在各自的 pubspec.yaml 中声明一个最小 Dart SDK 版本约束,例如本仓库根目录 pubspec.yaml 中的写法:

environment:
  sdk: ^3.11.0-0

这是一条标准的 Dart pubspec.yaml 依赖约束声明,其含义为"该包要求 Dart SDK 版本为 3.11.0 及以上且低于 4.0.0,并允许 3.11 主版本的预发布版"。由于仓库内所有包由同一工作区统一求解(见下文 update-packages 命令),因此每次约束升级都必须在全部 pubspec.yaml 文件中同步推进。

必须保留 -0 预发布后缀

写约束时不能省略 -0 后缀(即应写 ^3.11.0-0,而不是 ^3.11.0),原因来自 Dart 包解析(pub package resolution)的语义规则:

  • 标准的 caret 约束(如 ^3.11.0会排除该主/次版本的预发布版本,例如 3.11.0-dev3.11.0-5.0.pre 均不被其接受。
  • 而 Flutter 的 master 分支始终跟踪 Dart 的 active development 与 beta 分支,开发者本地与 CI 实际运行的都是预发布版 Dart SDK
  • 一旦省略 -0 后缀,pub get/pub upgrade 在预发布 SDK 环境下的包解析就会失败(找不到满足约束的 SDK 版本),仓库的日常构建与测试将立刻中断。

因此,-0 是 Flutter 仓库中 SDK 约束的强制约定,升级脚本做字符串替换时也必须保留这一后缀。


升级策略与节奏:谁负责、何时升、为什么卡在 stable

归属与节奏

约束升级通常由 team-framework(framework 团队)主导,节奏约为每季度一次,时机选在某个 Dart stable 版本发布之后不久。这样可以将格式化与 lint 迁移带来的大规模改动收敛到确定的频次窗口内,而非被不稳定 SDK 持续打断。

stable 版本约束策略

一个容易被误解的关键点是:即便 master/main 分支实际上通过 Engine stamp 下载并运行着更新的预发布 Dart SDKpubspec.yaml 中声明的 sdk 最小约束仍不得超过当前 Dart stable 发布版本。仓库严格强制执行这一上限策略,主要出于两点考虑:

  1. stable 分支的 cherry-pick Flutter 需要频繁把 main/master 上的 bug 修复 cherry-pick 到活动的 stable(或 beta)分支。如果 main 代码依赖了比 stable SDK 更新的语言特性或依赖版本,那么把相关提交直接搬到 stable 分支就会变得极其困难(甚至无法编译)。

  2. 格式化、lint 与大规模迁移 提高最小 Dart 版本通常会触发新的静态分析 lint、弃用警告,或产生 dart format 的格式化差异。这些变化有时需要全仓库范围的代码重构或格式化迁移(repo-wide refactoring / massive formatting migrations)。如果仓库始终跟着不稳定版本滚动式地吸收这类变化,维护成本极高且高度扰民。改为"每季度、面向一个明确的 stable 目标"统一升级,团队就能以可预期的方式消化格式化与 lint 扫描。

[!IMPORTANT] 动手前的前置条件:规划或执行约束升级之前,目标 Dart SDK 版本必须已经通过一次 Engine roll 合入 flutter/flutter 仓库。如果 pubspec 中的 SDK 约束被提升到高于本地缓存中实际下载的 SDK 版本,那么 CI、测试与静态分析会立即失败。换言之:先随 Engine 滚动到位,再改 pubspec 约束

新语言特性与 Style Guide 更新

一次 Dart 大版本升级往往带来新的语言特性。Flutter 仓库的约定是不鼓励在全仓库零散即兴地采用新特性,而是:

  1. 先更新 Style Guide:新特性应先在 Style guide for Flutter repo 中评估与成文,确定统一的使用模式,并决定哪些用法应当限制或鼓励。
  2. 组织化迁移:为保持风格一致、避免碎片化代码风格,采纳新特性的迁移通常作为协同工作统一推进——一般会开启一个 tracking issue(如仓库历史中 issue #172188 类型的追踪问题)来系统化地管理与评审整个迁移过程。

四步实操:如何提升 Dart SDK 约束

仓库包含 超过 100 个 pubspec.yaml(包、工具、手动/集成测试与示例均在内)。实测本仓库不含 .dart_toolpubspec.yaml142 个,因此约束升级必须系统化执行,不能逐个手改。以下四步来自原文档,并结合仓库源码补充了底层细节。

Step 1:更新全部 pubspec.yaml 的 SDK 约束

在原文档给出的示例中,使用 find + sed 做全局替换(从 ^3.10.0-0 升到 ^3.13.0-0):

# Example using find and sed to bump from ^3.10.0-0 to ^3.13.0-0
find . -name "pubspec.yaml" -not -path "*/.dart_tool/*" -exec sed -i '' 's/sdk: \^3.10.0-0/sdk: \^3.13.0-0/g' {} +

补充说明:

  • 正则中的 \^ 转义保证只替换 sdk: 行上的 caret 约束,-not -path "*/.dart_tool/*" 用于排除 pub 缓存产物,避免误改。
  • 替换后的版本号必须保留 -0 后缀(见上文约束语义),同时不应超过当前 Dart stable 版本
  • 若目标是未来版本,可把示例正则中的源与目标版本替换为实际值,例如本仓库当前为 ^3.11.0-0(见 pubspec.yaml)。

Step 2:用 update-packages 强制重解并刷新校验和

Flutter 在每个 pubspec.yaml 的底部强制写入一条依赖校验和(dependency checksum)。本仓库根 pubspec 末尾即为:

# PUBSPEC CHECKSUM: 4h4hgm

源码层面,校验和格式定义于 update_packages.dart

static const kDependencyChecksum = '# PUBSPEC CHECKSUM: ';
final checksumRegex = RegExp('$kDependencyChecksum([a-zA-Z0-9]+)');

工具在升级后会比对 pubspec.lock 实际求解结果与 pubspec 内嵌的校验和;若二者不一致,会抛出类似 "The hash (...) does not match the ..." 的错误并终止。因此改完约束后必须运行仓库维护专用的 update-packages 子命令对工作区重新求解:

flutter update-packages --force-upgrade --update-hashes

update_packages.dart 的命令定义可知:

  • --force-upgrade:尝试把所有依赖更新到各自最新兼容版本,并实际改写仓库中的 pubspec.yaml(该 flag 为 negatable: false,与 --offline 互斥,见 runCommand 校验)。
  • --update-hashes:重算并更新各 pubspec 底部的依赖校验和,是约束升级后刷新 # PUBSPEC CHECKSUM 的关键开关(该 flag 对普通开发者隐藏,仅 verboseHelp 时可见)。
  • 该命令的说明与别名(aliases = <String>['upgrade-packages'])均强调:它面向 CI 与仓库维护者("This is intended for CI and repo maintainers"),普通 Flutter 应用开发者不应依赖它管理自己项目。

执行该步骤会生成更新后的 pubspec.lock,并让所有依赖与新的 SDK 约束重新对齐。

Step 3:仓库级静态分析与测试验收

约束改动可能触发新 lint 或分析告警,需要跑仓库级分析脚本做全量验证。原文档给出的命令为:

dart --enable-asserts dev/bots/analyze.dart

对照 dev/bots/analyze.dart 的源码,--enable-asserts 是硬性要求——脚本在非 assert 模式下会直接记录错误 "The analyze.dart script must be run with --enable-asserts."。同时该脚本支持 --dart-sdk=/path/to/dart-sdk(或 --dart-sdk <path>)指定自定义 SDK,默认使用 bin/cache/dart-sdk,见 _getDartSdkFromArgumentsmain。其内部会执行一系列 validate 规则(可用 --only=rule1,rule2--skip=rule1,... 过滤),失败时打印 "Analysis failed." 并以错误码退出。

分析通过后,继续在目标平台运行测试套件,确认没有运行时回归,例如:

flutter test packages/flutter_tools
flutter test packages/flutter

Step 4:提交 PR 并关注 Google3 集成

将更新后的 pubspec.yamlpubspec.lock 以及校验和刷新一并提交,发起 Pull Request。

[!WARNING] 如果 PR 因 Dart SDK 版本不匹配破坏了 Google 内部(Google3)集成,该 PR 可能被回滚。提交前应与当前的 Flutter roll managers(滚动协调者)沟通对齐,确保 Engine/SDK 版本与仓库约束在各方保持一致。


何时不该做:约束升级的边界提醒

结合上文,以下几点是升级前必须核对的红线:

  • 目标版本未随 Engine roll 合入仓库前,不要动 pubspec 约束——本地缓存、CI、测试与静态分析会因 SDK 缺失/过低而失败。
  • 目标版本超过 Dart stable 时,不要动约束——违反 stable 上限策略,会阻断 stable 分支的 cherry-pick,并打乱格式化的季度化吸收节奏。
  • 替换时不要丢失 -0 后缀——否则预发布 SDK 环境下的包解析直接失败。
  • 不要手动逐个修改后跳过 update-packages——校验和不同步,analyze 及后续 CI 会拒绝合并。

延伸阅读

  • Rolling the Dart SDK:滚动 Engine 中实际 Dart SDK 二进制/编译器版本的独立流程,与本文的"版本约束"是互补关系。
  • Style guide for Flutter repo:升级引入新语言特性后,先在此定义标准用法再做组织化迁移。
  • update-packages 命令实现:校验和格式、flag 语义与强制约束的源码级细节。
  • 仓库级分析脚本:约束升级后全量静态分析的具体 validate 规则与 --dart-sdk 参数用法。
  • 根 pubspec.yaml:可查看当前仓库的 Dart SDK 约束(^3.11.0-0)与文件底部 # PUBSPEC CHECKSUM 的实际形态。
登录后查看全文
热门项目推荐
相关项目推荐