近期准备学习WebRTC源码,发现WebRTC构建使用的是GN,花了些时间进行学习,这里做下笔记。
GN是ninja构建文件的元构建工具,能够构建出ninja的.ninja文件,比起ninja原本的构建命令GN能够比较好地进行依赖管理,并且能够很方便的输出构建图谱。
变量 GN是动态类型语言,支持以下五种变量类型 布尔类型 64位有符号整数 字符串 Lists,类似数组 Scopes,类似字典
字符串 字符串是用双引号括起来的一串字符,使用反斜线作为转义字符,转义字符只支持以下三种字符的转义
\" (for literal quote)
\$ (for literal dollars sign)
\\ (for literal backslash)
字符串中允许使用美元符号$来代表变量,如果变量和其他字符连在一起了,我们可以使用大括号分隔开,例如
a = "mypath"
b = "$a/foo.cc" # b -> "mypath/foo.cc"
c = "foo${a}bar.cc" # c -> "foomypathbar.cc"
对于单个字符我们可以使用这样的十六进制数值表示“$0xFF”,例如 "look$0x0Alike$0x0Athis", 这里$0x0A会作为一个newline字符进行换行
Lists gn没办法获取Lists的长度,只能通过(a == [])这样的表达式判断Lists是不是空 Lists支持追加内容,例如
a = [ "first" ]
a += [ "second" ] # [ "first", "second" ]
a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]
我们也可以从Lists里删除内容,例如
a = [ "first", "second", "third", "first" ]
b = a - [ "first" ] # [ "second", "third" ]
a -= [ "second" ] # [ "first", "third", "first" ]
?减号操作符-会搜索所有匹配的内容,然后全部删除。从a减去一个Lists,会从被减的Lists里删掉指定的内容 这里必须注意,减号操作符删除的内容必须存在,如果不存在,这个表达式会抛出错误,同时gn也不提供查询被删内容是否存在的检查,就问你头大不大 Lists内的元素也是从0开始的,可以使用中括号取内容
a = [ "first", "second", "third" ]
b = a[1] # -> "second"
其中,中括号取内容是一个只读操作,这里主要的使用场景是从外部脚本返回的列表中读取内容,读取列表内容的时候我们用得到这个中括号。
不能够给非空的Lists赋值非空Lists,但是可以给Lists赋值空值,例如
a = [ "one" ]
a = [ "two" ] # Error: overwriting nonempty list with a nonempty list.
a = [] # OK
a = [ "two" ] # OK
条件语句
类似C/C++
if (is_linux || (is_win && target_cpu == "x86")) {
sources -= [ "something.cc" ]
} else if (...) {
...
} else {
...
}
循环
foreach(i, mylist) {
print(i) # Note: i is a copy of each element, not a reference to it.
}
?gn认为通常我们构建时应该不需要做循环操作,如果确实需要可以确认下构建是否能够优化
函数调用 除了使用模板之外,通常用户无法自定义函数,gn中只能够调用函数,例如
print("hello, world")
assert(is_win, "This should only be executed on Windows")
?在gn更常见的函数调用会带上一个大括号,例如
static_library("mylibrary") {
sources = [ "a.cc" ]
}
两个重要的配置文件
.gn文件: 定义了构建的根目录
//build/config/BUILDCONFIG.gn: 全局变量和配置的配置文件
输出构架目录
> gn gen out/Default
Done.
> touch base/BUILD.gn
> ninja -C out/Default base
[1/1] Regenerating ninja files
[101/323] CXX obj/base/icu_utf.o
...
> gn clean out/Default
输出构建目录后,在后续ninja编译时会自动重新生成构建文件
最简单的例子
BUILD.gn
static_library(“base”) {
sources = [
“a.cc”,
“b.cc”,
]
}
该配置在构建时会生成ninja的构建文件,将a.cc/b.cc两个文件编译成静态库,而base在gn中称为target
指定依赖
static_library(“base”) {
sources = [
“a.cc”,
“b.cc”,
]
deps = [
“//fancypants”,
“//foo/bar:baz”,
]
}
以上描述指定了base的两个依赖, 而依赖中出现的标志称为标签
关于标签
Full label
//chrome/browser:version
→ 在/根目录/chrome/browser/BUILD.gn里搜索version
Implicit name
//base
→ //base:base的缩写,在/根目录/base/BUILD.gn里搜索base
In current file
:baz
→ 在当前的BUILD.gn里搜索baz
?构建目标的类型
executable, shared_library, static_library
loadable_module: 运行时加载模块,类似shared_library
source_set: 编译的源文件,这些源文件不会构建出中间
group: 一组被命名的构建目标
copy
action, action _foreach: 执行脚本
bundle_data, create_bundle: Mac & iOS
条件和表达式
component(“base”) {
sources = [
“a.cc”,
“b.cc”,
]
if (is_win || is_linux) {
sources += [ “win_helper.cc” ]
} else {
sources -= [ “a.cc” ]
}
}
构建时可以使用判定语句和表达式
编译器配置
executable(“doom_melon”) {
sources = [ “doom_melon.cc” ]
cflags = [ “-Wall” ]
defines = [ “EVIL_BIT=1” ]
include_dirs = [ “.” ]
deps = [ “//base” ]
}
?使用config定义配置
config(“myconfig”) {
defines = [ “EVIL_BIT=1” ]
}
executable(“doom_melon”) {
...
configs += [ “:myconfig” ]
}
test(“doom_melon_tests”) {
...
configs += [ “:myconfig” ]
}
不同的target可以使用相同的配置
配置共享
config(“icu_dirs”) {
include_dirs = [ “include” ]
}
shared_library(“icu”) {
public_configs = [ “:icu_dirs” ]
}
executable(“doom_melon”) {
deps = [
# Apply ICU’s public_configs.
“:icu”,
]
}
目标doom_melon依赖了共享库icu,由于共享库icu使用public_config引用了配置icu_dirs,doom_melon也都会引用配置icu_dirs。但由于doom_melon依赖icu使用的是deps而不是public_deps,因此其他依赖doom_melon的目标无法继承来自icu的config icu_dirs.
共享依赖
shared_library(“i18n_utils”) {
…
public_deps = [
“//third_party/icu”,
]
}
executable(“doom_melon”) {
deps = [
# Apply ICU’s public_configs.
“:i18n_utils”,
]
}
以上由于动态库i18n_utils通过public_deps依赖了icu,因此依赖i18n_utils的doom_melon会继承到来自icu的config
使用desc输出目标的相关信息
> gn desc out/Default //base
… <lots o’ stuff> …
> gn desc out/Default
//tools/gn deps --tree
//base:base
//base:base_paths
//base:base_static
//base:build_date
//base:copy_dbghelp.dll
//base:debugging_flags
//base/allocator:allocator
//base/allocator:allocator_shim
//base/allocator:prep_libc
//base/third_party/dynamic_annotations:dynamic_annotations
//base/trace_event/etw_manifest:chrome_events_win
//build/config/sanitizers:deps
//third_party/modp_b64:modp_b64
//build/config/sanitizers:deps
//tools/gn:gn_lib
//base:base...
//base/third_party/dynamic_annotations:dynamic_annotations
//tools/gn:last_commit_position
如上: 1.通过gn desc out/Default //base 输出base这个目标的所有信息 2.通过gn desc out/Default //tools/gn deps --tree 指定输出目标gn的依赖关系树
列出所有目标 我们可以通过gn ls列出所有目标,gn ls支持过滤,如下
> gn ls out/Default “//base/*”
//base:base
//base:base_i18n_perftests
//base:base_i18n_perftests_run
//base:base_paths
//base:base_perftests
//base:base_perftests_run
//base:base_static
//base:base_unittests
//base:base_unittests_bundle_data
//base:base_unittests_run
//base:build_date
//base:build_utf8_validator_tables
//base:check_example
//base:debugging_flags
//base:i18n
//base:message_loop_tests
//base/allocator:allocator
//base/allocator:features
//base/allocator:tcmalloc
列出依赖路径
> gn path out/Default
//content/browser //cc/base
//content/browser:browser --[private]-->
//cc:cc --[private]-->
//cc/base:base
Showing one of 118 unique non-data paths.
0 of them are public.
Use --all to print all paths.
?如上,我们可以列出//content/browser到//cc/base的依赖路径。 还可以看到公共依赖是从哪里开始断开的,例如某个头文件在模块A明明是被通过public_deps引入的,为什么引入A之后头文件不能使用了
模块被谁依赖了?
> gn refs out/Default //cc
//ash:ash
//ash/mus:lib
//blimp/client:blimp_client
...
> gn refs out/Default //cc --tree
//media/blink:blink
//media/blink:media_blink_unittests
//media/blink:media_blink_unittests_run
...
> gn refs out/Default
//base/macros.h
//base:base
?以上通过gn refs查看//cc这个目标被那些目标给依赖了, 带上--tree参数还能以树状输出依赖树, 第三个例子演示了通过refs搜索哪个目标引用了macros.h这个头文件
构建流程的设计原则 1.模块化,我们应该把我们的代码按不同的文件夹和目标来组织 2.不同模块要有非常清晰的依赖关系或者要非常清晰的不产生依赖
团队开发时如何隔离代码 1.使用deps和public_deps来区分哪些依赖是需要公布出去的,哪些是私有的 2.使用visibility限制哪些目标能够依赖我们编写的目标 3.使用testonly限制哪些目标不能够链接到发布代码中 4.只有公共的头文件才能够使用public公布出去 5.必要的时候使用assert_no_deps
校验引用头文件是否正确
> gn check out/Default
ERROR at //base/files/file_path.cc
#include "sql/statement.h"
^--------------
It is not in any dependency of
//base:base
The include file is in the target(s):
//sql:sql
which should somehow be reachable.
?以上结果输出,但由于base目标没有依赖sql,因此file_path.cc引用了sql的头文件失败
构建的流程 1.首先,配置文件//build/config/BUILDCONFIG.gn定义了全局变量以及默认配置 2.其次,一个工程里面会有多个构建模块,每个模块会有一个BUILD.gn,所有的构建都是没有先后顺序,同时运行的。如果不同的构建模块有依赖关系,只能通过Build.gn内的标签来指定.每个模块构建的时候都会加载BUILDCONFIG的默认配置。
如果我们想查看运行时候都定义了哪些配置,可以通过print输出,例如
executable(“doom_melon”) {
print(configs)
...
}
> gn gen out/Default
["//build/config:feature_flags",
"//build/config/compiler:compiler",
"//build/config/compiler:clang_stackrealign",
"//build/config/compiler:compiler_arm_fpu",
"//build/config/compiler:chromium_code",
"//build/config/compiler:default_include_dirs",
"//build/config/compiler:default_optimization", "//build/config/compiler:default_symbols",
"//build/config/compiler:no_rtti",
"//build/config/compiler:runtime_library",
"//build/config/sanitizers:default_sanitizer_flags",
"//build/config/sanitizers:default_sanitizer_coverage_flags",
"//build/config/win:lean_and_mean",
"//build/config/win:nominmax",
"//build/config/win:unicode",
"//build/config/win:winver",
"//build/config:debug"]
修改配置 每一个目标都可以修改默认配置,例如
executable(“doom_melon”) {
configs -= [
"//build/config/compiler:chromium_code",
]
configs += [
"//build/config/compiler:no_chromium_code",
]
}
编译参数 gn允许定义编译参数,用的在编译时候可以给这些参数传值
declare_args() {
# Allow unlimited requests
# to the Google speech API.
bypass_speech_api_quota = false
}
executable(“doom_melon”) {
if (bypass_speech_api_quota) {
…
}
}
以上定义了一个参数bypass_speech_api_quota,默认值是false,并且在doom_melon进行使用 只要在命令行输入以下命令
> gn args out/Default
就会弹出一个编辑文本让用户输入需要修改的参数值
bypass_speech_api_quota = true
is_debug = false
is_component_build = true
列出所有编译参数
> gn args --list out/Default
v8_use_snapshot Default = true
//v8/BUILD.gn:23
Enable the snapshot feature, for fast context creation.
http://v8project.blogspot.com/2015/09/custom-startup-snapshots.html
visual_studio_path Default = ""
//build/config/win/visual_studio_version.gni:9
Path to Visual Studio. If empty, the default is used which is to use the
automatic toolchain in depot_tools. If set, you must also set the
visual_studio_version and wdk_path.
visual_studio_version Default = ""
//build/config/win/visual_studio_version.gni:13
Version of Visual Studio pointed to by the visual_studio_path.
Use "2013" for Visual Studio 2013, or "2013e" for the Express version.
wdk_path Default = ""
//build/config/win/visual_studio_version.gni:17
Directory of the Windows driver kit. If visual_studio_path is empty, this
will be auto-filled.
win_console_app Default = false
//build/config/win/console_app.gni:12
If true, builds as a console app (rather than a windowed app), which allows
logging to be printed to the user. This will cause a terminal window to pop
up when the executable is not run from the command line, so should only be
used for development. Only has an effect on Windows builds.
windows_sdk_path Default = "C:\Program Files (x86)\Windows Kits\10"
//build/config/win/visual_studio_version.gni:22
Full path to the Windows SDK, not including a backslash at the end.
This value is the default location, override if you have a different
installation location.
定义导入文件*.gni 我们可以在被导入的文件中定义编译参数和变量,例如我们可以写一个这样的build.gni
declare_args() {
# Controls Chrome branding.
is_chrome_branded = false
}
enable_crashing = is_win
?然后我们可以把这个文件导入到我们的BUILD.gn里
import(“//foo/build.gni”)
executable(“doom_melon”) {
if (is_chrome_branded) {
…
}
if (enable_crashing) {
…
}
}
这样我们就能够使用gni文件中定义的编译参数和变量了
相关链接:
本文系外文翻译,前往查看
如有侵权,请联系?cloudcommunity@tencent.com?删除。
本文系外文翻译,前往查看
如有侵权,请联系?cloudcommunity@tencent.com 删除。