0%

用 Xcode 编译 Flutter Engine 源码

准备工作

参考官方 wiki Setting up the Engine development environment搭建Flutter Engine源码编译环境,设置好环境。

安装 depot_tools

  1. clone depot_tools 以获取 gclient 命令;
    1. git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  2. .bashrc 或者 ~/.zshrc 设置环境变量
    1. export PATH="$PATH:$HOME/Documents/engine/depot_tools"
    2. 如果后面拉取源码提示 zsh: command not found: gclient 的话,那就在这两个文件里都设置一下
    3. 最好的办法是根据 echo $SHELL 命令 来获取当前是用的哪个 shell 终端,摘自 flutter/macOS install/Update your path

Homebrew 安装 ant 和 ninja

1
2
3
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install ant
brew install ninja

下载源码

I. fork flutter/engine repo

II. github ssl 处理

III. 创建 engine 文件夹,添加 .gclient 文件

1
2
3
4
5
6
7
8
9
10
solutions = [
{
"managed": False,
"name": "src/flutter",
"url": "git@github.com:JoakimLiu/engine.git",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "",
},
]

这一步耗时有点久,大概 3h, engine文件夹内容如下:

1
2
3
.
├── depot_tools
└── src

21.3.2 遇到的问题,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Activated generate_package_config 0.0.0 at path "/Users/joakim.liu/Documents/engine/src/flutter/tools/generate_package_config".
Unable to spawn isolate: src/flutter/tools/generate_package_config/bin/generate_from_legacy.dart:10:8: Error: Error when reading '../../.pub-cache/hosted/pub.flutter-io.cn/package_config-2.0.0/lib/packages_file.dart': No such file or directory
import 'package:package_config/packages_file.dart'; // ignore: deprecated_member_use
^
src/flutter/tools/generate_package_config/bin/generate_from_legacy.dart:28:20: Error: Method not found: 'parse'.
var packageMap = parse(await packagesFile.readAsBytes(), packagesFile.uri);
^^^^^
Traceback (most recent call last):
File "src/flutter/tools/run_third_party_dart.py", line 14, in <module>
subprocess.check_call([os.path.join(leading, pub), "global", "run", "generate_package_config:generate_from_legacy", "src/flutter/flutter_frontend_server/.packages"])
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 190, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['src/third_party/dart/tools/sdks/dart-sdk/bin/pub', 'global', 'run', 'generate_package_config:generate_from_legacy', 'src/flutter/flutter_frontend_server/.packages']' returned non-zero exit status 1
Error: Command 'vpython src/flutter/tools/run_third_party_dart.py' returned non-zero exit status 1 in /Users/joakim.liu/Documents/engine

搜了一波关键字都没找到方案,只能从源码文件找解决方案了

  1. /Users/joakim.liu/.pub-cache/hosted/pub.flutter-io.cn/package_config-2.0.0/lib,从 pub.dev 发现 package_config-2.0.0 是预览版,于是将 package_config-2.0.0 强制替换成 package_config-1.9.3,不行;
  2. 根据经验猜测应该是在某个 yaml 文件里面指定的版本,于是在 /Users/joakim.liu/Documents/engine/src/flutter/tools/generate_package_config 找到了 pubspec.yaml,将 package_config: any 改成 package_config: 1.9.3

Ⅴ. 将 engine(path:src/flutter) 和本地 flutter sdk 版本匹配上。

