Flutter Engine 开发工具 et(Engine Tool)完全指南:统一构建与本地调试工作流
导读
本文基于 engine/src/flutter/tools/engine_tool/README.md 展开,系统讲解 Flutter 引擎仓库中命令行工具 et(engine tool) 的设计初衷与全部用法。et 面向在 engine 源码中"改一行、编一次、跑一个测试"的高频迭代场景,将此前分散的 GN/Ninja 构建、测试、格式化、lint、本地运行等操作收敛为统一命令接口。读完本文,你将掌握:如何配置环境并理解"构建配置(build configuration)"这一核心抽象、如何构建 host/target 引擎与指定 GN 目标、如何运行 C++ 测试、如何用本地引擎产物直接运行 Flutter App,以及 RBE 远程构建、自定义引擎配置、输出目录清理等高级能力。
一、et 是什么
et(engine tool)是一个用 Dart 编写的命令行工具,其目标是为"构建并开发 Flutter engine"提供统一接口。它解决的核心痛点在于:engine 构建依赖大量 GN 参数、平台工具链与 CI 配置,手工记忆并拼装这些命令既易错又难以复用,而 et 将这些复杂性封装为语义化的子命令(build、test、format、lint、run 等)。
从入口源码看,et 的执行链非常清晰:
- 仓库内启动脚本 engine/src/flutter/bin/et(以及 Windows 版
et.bat)根据uname探测当前平台(linux/macos×x64/arm64),定位引擎自带的 Dart SDK(${ENGINE_DIR}/prebuilts/.../dart-sdk),再调用 Dart 入口 engine/src/flutter/tools/engine_tool/bin/et.dart。若tools/engine_tool/.dart_tool不存在,脚本会直接提示 "You must run 'gclient sync -D' before using this tool."——即工具只能在完成依赖同步的合法仓库中运行。 - Dart 入口 lib/main.dart 会调用
Engine.findWithin(...)向上查找 engine 仓库根目录,随后加载ci/builders目录下的全部构建配置;若未找到任何构建配置或配置解析出错,直接报错退出。 - 最后由 lib/src/commands/command_runner.dart 中的
ToolCommandRunner注册并派发所有子命令。
适用前提
- 具备 Flutter framework 与 engine 的基础知识(如架构分层);
- 已获得一份合法且同步完毕的 engine 源码检出,并满足 engine 开发环境搭建文档 的要求(该文档对应 README 中引用的 external 链接,仓库内同步维护于 docs/engine/contributing 目录);
- 运行平台受支持(从启动脚本可看出仅支持 Linux/macOS 的
x64与arm64)。
将 et 加入 PATH
README 建议将引擎自带的 bin 目录加入 PATH:
PATH=$PATH:/path/to/engine/flutter/bin
这里的 /path/to/engine/flutter/bin 即当前仓库中的 engine/src/flutter/bin 目录(内含 et 与 et.bat 两个启动脚本)。
验证安装:et help
$ et help
A command line tool for working on the Flutter Engine.
This is a community supported project, file a bug or feature request:
https://flutter.dev/to/engine-tool-bug.
Usage: et <command> [arguments]
Global options:
-h, --help Print this usage information.
-v, --verbose Prints verbose output
Available commands:
build Builds the engine
fetch Download the Flutter engine's dependencies
format Formats files using standard formatters and styles.
lint Lint the engine repository.
query Provides information about build configurations and tests.
run Run a Flutter app with a local engine build.
test Runs a test target
Run "et help <command>" for more information about a command.
从帮助信息可以看到全部 7 个公开子命令;此外从 command_runner.dart 的注册代码可知,内部还实现了 CleanupCommand 与 StampCommand。全局选项仅有两个:-h/--help 与 -v/--verbose(其中 -v 在 main.dart 中会把日志级别切到 Logger.infoLevel,输出更详细的过程信息)。
二、核心抽象:构建配置(build configuration)
et 的许多命令都围绕一个 构建配置 展开,通常用 --config(简写 -c)显式指定。一个构建配置至少包含三要素:
| 要素 | 字段 | 含义 |
|---|---|---|
| 可运行平台 | drone_dimensions |
该构建可运行在哪些(CI)机器维度上 |
| 编译期标志 | gn |
传给 GN 生成工具的参数,决定构建行为 |
| 名称与说明 | name、description |
人类可读的配置名与描述 |
以 engine/src/flutter/ci/builders/local_engine.json 中的一个真实条目为例,可以看到一个完整配置包含 drone_dimensions、gn(如 --ios、--runtime-mode debug、--no-stripped、--no-lto、--rbe 等)、ninja.config(对应输出目录名)与 description 等字段。
配置的存放位置与命名解析
构建配置通常定义在 engine/src/flutter/ci/builders 下,分两类:
- CI 任务专用配置:为某个 CI 任务而建(构建并跑测试),例如 mac_unopt.json、linux_unopt.json、linux_host_engine.json 等;
- 仅用于本地开发与迭代的配置:集中在 local_engine.json,例如上例中的
macos/ios_debug。
--config 对配置名的解析规则与"是否带 ci/ 前缀"有关。仍以本地 debug 为例:
# 带前缀:隐式引用 ci/builders/<对应任务文件>.json
et build --config ci/host_debug_unopt_arm64
# 不带前缀:隐式引用 ci/builders/local_engine.json
et build --config host_debug_unopt_arm64
也就是说,et build --config host_debug_unopt_arm64 会自动去 local_engine.json 中查找名为 host_debug_unopt_arm64(该文件中通常写作 <os>/host_debug_unopt_arm64 形式,引用时省略平台前缀)的配置;而 ci/host_debug_unopt_arm64 则指向 CI 任务文件。
磁盘占用警告
[!CAUTION] 每个构建配置(也称一个 variant)会在
$ENGINE/src/out下生成不同的输出目录,例如$ENGINE/src/out/host_debug。这些输出动辄数 GB,且会快速累积。建议用et cleanup(见下文"回收旧的输出目录")自动删除过期的输出目录。
name/description 还会进入 et query 等命令的索引,为开发者提供构建配置与测试的查询能力。
三、常见任务(Common Tasks)
这一部分面向绝大多数 engine 贡献者与使用者,是最常用的操作。
3.1 构建 host 引擎
host 引擎指运行在当前桌面操作系统上的引擎变体。例如在 ARM64 macOS 笔记本上构建 ARM64 macOS 桌面引擎,或在 x64 Linux 桌面上构建 x64 Linux 桌面引擎。
# 构建当前平台(host)的 debug 构建
et build
# 与上面等价(显式指定配置)
et build --config host_debug
host 引擎适用于以下场景:
- 想脱离具体设备/平台,独立测试、调试或迭代某个功能;
- 正在开发当前桌面平台特有的功能;
- 想组合 host 引擎与 target 引擎来 运行 Flutter App。
3.2 构建 target 引擎
target 引擎指并非原生运行于当前桌面类操作系统、而是面向其它目标平台的引擎,例如 Android 或 iOS。例如在 macOS 上,可以构建 iOS 模拟器或真机引擎:
# 构建 iOS 真机(非模拟器)引擎
et build --config ios_debug
# 构建 iOS 模拟器引擎
et build --config ios_debug_sim
命名约定:target 引擎的配置名不带 host 前缀(而 host 引擎带 host_,如 host_debug)。target 引擎适用于:
- 正在开发特定(目标)平台的功能;
- 想组合 host 引擎与 target 引擎来 运行 Flutter App。
这与 local_engine.json 中的条目相印证——例如 macos/ios_debug 的 description 即 "Builds a debug mode engine that targets iOS from a macOS host.",其 gn 参数包含 --ios --runtime-mode debug。
3.3 构建指定 GN 目标
默认情况下 et build 构建整个引擎。例如下面两条命令等价:
et build --config host_debug
et build --config host_debug //flutter/...
尽管缓存通常能避免重建未变化的部分,但有时你比依赖树更清楚两次改动之间到底需要重编什么。此时可通过 et build 后追加目标参数,用 GN target 的全限定路径指定要构建的精确目标:
# 只构建 "flutter.jar" 这一个产物
et build --config android_debug_unopt_arm64 //flutter/shell/platform/android:android_jar
此外还支持两种"批量化"写法:
# 递归构建 //flutter/shell/platform 目录下的所有目标
et build --config android_debug_unopt_arm64 //flutter/shell/platform/...
# 非递归构建该目录下的全部目标(仅此一层)
et build --config android_debug_unopt_arm64 //flutter/shell/platform:all
语法要点归纳:
| 目标写法 | 含义 |
|---|---|
//path/to:target |
精确到单个 GN 目标 |
//path/to/dir/... |
递归构建该目录下所有目标 |
//path/to/dir:all |
非递归地构建该目录内全部目标(不含子目录) |
3.4 运行 C++ 测试
用 et test 可以同时重建并运行 C++ 单元测试:
et test //flutter/impeller:impeller_unittests
同样支持 /... 与 :all 两种批量写法。et test 的定位在源码中有对应实现(test_command.dart),其测试覆盖可见 test/commands/test_command_test.dart。
[!NOTE] 对非 C++ 测试的支持有限,参见下文 运行 Dart 测试。
3.5 运行格式化工具(formatters)
对改动过的文件运行全部 formatter:
et format
但有时某个依赖变更(或工具自身变更)会使你并未改动过的文件(处于 dirty 状态)无法通过格式检查,此时需要检查全部文件:
# 检查 *所有* 文件,这会 *慢得多*!
et format --all
对应的命令实现位于 format_command.dart,测试位于 test/commands/format_command_test.dart。
3.6 运行 linter
与 formatter 类似,可用 et lint 运行仓库级 linter:
et lint
截至本文所依据的文档版本,linter 总是对全仓库运行。实现见 lint_command.dart 与其测试 test/commands/lint_command_test.dart。
3.7 使用本地引擎构建运行 Flutter App
通常情况下,用预构建引擎运行 Flutter 应用的方式是:
cd to/project/dir
flutter run
而当你在迭代 engine 源码时,更希望用上面刚构建出的 engine 产物(host 与 target 都有)来运行应用,此时用 et run:
cd to/project/dir
et run
[!NOTE]
et run会按需重建 host 与 target 构建,耗时可能相当可观。
实现层面,et run 通过 flutter_tool 互操作层与本地 Flutter 工具协作完成设备探测与启动(可参考 lib/src/flutter_tool_interop/ 下的 flutter_tool.dart、device.dart、target_platform.dart),对应测试为 test/commands/run_command_test.dart。
四、高级特性(Advanced Features)
以下功能可能仅面向部分 engine 团队或上游用户(例如 Dart VM/SDK 团队的开发者),其完善程度与易用性相对常见任务可能略逊。
4.1 启用远程构建执行(RBE)
Google 员工可选择 RBE(remote build execution) 来大幅加速构建:一方面复用此前构建并被缓存的产物,另一方面把编译任务委托给高性能远程虚拟机。
启用方式:参考外部文档(README 所指向的 flutter.dev/to/engine-rbe)。启用后,默认情况下 et 构建会尽量使用 RBE,这同时也意味着构建隐式依赖活跃的网络连接。
可通过 --build-strategy 在单次命令内临时切换"偏好远程"还是"纯本地":
# 纯本地构建;某些增量构建反而更快;无需联网
et build --build-strategy=local
# 纯远程构建;对本机负载更小;需要高速网络
et build --build-strategy=remote
如果希望彻底关闭 RBE(已启用的情况下),可使用 --no-rbe:
et build --no-rbe
[!CAUTION] 关闭 RBE 会使构建上下文失效——即此前在启用 RBE 时构建出的产物不会被复用。除非你在调试工具本身或 RBE 配置,否则更推荐用
--build-strategy=local代替--no-rbe。
4.2 运行 Dart 测试
et 对运行 Dart 单元测试提供有限支持:
et test //flutter/tools/engine_tool/...
[!NOTE] 与 C++ 不同,目前 Dart 测试不要求声明
BUILD.gntarget,且绝大多数 Dart 包也没有声明;随着 GN 在 Dart 侧被更广泛采用,该命令会越来越通用。
这一点在本仓库中也能得到印证:整个 tools/engine_tool 的 Dart 测试(test/ 目录下二十余个 *_test.dart)并不需要逐一登记进 GN 即可通过 et test 运行。
4.3 使用自定义引擎配置
绝大多数情况下应使用预置配置(它们经过 CI 验证),但当你需要构建一个尚未预置的配置时,README 给出了两步决策清单:
-
我的配置是否代表一组应当在 CI 上测试、或值得他人复用的标志组合? 如果是,最佳做法是把该构建加入 engine/src/flutter/ci/builders——既可作为 CI 构建,也可仅作为 local_engine.json 中的本地引擎构建。这样该构建对其它开发者可复现、有文档,并能被
et命令行自动识别。 -
我的配置仅用于一次性测试或验证? 如果是,可以通过
--gn-args追加任意 GN 参数(即本来会由 tools/gn 解析的那些参数),通常配合某个现成配置模板一起使用。
例如启用链接期优化(LTO):
et build --config host_release --lto
例如使用从源码构建的 Dart SDK(常被 Dart SDK/VM 开发者使用):
et build --config host_debug --gn-args="--no-prebuilt-dart-sdk"
[!TIP] 关于构建配置的更多信息,参见 engine/src/flutter/ci/builders/README.md。
4.4 回收旧的输出目录
et cleanup 会删除长期未被访问的输出目录,默认阈值为最近 30 天,可用 --untouched-since 自定义。强烈建议先用 dry-run 预览将要删除的内容:
# 删除所有超过 30 天未访问的输出目录
et cleanup
# 预览:显示上面命令会删除哪些目录(实际不删)
et cleanup --dry-run
# 删除所有最后访问时间早于 2024-01-01 的输出目录
et cleanup --untouched-since=2024-01-01
从命名看,--dry-run 是安全预览开关,--untouched-since 接受一个日期值(示例中 2024-01-01 表示删除 2023 年及以前访问过的目录)。该命令的实现与测试位于 cleanup_command.dart 和 test/commands/cleanup_command_test.dart,配合前面"输出目录可达数 GB"的警告,这是每个长期本地构建者都应养成的磁盘卫生习惯。
五、工程架构与测试约定(Contributing 指南精华)
et 欢迎社区贡献,README 中的开发约定(连同 contributing 相关规范)值得任何想为 engine_tool 提交代码的开发者遵守:
- 遵循 Flutter 风格指南 中适用于 framework 仓库之外 Dart 代码的部分;其中包含超出纯代码格式的约定,未来即使改用
dart format也会继续遵守。 - 除
main.dart外,禁止直接调用dart:io,只能通过Environment对象访问系统。这一点与源码结构严格对应:唯一直接import 'dart:io'的是 lib/main.dart(它构造 environment.dart 中的Environment),其余代码均经由该对象与宿主系统交互。 - 所有命令都必须有单元测试;若某些功能需要 fake 实现,就写 fake 实现。这正是 test/commands/ 下每个子命令都对应一个
*_test.dart的原因。 - 新增或修改功能时,同步更新本 README。
- Begin with the end in mind:从"该工具应提供的接口"出发设计,再反过来改造底层脚本与工具以提供支撑 API。
运行测试(用 et 自举):
et test //flutter/tools/engine_tool/...
如果不知道从何下手,可以关注仓库中标有 e: engine-tool label 的 issue。
六、总结:一张 et 速查表
| 意图 | 命令 |
|---|---|
| 查看帮助与所有子命令 | et help / et help <command> |
| 构建当前平台 host debug 引擎 | et build / et build --config host_debug |
| 构建 target(如 iOS)引擎 | et build --config ios_debug / et build --config ios_debug_sim |
| 只构建某个 GN 目标 | et build --config <cfg> //path/to:target |
| 递归/非递归构建目录目标 | et build --config <cfg> //dir/... 或 //dir:all |
| 重建并运行 C++ 测试 | et test //flutter/impeller:impeller_unittests |
| 格式化改动文件 / 全部文件 | et format / et format --all |
| 运行全仓库 linter | et lint |
| 用本地引擎产物运行 App | et run(在 App 工程目录下) |
| 纯本地 / 纯远程构建(单次) | et build --build-strategy=local / --build-strategy=remote |
| 关闭 RBE(慎用) | et build --no-rbe |
| 追加 GN 参数的一次性构建 | et build --config <cfg> --gn-args="--no-prebuilt-dart-sdk" |
| 删除 30 天以上未访问输出目录 | et cleanup(可用 --dry-run 预览、--untouched-since=<date> 自定义阈值) |
| 运行 engine_tool 自身的 Dart 测试 | et test //flutter/tools/engine_tool/... |
et 的价值在于把"查找构建配置、拼装 GN 参数、驱动 ninja、管理测试与产物"这套引擎开发高频动作,收敛为一份配置(ci/builders 下的 JSON)+ 一组语义命令的稳定接口。无论你是偶尔编译一次 host debug 引擎、还是长期迭代 Impeller 或 Android shell 的 engine 开发者,掌握 et 都能显著缩短"改动 → 验证"的反馈回路;而其"配置即代码、全部命令有测试、系统访问一律走 Environment"的工程约束,也为后续维护者提供了一套清晰可循的质量基线。继续深挖实现细节,可从 engine/src/flutter/tools/engine_tool/lib/src/commands/ 的各个子命令源码与 engine/src/flutter/ci/builders 的配置样例入手。
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 StartedRust0625
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