1.简介
gyp是google为chromium项目开发的管理工具,功能类似于cmake。gyp只能产生编译脚本,真正的编译工作还有靠其他工具,我选择了ninja。
2.安装gyp和ninja
$ sudo apt-get install gyp
$ sudo apt-get install ninja-build
3.一个简单的例子
1.首先准备一个源文件,我写了template_sample.cpp,代码如下:
#include <iostream>
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;
}
2.编写gyp使用的文件,build.gyp(后缀是.gyp即可,名字随便)
{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'template_sample.cpp',
],
'conditions':[]
}
],
}
3.生成ninja脚本,命令如下:
$ gyp –depth=. –format=ninja ./build.gyp
其中–depth=. 表示在当前文件夹下寻找开始的gyp脚本,必须显示的指出gyp没有默认设置。–format=ninja表示要生成ninja的脚本,默认生成的是Makefile。
执行后,会在当前目录下生成一个out目录。其结构如下:
4.编译,执行如下命令:
$ ninja -C out/Default
注意-C是大写,执行成功后会在out/Default目录下生成一个叫做an的可执行文件,这个an就是在target_name字段指定的名字。
5.clean整个工程重新编译
目前,gyp没有提供clean的方案,所以只能手动删除out目录。
4.gyp脚本的编写
4.1 如何增加编译、链接参数
编译参数一般增加在conditions字段,根据系统的不同增加cflags和ldflags,如下:
{
'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字段里如:
{
'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'
]
}
],
]
}
],
}
4.2头文件路径设定—–include_dirs字段的使用
有如下工程目录结构
main.cpp的代码如下:
#include "defines.h"
#include <iostream>
using namespace std;
int main(){
cout<< MY_NUMBER <<endl;
return 1;
}
可见main.cpp文件引用了defines.h这个头文件,所以在编译的时候需要告诉编译系统到哪里去寻找这个defines.h文件。为此buid.gyp如下:
{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[],
'include_dirs':[
'include'
],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
可以看出头文件的位置include在include_dirs字段中被指出。这里需要注意,两个单引号之间一定是文件的路径,不要包含不必要的空格等其他字符。
4.3 编译参数增加宏定义—defines字段使用
测试程序的源码如下:
#include <iostream>
using namespace std;
#ifdef BIG_NUMBER
int out_number = 10;
#else
int out_number = 1;
#endif
int main(){
cout<< out_number <<endl;
return 1;
}
可以看出,如果BIG_NUMBER宏被定义,则输出为10 ,否则为1。在使用Makefile的时候,我们可以通过为gcc增加-DBIG_NUBMER参数的方式来定义这个宏。那在gyp管理的时候,我们就要使用defines字段,代码如下:
{
'targets':[
{
'target_name':'an',
'type':'executable',
'dependencies':[],
'defines':[
'BIG_NUMBER'
],
'include_dirs':[],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
4.4 编译静态库(动态库)
有时我们不是要输出一个可执行文件,而是要编译一个静态库或者动态库。这就要修改type字段,上文中type字段使用了executable,实际还可以设为static_library,修改后的gyp脚本如下:
{
'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)
,完整的代码如下:
{
'targets':[
{
'target_name':'an',
'type':'<(library)',
'dependencies':[],
'defines':[],
'include_dirs':[],
'sources':[
'main.cpp',
],
'conditions':[]
}
],
}
然后在使用gyp工具的时候指定这个library的值,如:
$ gyp –depth=. –format=ninja -Dlibrary=static_library ./build.gyp
这样生成ninja脚本后,也可以生成静态库,这样做的好处是你可以在真正输出时再决定输出静态库还是动态库。尤其对于要一次生成多个库的情况下,这种方法尤其的好用。
5.gyp的缺点
目前我对gyp唯一的不满就是满屏的中括号和花括号,这个很讨厌,但这也是为了符合json标准的要求,所以编写时一定要细心,其他就没什么了。