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": "[email protected]:/engine.git",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

3. 同步代码

$ gclient sync -v
  • ps: 开始会有进度显示,后面会下载大文件,不要终止,可以通过网络监控查看网速或流量,好几个G的内容,稍安勿躁。

4.与原仓库关联

将自己fork出来的仓库与原仓库关联同步,方便日后更新,进入到src/flutter目录:

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

  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 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 的构建模式

你可能感兴趣的:(Flutter Engine环境搭建与编译)