Flutter - 引擎调试(iOS篇)🛠

3,948 阅读5分钟

本文正在参加「金石计划」

欢迎关注微信公众号:FSA全栈行动 👋

一、depot_tools

depot_tools 是调试 Flutter 引擎的必备工具包,包含了 gclientgnninja 等工具,这些在下面会用到!

拉取 depot_tools 到本地,我一般是放到 ~/developer/

$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

depot_tools 的路径补充到 PATH

$ export PATH=/path/to/depot_tools:$PATH

# 如: export PATH=~/developer/depot_tools:$PATH

二、配置.gclient

创建 engine 目录,且终端进入到该目录,生成并配置 .gclient 文件

➜  ~ cd /Users/lxf/Desktop/LXF/engine
➜  engine touch .gclient
➜  engine vim .gclient

配置内容:

solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "git@github.com:flutter/engine.git@1837b5be5f0f1376a1ccf383950e83a80177fb4e",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

如果你是仅调试,那链接可以用 flutter 的,但如果你想后面提 PR,则需要使用自己 fork 后的链接,如:git@github.com:LinXunFeng/engine.git

引擎 git 地址后面携带的 commit id 最好与当前 Flutter 所使用的引擎版本保持一致,可以通过如下命令获取

flutter --version

输出:

Flutter 3.7.7 • channel stable • 
...
Engine • revision 1837b5be5f
...

或者取如下文件里的值

flutter/bin/internal/engine.version

# 1837b5be5f0f1376a1ccf383950e83a80177fb4e

三、拉取源码

使用终端,进行到 .gclient 所在目录,即上面创建的 engine 目录,执行如下命令

gclient sync

注:gclient 命令来自于 depot_tools

然后坐等完成即可,如果失败了就反复执行 gclient sync 直到成功,大概近10个G

➜  engine gclient sync
Updating depot_tools...
using /var/folders/36/n74_k53x5h5fz70gkd3jr5p00000gn/T/goma_lxf as tmpdir
INFO: creating cache dir (/var/folders/36/n74_k53x5h5fz70gkd3jr5p00000gn/T/goma_lxf/goma_cache).
compiler_proxy is not running

________ running 'git -c core.deltaBaseCacheLimit=2g clone --no-checkout --progress git@github.com:flutter/engine.git /Users/lxf/Desktop/LXF/engine/src/_gclient_flutter_t0dh3d3h' in '/Users/lxf/Desktop/LXF/engine'
Cloning into '/Users/lxf/Desktop/LXF/engine/src/_gclient_flutter_t0dh3d3h'...
remote: Enumerating objects: 326233, done.
remote: Counting objects: 100% (42/42), done.
remote: Compressing objects: 100% (39/39), done.
remote: Total 326233 (delta 11), reused 8 (delta 2), pack-reused 326191
Receiving objects: 100% (326233/326233), 394.55 MiB | 3.88 MiB/s, done.
Resolving deltas: 100% (242690/242690), done.
30>WARNING: subprocess '"git" "-c" "core.deltaBaseCacheLimit=2g" "fetch" "origin" "d40d15e994ed60d32bcfc9ab87004dfb028dfbd6" "--no-tags"' in /Users/lxf/Desktop/LXF/engine/src/third_party/harfbuzz failed; will retry after a short nap...
Syncing projects: 100% (134/134), done.
Hook 'python3 src/flutter/tools/pub_get_offline.py' took 19.13 secs
Running hooks: 100% (11/11), done.
➜  engine

拉取完成后你可以进入 src/flutter 查看当前下载下来的 enginecommit id

➜  cd /Users/lxf/Desktop/LXF/engine/src/flutter
➜  flutter git:(1837b5be5f)

如果你的终端没有显示 git:(commit id),那可以使用如下命令进行查看

git rev-parse HEAD

# 1837b5be5f0f1376a1ccf383950e83a80177fb4e

四、编译

进入 tools 目录,执行几条 gn 命令

➜  cd engine/src/flutter/tools

生成 iOS 设备端可执行文件所需的构建文件

➜  tools git:(1837b5be5f) ./gn --ios --unoptimized