1
2
3
4
5
6
7
8
➜ flutter git:(c2311c7c0) flutter --version
Flutter 1.22.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 84f3d28555 (3 months ago) • 2020-10-15 16:26:19 -0700
Engine • revision b8752bbfff
Tools • Dart 2.10.2
➜ flutter git:(c2311c7c0) git reset --hard b8752bbfff
HEAD is now at b8752bbff [flutter_releases] Flutter 1.22.2 engine cherrypicks (#21841)
➜ flutter git:(b8752bbff) gclient sync --with_branch_heads --with_tags

参考自 Flutter(7) — Flutter Engine初始化(上) 这一步非常重要,否则后面用编译后生成的 simulator debug 产物,来运行 flutter demo 是会报错,大致信息如下:

1
2
3
4
[ +783 ms] Crash report sent (report ID: bbed038d6c5d852a)
[ ] Oops; flutter has exited unexpectedly: "NoSuchMethodError: The method 'matchAsPrefix' was called on null.
Receiver: null
Tried calling: matchAsPrefix("ERROR: FormatException: Could not find an option named \"bytecode-options\".", 0)".

编译源码

参考官方 wiki 和 怎样的Flutter Engine定制流程,才能实现真正“开箱即用”?

1
2
3
4
5
6
7
8
9
10
11
12
13
cd src
# 编译 iOS 模拟器产物
./flutter/tools/gn --ios --simulator --unoptimized
./flutter/tools/gn --unoptimized
ninja -C out/ios_debug_sim_unopt && ninja -C out/host_debug_unopt
# 编译 arm64 产物
joakim.liu@joakimdeiMac src % ./flutter/tools/gn --runtime-mode=debug --ios --ios-cpu=arm64 --unoptimized
Generating GN files in: out/ios_debug_unopt
Generating Xcode projects took 108ms
Done. Made 418 targets from 190 files in 4849ms
joakim.liu@joakimdeiMac src % ninja -C out/ios_debug_unopt
ninja: Entering directory `out/ios_debug_unopt'
[3819/3819] STAMP obj/default.stamp

这一步耗时比较久,大概 40min.
编译后的内容如下图,

此时打开 all.xcodeproj 就能看源码了。

调试源码

I. 创建并运行项目,

1
2
➜ Demo flutter create my_app    
➜ my_app flutter run --local-engine-src-path=/Users/joakim.liu/Documents/engine/src --local-engine=ios_debug_sim_unopt --verbose

如果上一步没将 engine 和本地 flutter sdk 匹配上的话,这里会运行失败的,用 Xcode 运行的话,会报以下错误

1
ios_debug_sim_unopt No `Podfile' found in the project directory.

其实 怎样的Flutter Engine定制流程,才能实现真正“开箱即用”? 里面有提到,当时没注意:

第一次gclient sync 执行完成了,engine/src/flutter为Flutter Engine源码的位置,我们需要手动切换到对应的版本分支,然后再次执行gclinet sync对此版本的依赖重新同步下,此次执行会比首次执行快很多。

参考自 Compiling flutter/engine on OSX fails. #16114,总结下重要的点

  1. gclient sync 的时候要 git rev-parse HEAD
  2. 模拟器运行 flutter run -d”iPhone 8”
  3. pubspec.yaml 的 dependency_overrides // 适用于 当引擎中修改了Dart源代码,摘自 搭建Flutter Engine源码编译环境
  4. 手动修改 manually edit an Xcode config file
  5. 同上 `the engine path should be “ FLUTTER_ROOT/engine/src “ because the compile script would compare the engine path with “FLUTTER_ROOT/engine/src” .
  6. engine 版本要和 flutter sdk 版本相对应,The building engine version may not same as FLUTTER_ROOT version. Try to change the engine src version. // 也就是 “Flutter SDK依赖的Engine的commit id的值”

II. 如果终端运行失败的话,那就用 Android Studio 运行,然后查看 /Users/joakim.liu/Documents/Demo/my_app/ios/Flutter 下的 Generated.xcconfig 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/joakim.liu/development/flutter-1.22.2
FLUTTER_APPLICATION_PATH=/Users/joakim.liu/Documents/Demo/my_app
FLUTTER_TARGET=/Users/joakim.liu/Documents/Demo/my_app/lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
OTHER_LDFLAGS=$(inherited) -framework Flutter
FLUTTER_FRAMEWORK_DIR=/Users/joakim.liu/Documents/engine/src/out/ios_debug_sim_unopt
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
FLUTTER_ENGINE=/Users/joakim.liu/Documents/engine/src
LOCAL_ENGINE=ios_debug_sim_unopt
ARCHS=arm64
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.packages

III.
想用 Xcode 调试 engine 源码的话,那就把 engine 源码导入去,把编译后的 ios_debug_sim_unopt/all.xcodeproj 工程拖进 demo Runner.xcworkspace 中,注意:此时不能打开 all.xcodeproj 工程。

但用 Xcode12 打开工程后,不会出现模拟器选项,用 Android Studio 才能运行后就会出现模拟器选项,很怪异,可是 Android Studio 运行后 Generated.xcconfig 会发生变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/joakim.liu/development/flutter-1.22.2
FLUTTER_APPLICATION_PATH=/Users/joakim.liu/Documents/Demo/my_app
FLUTTER_TARGET=/Users/joakim.liu/Documents/Demo/my_app/lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
OTHER_LDFLAGS=$(inherited) -framework Flutter
FLUTTER_FRAMEWORK_DIR=/Users/joakim.liu/development/flutter-1.22.2/bin/cache/artifacts/engine/ios
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.packages

如果将 Generated.xcconfig 改成终端运行后的内容,会报错

1
2
Package generate_package_config is currently active at path "/Users/joakim.liu/Documents/engine/src/flutter/tools/generate_package_config".
Activated generate_package_config 0.0.0 at path "/Users/joakim.liu/Documents/engine/src/flutter/tools/generate_package_config".

Generated.xcconfig 后面添加 FLUTTER_ENGINELOCAL_ENGINE 即可,最终内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/joakim.liu/development/flutter-1.22.2
FLUTTER_APPLICATION_PATH=/Users/joakim.liu/Documents/Demo/my_app
FLUTTER_TARGET=/Users/joakim.liu/Documents/Demo/my_app/lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
OTHER_LDFLAGS=$(inherited) -framework Flutter
FLUTTER_FRAMEWORK_DIR=/Users/joakim.liu/development/flutter-1.22.2/bin/cache/artifacts/engine/ios
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.packages
FLUTTER_ENGINE=/Users/joakim.liu/Documents/engine/src
LOCAL_ENGINE=ios_debug_sim_unopt

嗯,终于能用 Xcode 把 demo 给跑起来在模拟器运行了,并且也能在 engine 源码里面下断点了。

参考链接