Flutter Engine环境搭建与编译

一、概念

  • 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": "git@github.com:<YOUR_NAME>/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 git@github.com:<YOUR_NAME>/engine.git (fetch)
     origin git@github.com:<YOUR_NAME>/engine.git (push)
    
  • b.添加指向原仓库的upstream:
    git remote add upstream git@github.com:flutter/engine.git
    
  • c.查看origin和upstream
     $ git remote -v
     origin git@github.com:<YOUR_NAME>/engine.git (fetch)
     origin git@github.com:<YOUR_NAME>/engine.git (push)
     upstream git@github.com:flutter/engine.git (fetch)
     upstream git@github.com:flutter/engine.git (push)
    
  • d.直接从原仓库的分支拉取代码并直接合并代码,其中pull = fetch + merge:
    $ git pull upstream <BRANCH_NAME>
    

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 <engine_version>
$ 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: 扩展说明:

  1. 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
  1. 建议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<String> 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 的构建模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容