Gyp 学习记录
GYP(Generate Your Projects)是由 Chromium 团队开发的跨平台自动化项目构建工具。
gyp简述
GYP的输入是.gyp和.gypi文件,.gypi文件是用于.gyp文件include使用的。.gyp文件就是符合特定格式的json文件。
gyp文件中包含target,Visual C++下,gyp生成sln,target生成vcproj或vxcproj。
1.1 设计目标
gyp设计针对目标就是为了解决chromium浏览器构建问题,最重要的就是支持多平台的构建。因为Chromium内部都是c/c++文件,因此主要考虑方便c/c++程序的构建。设计时候还考虑到下面这些问题:
- debug vs release
- cross compile
- toolchain interface
1.2 构建文件
构建文件名字不固定,但是后缀通常是.gyp和.gypi(gyp included).构建文件内容就是python的一个数据结构.
example:
1 | { |
1.3 .gyp文件剖析
整个构建文件最顶层是一个字典,包含了下面这些key:
- conditions //条件判断
- includes // 包含的构建文件
- target defualt //构建目标默认属性
- targets // 构建目标列表
- variables // 构建文件使用的变量
1.3.1 conditions
conditions分两种行为。 普通的conditions就在load构建文件之后立即计算,另外一种是target(conditions)是在计算完成依赖之后然后来进行计算的,两个过程分别就是early and late phases阶段。对于conditions写法非常简单
1 | 'conditions':[ |
对于condition的判断,主要还是为了能够修改一些描述属性。从文档上来看,默认提供的条件就是OS判断,其他判断应该都是变量的判断。
1.3.2 targets
target部分的话对target(default)里面设定的内容默认进行merge。比如上面例子的话,对于target/test来说,使用的define就会是-DDEBUG -DFOO.当然对于这种东西是可以进行其他策略选择的,比如如果修改成下面格式,那么就是直接替换:
1 | 'defines=':['FOO'] |
生成的defines就是-DFOO了。或者是可以剔除掉:
1 | 'defines!':['DEBUG'] |
生成的defines就没有任何内容了。通过在选项key后面添加操作符号来达到自定义目的(相对于全局环境).
对于一个target包括了下面这些重要属性:
- actions(list) //执行命令
- alldependentsettings(dict) //如果依赖这个target的话,需要使用的设置
- configurations(dict) 配置
- defines(list) //对于C/C++的defines
- dependencies(list) //依赖对象。如果是本文件的话那么直接引用,如果是其他文件的话,使用path/xxx.gyp:target.
- directdependentsetting(dict) //直接依赖这个target的话,需要使用的设置
- includedirs(list) // 头文件目录
- libraries(list)//目标需要链接的库
- linksettings //依赖这个target,需要使用的链接参数
- sources(list) //源文件
- targetconditions(list) // 和conditions类似,但是是在完全计算之后然后来判断
- targetname(string) //名字
- type //目标类型,现在只是支持staticlibrary,sharedlibrary,executable和none
1.3.3 includes
gyp倾向的组织就是在toplevel上面存在一个gyp文件,可以存在子目录下面,但是子目录下面并不存放一个完整的构建文件, 通常只是存放构建文件的片段。为了区分,后缀为gypi。本身来说,这个gypi并不可以直接被gyp所接受生成native构建系统文件, 唯一的作用就是被toplevel的gyp文件进行include。如果对于Linux系统来说,最终生成的Makefile应该是一份大Makefile并且没有递归make的操作。 关于构造一个没有递归的Makefile是非常有价值的,不管是对于调试还是提升编译速度方面。可以参考文章Recusive Make Considered Harmful.
一旦我们允许include子目录的gypi文件进来,我们就必须规定哪些字段应该是文件。原因是假设存在src目录下面有src/BUILD.gypi这样一个文件,sources内容如下:
1 | 'sources':['src.cc'] |
而在上层BUILD.gyp文件里面,使用includes语法:
1 | 'includes':['./src/BUILD.gypi'] |
那么在生成大Makefile的时候,我们必须清楚’sources’字段里面内容都是文件,不应该直接使用src.cc, 相反应该加上目录前缀src,
最终应该使用src/src.cc这样一个文件。关于哪些字段里面内容是路径, 这个在gyp里面有详细规定,在后面小节里面我们也会提到。
1.3.4 actions
actions是targets里面的一个特殊属性,主要是用来进行target的自定义操作的.
每个action是一个dict,主要包含4个属性:
- actionname(string). //操作名称
- input(list) //输入文件
- outputs (list) //输出文件
- actions(list) //命令
有了这些属性就可以做一个完整的操作抽象。
1.3.5 variable
variables这个小节里面是进行变量的定义,格式是dict。下面是一个例子:
1 | 'variables':{ |
为了引用变量,我们可以这样写:
1 | <(common_files) |
总之引用变量必须加上(),同时在前面加上<,<@,>,>@的4种中一种前缀符号。关于前缀符号的含义, 会在后面的operator小节里面说明。
对于变量类型,一共分为3类:
- predefined variables //预定义变量
- user-defined variables //用户定义变量
- automatic variables //自动变量
预定义变量比如OS(系统环境),EXECUTABLESUFFIX(可执行文件后缀).用户自定义变量就不再赘述。
自动变量类似于Makefile里面的$@,$这样的变量,好比反射。比如在targetconditions部门的话,我们根据不同类型程序来做不同的condition:
1 | 'target_conditions':[ |
这样对于target为staticlibrary都会联编func.cc这个文件了,自动变量就是属性名称之前加上构成的。
存在自动变量非常必要。有时候我们在全局环境中,希望根据不同的条件来定义不同的行为,并且是在计算的同时在来做条件判断的。 这样就提出一个要求就是,条件判断部分必须有能力知道,当前到底在计算什么东西(反射)。
1.4 early and late phases
对于变量展开和条件判断有两个不同的阶段:
载入文件之后进行,就是early phase。
计算完成之后进行,就是late phase。
对于两个阶段允许不同操作是非常必要的。对于early phase这个肯定需要,而对于late phase的话, 有时候我们是希望了解到gyp处理完成某个target之后所有信息,然后进行判断的。
ps:comake2在设计的时候,就没有考虑late phase这个功能。造成没有办法在应用层添加延迟计算这样一个特性。 最终只能够是修改comake2代码来完成需求。
1.5 operator
关于每个操作符号的含义:
- x= //字段内容进行覆盖
- x? //如果字段没有定义,那么就进行覆盖
- x+ //字段内容进行merge
- < (x) //early phase计算变量x,并且以string类型返回结果
(x) //late phase计算变量x,并且以string类型返回结果
- < @(x) //early phase计算变量x,并且以list类型返回结果
@(x) //late phase计算变量x,并且以list类型返回结果
- x! //从已有的x字段中排除部分
- x/ //操作允许使用include/exclude,内容是一个正则表达式来进行包含和排除列表里面内容
- < !(x) //认为x是一个shell command,得到执行结果作为string类型返回
- < !@(x) //认为x是一个shell command,得到执行结果作为list类型返回
1.6 路径内容属性
在includes这个小节提到了,gyp规定了某些属性的内容必须为路径。这些属性是:
- files.
- includedirs.
- inputs.
- libraries.
- outputs.
- sources.
但是gyp对于里面的内容也做了一些特殊处理。对于内容来说,如果以下面这些字符开头:
- / //绝对路径
- $ //变量
- //链接参数比如-lm
- < ,>,! //operator
- 其他作为相对路径
1.7 在Ubuntu下使用gyp
安装工具: sudo apt-get install gyp
1.7.1 简单实例
hello.c
1 |
|
main.gyp
1 | { |
构建:
1 | gyp --depth=./ main.gyp |
编译1
make
运行1
./hello
1.7.2 改进
把上面的构建命令做成脚本 genPrj.sh
1 |
|
所有生成非源码文件都生成到了build目录下,看起来比较干净,在做一个清除脚本,清除就更省事了。do_clean.sh:
1 |
|
1.7.3 c++实例
目录结构
1 | |-- do_clean.sh |
genPrj.sh
1 |
|
hello.cc
1 |
|
main.gyp
1 | { |
my_class.cc
1 |
|
my_class.h
1 | class MyClass { |
1.8 ninja
gyp是google为chromium项目开发的管理工具,功能类似于cmake。gyp只能产生编译脚本,真正的编译工作还有靠其他工具,例如: ninja。
1.8.1 安装gyp和ninja
1 | sudo apt-get install gyp |
1.8.2 一个简单的例子
首先准备一个源文件,我写了template_sample.cpp,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
using namespace std;
template<typename T>
T add(T a, T b){ return a+b; }
int main()
{
cout<< add<int>(5,6)<<endl;
cout<< add<double>(3.4,4.0)<<endl;
return 0;
}编写gyp使用的文件,build.gyp(后缀是.gyp即可,名字随意)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'template_sample.cpp',
],
'conditions':[]
}
],
}生成ninja脚本,命令如下:
1
gyp -depth=. -format=ninja ./build.gyp
其中`-depth=.` 表示在当前文件夹下寻找开始的gyp脚本,必须显示的指出gyp没有默认设置。 `-format=ninja` 表示要生成ninja的脚本,默认生成的是Makefile
执行后,会在当前目录下生成一个out目录。其结构如下:
1
2
3
4
5
6
7
out
|-- Default
|-- an
|-- build.ninja
|-- obj
|-- an.ninja
|-- an.template_sample.o
编译,执行如下命令:
1
ninja -C out/Default
**注意**: -C是大写,执行成功后会在out/Default目录下生成一个叫做an的可执行文件,这个an就是在target_name字段指定的名字。
clean整个工程重新编译
目前,gyp没有提供clean的方案,所以只能手动删除out目录。
1.8.3 gyp脚本的编写
如何增加编译、链接参数
编译参数一般增加在conditions字段,根据系统的不同增加cflags和ldflags,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'template_sample.cpp',
],
'conditions':[
['OS=="win"',{
'cflags':[],
'ldflags':[]
},{
'cflags':[
'--std=c++11',
],
'ldflags':[]
}
],
]
}
],
}
注意conditions字段的变化:`'OS="win"'`是条件语句的后要有`,`号,第一个括号内表示条件为真时的参数,第二个括号表示条件为假时的参数。上面的例子中,我在不是win的系统上增加了`--std=c++11`这个参数。
另外,这里有必要提一下链接顺序问题。在链接的时候,链接器对符号表的寻找是按照你输入的命令的顺序进行的,比如a.o需要libm.so的函数,那么libm.so在链接命令里就必须写在a.o的后面,因为链接器发现a.o有函数没有找到,就只会去他后面的库里找,如果你把libm.so写在前面,自然就找不到。所以在增加链接库的时候,不要在ldflags里增加-l类的参数,这类参数要加到libraries字段里 如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'template_sample.cpp',
],
'conditions':[
['OS=="win"',{
'cflags':[],
'ldflags':[]
},{
'cflags':[
'--std=c++11',
],
'ldflags':[],
'libraries':[
'-lm'
]
}
],
]
}
],
}
头文件路径设定 –include_dirs字段的使用
有如下工程目录结构1
2
3
4|-- build.gyp
|-- include
| |-- define.h
|-- main.cpp
main.cpp的代码如下
1
2
3
4
5
6
7
8
using namespace std;
int main(){
cout<< MY_NUMBER <<endl;
return 1;
}
可见main.cpp文件引用了defines.h这个头文件,所以在编译的时候需要告诉编译系统到哪里去寻找这个defines.h文件。为此buid.gyp如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[
'include'
],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
可以看出头文件的位置include在include_dirs字段中被指出。这里需要注意,两个单引号之间一定是文件的路径,不要包含不必要的空格等其他字符。
编译参数增加宏定义 –defines字段使用
测试程序的源码如下:1
2
3
4
5
6
7
8
9
10
11
12
using namespace std;
int out_number = 10;
int out_number = 1;
int main(){
cout<< out_number <<endl;
return 1;
}可以看出,如果BIG_NUMBER宏被定义,则输出为10 ,否则为1。在使用Makefile的时候,我们可以通过为gcc增加-DBIG_NUBMER参数的方式来定义这个宏。那在gyp管理的时候,我们就要使用defines字段,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[
'BIG_NUMBER'
],
'include_dirs':[],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}编译静态库(动态库)
有时我们不是要输出一个可执行文件,而是要编译一个静态库或动态库。这就要修改type字段,上文中type字段使用了executable,实际还可以设置为
static_library
,修改后的gyp脚本如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
'targets':[
{
'target_name':'an',
'type':'static_library',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
这样在out/Default/obj的目录下就会生成一个liban.a的静态库。
动态库的方式类似,只需把type字段设置为shared_library即可。
另外还有一种使用变量的方法,在这里提前介绍一下。就是将type字段设置为`<(library)`,完整的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
'targets':[
{
'target_name':'an',
'type':'<(library)',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
然后在使用gyp工具的时候指定这个library的值,如:
1
gyp -depth=. -format=ninja -Dlibrary=static_library ./build.gyp
这样生成ninja脚本后,也可以生成静态库,这样做的好处是你可以在真正输出时再决定输出静态库还是动态库。尤其对于要一次生成多个库的情况下,这种方法尤其好用。
1.9 更多功能介绍
设计准则:
- 关键字一致性:所有的关键字都和平台项目的配置字段相同
- 通过前缀表明配置属于的特定平台。比如:
msvs_disabled_warnings
,xcode_framework_dirs
样例:
1 | { |
结构元素
.gyp文件中定义了一些targets和构建规则。
下面的关键字可以定义在最顶层:
- conditions:条件定义。
includes:包含.gypi文件的列表
1
2
3
4
5
6
7
8
9
10
11
12
13{
"targets": [
{
"target_name": "Thread",
"type": "executable",
"includes": [
"../common.gypi",
"./thread.gypi"
]
...
}
]
}target_defaults:默认的项目配置,每个项目(targets)的配置都需要从这个配置继承
targets:项目列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
"targets": [
{
"target_name": "hello1",
"product_extension": "stuff",
"type": "executable",
"sources": [
"hello.c"
]
},
{
"target_name": "hello2",
"target_extension": "stuff",
"type": "executable",
"sources": [
"hello.c"
]
}
]
}variable: 定义了键值对,可以被其他地方以
<(varname)
的方式引用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31{
'variables': {
'pi': 'import math; print math.pi',
'third_letters': "<(other_letters)HIJK",
'letters_list': 'ABCD',
'other_letters': '<(letters_list)EFG',
'check_included': '<(included_variable)',
'check_lists': [
'<(included_variable)',
'<(third_letters)',
],
'check_int': 5,
'check_str_int': '6',
'check_list_int': [
7,
'8',
9,
],
'not_int_1': ' 10',
'not_int_2': '11 ',
'not_int_3': '012',
'not_int_4': '13.0',
'not_int_5': '+14',
'negative_int': '-15',
'zero_int': '0',
},
...
}
项目(targets)
.gyp文件定义的一套构建项目的规则。targets中也可以包含includes
,conditions
和variables
。下面的是一些targets中的专有字段:
- target_name: 指定定义的target的名称
- type: target的类型。支持
executable
、static_library
、shared_library
和none
.其中none
类型也很有用,可以作为处理资源、文档等特别项目的类型。例如,在Windows中,none
将作为工具集类型的项目存在 - product_extension:指定target生成目标的扩展名,不包含’.’
- product_name: 指定tareget生成目标的文件名,与
product_extension
组成一个文件全名 dependencies:指定target依赖的其他target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
'targets': [
{
'target_name': 'a',
'type': 'static_library',
'dependencies': [
'b/b.gyp:b',
'c/c.gyp:*'
],
'sources': [
'a.c',
],
},
],
}
}
defines: 定义了预处理宏。类似于C/C++命令行编译中的-D或者/D选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21{
'targets': [
{
'target_name': 'defines',
'type': 'executable',
'defines': [
'FOO',
'VALUE=1',
'PAREN_VALUE=(1+2+3)',
'HASH_VALUE="a#1"',
],
'sources': [
'defines.c',
],
},
]
}
include_dirs: 指定了包含文件的查找目录。类似于C/C++命令行编译中的-I或/I选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30{
'targets': [
{
'target_name': 'includes',
'type': 'executable',
'dependencies': [
'subdir/subdir_includes.gyp:subdir_includes',
],
'cflags': [
'-Ishadow1',
],
'include_dirs': [
'.',
'inc1',
'shadow2',
'subdir/inc2',
],
'sources': [
'includes.c',
],
},
],
}sources:列出了项目中的代码文件和一些项目相关的文件.
sources!
段中可以指定被排除的文件- configurations:为targets定义的一套构建配置。
- link_settings:指定target需要链接的库。
executable
和shared_library
类型的target需要指定链接库。这个段内可以指定target中可包含的除了configurations
、target_name
和type
的所有配置。可以与all_dependent_settings
、direct_dependent_setting
做对比 - direct_dependent_settings:指定依赖本target的target设置。这个段内可以指定target中可包含的除了
configurations
、target_name
和type
的所有属性。可以与all_dependent_settings
、link_settings
做对比 - all_dependent_settings:
libraries:指定target依赖的库,见
link_settings>libraries
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15...
'link_settings': {
'libraries': [
'libiconv.dylib',
'<(ZLIB_DIR)contrib/minizip/libminizip.a',
'libcurl.dylib',],
},
...actions:针对输入的文件,定义了一组自定义的构建动作。
- copies:定义了一套拷贝动作。
- rules:
- target_conditions:类似于conditions,但是起左右的时间比conditions晚
- msvs_precompiled_header:指定预编译头文件。只能用于Visual Studio
- msvs_precompiled_source:指定预编译源文件。只能用于Visual Studio
- msvs_prebuild:生成之前事件。只能用于Visual Studio
msvs_postbuild:生成之后事件。只能用于Visual Studio
1
'msvs_postbuild':r'copy "${OutDir}${TargetName}" "C:\${TargetName}"'
- msvs_props:指定target的属性页文件(.vsprops)。只能用于Visual Studio
- msvs_cygwin_shell:指定action运行在cygwin下。只能用于Visual Studio
- msvs_cygwin_dirs:指定cygwin的目录。只能用于Visual Studio
- xcode_config_file:在xcode中,指定target的配置文件(.xcconfig)。只能用于xcode
- xcode_framework_dirs:在xcode中,指定框架的目录。只能用于xcode