Setting up the Framework/Engine development environment
背景
关于参与 Flutter 项目的共建,从 framework 到 engine,本文提供了环境搭建到调试的主要流程说明,可以快速帮助你搭建起来。
由于侧重讲述 Flutter web 平台的建设,可以通过本文提供的官方文档链接获得其它平台更多指引。
搭建 framework 开发环境
参考官方文档:Setting up the Framework development environment。
需要具备这些条件搭建 framework 开发环境:
-
Linux,macOS 或 Windows。
-
git(代码版本管理)。
-
IDE:Android Studio、vscode 等主流IDE可安装插件支持 flutter/Dart 代码高亮。
-
Python( 一些工具需要使用)。
-
ssh 客户端(GitHub 身份验证)。
请先 参考 Connecting to GitHub with SSH,配置 Github 认证需要的 SSH Key。
- 检查是否已经有 SSH 密钥:Reviewing your SSH keys
- 生成新的 SSH 密钥对(id_rsa,id_rsa.pub):Generating a new SSH key and adding it to the ssh-agent
- 将 SSH 公钥(id_rsa.pub)上传到 github:Adding a new SSH key to your GitHub account - Settings - SSH and GPG keys
- git clone 使用 SSH 地址替代 HTTPS 链接:Switching remote URLs from HTTPS to SSH
按照以下步骤搭建 Framework 开发环境:
-
Fork https://github.com/flutter/flutter 到自己的 GitHub 账号。如果已经 frok 过了,可先更新 Sync Fork。
-
git clone git@github.com:<github_username>/flutter.git
(替换 <github_username> 为你的 GitHub 账户名)。如果是参与外部开源项目,可考虑执行 git config 命令配置个人开发者用户名和邮箱:- git config user.name <github_username>
- git config user.email <github_useremail>
-
cd flutter
-
git remote add upstream git@github.com:flutter/flutter.git
:添加跟踪官方上游源仓(fetch-merge同步)。 -
配置仓库的 flutter/bin 目录到环境变量,这样后续将使用本地编译出来的 SDK/Framework 工具链。
$ export PATH=/path/to/flutter/bin:$PATH
- 执行
vim ~/.zhsrc
编辑 zsh 配置文件,将flutter仓库编译产物目录bin前插到环境变量 PATH(override fvm):- 为了后续脚本引用方便,可以将一些常用目录定义成环境变量。
# ~/.zhsrc
# fvm home
export FVM_HOME="$HOME/.fvm"
export PATH=$HOME/.fvm/default/bin:$PATH
# flutter project
export FLUTTER_PROJECT_DIR=$HOME/Projects/github/flutter
# framework(sdk)
export FLUTTER_SDK_PROJECT_DIR=$FLUTTER_PROJECT_DIR/flutter
# override fvm
export PATH=$FLUTTER_SDK_PROJECT_DIR/bin:$PATH
## packages/flutter
export FLUTTER_SDK_PACKAGES=$FLUTTER_SDK_PROJECT_DIR/packages
export FLUTTER_SDK_SRCROOT=$FLUTTER_SDK_PACKAGES/flutter
flutter update-packages
(拉取 Flutter 依赖的 Dart 包)。- 执行
which flutter
和flutter --version
验证,flutter 工具链版本是否符合预期。
修改调试 framework 源码
用 IDE 打开 packages/flutter
目录(FLUTTER_SDK_SRCROOT)可以修改 framework 源码。
用 IDE 打开示例项目,确保已经安装了 Flutter/Dart 相关语言支持插件。
指定 Flutter SDK 目录(即上面第 3 步 clone 到本地的 SDK/Framework 目录),具体步骤如下:
- vscode 打开偏好设置(JSON),配置 “dart.flutterSdkPath”,指定 Flutter SDK 目录:
The location of the Flutter SDK to use. If blank, Dart Code will attempt to find it from the project directory,
FLUTTER_ROOT
environment variable and the PATH environment variable.
"dart.flutterSdkPath": "/Users/fantasy/Projects/github/flutter/flutter", // FLUTTER_SDK_PROJECT_DIR
// "dart.flutterSdkPaths": [
// "/Users/fantasy/.fvm/versions"
// ],
- Android Studio 打开偏好设置,Languages & Frameworks | Flutter | Flutter SDK path: 指定 Flutter SDK 目录为 /Users/fantasy/Projects/github/flutter/flutter(FLUTTER_SDK_PROJECT_DIR)。
接下来,看看如何 修改和调试 SDK/Framework 源码
。
在 flutter web app 项目工程目录执行 flutter pub get
,IDE 点击符号 MaterialApp 可跳转打开源码所在文件 flutter/packages/flutter/lib/src/material/app.dart。
也可使用 vscode 或 Android Studio 等 IDE,打开 FLUTTER_SDK_SRCROOT(flutter/packages/flutter)文件夹进行编辑修改。
修改打开的 packages/flutter/lib/src/material/app.dart 文件,添加调试日志:
class _MaterialAppState extends State<MaterialApp> {
late HeroController _heroController;
bool get _usesRouter => widget.routerDelegate != null;
@override
void initState() {
super.initState();
if (kDebugMode) {
print('_MaterialAppState initState');
}
_heroController = MaterialApp.createMaterialHeroController();
}
flutter web app 调试运行起来,查看 console 日志以验证 framework 代码修改效果,正常会有以下输出:
修改 SDK/Framework 源码,web app 项目支持热加载,可实时调试查看修改后的效果。
web app 打开 chrome 运行起来后,可使用浏览器调试工具(例如 Chrome DevTools )打开查看和调试 Framework 源码。
在 Chrome DevTools 的 Sources 标签面板下,可查找定位到对应文件 packages/flutter/src/material/app.dart
,可以确认修改效果和断点调试。
http://localhost:8080/packages/flutter/src/material/app.dart
运行 framework 测试用例
flutter test
可以运行 framework 测试用例。
# 在 Chrome 上运行所有测试用例
flutter test -v --platform=chrome
# 在 Chrome 上运行测试用例,并指定渲染方式为移动端的 html 方式
flutter test -v --platform=chrome --web-renderer=html
# 在 Chrome 上运行指定测试用例,并指定渲染方式为移动端的 html 方式
flutter test -v --platform=chrome --web-renderer=html test/material/text_field_focus_test.dart
同步更新 framework 源码
在 Flutter SDK 目录下 master 分支,git pull
即可更新最新仓库代码。
如果需要同步上游官方源头仓库,可以参考 Configuring a remote for a fork & Syncing a fork。
- 也可 cd $FLUTTER_SDK_PROJECT_DIR,执行
git checkout
命令切换源码到指定版本进行开发调试或验证。
搭建 engine 开发环境
参考官方文档:
- Setting up the Engine development environment
- Compiling the engine - Compiling for the Web
- Contributing to the Flutter engine
需要具备这些条件搭建 engine 开发环境:
-
git(代码版本管理)。
-
IDE:Android Studio、vscode 等主流IDE可安装插件支持 flutter/Dart 代码高亮。
-
Python( gclient 等工具依赖)。
-
ssh 客户端(GitHub 身份验证),请先 配置 Github 认证需要的 SSH Key。
-
chromium depot_tools 是基于 Python(封装 git 等工具)实现的用于代码迁出管理的工具,包含
gclient
,gn
和ninja
等工具。- ninja 是 Google 推出的注重速度的构建工具,将编译任务并行组织,大大提高构建速度。
- 在 macOS 和 Linux 中,gclient sync 命令依赖 curl 和 unzip (macOS 已自带)。
-
felt:flutter engine 内置的编译 web engine 的工具,支持 Linux、macOS 和 Windows。对于其它平台,下表展示了 Linux,macOS 或 Windows 跨平台产物编译支持情况。
Android iOS Fuchsia Linux o x o Windows x x x macOS o o o -
Windows 平台需要:
- Visual Studio 2017 或更高版本。
- Windows 10 SDK。
- 确保安装了 Debugging Tools for Windows。
-
macOS 平台需要最新版本的 Xcode。
准备 depot_tools
Linux / Mac
Clone depot_tools 仓库到本地 /path/to/depot_tools(例如 $HOME/Library/Developer/chromium/tools/depot_tools):
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
将 git clone 下来的 depot_tools 本地仓库目录添加到环境变量 PATH,以使命令行可以找到相关工具链。
# ~/.bashrc 或 ~/.zshrc
$ export PATH=/path/to/depot_tools:$PATH
Windows
参考 depot_tools_tutorial 介绍。
确认 depot_tools 是否安装成功:
- 执行
gclient --version
和gclient help
可以查看 gclient 版本及帮助。 - 执行
ninja --version
查看 ninja 版本;执行ninja -h
查看 ninja 帮助。
$ gclient --version
Updating depot_tools...
gclient.py 0.7
$ ninja --version
1.8.2
部署 engine 源码
不需要提前安装 Dart,也不需要手动 clone engine 到本地,gclient 会将 engine 相关工具链和依赖库拉取到本地。
按照下面的步骤部署:
- Fork https://github.com/flutter/engine 到自己的 GitHub 账号。如果已经 frok 过了,可先更新 Sync Fork。
- 本地创建一个约定俗成的空目录
engine
,用来保存 engine 项目相关工具链和依赖库。 cd engine
,创建.gclient
配置文件,填入以下内容,替换 <your_name_here> 为你的 GitHub 账户名。
solutions = [
{
"managed": False,
"name": "src/flutter",
"url": "git@github.com:<your_name_here>/engine.git",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "",
},
]
-
在 engine 目录下执行
gclient sync
,将自动创建 engine/src 目录。- 其中
src/flutter
才是真正的 flutter engine 代码所在地。 - 此外,src 目录下包括第三方依赖库 third_party、工具链(build、tools)以及 fuchsia sdk 等。
- 其中
-
添加跟踪官方 flutter engine 上游源仓:
$ cd src/flutter
$ git remote add upstream git@github.com:flutter/engine.git
$ cd -
-
如果是参与外部开源项目,可考虑执行 git config 命令配置个人开发者用户名和邮箱:
- git config user.name <github_username>
- git config user.email <github_useremail>
编译 engine 源码
src/flutter
才是真正的 flutter engine 代码所在地,其中包括 sky、runtime、flutter_frontend_server、common(/graphics)、vulkan、shell(platform,vmservice) 等。lib 目录下包括 SDK/Framework 端 ui 和 web_ui 的实现代码:
src/flutter/lib/web_ui 为 Flutter Web Engine 代码目录。
$ tree -L 1 lib
lib
├── io
├── snapshot
├── spirv
├── ui
└── web_ui
编译 web engine 源码需要使用 felt(Flutter Engine Local Tester) 这个命令行工具,目标是让开发 Flutter web engine 更高效。
felt 内置于 flutter engine 仓库中的 lib/web_ui/dev 中,将 FLUTTER_ENGINE_SRCWEBUI/dev 配置到环境变量,以使命令行可以找到相关工具链:
可执行
felt help
(或 felt help build)验证 felt 命令是否安装成功。
# flutter project
export FLUTTER_PROJECT_DIR=$HOME/Projects/github/flutter
# engine
export FLUTTER_ENGINE_PROJECT_DIR=$FLUTTER_PROJECT_DIR/engine
export FLUTTER_ENGINE_PROJECT_SRCROOT=$FLUTTER_ENGINE_PROJECT_DIR/src
export FLUTTER_ENGINE_SRCROOT=$FLUTTER_ENGINE_PROJECT_SRCROOT/flutter
## lib/web_ui
export FLUTTER_ENGINE_SRCWEBUI=$FLUTTER_ENGINE_SRCROOT/lib/web_ui
export PATH=$FLUTTER_ENGINE_SRCWEBUI/dev:$PATH
在存放 .gclient 配置的 engine 目录(FLUTTER_ENGINE_PROJECT_DIR),执行 felt build
命令编译 flutter web 引擎,把 dart 转换成 js。
如果在其他目录执行 felt build,可能会报错
Error: client not configured; see 'gclient config'
。
默认的产物输出目录为 src/out/host_debug_unopt。
# 编译引擎
$ felt build
……
Done. Made 905 targets from 280 files in 1152ms
Running autoninja...
ninja: Entering directory `/Users/fantasy/Projects/github/flutter/engine/src/out/host_debug_unopt'
ninja: no work to do.
每次修改 web engine 后,在 FLUTTER_ENGINE_PROJECT_DIR 目录执行 felt build
即可重编引擎。
felt build
可能会遇到以下报错:
$ felt build
Running on MacOS. Will check the file and user limits.
File limits too low increasing the file limits
Can't load Kernel binary: Invalid kernel binary format version.
一般是因为flutter内核修改后无法识别,参考 Unable to build web flutter engine locally #70372,执行以下命令清除 felt.snapshot 可解决。
# rm $FLUTTER_SDK_ROOT/bin/cache/flutter_tools.stamp
rm $FLUTTER_ENGINE_SRCWEBUI/.dart_tool/felt.snapshot*
修改调试 engine 源码
请确保指定了 Engine 对应的 Flutter SDK/Framework 目录,具体参见 指定 Flutter SDK 目录。
用 IDE 打开 engine/src/flutter/lib/web_ui
目录(FLUTTER_ENGINE_SRCWEBUI),修改 web engine 源码后,执行 flet build 编译 engine。
那么,本地 flutter web app 运行时,如何指定使用本地修改编译的 flutter web engine 进行联调呢?
指定 --local-engine
Compiling the engine - Compiling for the Web:
To test Flutter with a local build of the Web engine, add
--local-engine=host_debug_unopt
to your flutter command.
Contributing to the Flutter engine:
Most developers will use the flutter tool in the main Flutter repository for interacting with their built flutter/engine. To do so, the
flutter
tool accepts two global parameterslocal-engine-src-path
andlocal-engine
, a typical invocation would be:--local-engine-src-path /path/to/engine/src --local-engine=android_debug_unopt
.
根据以上文档,flutter 全局命令选项 --local-engine=host_debug_unopt
,支持指定本地引擎(如果已经指定本地引擎目录到环境变量)。
针对 web 指定为 host_debug_unopt,具体参考 felt build 日志中的
ninja: Entering directory
。
--local-engine
针对终端开发时,其值可能为 ios_debug_unopt、android_debug_unopt。
通过以下方法运行 flutter web app 项目,就可以看到 flutter web engine 源码修改的效果。
$ cd /path/to/web/app
# 指定本地引擎,编译(链接)运行调试 Flutter Web 应用
$ flutter --local-engine=host_debug_unopt run -d chrome
如果未指定本地引擎目录到环境变量,则需要通过 --local-engine-src-path
选项指定(推荐)。
# 指定本地引擎,编译(链接)运行调试 Flutter Web 应用
$ flutter --local-engine=host_debug_unopt --local-engine-src-path=$FLUTTER_ENGINE_PROJECT_SRCROOT run -d chrome
可以在 flutter web app 的 vscode 启动配置 .vscode/launch.json 中的 toolArgs 添加 --local-engine 和 --local-engine-src-path 这两个选项参数:
也可在 Android Studio Run/Debug Configurations 的 Additional run args 开头指定 --local-engine 和 --local-engine-src-path 这两个选项参数:
修改源码调试示例
接下来以 PR 31718 修改的 engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart 为例,我们在 IOSTextEditingStrategy.addEventHandlers 监听的 onBlur 事件中加一句日志。
// text_editing.dart
class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
IOSTextEditingStrategy(HybridTextEditing owner) : super(owner);
@override
void addEventHandlers() {
subscriptions.add(activeDomElement.onBlur.listen((_) {
if (windowHasFocus) {
print('IOSTextEditingStrategy addEventHandlers onBlur event');
activeDomElement.focus();
} else {
owner.sendTextConnectionClosedToFrameworkIfAny();
}
}));
}
}
- flutter web app(coding-app)项目下执行
./scripts/proxy/launch_shelf.sh -P
启动 shelf_proxy 代理,日志输出 ✅ proxy listening on http://10.20.89.64:8010。 - 执行以下命令,通过指定全局选项 --local-engine 和 --local-engine-src-path 指定本地 web engine 产物和源码目录,run -d web-server 启动 web 服务监听 8080 端口。
# 客户端模式,直接拉起 chrome 运行调试
# flutter --local-engine=host_debug_unopt --local-engine-src-path=$FLUTTER_ENGINE_PROJECT_SRCROOT run -d chrome --web-renderer=html --web-port=8080 --dart-define=API_BASE_URL=10.20.89.64:8010 --dart-define=AUTH_TOKEN=$DEBUG_AUTH_TOKEN
# server模式,可供局域网通过 LAN IP 访问
$ flutter --local-engine=host_debug_unopt --local-engine-src-path=$FLUTTER_ENGINE_PROJECT_SRCROOT run -d web-server --web-renderer=html --web-port=8080 --web-hostname=0.0.0.0 --dart-define=API_BASE_URL=10.20.89.64:8010 --dart-define=AUTH_TOKEN=$DEBUG_AUTH_TOKEN
lib/main.dart is being served at http://0.0.0.0:8080
- 执行
open -a Simulator
命令打开 iOS 模拟器,在safari浏览器地址栏输入 http://0.0.0.0:8080 访问 coding-app 页面。 - 打开 macOS Safari,Develop 菜单点击 Simulator - 0.0.0.0,将打开 Web Inspector。
- 在 Web Inspector 的 Sources 标签面板下,在
dart_sdk.js/lib/_engine/engine
下定位到 text_editing 目录,点击打开 text_editing.dart,查找到修改的地方,点击行号Gutter添加断点。点击搜索栏,调起键盘输入,聚焦或点击键盘 Done 按钮,应该会命中断点:
- 同时切换到 Web Inspector 的 Console 标签面板,应该可以看到 onBlur 日志输出。
macOS 桌面端 Chrome 访问 http://10.20.89.64:8080,可打开调试工具 DevTools 查看和调试 Engine 源码。在 DevTools 的 Sources 标签面板下,在 lib/_engine/engine
下定位到 text_editing 目录,点击打开 text_editing.dart,可以确认修改是否生效和进行断点调试。
由于修改的示例代码 IOSTextEditingStrategy 仅针对 iOS 设备,可以给 DefaultTextEditingStrategy.setEditingState 加个断点调试验证。点击搜索栏,聚焦输入框,就会命中断点:
运行 engine 测试用例
felt test
可以运行 web engine 测试用例,默认情况下使用 Chromium 作为平台。
# 运行所有 Chromium 测试用例
felt test
# 运行特定的测试用例
felt test test/engine/util_test.dart
# 在 iOS Safari 上运行测试用例
felt test --browser=ios-safari
更多命令运行 felt help [SUBCOMMAND]
查询。
同步更新 engine 源码
更新远程的官方 engine 仓库,只需要在本地 engine 目录(FLUTTER_ENGINE_PROJECT_DIR)执行 gclient sync
即可。
协同联调 Framework 和 Engine
如果要同时调试 Engine 和 SDK/Framework(FLUTTER_SDK_ROOT),需要先导出工具链到环境变量,并在 IDE 指定好配套的 Flutter SDK 目录(flutterSdkPath)。
最新 fvm global stable 为最新的 2.10.5,执行 flutter --version
可以查看 SDK/Framework 和 Engine 的搭配版本:
$ flutter --version
Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 5464c5bac7 (4 days ago) • 2022-04-18 09:55:37 -0700
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2
如果在 SDK/Framework 仓库执行 git checkout 切换到了 2.10.5,也可将 Flutter Engine 切换到对应版本 57d3bac3dd。
这样搭配进行开发调试或验证,避免出现一些由于 SDK 和 Engine 版本不协同,造成的接口和工具链兼容性问题。
# engine/src/flutter
$ cd $FLUTTER_ENGINE_SRCROOT
$ git checkout 57d3bac3dd
注意:
切换 Flutter Engine 仓库版本后,需要在 FLUTTER_ENGINE_PROJECT_DIR 目录重新执行 gclient sync
命令同步工具链。
否则,可能会报以下错误:
Running gn...
GOMA usage was specified but can't be found, falling back to local builds. Set the GOMA_DIR environment variable to fix GOMA.
Using prebuilt Dart SDK binary. If you are editing Dart sources and wish to compile the Dart SDK, set `--no-prebuilt-dart-sdk`.
Generating GN files in: out/host_debug_unopt
ERROR at //BUILD.gn:28:7: Can't load input file.
"//flutter/build/archives:artifacts"
^-----------------------------------
Unable to load:
/Users/fantasy/Projects/github/flutter/engine/src/flutter/build/archives/BUILD.gn
I also checked in the secondary tree for:
/Users/fantasy/Projects/github/flutter/engine/src/build/secondary/flutter/build/archives/BUILD.gn
description: Sub-process failed.executable: /Users/fantasy/Projects/github/flutter/engine/src/flutter/tools/gn arguments: [--unopt, --xcode-symlinks, --full-dart-sdk] exit code: 1