GOMA usage was specified but can't be found, falling back to local builds. Set the GOMA_DIR environment variable to fix GOMA.
Generating GN files in: out/ios_debug_unopt
Generating Xcode projects took 211ms
Generating compile_commands took 55ms
Done. Made 801 targets from 274 files in 6165ms

如果你想调试模拟器,则加上 --simulator

gn --ios --simulator --unoptimized

生成主机端可执行文件所需的构建文件

➜  tools git:(1837b5be5f) ./gn --unoptimized

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
Generating Xcode projects took 273ms
Generating compile_commands took 70ms
Done. Made 1110 targets from 340 files in 46800ms

执行完成之后,在 engine/src/out 目录下就会多出相应端的构建文件目录

接下来使用 Ninja 进行编译

ninja -C host_debug_unopt && ninja -C ios_debug_unopt

如果你是用 M1/M2 电脑去编译旧引擎源码,可能会在编译 ios_debug_unopt 时遇到如下错误

ninja: Entering directory `host_debug_unopt'
ninja: no work to do.
ninja: Entering directory `ios_debug_unopt'
[13/369] ACTION //flutter/lib/snapshot:create_arm_gen_snapshot(//build/toolchain/mac:ios_clang_arm)
FAILED: clang_x64/gen_snapshot_arm64
python3 ../../flutter/sky/tools/create_macos_gen_snapshots.py --dst /Users/lxf/Desktop/LXF/engine/src/out/ios_debug_unopt/clang_x64 --arm64-out-dir /Users/lxf/Desktop/LXF/engine/src/out/ios_debug_unopt
Cannot find gen_snapshot at /Users/lxf/Desktop/LXF/engine/src/out/ios_debug_unopt/clang_x64/gen_snapshot
[16/369] LINK clang_arm64/impellerc
ninja: build stopped: subcommand failed.

这时你只需要进入到 engine/src/out/ios_debug_unopt 目录下,将 clang_arm64 目录中的 gen_snapshot 拷贝到 clang_x64,然后再继续执行 ninja -C ios_debug_unopt 即可。

这里强调一下:

  • 如果你修改了引擎的源代码,需要重新执行相应的 ninja 命令,如调试引擎时我们会修改代码,如果不执行 ninja 命令,你会发现断点修改的代码其它是旧代码!
  • 如果是新增或删除文件,则需要先执行 gn 命令,再执行 ninja 命令。
  • 我建议直接 gnninja 命令一起执行,不用管什么情况,脚本会自己检查是否有文件变更,以及是否需要重新生成相应的文件的。

五、调试

打开你的Flutter 项目,IDEVSCode 为例,添加 --local-engine-src-path--local-engine 两个参数,完整配置如下

{
  "name": "flutter_demo",
  "request": "launch",
  "type": "dart",
  "args": [
    "--local-engine-src-path",
    "/Users/lxf/Desktop/LXF/engine/src",
    "--local-engine",
    "ios_debug_unopt"
  ]
},
参数
--local-engine-src-path固定为引擎的 src 路径
--local-engine真机: ios_debug_unopt
模拟器: ios_debug_sim_unopt

F5 跑一遍就可以使用指定引擎运行 Flutter 项目了。

细心的你可能会发现在 flutter_demo/ios/Flutter/Generated.xcconfig 配置文件内新增相应的两个环境变量

...
FLUTTER_ENGINE=/Users/lxf/Desktop/LXF/engine/src
LOCAL_ENGINE=ios_debug_unopt
...

如果我们要调试引擎代码,请打开你的 Runner.xcworkspace,然后将 flutter_engine 拖入项目

找到 FlutterViewController.mmtouchesBegan 打个断点,再运行项目,点击屏幕就会进入到断点处,现在就可以开始愉快的调试 flutter_engine

如果你的断点不生效,那请先检查 Generated.xcconfig 里的 FLUTTER_ENGINELOCAL_ENGINE 变量。

到这里,引擎的编译调试就介绍完毕了。

下一篇,我们来详细说说怎么给 Flutter 引擎提交 PR 🤠

六、资料

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~