以前chromium采用的是GYP构建系统,最新的版本已经使用GN构建系统。
GN的优点反正就是编译速度快,GYP比makefile快,GN比GYP快20倍(据说是),而且书写不再那么复杂难懂。所以如果你的代码很庞大,那么GN + ninja是个不错的搭配选择。我现在也是在搭建这样的编译环境。
也参考了别人的一些学习笔记,总之也是有收获的,要想融会贯通,还是需要自己学习不同人的长处,并且自己去实践。我也是刚入皮毛,有不对之处还望指出。
我的排版一向都很糟糕,不会排。 反正现在开始进入正题了。
--------------------------------------------------------------------------------
1. 获取GN代码,自己编译GN命令(本人linux用户)。
git clone https://gn.googlesource.com/gn
这些命令在你克隆完代码后到gn目录下面,打开README.md文件可以找到
cd gn
python build/gen.py
ninja -C out
2. 编译完成后,到out目录下面可以看到已经生成gn可执行文件。
然后你可以将gn拷贝到/usr/sbin下面,这样你在任何地方都可以直接执行gn命令,类似于ls等系统命令。
3. gn gen —— 根据GN规则生成ninja文件
gn gen out —— out目录如果第一次时不存在, 执行完这条命令之后就会生成一个out目录,你也可以把out换成别的名字,out目录下面会有buid.ninja等几个文件。
3.1 注意
首先你要有".gn"文件,切记文件的全名就是 ".gn" ,这个文件是必须要有的,不然就会报错,因为它里面指定了 buildconfig的值,假如你的源文件在src下面,那么你的src下面也应该有个.gn文件。(是否可以有多个.gn还未测试)
其次,获取到的gn源码中有.gn文件,自己可以看看。
3.2 BUILDCONFIG.gn
这文件配置了一些默认的配置,还有使用的toolchain工具链。这个文件中的配置是全局的。
当你编译的时候首先就会去找.gn文件,然后找到BUILDCONFIG.gn,再根据这个文件找到toolchain,
有了toolchain就开始编译了,那么首先要找target, 入口就是和.gn同级目录下面的BUILD.gn文件。所以
在你的.gn下面一定要有BUILD.gn文件。当你这些文件都固定之后,无论你在哪个路径下执行gn gen命令,
生成的ninja文件内容都是一样的。因为你的target存在于BUILD.gn文件,而这些文件里面的配置没有变,所以即使
你在src/server下面去执行gn gen out,那么你得到的东西和在src下面执行是完全一样的。(不好意思,一句话喜欢 反复说,很罗嗦的我)
3.3 两个斜杠 "//" 路径到底是什么 ?
你看一些学习文档会发现它们都有提到一个源树绝对路径"//", 知道linux的都了解一个 "/"表示的是系统的根路径,
在gn中,一个"/"也是代表了系统根路径,但两个斜杠"//"显然不是系统的根路径了。
其实, "//" 表示的是.gn所在的路径,假如你的.gn 在src下面, 那么"//"就表示的是 src路径,因此,假如src下 面有server目录,表示方法就是"//server"。
4. target 有哪些类型 ?
在GN中,它表示你要编译什么文件出来,比如executable(可执行文件),static_library(静态库),shared_library(动态 库),... ...
●executable, shared_library, static_library
●loadable_module: 运行时加载模块
●source_set: 被编译的源文件集合,这种编译不会生成任何库文件
●group: 声明一组target,你可以写一个group,里面放不同的库target,使用的时候就用group的名字代表这些targets
●copy: 拷贝文件
●action, action _foreach: 运行一个脚本时所用
●bundle_data, create_bundle: Mac & iOS
●component: shared library or source set depending on mode
●test
●app: executable or iOS application + bundle
●android_apk, generate_jni, etc.: Lots of Android ones!
5. 条件及表达式
语法类似C/C++, 比如判断语句: if() { }
component() {
sources = [
"a.cc",
"b.cc",
]
if(is_win || is_linux) {
sources += ["win_helprt.cc"]
} else {
sources -= ["a.cc"]
}
}
6. 编译配置
常用到的:
6.1 flag标志
cflags —— 会传递给C/C++/Object C/Object C++的编译器
如果想只传给某一个编译器的话,使用下面的:
cflags_c —— 跟在cflags后面,传递给C代码编译器
cflags_cc —— 跟在cflags后面,传递给C++代码编译器
cflags_objc —— 跟在cflags后面,传递给Object C代码编译器
cflags_objcc —— 跟在cflags后面,传递给Object C++代码编译器
ldflags —— 传递给链接器的参数,大部分target用不到这个,一般使用libs和lib_dirs
6.2 target依赖关系
deps —— 私有链接依赖
public_deps —— 公有链接依赖
先比较上面两个, deps私有的,也就是无法继承依赖,A -> B -> C(A依赖B,B依赖C),
假设C里面有个头文件c.h, 那么B可以 #include "c.h" , 但是 A不行。
但如果是public_deps,那么A也可以像B一样 #include "c.h" 。
也就是是否可以越级传递关系。(我是这样理解的,比那些一大堆抽象的东西容易明白,如果B里面声明为public,那 么 A应该也是可以使用, 请参考C++类的继承吧,需要验证哈,我不确定,只是看到文档有这样写)
像这样的组合还有configs/public_configs
data —— Runtime data file dependencies (没搞懂这个,直接从文档粘过来了)
data_deps —— 运行时依赖,比如plugin,这些库可能编译时是没有的, 也许是第三方提供的, 那么在没有的时候
需要编译通过,而在运行时只要有这个库就可以了,这样的库就设置在这里,等运行时使用。
6.3 include_dirs/libs/lib_dirs
include_dirs —— target所要使用的头文件所在路径
libs —— target需要依赖的第三方库,参考下 "-l"的作用
lib_dirs —— target需要依赖的第三方库的路径,参考 "-L"的作用
6.4 defines
定义变量的,也就是类似于C/C++定义宏开关。 你想在对哪个target生效,你可以在相应的target里面定义。例如:
executable("testgn") {
sources = [
"testgn.cpp",
]
defines = ["FOR_TEST=true"]
}
那么你就可以在testgn.cpp中使用该宏。如果定义多个呢?
defines = [
"FOR_TEST=true",
"FOR_TESTGN=true",
...
]
以此类推放进去。
6.5 visibility —— 对哪些targets可见(默认是public)
visibility = [":*"] —— 表示私有的,本target只能被本文件中的target所使用依赖
visibility = [ "*" ] —— 表示公有的,任何target都可以使用依赖本target
visibility = [ "./*" ] —— 当前目录及子目录下的target都可以依赖
visibility = [ "//server:*" ] —— 表示只有//server/BUILD.gn文件中的target可以使用依赖本target
visibility = [ "//server/*" ] —— 表示server或者子目录下的target可以使用依赖本target
visibility = [ ] —— 用于叶子节点(这个不是很懂现在,感觉是不让别人依赖了)
7. action/template/copy/defined/declare_args
7.1 action
运行python脚本来完成任务。
action(“myaction”) {
script = “myscript.py”
inputs = [ “myfile.txt” ]
outputs = [
“generated.txt”, # Error!
]
}
GN不允许写到源树绝对路径下面,也就是你不能输出到 // 下面, 对于输出路径我们一般都选择在 $target_out_dir 或者$target_gen_dir下面,因此上面可以这样改写:
action(“myaction”) {
script = “myscript.py”
inputs = [ “myfile.txt” ]
outputs = [
target_out_dir + “/output.txt”,
]
print(outputs)
}
或者
action(“myaction”) {
script = “myscript.py”
inputs = [ “myfile.txt” ]
outputs = [
“$target_out_dir/output.txt”,
]
print(“out = $outputs”)
}
注意下区别,上面的target_out_dir没有$符号,且与后面的字符串用的是“+”连接;
下面的有 $符号,且类似于linux shell的变量用法。
target_out_dir/target_gen_dir是可以直接使用的, 不是你自己定义的变量,应该是GN定义的。
用法1:
action(“myaction”) {
script = “myscript.py”
inputs = [ “myfile.txt” ]
outputs = [
“$target_out_dir/output.txt”,
]
args = [
“-i”, inputs[0], outputs[0],
]
}
用法2:
action(“myaction”) {
script = “myscript.py”
inputs = [ “myfile.txt” ]
outputs = [
“$target_out_dir/output.txt”,
]
args = [
“-i”,
rebase_path(inputs[0],
root_build_dir)
rebase_path(outputs[0],
root_build_dir)
]
}
首先,想给脚本传递参数必须使用args。
然而用法1中虽然使用了args,但是传递进去的参数有错误。GN默认的是"//"路径。
python无法识别GN的风格,所以需要将"//"进行转化,用到rebase_path,其中root_build_dir就是"//out"路径。
7.2 template
模板是可以被重复使用的,所以一般会写在gni文件中,这样只要gn文件导入后就可以使用了。
具体用法可看执行命令 gn help template
7.3 copy —— 声明一个拷贝文件的target
所有文件的输出都在编译时指定的输出文件目录下面。(gn gen out, 则所有的输出文件都在out下面)
通常我们会用到$target_out_dir和$target_gen_dir目录
target_out_dir —— 指定你当前BUILD.gn所在路径,前缀是//out/obj
target_gen_dir —— 指定你当前BUILD.gn所在路径,前缀是//out/gen
假如当前编译的target为//server:server, 那么当前BUILD.gn目录就是server。
则 target_out_dir => //out/obj/server
target_gen_dir => //out/gen/server
7.4 defined
返回一个标识是否有定义过,通常在template中使用。可以检查一个标识的作用域。
例如:
defined(foo) —— 检查foo是否在当前的作用域内被定义
defined(foo.bar) —— 检查bar是否在作用域foo中被定义
7.5 declare_args —— 声明编译时的参数
比如下面写的定义变量或者常量,在编译期间会传递给别的*.gn或者*.gni文件使用。
可参考 gn help buildargs
8. import
import —— 导入*.gni文件, *.gni文件可以定义共用的变量或者常量,如:
declare_args() {
is_chrome_branded = false
}
enable_crashing = is_win
然后在BUILD.gn文件中import即可使用is_chrome_branded/enbable_crashing
9. gn help
如果你想查看gn支持哪些命令,可以直接运行 gn help 。
如果想查看命令或者函数或者参数的具体用法,那么就gn help <命令|函数|参数>。
例如:
查看lib_dirs详解: gn help lib_dirs
10. configs
包括flags,defines,include_dirs等,但是不包括sources和deps/public_deps等依赖性文件