一、概念
- depot_tools: python 实现的用于代码迁出管理的工具,包含 gclient,gn 和 ninja 等工具。
- ninja :是google推出的注重速度的构建工具,将编译任务并行组织,大大提高构建速度。
- gclient:代码获取工具,是 google 推出的用于管理多源项目所编写的脚本,可以将多个源码管理系统中的代码放在一起管理。
- .gclient文件:是 gclient 的控制文件,是一个 python 脚本。
二、工具准备
- 梯子
- git
- github、ssh
- curl、unzip (gclient sync 需要)
- xcode (只编译android也是需要装xcode环境的)
- python --version 2.x版本
- depot_tools
国外:git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 国内:git clone https://source.codeaurora.org/quic/lc/chromium/tools/depot_tools 其他:https://storage.googleapis.com/chrome-infra/depot_tools.zip 配置环境变量:export PATH=$PATH:/path/to/depot_tools
三、源码准备
1. 克隆源码
fork Flutter Engine 的源码到自己的github仓库
2. 创建 目录并添加 .gclient
文件
$ mkdir engine
$ cd engine
$ touch .gclient
.gclient
文件内容:(改为自己的engine仓库)
solutions = [
{
"managed": False,
"name": "src/flutter",
"url": "[email protected]:/engine.git",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "",
},
]
3. 同步代码
$ gclient sync -v
- ps: 开始会有进度显示,后面会下载大文件,不要终止,可以通过网络监控查看网速或流量,好几个G的内容,稍安勿躁。
4.与原仓库关联
将自己fork出来的仓库与原仓库关联同步,方便日后更新,进入到src/flutter目录:
- a. 查看现有的远程仓库
$ cd src/flutter $ git remote -v origin [email protected]:
/engine.git (fetch) origin [email protected]: /engine.git (push) - b.添加指向原仓库的upstream:
git remote add upstream [email protected]:flutter/engine.git
- c.查看origin和upstream
$ git remote -v origin [email protected]:
/engine.git (fetch) origin [email protected]: /engine.git (push) upstream [email protected]:flutter/engine.git (fetch) upstream [email protected]:flutter/engine.git (push) - d.直接从原仓库的分支拉取代码并直接合并代码,其中pull = fetch + merge:
$ git pull upstream
5.匹配版本
在实际开发中,一般不直接使用master的代码直接编译,都是需要获取指定版本的engine代码。可以通过本地安装的flutter sdk(framework)版本来获取所对应的engine版本。
framework的分支规则如下:
a. stable是当前的稳定分支,无特殊情况,推荐开发者使用该分支作为flutter sdk
b. master包含最新的特性,但是不稳定
c. 每个版本会打上对应的tag
$ cat /Users/xxx/Library/Android/flutter/bin/internal/engine.version
2f0af3715217a0c2ada72c717d4ed9178d68f6ed
或者
$ flutter doctor -v
[✓] Flutter (Channel stable, 1.22.6, on Mac OS X 10.15.7 19H2 darwin-x64, locale zh-Hans-CN)
• Flutter version 1.22.6 at /Users/shawpoo/Library/Android/flutter
• Framework revision 9b2d32b605 (4 weeks ago), 2021-01-22 14:36:39 -0800
• Engine revision 2f0af37152
• Dart version 2.10.5
• Pub download mirror https://pub.flutter-io.cn
• Flutter download mirror https://storage.flutter-io.cn
...省略其他信息
其实engine的version就是对应的一次commit id,接下来需要切到对应的commit版本,因为切换分支之后,某些依赖的版本可能有更改,所以需要再次同步。
$ cd engine/src/flutter
$ git reset --hard
$ gclient sync -D --with_branch_heads --with_tags -v
四、开始编译
编译使用src/flutter/tools/gn工具:
gn -h 参数如下
usage: gn [-h] [--unoptimized]
[--runtime-mode {debug,profile,release,jit_release}] [--interpreter]
[--dart-debug] [--full-dart-debug]
[--target-os {android,ios,linux,fuchsia}] [--android]
[--android-cpu {arm,x64,x86,arm64}] [--ios] [--ios-cpu {arm,arm64}]
[--simulator] [--fuchsia] [--linux-cpu {x64,x86,arm64,arm}]
[--fuchsia-cpu {x64,arm64}] [--arm-float-abi {hard,soft,softfp}]
[--goma] [--no-goma] [--lto] [--no-lto] [--clang] [--no-clang]
[--clang-static-analyzer] [--no-clang-static-analyzer]
[--target-sysroot TARGET_SYSROOT]
[--target-toolchain TARGET_TOOLCHAIN]
[--target-triple TARGET_TRIPLE]
[--operator-new-alignment OPERATOR_NEW_ALIGNMENT] [--enable-vulkan]
[--enable-fontconfig] [--enable-skshaper]
[--enable-vulkan-validation-layers] [--embedder-for-target]
[--coverage] [--out-dir OUT_DIR] [--full-dart-sdk]
[--no-full-dart-sdk] [--ide IDE] [--build-glfw-shell] [--bitcode]
[--stripped]
我们一般用到的构建参数有以下几种:
--android 指定android平台
--ios 指定ios平台
--runtime-mode debug,profile,release,jit_release
--unoptimized 默认是optimized优化过的
--android-cpu {arm,x64,x86,arm64} 默认是arm(对应arm-v7)
--ios --ios-cpu {arm,arm64}
开始编译,通过gn生成ninja需要的元数据:以为编译android debug和release为例:
$ cd src
# Android debug版本
$ ./flutter/tools/gn --android --runtime-mode=debug
$ ./flutter/tools/gn --no-lto --runtime-mode=debug
$ ninja -C out/android_debug -j 8
$ ninja -C out/host_debug -j 8
# Android arm64的release版本
$ ./flutter/tools/gn --android --runtime-mode=release --android-cpu arm64
$ ./flutter/tools/gn --no-lto --android-cpu arm64 --runtime-mode=release
$ ninja -C out/android_release -j 8 && ninja -C out/host_release -j 8
编译之后,生成的产物在 src/out 目录下,格式如下:
$ tree -L 1
.
├── android_debug
├── android_release
├── compile_commands.json
├── host_debug
└── host_release
compile_commands.json
可以作为IDE的索引文件,提供类/函数/变量的跳转等能力。
ps: 扩展说明:
- gn编译时需要上
--no-lto
参数,否则执行ninja命令可能出现以下错误:
➜ $ ninja -C out/host_release -j 8
ninja: Entering directory `out/host_release'[316/634] LINK ./fml_benchmarksFAILED: fml_benchmarks../../buildtools/mac-x64/clang/bin/clang++ -isysroot /Applications/Xcode.app/.../SDKs/MacOSX11.1.sdk -mmacosx-version-min=10.11.0 -flto -arch x86_64 -nostdlib++ -stdlib=libc++ -Wl,-dead_strip -Wl,-search_paths_first -L. -Wl,-rpath,@loader_path/. -Wl,-rpath,@loader_path/../../.. -Wl,-pie -Xlinker -rpath -Xlinker @executable_path/Frameworks -o .error: linker command failed with exit code 1 (use -v to see invocation)[318/634] LINK ./fml_unittests
- 建议ninja 通过
-j
参数指定并行的任务数,不指定则cpu拉满进行编译。
run N jobs in parallel [default=18, derived from CPUs available]
3.ninja 命令可以合并执行:
$ ninja -C out/android_debug -j 8
$ ninja -C out/host_debug -j 8
等同于:
$ ninja -C out/android_debug -j 8 && ninja -C out/host_debug -j 8
五、应用产物
1、命令行指定使用engine产物:
$ flutter create testapp
$ cd testapp
$ flutter run --local-engine-src-path /Users/xxx/Library/Android/engine/src --local-engine=android_debug
2、也可以在flutter项目的android工程里gradle.properties配置:
local-engine-repo=/Users/xxx/Library/Android/engine/src/out/android_debug
local-engine-out=/Users/xxx/Library/Android/engine/src/out/android_debug
local-engine-build-mode=debug
其次,在app的build.gradle中可以看到依赖了flutter sdk中的flutter.gradle文件,我们需要修改此脚本,建议将此文件copy到app根目录下:
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
修改为:
apply from: "flutter.gradle"
$ app tree -L 1
.
├── app.iml
├── build.gradle
├── flutter.gradle
└── src
修改 flutter.gradle 脚本内容:
void addFlutterDependencies(buildType) {
String flutterBuildMode = buildModeFor(buildType)
if (!supportsBuildMode(flutterBuildMode)) {
return
}
// add local engine dependencies [start]
if (useLocalEngine()) {
String engineOutPath = project.property('local-engine-out')
File engineOut = project.file(engineOutPath)
if (!engineOut.isDirectory()) {
throw new GradleException('local-engine-out must point to a local engine build')
}
// 当使用本地engine时添加flutter.jar文件依赖
File flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile()
if (!flutterJar.isFile()) {
throw new GradleException('Local engine build does not contain flutter.jar')
}
project.dependencies {
if (project.getConfigurations().findByName("api")) {
println "api"
"${flutterBuildMode}Api" project.files(flutterJar)
} else {
println "compile"
"${flutterBuildMode}Compile" project.files(flutterJar)
}
}
return
}
// add local engine dependencies [end]
String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST
String repository = useLocalEngine()
? project.property('local-engine-repo')
: "$hostedRepository/download.flutter.io"
project.rootProject.allprojects {
repositories {
maven {
url repository
}
}
}
// Add the embedding dependency.
addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
List platforms = getTargetPlatforms().collect()
// Debug mode includes x86 and x64, which are commonly used in emulators.
if (flutterBuildMode == "debug" && !useLocalEngine()) {
platforms.add("android-x86")
platforms.add("android-x64")
}
platforms.each { platform ->
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
// Add the `libflutter.so` dependency.
addApiDependencies(project, buildType.name,
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
}
}
修改configurePluginProject方法,plugin project添加依赖:
// Adds the plugin project dependency to the app project .
private void configurePluginProject(String pluginName, String _) {
Project pluginProject = project.rootProject.findProject(":$pluginName")
if (pluginProject == null) {
project.logger.error("Plugin project :$pluginName not found. Please update settings.gradle.")
return
}
// Add plugin dependency to the app project.
project.dependencies {
implementation pluginProject
}
Closure addEmbeddingCompileOnlyDependency = { buildType ->
String flutterBuildMode = buildModeFor(buildType)
// In AGP 3.5, the embedding must be added as an API implementation,
// so java8 features are desugared against the runtime classpath.
// For more, see https://github.com/flutter/flutter/issues/40126
if (!supportsBuildMode(flutterBuildMode)) {
return
}
// add local engine dependencies [start]
if (useLocalEngine()) {
String engineOutPath = project.property('local-engine-out')
File engineOut = project.file(engineOutPath)
if (!engineOut.isDirectory()) {
throw new GradleException('local-engine-out must point to a local engine build')
}
// 当使用本地engine时添加flutter.jar文件依赖
File flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile()
if (!flutterJar.isFile()) {
throw new GradleException('Local engine build does not contain flutter.jar')
}
pluginProject.dependencies {
if (pluginProject.getConfigurations().findByName("api")) {
println "api"
"${flutterBuildMode}Api" pluginProject.files(flutterJar)
} else {
println "compile"
"${flutterBuildMode}Compile" pluginProject.files(flutterJar)
}
}
} else {
addApiDependencies(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
)
}
// add local engine dependencies [end]
}
pluginProject.afterEvaluate {
pluginProject.android.buildTypes {
profile {
initWith debug
}
}
pluginProject.android.buildTypes.each addEmbeddingCompileOnlyDependency
pluginProject.android.buildTypes.whenObjectAdded addEmbeddingCompileOnlyDependency
}
}
修改之后,直接运行项目即可。
参考
设置engine编译环境
Flutter engine host release build fails on macos
Flutter 的构建模式