引言
克隆 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/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 代表切换工作目录到指定目录