关于 codegen-and-toolchain
codegen-and-toolchain 是面向使用 build_runner 或 gen-l10n 的 Flutter/Dart 项目的开发技能。它规定代码生成、格式化、分析与测试的可复现顺序,并说明生成范围、文件策略和工作区配置。
主要功能
- 01确定性代码生成流程
- 02使用 generate_for 限定生成范围
- 03生成文件提交与 CI 策略
- 04分析器与覆盖率排除项
- 05SDK 固定与工作区指南
适用场景
- 01注解变更后重新生成代码
- 02按包范围配置 build.yaml
- 03选择生成文件的提交策略
- 04设置多包工作区
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zakariaf/ flutter-skills --skill codegen-and-toolchain安装后在 Claude Code 中输入/codegen-and-toolchain确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/build.yamlgitattributes.examplereferences/codegen-scoping.mdcommit-vs-gitignore.mdtoolchain-and-workspace.mdscripts/check-codegen-hygiene.shregen.sh详细介绍
代码生成与工具链
一次确定性的代码生成流程是所有生成文件的唯一事实来源;
先运行它,严格限定范围,并一次性决定其输出是否纳入 Git。
适用于使用 build_runner 或 gen-l10n 的项目。
阅读与当前任务相关的参考文档:
references/codegen-scoping.md— 生成器清单、generate_for:范围限定表,以及错误到修复的恢复映射。references/commit-vs-gitignore.md— 两种提交/加入 Git 忽略规则的策略、 各自的权衡,以及每种策略所需的确切 CI 门禁。references/toolchain-and-workspace.md— SDK 固定、分析器/覆盖率 排除规则,以及多包工作区分层(有边界、可选启用)。
提交 PR 前运行 scripts/regen.sh(代码生成 → 格式化 → 分析),并运行
scripts/check-codegen-hygiene.sh,以验证排除规则与提交策略的一致性。
不可妥协的规则
- 代码生成必须先于
flutter analyze运行。 全新克隆后,生成文件可能 不存在;编辑注解后,生成文件也可能已过时。分析这样的代码树会产生误导性的 “缺少 part 文件”和“未定义类_$Foo”错误。先重新生成,再分析——绝不要在调用处追查症状。 - 始终传入
--delete-conflicting-outputs。 不传此参数时,build_runner会拒绝覆盖已重命名/删除源文件所对应的输出,并报出一大串“输出冲突”信息后失败。 这不是需要调查的 bug。 - 绝不要手动编辑生成文件。 下一次构建会悄无声息地还原文件,并一并抹掉你的修改。 修改带注解的源文件,然后重新运行代码生成。
- 在每个包的
build.yaml中,用generate_for:限定每个构建器的范围。 一个目录中的更改不得触发整个代码树重新生成。每个 glob 都应指向拥有该注解的最窄目录。 - 一次性决定提交还是加入 Git 忽略规则,并配套相应的 CI 门禁。
加入 Git 忽略规则 → CI 先重新生成,再进行分析。提交 → CI 重新生成后,
运行
git diff --exit-code作为新鲜度门禁。两种方式都有效;没有对应门禁的策略无效(参见references/commit-vs-gitignore.md)。 - 将生成文件的 glob 同步添加到分析器和覆盖率排除规则中。 对机器输出运行 lint 会浪费审查精力;lcov 中缺少 glob 则会虚增覆盖率。排除实际 生成的后缀——应核实,而非猜测。
- 固定 SDK;本地版本必须与 CI 一致。 CI 使用的 Flutter/Dart 版本必须与本地固定的版本一致
(使用 FVM 时为
.fvmrc,或使用仓库规定的工具)。版本偏差会导致“在我的机器上能运行”的格式化/代码生成反复变动。 watch仅用于本地编辑循环。 CI 和分析前的步骤始终使用一次性build命令。
标准命令
从仓库根目录运行:
dart run build_runner build --delete-conflicting-outputs
此命令会在一次流程中重新生成所有构建器的输出——drift_dev、freezed、json_serializable、
riverpod_generator、mockito。gen-l10n 通过 Flutter 工具运行,但处理方式相同(先重新生成,采用相同的提交策略):
flutter gen-l10n # if not auto-triggered by `flutter pub get` via l10n.yaml
全新克隆/拉取之后/编辑任何注解之后,固定顺序如下:
build_runner build --delete-conflicting-outputs → format → analyze → test
绝不要把代码生成调整到分析之后。在活跃编辑期间,只有此时才使用 监视器在保存时重新构建受影响的输出:
dart run build_runner watch --delete-conflicting-outputs
使用 build.yaml 限定构建器范围
为每个包含代码生成的包(或单个应用包)分别提供一个
build.yaml,并通过 generate_for: 将每个构建器限定在拥有其注解的最窄目录
中。未限定范围的根配置会扩展到整个代码树中的每个注解,因此编辑一个 Task
模型会重新生成无关的 Order 和 Account 输出。复制 examples/build.yaml;
references/codegen-scoping.md 提供完整的生成器到后缀对应表和各构建器的 glob。
Commit 与 gitignore:二选一,并配置相应的门禁
两种策略都合理;它们面临的风险不同(参见规则 5)。gitignore 可以保持目录树整洁,但需要可用的工具链才能构建。提交生成文件即使未来工具链损坏也能继续使用,还能让 Schema 差异可供审查,但需要配置 .gitattributes 差异折叠(examples/gitattributes.example),并设置强制的新鲜度门禁——重新生成,然后运行
git diff --exit-code -- '*.g.dart' '*.freezed.dart' '*.drift.dart'。没有门禁的策略不算策略。两种策略、各自的权衡以及所需的具体门禁见:references/commit-vs-gitignore.md。
Analyzer 和覆盖率排除项
无论提交还是 gitignore,每一种实际生成的后缀都必须同时从 analyzer 和 lcov 覆盖率过滤器中排除——先确认配置实际生成的后缀,不要猜。遗漏 analyzer glob 会让审查浪费在机器生成的输出上;遗漏(或锚定位置错误的)lcov glob 会把未经测试的代码计入覆盖率。保存了生成时代兼容数据类的迁移测试基准目录也需要加入 analyzer 排除项——如果自动修复改动了已导出的 Schema 快照,就会破坏迁移验证(参见 run-migration)。规范的排除代码块和正确锚定的 lcov 模式见 references/toolchain-and-workspace.md。
多包场景(工作区)
仅适用于确实已不止一个包可满足需求的仓库。单应用项目不需要这些设置。
- 使用 Dart 原生 pub 工作区关联各成员:在每个成员的 pubspec 中设置
resolution: workspace,并在根目录中添加workspace:列表,从而生成一个根pubspec.lock。切勿添加每个包各自的锁文件。 - 如果叠加使用 Melos,只将它用于脚本编排(
melos run gen/format/analyze/test),绝不用它进行路径关联——关联工作由 pub 完成。将gen脚本限制为依赖build_runner的包。 - 在工作区根目录运行一次
build_runner,让每个成员在同一轮中重新生成;在某个包目录内运行会导致其他包过期。
反模式
- 重新生成前先运行分析——所有后续错误都是假象;应修正顺序,而不是代码。
- 省略
--delete-conflicting-outputs——会让常规重命名变成构建障碍。 - 手动编辑
.g.dart/.drift.dart来消除错误——下次构建时会被还原;真正的修复应在带注解的源文件中进行。 - 提交生成代码却没有新鲜度门禁——输出会悄悄偏离源文件,审查者却会相信谎言。
- 使用一个不加范围限制的根级
build.yaml——每次微小编辑都会重新生成整个目录树,拖慢每次构建。 - 覆盖率或 analyzer glob 匹配了错误的后缀——在 analyzer 中静默失效,在 lcov 2.x 中明确报错,并悄悄抬高覆盖率。
- CI Flutter 版本 ≠ 本地固定版本——导致无人能解释、反复出现的格式或代码生成变更。
- 生成的迁移测试留有空数据列表——会无条件通过(
[] == []),却无法证明任何事情;应填入数据或删除(参见run-migration)。
完成定义
- 在干净的目录树中运行
dart run build_runner build --delete-conflicting-outputs成功,然后flutter analyze无错误。 - 每个承载代码生成的包都有包含限定范围
generate_for:glob 的build.yaml。 - 已决定使用 commit 还是 gitignore,并配置了相应的 CI 门禁(新鲜度差异检查,或先运行代码生成)。
- 实际生成的后缀已同时从
analysis_options.yaml和 lcov 覆盖率过滤器中排除。 - SDK 已固定,且 CI 工具链版本与之匹配。
- 没有手动编辑生成文件;CI 中没有
watch。
相关技能
- 查看
run-codegen,了解操作步骤简单、自由度低的“运行此确切命令”循环及其故障排查表。 - 查看
run-migration,了解 Drift 代码生成流程所产出的架构快照与迁移测试惯例。 - 查看
persistence-drift,了解这些生成器所服务的 Drift/DAO 数据层。 - 查看
lint-and-style-config,了解此处镜像的分析器规则集与排除项。 - 查看
ci-pipeline-and-gates,了解代码生成和新鲜度检查门槛在流水线中的位置。 - 查看
dependency-hygiene,了解锁文件与 SDK 固定机制。
参考资料
- build_runner:https://dart.dev/tools/build_runner
- Drift 代码生成与构建配置:https://drift.simonbinder.eu/docs/advanced-features/builder_options/
- freezed:https://pub.dev/packages/freezed
- json_serializable:https://pub.dev/packages/json_serializable
- riverpod 代码生成:https://riverpod.dev/docs/concepts/about_code_generation
- Flutter gen-l10n:https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization
- Dart pub 工作区:https://dart.dev/tools/pub/workspaces