前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GN学习记录

GN学习记录

作者头像
tyrionchen
发布2021-01-31 16:42:11
2.9K0
发布2021-01-31 16:42:11
举报
文章被收录于专栏:WebRTCWebRTC

近期准备学习WebRTC源码,发现WebRTC构建使用的是GN,花了些时间进行学习,这里做下笔记。

GN是ninja构建文件的元构建工具,能够构建出ninja的.ninja文件,比起ninja原本的构建命令GN能够比较好地进行依赖管理,并且能够很方便的输出构建图谱。

一、基础语法

变量 GN是动态类型语言,支持以下五种变量类型 布尔类型 64位有符号整数 字符串 Lists,类似数组 Scopes,类似字典

字符串 字符串是用双引号括起来的一串字符,使用反斜线作为转义字符,转义字符只支持以下三种字符的转义

代码语言:javascript
复制
\" (for literal quote)
\$ (for literal dollars sign)
\\ (for literal backslash)

字符串中允许使用美元符号$来代表变量,如果变量和其他字符连在一起了,我们可以使用大括号分隔开,例如

代码语言:javascript
复制
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支持追加内容,例如

代码语言:javascript
复制
a = [ "first" ]
a += [ "second" ] # [ "first", "second" ]
a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]

我们也可以从Lists里删除内容,例如

代码语言:javascript
复制
a = [ "first", "second", "third", "first" ]
b = a - [ "first" ] # [ "second", "third" ]
a -= [ "second" ] # [ "first", "third", "first" ]

?减号操作符-会搜索所有匹配的内容,然后全部删除。从a减去一个Lists,会从被减的Lists里删掉指定的内容 这里必须注意,减号操作符删除的内容必须存在,如果不存在,这个表达式会抛出错误,同时gn也不提供查询被删内容是否存在的检查,就问你头大不大 Lists内的元素也是从0开始的,可以使用中括号取内容

代码语言:javascript
复制
a = [ "first", "second", "third" ]
b = a[1] # -> "second"

其中,中括号取内容是一个只读操作,这里主要的使用场景是从外部脚本返回的列表中读取内容,读取列表内容的时候我们用得到这个中括号。

不能够给非空的Lists赋值非空Lists,但是可以给Lists赋值空值,例如

代码语言:javascript
复制
a = [ "one" ]
a = [ "two" ] # Error: overwriting nonempty list with a nonempty list.
a = [] # OK
a = [ "two" ] # OK

条件语句

类似C/C++

代码语言:javascript
复制
if (is_linux || (is_win && target_cpu == "x86")) {
sources -= [ "something.cc" ]
} else if (...) {
...
} else {
...
}

循环

代码语言:javascript
复制
foreach(i, mylist) {
print(i) # Note: i is a copy of each element, not a reference to it.
}

?gn认为通常我们构建时应该不需要做循环操作,如果确实需要可以确认下构建是否能够优化

函数调用 除了使用模板之外,通常用户无法自定义函数,gn中只能够调用函数,例如

代码语言:javascript
复制
print("hello, world")
assert(is_win, "This should only be executed on Windows")

?在gn更常见的函数调用会带上一个大括号,例如

代码语言:javascript
复制
static_library("mylibrary") {
sources = [ "a.cc" ]
}

二、基础操作

两个重要的配置文件

.gn文件: 定义了构建的根目录

//build/config/BUILDCONFIG.gn: 全局变量和配置的配置文件

输出构架目录

代码语言:javascript
复制
> 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

代码语言:javascript
复制
static_library(“base”) {
  sources = [
    “a.cc”,
    “b.cc”,
  ]
}

该配置在构建时会生成ninja的构建文件,将a.cc/b.cc两个文件编译成静态库,而base在gn中称为target

指定依赖

代码语言:javascript
复制
static_library(“base”) {
  sources = [
    “a.cc”,
    “b.cc”,
  ]

  deps = [
    “//fancypants”,
    “//foo/bar:baz”,
  ]
}

以上描述指定了base的两个依赖, 而依赖中出现的标志称为标签

关于标签

代码语言:javascript
复制
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

?构建目标的类型

代码语言:javascript
复制
executable, shared_library, static_library
loadable_module: 运行时加载模块,类似shared_library
source_set: 编译的源文件,这些源文件不会构建出中间

group: 一组被命名的构建目标
copy
action, action _foreach: 执行脚本
bundle_data, create_bundle: Mac & iOS

条件和表达式

代码语言:javascript
复制
component(“base”) {
  sources = [
    “a.cc”,
    “b.cc”,
  ]

  if (is_win || is_linux) {
    sources += [ “win_helper.cc” ]
  } else {
    sources -= [ “a.cc” ]
  }
}

构建时可以使用判定语句和表达式

编译器配置

代码语言:javascript
复制
executable(“doom_melon”) {
  sources = [ “doom_melon.cc” ]

  cflags = [ “-Wall” ]
  defines = [ “EVIL_BIT=1” ]
  include_dirs = [ “.” ]

  deps = [ “//base” ]
}

?使用config定义配置

代码语言:javascript
复制
config(“myconfig”) {
  defines = [ “EVIL_BIT=1” ]
}

executable(“doom_melon”) {
  ...
  configs += [ “:myconfig” ]
}

test(“doom_melon_tests”) {
  ...
  configs += [ “:myconfig” ]
}

不同的target可以使用相同的配置

配置共享

代码语言:javascript
复制
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.

共享依赖

代码语言:javascript
复制
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输出目标的相关信息

代码语言:javascript
复制
> 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支持过滤,如下

代码语言:javascript
复制
> 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

列出依赖路径

代码语言:javascript
复制
> 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之后头文件不能使用了

模块被谁依赖了?

代码语言:javascript
复制
> 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

校验引用头文件是否正确

代码语言:javascript
复制
> 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输出,例如

代码语言:javascript
复制
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"]

修改配置 每一个目标都可以修改默认配置,例如

代码语言:javascript
复制
executable(“doom_melon”) {
configs -= [
"//build/config/compiler:chromium_code",
]
configs += [
"//build/config/compiler:no_chromium_code",
]
}

编译参数 gn允许定义编译参数,用的在编译时候可以给这些参数传值

代码语言:javascript
复制
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进行使用 只要在命令行输入以下命令

代码语言:javascript
复制
> gn args out/Default

就会弹出一个编辑文本让用户输入需要修改的参数值

代码语言:javascript
复制
bypass_speech_api_quota = true
is_debug = false
is_component_build = true

列出所有编译参数

代码语言:javascript
复制
> 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

代码语言:javascript
复制
declare_args() {
# Controls Chrome branding.
is_chrome_branded = false
}

enable_crashing = is_win

?然后我们可以把这个文件导入到我们的BUILD.gn里

代码语言:javascript
复制
import(“//foo/build.gni”)

executable(“doom_melon”) {
if (is_chrome_branded) {
…
}
if (enable_crashing) {
… 
}
}

这样我们就能够使用gni文件中定义的编译参数和变量了

相关链接:

https://gn.googlesource.com/gn/

本文系外文翻译,前往查看

如有侵权,请联系?cloudcommunity@tencent.com?删除。

本文系外文翻译前往查看

如有侵权,请联系?cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、基础语法
  • 二、基础操作
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com