Flutter Engine What's New 生成器:一键产出两个版本间引擎变更摘要与 Diff 的技术实现解析
Flutter 单仓(monorepo)模式下,引擎源码位于 engine/src/flutter 下,每个季度版本之间会有上千个引擎提交。本文基于仓库内的 engine-whats-new Agent 技能,讲解如何对比两个 Flutter 发布版本在引擎目录下的全部变更,自动生成统一 diff 文件与按子系统分类的 "What's New" Markdown 摘要,并深入拆解其版本引用解析、提交归类规则与产物结构,读完你可以直接在本地复刻整个发布差异分析流程。
技能定位与触发边界
该技能定义于 SKILL.md,元信息声明了它的使用边界:
- 技能名称:
engine-whats-new - 做什么:为
//engine/src/flutter下两个发布版本(如 3.47 对比 3.44)之间的引擎变更,生成 "what's new" 发布摘要和 diff 文件; - 何时激活:仅当用户明确要求"生成引擎 what's new"、"对比引擎版本"或"产出引擎发布摘要"时;
- 何时禁用:对于普通的提交查询、单文件历史或代码库搜索,除非明确要求生成引擎发布 diff/摘要,否则不使用该技能。
这种"何时用/何时不用"的显式声明,是 Flutter Agent Skills 目录所要求的良好实践之一——该 README 要求新技能面向 Flutter 贡献者设计、提供使用示例、遵循 agentskills 开放规范,并可用 dart_skills_lint 工具校验(命令见 skills README 的 "Validating Skills" 一节)。
输入识别:目标版本与基线版本
按技能工作流,第一步是从用户请求中提取两个输入:
| 输入 | 变量名 | 是否必填 | 示例 |
|---|---|---|---|
| 目标版本 | <TARGET_RELEASE> |
必填 | 3.47、3.47.0、flutter-3.47-candidate.0 |
| 基线版本 | <BASE_RELEASE> |
可选 | 3.44;省略时脚本自动推算前一个季度版本 |
基线省略时的推算逻辑在 generate_engine_whats_new.dart 的 deducePreviousRelease 中实现:先剥离 flutter- 前缀和 -candidate.0 后缀,若主版本为 3 且次版本号可解析,则用 次版本号 − 3 得到上一季度版本(3.47 → 3.44,3.44 → 3.41)。其中有一个针对历史发布节奏的特殊修正:若结果次版本号为 28,则回退为 27。从源码结构看,这与 Flutter 历史上 3.28 前后发布节奏调整有关,属于对版本序列硬编码的补丁。
运行生成脚本
脚本为纯 Dart 实现,无第三方依赖(仅 dart:convert 与 dart:io),在仓库根目录执行:
基本用法(自动推算基线)
dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release 3.47
指定基线版本与自定义输出路径
dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart \
--release 3.47 --from 3.44 \
--output-diff engine_diff_3.44_to_3.47.diff \
--output-summary engine_whats_new_3.47.md
输出结构化 JSON 供程序消费
dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release 3.47 --format json
完整参数说明
参数解析逻辑见 main 函数,与 scripts/README.md 保持一致:
| 参数 | 别名 | 默认值 | 说明 |
|---|---|---|---|
--release |
--to、--target |
必填 | 目标 Flutter 发布版本,如 3.47 |
--from |
--base |
自动推算 | 基线版本;省略时按季度节奏推算(见上文 deducePreviousRelease) |
--engine-path |
— | engine/src/flutter |
相对仓库根目录的引擎目录 |
--output-diff |
— | engine_diff_<base>_to_<target>.diff |
diff 文件输出路径 |
--output-summary |
— | engine_whats_new_<target>.md |
Markdown 摘要输出路径 |
--format |
— | markdown |
标准输出格式:markdown / json / text |
-h / --help |
— | — | 打印帮助信息(无参数运行时打印帮助并以退出码 1 结束) |
另外脚本支持位置参数:第一个不以 - 开头的参数会被当作目标版本,等价于 --release。
版本引用的解析策略
发布版本字符串(如 3.47)并不总能直接对应一个 git 引用,脚本的 tryResolveGitRef 按以下顺序逐一尝试 git rev-parse --verify:
- 原样版本串,如
3.47; - 无点号的补全形式,如
7→3.7.0; - 主版本
3的两段式版本补.0,如3.47→3.47.0; - release-candidate 分支(远程与本地):
origin/flutter-3.47-candidate.0、flutter-3.47-candidate.0,其中两段式输入还会尝试origin/flutter-3.47-candidate.0的缩写形式; - 带
v前缀的 tag:v3.47、v3.47.0。
若以上引用均解析失败,则退化为 tag 模糊匹配:执行 git tag -l '*<version>*',优先返回精确等于 <version> 或 <version>.0 的 tag,否则取匹配列表的第一个。任一候选成功即返回该候选,全部失败则抛出 ArgumentError 终止分析。
以当前仓库实际验证:tag 3.47.0 可正常解析;而 flutter-3.47-candidate.0 在本地 clone 中不存在(git rev-parse 报 "Needed a single revision"),此时脚本会按候选列表继续尝试其他形式,体现了该回退链的必要性。
差异分析流程
analyzeEngineDiff 是核心入口,依次执行:
- 生成统一 diff:
git diff <baseRef>..<targetRef> -- engine/src/flutter,结果完整写入 diff 文件; - 统计短摘要:
git diff --shortstat获取变更文件数、新增行、删除行,再用正则(\d+)\s+files? changed等分别提取三个数值; - 枚举提交:
git log --pretty=format:%H%x09%h%x09%an%x09%ad%x09%s --date=short以制表符分隔输出每个提交的完整哈希、短哈希、作者、日期与标题,并从中用#(\d+)正则提取 PR 编号; - 逐条归类:按
categorizeCommit的关键词规则将提交分入九大桶; - 落盘摘要:由 generateMarkdownSummary 渲染 Markdown 并写入摘要文件。
在本地仓库实测 git diff --shortstat 3.44.9..3.47.0 -- engine/src/flutter 得到 1105 files changed, 47353 insertions(+), 18630 deletions(-),与脚本输出量级一致(技能文档示例中 3.44→3.47 报告约 354 commits、1179 files changed,数值差异源于基线 tag 的具体落点)。
提交分类体系
categorizeCommit 按"先匹配先得"的顺序用标题关键词判定分类,顺序本身即优先级(例如含 "Roll Skia" 的提交不会落入渲染类):
| 优先级 | 分类 | 命中关键词(小写匹配,节选) |
|---|---|---|
| 1 | 🔄 Dependency Rolls | Roll Skia、Roll Dart SDK、Roll ICU、Roll HarfBuzz、Roll ANGLE 前缀及小写变体 |
| 2 | 🚀 Impeller & Graphics Rendering | impeller、ubersdf、vulkan、metal、opengl、shader、display_list、render、flow 等 |
| 3 | 🌐 Web Engine & Wasm | [web]、web_ui、web_sdk、wasm、skwasm、canvaskit、html |
| 4 | 📱 Android Embedding | [android]、android、agp、gradle、embedding/engine |
| 5 | 🍎 iOS & macOS Embeddings | [ios]、[macos]、[darwin]、xcode、metalview 等 |
| 6 | 🪟 Windows & Linux Desktop Embeddings | [windows]、[linux]、win32、embedder 等 |
| 7 | 🔤 Text, Typography & Accessibility | [a11y]、semantics、accessibility、typography、txt、font、text input、autofill |
| 8 | 🛠️ Build System, CI & Tooling | [ci]、ci:、build.gn、tools、testing、header_guard、license、format |
| 9(兜底) | ⚙️ Core Runtime & Shell | 以上均未命中 |
这套关键词体系与引擎目录的实际结构相互印证:engine/src/flutter 顶层即包含 impeller(Impeller 渲染系统,下设 renderer、typographer、shader_bundle 等子目录)、shell(运行核心)、txt(文本布局)、flow、display_list、skwasm、wasm、web_sdk、vulkan、testing、tools 等目录,分类规则基本覆盖了引擎的主要子系统。
产物结构
工具在仓库根目录产出两份主要文件:
- Diff 文件(
engine_diff_<BASE>_to_<TARGET>.diff,路径中/会被替换为_):两个版本间//engine/src/flutter的完整 unified git diff; - 摘要文档(
engine_whats_new_<TARGET>.md):结构化 Markdown,包含- Overview & Statistics:diff 文件链接、引擎提交总数、变更文件数、新增行(
+N)、删除行(-N); - 🌟 Subsystem Breakdown:按上述九大分类逐节列出提交,每行格式为
提交标题 (PR 链接 或 短哈希 by *作者*),其中标题含#数字的提交会生成指向 Flutter 仓库对应 PR 的链接,空分类会被跳过。
- Overview & Statistics:diff 文件链接、引擎提交总数、变更文件数、新增行(
--format json 模式下,标准输出为 ReleaseAnalysis.toJson 的编码结果,字段包括 baseRelease/baseRef/targetRelease/targetRef/enginePath/diffPath/summaryPath、stats(totalCommits、filesChanged、insertions、deletions)以及 categories(分类名到 CommitInfo 数组的映射,每条含 hash、shortHash、author、date、title、prNumber),可直接被脚本或 Agent 二次消费。
文档中的两个典型用例
技能文档给出了完整的交互范式:
- 用例一:用户说 "Generate what's new in the engine for Flutter 3.47"。Agent 识别目标版本
3.47,执行dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release 3.47,回报摘要统计(如 354 commits、1179 files changed),并给出生成的engine_diff_3.44_to_3.47.diff与engine_whats_new_3.47.md两个产物。 - 用例二:用户说 "Diff the engine changes between Flutter 3.41 and 3.44"。Agent 识别基线
3.41、目标3.44,执行带--from 3.41 --to 3.44的命令,呈现引擎变更分解与 diff 文件。
使用前提与限制
- 需要在包含完整 tag 历史的 Flutter 仓库根目录(或其子目录)运行——findRepoRoot 会从当前目录向上查找
.git定位仓库根,找不到则退回当前目录; - 基线与目标版本必须都能被
tryResolveGitRef解析,否则抛出ArgumentError("Could not resolve git reference for ..."); - 分类基于提交标题关键词,属于启发式规则,标题未含相关关键词的跨子系统提交可能落入兜底类 "⚙️ Core Runtime & Shell";
- 前向推算基线仅对
3.x序列有效,推算结果为空时必须显式指定--from,脚本会报错退出。
小结
engine-whats-new 技能以一份无依赖的 Dart 脚本 generate_engine_whats_new.dart 串联了"版本引用解析 → git diff/log → 关键词归类 → Markdown/JSON 产物"的完整链路,把跨季度引擎变更审阅从手工翻日志简化为一条命令。配合 scripts/README.md 的参数说明,你可以将其用于发布前的引擎变更盘点,或作为 Agent 自动化发布摘要的标准工作流。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00