一、簡(jiǎn)介
GYP是google的一套構(gòu)建系統(tǒng),和 cmake 的目的很像。GYP和CMake的主要作用是,從用戶編寫的一套配置文件,針對(duì)不同的工具鏈生成不同的項(xiàng)目文件(如Makefile/vc projects/xcode projects)。
GYP安裝:
svn co http://gyp.googlecode.com/svn/trunk gyp cd gyp ./setup.py build ./setup.py install
下面是GYP的配置文件的示例:
{ 'targets': [ { 'target_name': 'hello', 'type': 'executable', 'sources': [ 'main.cpp' ] } ] }
看上去基本就是一個(gè)json。它定義了一個(gè)名為hello的target。target的類型是executable,表明它是一個(gè)可執(zhí)行文件。如果要編譯庫(kù),就換成static_library或者shared_library。sources是一個(gè)數(shù)組,列出所有的源文件。
然后用
# gyp hello.gyp --depth=. -f make
生成makefile。
然后執(zhí)行make命令即可。
從設(shè)計(jì)目標(biāo)上來說,它和autotools還不大一樣。autotools只針對(duì)make,且僅限于gnu make。autotools的核心是autoconf,如何利用shell腳本在不同的操作系統(tǒng)環(huán)境下生成相應(yīng)的config.h文件。它利用它強(qiáng)大的檢測(cè)功能很容易適應(yīng)不同的Unix/Linux環(huán)境。
而GYP和CMake都支持各種主流的構(gòu)建系統(tǒng)。如make/Visual studio/XCode/Ninja。CMake支持構(gòu)建系統(tǒng)的種類要更多些,比如eclipse cdt、Sublime Text 2、CodeBlocks、Borland C++等等。而這些非主流的東西GYP壓根就不會(huì)去碰,不敢碰。
按最理想的情況,我們寫一套配置規(guī)則,在所有平臺(tái)上都能執(zhí)行。此時(shí)我們可以不關(guān)心操作系統(tǒng)是什么。拿autotools來說,假如你要include某個(gè)頭文件,那么就在autoconf執(zhí)行的時(shí)候檢查下有沒有這個(gè)頭文件,然后在真正使用的時(shí)候,利用ifdef/else/endif來?xiàng)l件編譯,假如有,我們就用它,沒有就砍掉某個(gè)功能,或者使用替代實(shí)現(xiàn)。由于unix種類甚多,差異甚大,按照這樣方式寫的程序,即便被扔到一個(gè)作者從不知曉的陌生環(huán)境里,(也許)也能正常運(yùn)行。
cmake和autotools都是這樣的理想主義者,它們?cè)噲D把不同的工具鏈的相同部分抽象出來,用一套統(tǒng)一的配置文件來適應(yīng)不同的平臺(tái)。迫不得已的時(shí)候你可以寫 if(OS==WIN) ... else if (OS== Linux) ... 。 于是就這樣工作了很多年,很好。而GYP覺得我們不該做這樣的過分抽象,構(gòu)建系統(tǒng)自身應(yīng)該簡(jiǎn)潔,把適應(yīng)不同平臺(tái)的事情交給程序員自己去做。比如,不同的工具鏈參數(shù)不同,gcc編譯多線程程序的時(shí)候要加-pthread或-pthreads或-lpthread,而vc則要在4種不同的CRT做出選擇。那么,你自己去把不同平臺(tái)的編譯參數(shù)挨個(gè)標(biāo)明,GYP不管這事。所以,GYP的項(xiàng)目不可能盲目的去支持Sublime Text 2、eclipse cdt這樣的小眾玩意兒。為了支持它們,condition會(huì)急劇膨脹。
GYP是為Chrome項(xiàng)目開發(fā)的,Chrome也是GYP的唯一成功案例。就比如前面編譯hello world的時(shí)候,加上"--depth=. ",這完全是Chrome的特殊遺留。Chrome是一個(gè)有600多萬(wàn)行代碼的大型C++項(xiàng)目,它的成功值得借鑒。
雖然GYP和CMake相比還很不成熟,而且很不獨(dú)立(它幾乎是專為Chrome項(xiàng)目服務(wù)),但是Chrome本身其實(shí)已經(jīng)給我們貢獻(xiàn)了足夠多的代碼。雖然GYP不像CMake那樣自帶了很多Module(豐富的FindXXX),但是我們完全可以去Chrome的項(xiàng)目中把那些GYP文件挖出來。
另外,如果你想復(fù)用Chrome的代碼,那么就得遷就GYP。比如apache的mod_spdy模塊,它的SPDY協(xié)議的實(shí)現(xiàn)就是從Chrome中直接拿去的。為了引用Chrome的代碼,mod_spdy就不得不采用GYP做構(gòu)建。
二、.gyp 文件的格式說明。
.gyp文件基本上就是一個(gè)json文件,和標(biāo)準(zhǔn)的json相比,它有兩點(diǎn)不同:
-
可以用#注釋
-
list或dictionary的最后一個(gè)元素后面可以多一個(gè)逗號(hào) (便于用程序自動(dòng)生成這樣的文件)
在它最頂層的大括號(hào)內(nèi),有5種對(duì)象:variables、includes、target_defaults、targets、conditions。
- 'variables': Definitions of variables that can be interpolated and used in various other parts of the file.
- 'includes': A list of of other files that will be included in this file. By convention, included files have the suffix .gypi (gyp include).
- 'target_defaults': Settings that will apply to all of the targets defined in this .gyp file.
- 'targets': The list of targets for which this .gyp file can generate builds. Each target is a dictionary that contains settings describing all the information necessary to build the target.
- 'conditions': A list of condition specifications that can modify the contents of the items in the global dictionary defined by this .gyp file based on the values of different variablwes. As implied by the above example, the most common use of a conditions section in the top-level dictionary is to add platform-specific targets to the targets list.
Chrome在跨平臺(tái)問題上采用了一個(gè)很有趣的事情,把第三方庫(kù)的源代碼copy到現(xiàn)有項(xiàng)目中,并且靜態(tài)鏈接進(jìn)來。相當(dāng)于,Chrome為它所有用到的第三方庫(kù)都做了SVN一個(gè)分支,在需要的時(shí)候與上游同步。然后它為所有的第三方庫(kù)都生成了一個(gè)GYP項(xiàng)目文件,然后在Chrome中引用這些第三方庫(kù)的項(xiàng)目文件,完成構(gòu)建。比如,不管你操作系統(tǒng)有沒有裝libevent版本是多少,我都用我自己帶的這個(gè),然后最終鏈接成一個(gè)無(wú)比巨大的exe或ELF文件。
我非常贊同Chrome的這種做法。有些第三方庫(kù)的接口變動(dòng)非常快,比如glib,單單為了在linux這一種操作系統(tǒng)下適應(yīng)不同的glib版本,就得在代碼中寫大量的條件編譯宏。查core dump的時(shí)候也困難很多,單是找源代碼都能找死一批人(別忘了發(fā)行版喜歡 編譯前打自己的patch)。另一點(diǎn)是,構(gòu)建系統(tǒng)本身也得以簡(jiǎn)化,我不需要去把a(bǔ)utoconf/automake/cmake/scons等等全裝一遍。最可恨的是,autotools自身還有很多版本,而且各不兼容。要把不同版本的autotools全裝上,并且用起來互不干擾,也需要很大技巧。
下面以glib為例演示下如何在一個(gè)項(xiàng)目中包含另一個(gè)項(xiàng)目。
首先從svn中checkout出zlib的代碼
svn co http://src.chromium.org/svn/trunk/src/third\_party/zlib
然后把下面代碼保存為test.cpp
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#define CHUNK 16384
/* Compress from file source to file dest until EOF on source.
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_STREAM_ERROR if an invalid compression
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
version of the library linked do not match, or Z_ERRNO if there is
an error reading or writing the files. */
int main(int argc, char** argv) {
FILE* source = stdin;
FILE* dest = stdout;
int level = Z_DEFAULT_COMPRESSION;
int flush;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
int ret = deflateInit(&strm, level);
if (ret != Z_OK) return ret;
/* compress until end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
deflateEnd(&strm);
return -1;
}
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
/* run deflate() on input until output buffer not full, finish
compression if all of source has been read in */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush); /* no bad return value */
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void) deflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);
/* done when last data in file processed */
} while (flush != Z_FINISH);
/* clean up and return */
deflateEnd(&strm);
return 0;
}
然后把前面的hello.gyp稍作修改
{
'targets': [
{
'target_name': 'hello',
'type': 'executable',
'sources': [
'test.cpp'
],
'dependencies': [
'zlib/zlib.gyp:zlib'
]
}
]
}
就是在target中加入了一個(gè)"dependencies"節(jié)。它引用了zlib/zlib.gyp這個(gè)文件中的zlib這個(gè)target。
然后生成makefile
$ gyp hello.gyp --depth=. -D OS=linux -D os_bsd=0 -D clang=0 -f make
加入了一些新的define,是因?yàn)閦lib/zlib.gyp中用到了這些variable。
然后make
$ make
用ldd看一下生成的結(jié)果文件會(huì)發(fā)現(xiàn),
# ldd out/Default/hello linux-vdso.so.1 => (0x00007fff87c1c000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x0000003b0d000000) libm.so.6 => /lib64/libm.so.6 (0x0000003b07000000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003b08400000) libc.so.6 => /lib64/libc.so.6 (0x0000003b06400000) /lib64/ld-linux-x86-64.so.2 (0x0000003b06000000)
它不依賴于zlib的so/dll。zlib已經(jīng)被靜態(tài)鏈接進(jìn)去了。
這樣當(dāng)需要在服務(wù)器上部署的時(shí)候就很容易了。不用再總是去編譯、安裝第三方的庫(kù)。