Flutter Engine 项目构建工具 - gn

1,634 阅读5分钟

引言

克隆 Flutter Engine 项目 或者 Dart SDK 项目,会发现项目中存在大量的BUILD.gn文件,而BUILD.gn文件跟Android项目中build.gradle文件性质一样,用于控制构建流程的。所有的GN文件组成一个构建系统,即GN构建系统。GN系统通过构建命令生成对应的Ninja文件,而生成的Ninja文件才是真正用于编译项目。

GN系统构建流程

  • 在当前工作目录寻找.gn文件 (如果不存在则继续往上级目录寻找),将.gn文件所在的目录设置为 "source root" 目录。
  • 从.gn文件中获取配置文件BUILDCONFIG.gn并读取,用于设置 toolchain 以及 Target默认配置 等。
  • 从 "source root" 目录中读取 BUILD.gn 文件以及其中引用的BUILD.gn文件。

GN常用指令

可通过 gn help 命令,了解所有指令。

1、添加构建参数(指令会打开一个文件编辑器,用于配置参数,形如:target_os = "android",配置完成后生成 <out_dir>/args.gn文件

gn args <out_dir>

2、查看构建参数

gn args --list <out_dir>

3、根据构建参数以及项目构建文件(.gn、BUILDCONFIG.gn、BUILD.gn)生成 ninja文件

gn gen <out_dir>

4、查看构建产物中的所有Target

gn ls <out_dir>

5、查看xx Target详细信息

gn desc <out_dir> xx

6、查看依赖xx配置的Target

gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)* [--all] [--all-toolchains] [--as=...] [--testonly=...] [--type=...]

7、清空所有构建缓存

gn clean <out_dir>

###GN构建文件编写

chromium.googlesource.com/chromium/sr…

GN 标识规则

  • 标识是有着预定义格式的字符串,依赖图中所有的元素(目标,配置和工具链)都由标识唯一识别,通常情况下,标识看上去是如下样子。它由三部分组成:source-tree绝对路径、冒号和名称,表示在/base/test/BUILD.gn中查找名称是“test_support”的标识。
   //base/test:test_support
  • 当加载构建文件时,如果在相对于source root给定的路径不存在时,GN将查找build/secondary中的辅助树,辅助树只是备用而不是覆盖,因此正常位置中的文件始终优先。
  • 完整的标识还包括处理该标识要使用的工具链。工具链通常是以继承的方式被默认指定,当然你也可以显示指定。这个标识会去“//build/toolchain/win”文件查到名叫”msvc”的工具链定义,那个定义会知道如何处理“test_support”这个标识。
  //base/test:test_support(//build/toolchain/win:msvc)
  • 如果你指向的标识就在此build文件,你可以省略路径,而只是从冒号开始,":base"
  • 你可以以相对于当前目录的方式指定路径。标准上说,要引用非本文件中标识时,除了它要在不同上下文运行,我们建议使用绝对路径。什么是要在不同上下文运行?举个列子,一个项目它既要能构造独立版本,又可能是其它项目的子模块。
  • 书写时,可以省略标识的第二部分、第三部分,即冒号和名称,这时名称默认使用目录的最后一段。标准上说,在这种情况建议省略第二、三部分。(以下的“=”表示等同)
