一、簡介
GYP是google的一套構建系統,和 cmake 的目的很像。GYP和CMake的主要作用是,從用戶編寫的一套配置文件,針對不同的工具鏈生成不同的項目文件(如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' ] } ] }
看上去基本就是一個json。它定義了一個名為hello的target。target的類型是executable,表明它是一個可執行文件。如果要編譯庫,就換成static_library或者shared_library。sources是一個數組,列出所有的源文件。
然后用
# gyp hello.gyp --depth=. -f make
生成makefile。
然后執行make命令即可。
從設計目標上來說,它和autotools還不大一樣。autotools只針對make,且僅限于gnu make。autotools的核心是autoconf,如何利用shell腳本在不同的操作系統環境下生成相應的config.h文件。它利用它強大的檢測功能很容易適應不同的Unix/Linux環境。
而GYP和CMake都支持各種主流的構建系統。如make/Visual studio/XCode/Ninja。CMake支持構建系統的種類要更多些,比如eclipse cdt、Sublime Text 2、CodeBlocks、Borland C++等等。而這些非主流的東西GYP壓根就不會去碰,不敢碰。
按最理想的情況,我們寫一套配置規則,在所有平臺上都能執行。此時我們可以不關心操作系統是什么。拿autotools來說,假如你要include某個頭文件,那么就在autoconf執行的時候檢查下有沒有這個頭文件,然后在真正使用的時候,利用ifdef/else/endif來條件編譯,假如有,我們就用它,沒有就砍掉某個功能,或者使用替代實現。由于unix種類甚多,差異甚大,按照這樣方式寫的程序,即便被扔到一個作者從不知曉的陌生環境里,(也許)也能正常運行。
cmake和autotools都是這樣的理想主義者,它們試圖把不同的工具鏈的相同部分抽象出來,用一套統一的配置文件來適應不同的平臺。迫不得已的時候你可以寫 if(OS==WIN) ... else if (OS== Linux) ... 。 于是就這樣工作了很多年,很好。而GYP覺得我們不該做這樣的過分抽象,構建系統自身應該簡潔,把適應不同平臺的事情交給程序員自己去做。比如,不同的工具鏈參數不同,gcc編譯多線程程序的時候要加-pthread或-pthreads或-lpthread,而vc則要在4種不同的CRT做出選擇。那么,你自己去把不同平臺的編譯參數挨個標明,GYP不管這事。所以,GYP的項目不可能盲目的去支持Sublime Text 2、eclipse cdt這樣的小眾玩意兒。為了支持它們,condition會急劇膨脹。
GYP是為Chrome項目開發的,Chrome也是GYP的唯一成功案例。就比如前面編譯hello world的時候,加上"--depth=. ",這完全是Chrome的特殊遺留。Chrome是一個有600多萬行代碼的大型C++項目,它的成功值得借鑒。
雖然GYP和CMake相比還很不成熟,而且很不獨立(它幾乎是專為Chrome項目服務),但是Chrome本身其實已經給我們貢獻了足夠多的代碼。雖然GYP不像CMake那樣自帶了很多Module(豐富的FindXXX),但是我們完全可以去Chrome的項目中把那些GYP文件挖出來。
另外,如果你想復用Chrome的代碼,那么就得遷就GYP。比如apache的mod_spdy模塊,它的SPDY協議的實現就是從Chrome中直接拿去的。為了引用Chrome的代碼,mod_spdy就不得不采用GYP做構建。
二、.gyp 文件的格式說明。
.gyp文件基本上就是一個json文件,和標準的json相比,它有兩點不同:
-
可以用#注釋
-
list或dictionary的最后一個元素后面可以多一個逗號 (便于用程序自動生成這樣的文件)
在它最頂層的大括號內,有5種對象: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在跨平臺問題上采用了一個很有趣的事情,把第三方庫的源代碼copy到現有項目中,并且靜態鏈接進來。相當于,Chrome為它所有用到的第三方庫都做了SVN一個分支,在需要的時候與上游同步。然后它為所有的第三方庫都生成了一個GYP項目文件,然后在Chrome中引用這些第三方庫的項目文件,完成構建。比如,不管你操作系統有沒有裝libevent版本是多少,我都用我自己帶的這個,然后最終鏈接成一個無比巨大的exe或ELF文件。
我非常贊同Chrome的這種做法。有些第三方庫的接口變動非常快,比如glib,單單為了在linux這一種操作系統下適應不同的glib版本,就得在代碼中寫大量的條件編譯宏。查core dump的時候也困難很多,單是找源代碼都能找死一批人(別忘了發行版喜歡 編譯前打自己的patch)。另一點是,構建系統本身也得以簡化,我不需要去把autoconf/automake/cmake/scons等等全裝一遍。最可恨的是,autotools自身還有很多版本,而且各不兼容。要把不同版本的autotools全裝上,并且用起來互不干擾,也需要很大技巧。
下面以glib為例演示下如何在一個項目中包含另一個項目。
首先從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中加入了一個"dependencies"節。它引用了zlib/zlib.gyp這個文件中的zlib這個target。
然后生成makefile
$ gyp hello.gyp --depth=. -D OS=linux -D os_bsd=0 -D clang=0 -f make
加入了一些新的define,是因為zlib/zlib.gyp中用到了這些variable。
然后make
$ make
用ldd看一下生成的結果文件會發現,
# 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已經被靜態鏈接進去了。
這樣當需要在服務器上部署的時候就很容易了。不用再總是去編譯、安裝第三方的庫。