Flutter Engine環(huán)境搭建與編譯

一、概念

  • depot_tools: python 實(shí)現(xiàn)的用于代碼遷出管理的工具,包含 gclient,gn 和 ninja 等工具。
  • ninja :是google推出的注重速度的構(gòu)建工具,將編譯任務(wù)并行組織,大大提高構(gòu)建速度。
  • gclient:代碼獲取工具,是 google 推出的用于管理多源項(xiàng)目所編寫的腳本,可以將多個(gè)源碼管理系統(tǒng)中的代碼放在一起管理。
  • .gclient文件:是 gclient 的控制文件,是一個(gè) python 腳本。

二、工具準(zhǔn)備

  • 梯子
  • git
  • github、ssh
  • curl、unzip (gclient sync 需要)
  • xcode (只編譯android也是需要裝xcode環(huán)境的)
  • python --version 2.x版本
  • depot_tools
    國(guó)外:git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
    國(guó)內(nèi):git clone https://source.codeaurora.org/quic/lc/chromium/tools/depot_tools
    其他:https://storage.googleapis.com/chrome-infra/depot_tools.zip
    配置環(huán)境變量:export PATH=$PATH:/path/to/depot_tools
    

三、源碼準(zhǔn)備

1. 克隆源碼

fork Flutter Engine 的源碼到自己的github倉(cāng)庫(kù)

2. 創(chuàng)建 目錄并添加 .gclient 文件

$ mkdir engine
$ cd engine
$ touch .gclient

.gclient文件內(nèi)容:(改為自己的engine倉(cāng)庫(kù))

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: 開(kāi)始會(huì)有進(jìn)度顯示,后面會(huì)下載大文件,不要終止,可以通過(guò)網(wǎng)絡(luò)監(jiān)控查看網(wǎng)速或流量,好幾個(gè)G的內(nèi)容,稍安勿躁。

4.與原倉(cāng)庫(kù)關(guān)聯(lián)

將自己fork出來(lái)的倉(cāng)庫(kù)與原倉(cāng)庫(kù)關(guān)聯(lián)同步,方便日后更新,進(jìn)入到src/flutter目錄:

  • a. 查看現(xiàn)有的遠(yuǎn)程倉(cāng)庫(kù)
     $ 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.添加指向原倉(cāng)庫(kù)的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.直接從原倉(cāng)庫(kù)的分支拉取代碼并直接合并代碼,其中pull = fetch + merge:
    $ git pull upstream <BRANCH_NAME>
    

5.匹配版本

在實(shí)際開(kāi)發(fā)中,一般不直接使用master的代碼直接編譯,都是需要獲取指定版本的engine代碼。可以通過(guò)本地安裝的flutter sdk(framework)版本來(lái)獲取所對(duì)應(yīng)的engine版本。

framework的分支規(guī)則如下:
a. stable是當(dāng)前的穩(wěn)定分支,無(wú)特殊情況,推薦開(kāi)發(fā)者使用該分支作為flutter sdk
b. master包含最新的特性,但是不穩(wěn)定
c. 每個(gè)版本會(huì)打上對(duì)應(yīng)的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

 ...省略其他信息

其實(shí)engine的version就是對(duì)應(yīng)的一次commit id,接下來(lái)需要切到對(duì)應(yīng)的commit版本,因?yàn)榍袚Q分支之后,某些依賴的版本可能有更改,所以需要再次同步。

$ cd engine/src/flutter
$ git reset --hard <engine_version>
$ gclient sync -D --with_branch_heads --with_tags -v

四、開(kāi)始編譯

編譯使用src/flutter/tools/gn工具:

gn -h 參數(shù)如下
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]

我們一般用到的構(gòu)建參數(shù)有以下幾種:

--android 指定android平臺(tái)
--ios 指定ios平臺(tái)
--runtime-mode debug,profile,release,jit_release
--unoptimized 默認(rèn)是optimized優(yōu)化過(guò)的
--android-cpu {arm,x64,x86,arm64} 默認(rèn)是arm(對(duì)應(yīng)arm-v7)
--ios --ios-cpu {arm,arm64}

開(kāi)始編譯,通過(guò)gn生成ninja需要的元數(shù)據(jù):以為編譯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

編譯之后,生成的產(chǎn)物在 src/out 目錄下,格式如下:

$ tree -L 1
.
├── android_debug
├── android_release
├── compile_commands.json
├── host_debug
└── host_release

compile_commands.json 可以作為IDE的索引文件,提供類/函數(shù)/變量的跳轉(zhuǎn)等能力。
ps: 擴(kuò)展說(shuō)明:

  1. gn編譯時(shí)需要上--no-lto參數(shù),否則執(zhí)行ninja命令可能出現(xiàn)以下錯(cuò)誤:
?  $ 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 通過(guò)-j參數(shù)指定并行的任務(wù)數(shù),不指定則cpu拉滿進(jìn)行編譯。
run N jobs in parallel [default=18, derived from CPUs available]

3.ninja 命令可以合并執(zhí)行:

$ 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

五、應(yīng)用產(chǎn)物

1、命令行指定使用engine產(chǎn)物:

$ flutter create testapp
$ cd testapp
$ flutter run --local-engine-src-path /Users/xxx/Library/Android/engine/src --local-engine=android_debug 

2、也可以在flutter項(xiàng)目的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 腳本內(nèi)容:

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')
            }
            // 當(dāng)使用本地engine時(shí)添加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')
                }
                // 當(dāng)使用本地engine時(shí)添加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
        }
    }


修改之后,直接運(yùn)行項(xiàng)目即可。

參考

設(shè)置engine編譯環(huán)境
Flutter engine host release build fails on macos
Flutter 的構(gòu)建模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容