`//net = //net:net //tools/gn = //tools/gn:gn

Flutter Engine 实际案例分析

  • 按照 官方引导 搭建flutter engine 环境
  • 查看 官方引导 了解重编引擎流程,主要涉及如下两个命令:
./flutter/tools/gn --android --unoptimized //通过gn系统生成构建产物
ninja ninja -C out/android_debug_unopt //使用ninja文件编译引擎

.flutter/tools/gn (python文件,并非真实gn)

...
 
def main(argv):
  //解析命令行参数 
  args = parse_args(argv)
  //找到真实的gn路径
  exe = '.exe' if sys.platform.startswith(('cygwin', 'win')) else ''
  command = [
    '%s/flutter/third_party/gn/gn%s' % (SRC_ROOT, exe),
    'gen',
    '--check',
  ]
  //添加--ide参数,指定生成项目的类型:xcode、vscode
  if args.ide != '':
    command.append('--ide=%s' % args.ide)
  elif sys.platform == 'darwin':
    # On the Mac, generate an Xcode project by default.
    command.append('--ide=xcode')
    command.append('--xcode-project=flutter_engine')
    command.append('--xcode-build-system=new')
  elif sys.platform.startswith('win'):
    # On Windows, generate a Visual Studio project.
    command.append('--ide=vs')
  //通过解析命令行参数,生成gn执行器参数  
  gn_args = to_command_line(to_gn_args(args))
  //获取gn构建产物生成输出目录
  out_dir = get_out_dir(args)
  command.append(out_dir)
  command.append('--args=%s' % ' '.join(gn_args))
  print("Generating GN files in: %s" % out_dir)
  try:
    //执行gn命令
    gn_call_result = subprocess.call(command, cwd=SRC_ROOT)
  except subprocess.CalledProcessError as exc:
    print("Failed to generate gn files: ", exc.returncode, exc.output)
    sys.exit(1)
    ...

if __name__ == '__main__':
    sys.exit(main(sys.argv))

最后调用真实gn命令如下:

/Users/xxx/flutter-workspace/engine/src/flutter/third_party/gn/gn gen  //命令gn gen
--check --ide=xcode --xcode-project=flutter_engine --xcode-build-system=new //gn内部参数
out/android_debug_unopt  //指定输出目录
//gn 构建参数
--args=skia_enable_pdf=false enable_lto=false full_dart_sdk=false use_clang_static_analyzer=false flutter_enable_skshaper=true skia_use_expat=true enable_bitcode=false skia_use_fontconfig=false skia_use_dng_sdk=false skia_enable_flutter_defines=true use_goma=false flutter_always_use_skshaper=false embedder_for_target=false is_official_build=true android_full_debug=true skia_use_icu=true is_clang=true stripped_symbols=true bssl_use_clang_integrated_as=true skia_use_sfntly=false dart_target_arch="arm" skia_gl_standard="gles" skia_use_wuffs=true flutter_use_fontconfig=false dart_component_kind="static_library" enable_desktop_embeddings=true flutter_runtime_mode="debug" goma_dir="None" dart_version_git_info=true target_os="android" skia_use_x11=false enable_coverage=false target_cpu="arm" dart_runtime_mode="develop" skia_enable_icu_ubrk_safeclone=true dart_lib_export_symbols=false is_debug=true'

生成Args.gn文件

skia_enable_pdf = false
enable_lto = false
full_dart_sdk = false
use_clang_static_analyzer = false
flutter_enable_skshaper = true
skia_use_expat = true
enable_bitcode = false
skia_use_fontconfig = false
skia_use_dng_sdk = false
skia_enable_flutter_defines = true
use_goma = false
flutter_always_use_skshaper = false
embedder_for_target = false
is_official_build = true
android_full_debug = true
skia_use_icu = true
is_clang = true
stripped_symbols = true
bssl_use_clang_integrated_as = true
skia_use_sfntly = false
dart_target_arch = "arm"
skia_gl_standard = "gles"
skia_use_wuffs = true
flutter_use_fontconfig = false
dart_component_kind = "static_library"
enable_desktop_embeddings = true
flutter_runtime_mode = "debug"
goma_dir = "None"
dart_version_git_info = true
target_os = "android"
skia_use_x11 = false
enable_coverage = false
target_cpu = "arm"
dart_runtime_mode = "develop"
skia_enable_icu_ubrk_safeclone = true
dart_lib_export_symbols = false
is_debug = true

####.gn

...
buildconfig = "//build/config/BUILDCONFIG.gn"
...

BUILDCONFIG.gn

整个BUILDCONFIG.gn文件分为如下几个代码块:

  • PLATFORM SELECTION: There are two main things to set: "os" and "cpu"
if (current_cpu == "") {
 current_cpu = target_cpu
}
if (current_os == "") {
 if (host_os == "win") {
   current_os = "win"
 } else {
   current_os = target_os
 }
}
  • BUILD FLAGS: This block lists input arguments to the build, along with their default values.
declare_args() {
  symbol_level = -1
  is_component_build = false
  is_official_build = false
  is_debug = true
  is_desktop_linux = current_os == "linux" && current_os != "chromeos"
  is_clang = current_os == "mac" || current_os == "ios" ||
             current_os == "linux" || current_os == "chromeos"
  is_asan = false
  is_lsan = false
  is_msan = false
  is_tsan = false
  is_ubsan = false
  if (current_os == "chromeos") {
    cros_use_custom_toolchain = false
  }
}
  • OS DEFINITIONS: We set these various is_FOO booleans for convenience in writing OS-based conditions.
...
 else if (current_os == "android") {
  is_android = true
  is_chromeos = false
  is_fuchsia = false
  is_fuchsia_host = false
  is_ios = false
  is_linux = false
  is_mac = false
  is_posix = true
  is_win = false
}
...
  • BUILD OPTIONS: These Sanitizers all imply using the Clang compiler
if (!is_clang && (is_asan || is_lsan || is_tsan || is_msan)) {
  is_clang = true
}

use_flutter_cxx = is_clang && (is_linux || is_android || is_mac)
  • TARGET DEFAULTS: Set up the default configuration for every build target of the given type
//设置编译配置
_native_compiler_configs = [
  "//build/config:feature_flags",
  "//build/config/compiler:compiler",
  "//build/config/compiler:cxx_version_default",
  "//build/config/compiler:compiler_arm_fpu",
  "//build/config/compiler:chromium_code",
  "//build/config/compiler:default_include_dirs",
  "//build/config/compiler:no_rtti",
  "//build/config/compiler:runtime_library",
]
...

//设置运行配置
_executable_configs =_native_compiler_configs + [ "//build/config:default_libs" ]
... 
  • TOOLCHAIN SETUP: Here we set the default toolchain
...
 else if (is_android) {
  if (host_os == "linux") {
    # Use clang for the x86/64 Linux host builds.
    if (host_cpu == "x86" || host_cpu == "x64") {
      host_toolchain = "//build/toolchain/linux:clang_$host_cpu"
    } else {
      host_toolchain = "//build/toolchain/linux:$host_cpu"
    }
  } else if (host_os == "mac") {
    host_toolchain = "//build/toolchain/mac:clang_$host_cpu"
  } else if (host_os == "win") {
    host_toolchain = "//build/toolchain/win:$current_cpu"
  } else {
    assert(false, "Unknown host for android cross compile")
  }
  if (is_clang) {
    set_default_toolchain("//build/toolchain/android:clang_$current_cpu")
  } else {
    set_default_toolchain("//build/toolchain/android:$current_cpu")
  }
...

source root

//默认执行的入口函数
group("default") {
  testonly = true
  deps = [
    "//flutter",  //缩写,全称为 //flutter:flutter
  ]
}
...

####src/flutter/BUILD.gn

// 入口函数
group("flutter") {
  ...
}

####调用Ninja命令生成编译产物

ninja -C out/android_debug_unopt  // -C 代表切换工作目录到指定目录