說起項(xiàng)目構(gòu)建工具,Linux 用戶最熟悉的恐怕就是 Autotools,它將編譯安裝這個(gè)步驟大大簡(jiǎn)化。但對(duì)于項(xiàng)目作者來說,想要使用 Autotools 生成有效的配置文件著實(shí)需要下一番功夫,用現(xiàn)在流行的話來說就是用戶體驗(yàn)不夠友好。對(duì) Unix shell 的依賴,也使得 Autotools 天生對(duì)于跨平臺(tái)支持不佳。

后來我從大貓同學(xué)那里聽說了 CMake,CMake 使用 C++ 編寫,原生支持跨平臺(tái),不需要像 Autotools 那樣寫一堆的配置文件,只需一個(gè) CMakeLists.txt 文件即可。簡(jiǎn)潔的使用方式,強(qiáng)大的功能使得我立馬對(duì) CMake 情有獨(dú)鐘。在后來的使用過程中,雖然會(huì)遇到一些因?yàn)槭褂昧?xí)慣帶來的小困擾,但我對(duì)于 CMake 還是基本滿意的。直到我發(fā)現(xiàn)了 GYP。

GYP(Generate Your Projects)是由 Chromium 團(tuán)隊(duì)開發(fā)的跨平臺(tái)自動(dòng)化項(xiàng)目構(gòu)建工具,Chromium 便是通過 GYP 進(jìn)行項(xiàng)目構(gòu)建管理。為什么我要選擇 GYP,而放棄 CMake 呢?功能上 GYP 和 CMake 很是相似,在我看來,它們的最大區(qū)別在于配置文件的編寫方式和其中蘊(yùn)含的思想。

編寫 CMake 配置文件相比 Autotools 來說已經(jīng)簡(jiǎn)化很多,一個(gè)最簡(jiǎn)單的配置文件只需要寫上源文件及生成類型(可執(zhí)行文件、靜態(tài)庫、動(dòng)態(tài)庫等)即可。對(duì)分支語句和循環(huán)語句的支持也使得 CMake 更加靈活。但是,CMake 最大的問題也是在這個(gè)配置文件,請(qǐng)看下面這個(gè)示例文件:

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 32 33 34 35 36 37 38 39 40 41 42 
cmake_minimum_required(VERSION 2.8) project(VP8 CXX)  add_definitions(-Wall) cmake_policy(SET CMP0015 NEW) include_directories("include") link_directories("lib") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "../lib") set(VP8SRC VP8Encoder.cpp VP8Decoder.cpp)  if(X86)     set(CMAKE_SYSTEM_NAME Darwin)     set(CMAKE_SYSTEM_PROCESSOR i386)     set(CMAKE_OSX_ARCHITECTURES "i386")      add_library(vp8 STATIC ${VP8SRC}) elseif(IPHONE)     if(SIMULATOR)         set(PLATFORM "iPhoneSimulator")         set(PROCESSOR i386)         set(ARCH "i386")     else()         set(PLATFORM "iPhoneOS")         set(PROCESSOR arm)         set(ARCH "armv7")     endif()      set(SDKVER "4.0")     set(DEVROOT "/Developer/Platforms/${PLATFORM}.platform/Developer")     set(SDKROOT "${DEVROOT}/SDKs/${PLATFORM}${SDKVER}.sdk")     set(CMAKE_OSX_SYSROOT "${SDKROOT}")     set(CMAKE_SYSTEM_NAME Generic)     set(CMAKE_SYSTEM_PROCESSOR ${PROCESSOR})     set(CMAKE_CXX_COMPILER "${DEVROOT}/usr/bin/g++")     set(CMAKE_OSX_ARCHITECTURES ${ARCH})      include_directories(SYSTEM "${SDKROOT}/usr/include")     link_directories(SYSTEM "${SDKROOT}/usr/lib")      add_definitions(-D_PHONE)     add_library(vp8-armv7-darwin STATIC ${VP8SRC}) endif() 

你能一眼看出這個(gè)配置文件干了什么嗎?其實(shí)這個(gè)配置文件想要產(chǎn)生的目標(biāo)(target)只有一個(gè),就是通過${VP8SRC} 編譯生成的靜態(tài)庫,但因?yàn)榧由狭藯l件判斷,及各種平臺(tái)相關(guān)配置,使得這個(gè)配置文件看起來很是復(fù)雜。在我看來,編寫 CMake 配置文件是一種線性思維,對(duì)于同一個(gè)目標(biāo)的配置可能會(huì)零散分布在各個(gè)地方。而 GYP 則相當(dāng)不同,GYP 的配置文件更多地強(qiáng)調(diào)模塊化、結(jié)構(gòu)化。看看下面這個(gè)示例文件:

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 32 33 34 35 36 37 38 39 40 41 
{   'targets': [     {       'target_name': 'foo',       'type': '<(library)',       'dependencies': [         'bar',       ],       'defines': [         'DEFINE_FOO',         'DEFINE_A_VALUE=value',       ],       'include_dirs': [         '..',       ],       'sources': [         'file1.cc',         'file2.cc',       ],       'conditions': [         ['OS=="linux"', {           'defines': [             'LINUX_DEFINE',           ],           'include_dirs': [             'include/linux',           ],         }],         ['OS=="win"', {           'defines': [             'WINDOWS_SPECIFIC_DEFINE',           ],         }, { # OS != "win",           'defines': [             'NON_WINDOWS_DEFINE',           ],         }]       ],     }   ], } 

我們可以立馬看出上面這個(gè)配置文件的輸出目標(biāo)只有一個(gè),也就是 foo,它是一個(gè)庫文件(至于是靜態(tài)的還是動(dòng)態(tài)的這需要在生成項(xiàng)目時(shí)指定),它依賴的目標(biāo)、宏定義、包含的頭文件路徑、源文件是什么,以及根據(jù)不同平臺(tái)設(shè)定的不同配置等。這種定義配置文件的方式相比 CMake 來說,讓我覺得更加舒服,也更加清晰,特別是當(dāng)一個(gè)輸出目標(biāo)的配置越來越多時(shí),使用 CMake 來管理可能會(huì)愈加混亂。

配置文件的編寫方式是我區(qū)分 GYP 和 CMake 之間最大的不同點(diǎn),當(dāng)然 GYP 也有一些小細(xì)節(jié)值得注意,比如支持跨平臺(tái)項(xiàng)目工程文件輸出,Windows 平臺(tái)默認(rèn)是 Visual Studio,Linux 平臺(tái)默認(rèn)是 Makefile,Mac 平臺(tái)默認(rèn)是 Xcode,這個(gè)功能 CMake 也同樣支持,只是缺少了 Xcode。Chromium 團(tuán)隊(duì)成員也撰文詳細(xì)比較了 GYP 和 CMake 之間的優(yōu)缺點(diǎn),在開發(fā) GYP 之前,他們也曾試圖轉(zhuǎn)到 SCons(這個(gè)我沒用過,有經(jīng)驗(yàn)的同學(xué)可以比較一下),但是失敗了,于是 GYP 就誕生了。

當(dāng)然 GYP 也不是沒有缺點(diǎn),相反,我覺得它的「缺點(diǎn)」一大堆:

  • 文檔不夠完整,項(xiàng)目不夠正式,某些地方還保留著 Chromium 的影子,看起來像是還沒有完全獨(dú)立出來。
  • 大量的括號(hào)嵌套,很容易讓人看暈,有過 Lisp 使用經(jīng)驗(yàn)的同學(xué)可以對(duì)號(hào)入座。對(duì)于有括號(hào)恐懼癥,或者不使用現(xiàn)代編輯器的同學(xué)基本可以繞行。
  • 為了支持跨平臺(tái),有時(shí)不得不加入某些特定平臺(tái)的配置信息,比如只適用于 Visual Studio 的 RuntimeLibrary配置,這不利于跨平臺(tái)配置文件的編寫,也無形中增加了編寫復(fù)雜度。
  • 不支持 make clean,唯一的方法就是將輸出目錄整個(gè)刪除或者手動(dòng)刪除其中的某些文件。

如果你已經(jīng)打算嘗試 GYP,那一定記得在生成項(xiàng)目工程文件時(shí)加上 --depth 參數(shù),譬如:

$ gyp --depth=. foo.gyp 

這也是一個(gè)從 Chromium 項(xiàng)目遺留下來的歷史問題。

也許你根本用不上跨平臺(tái)特性,但是 GYP 依然值得嘗試。我編寫了一份 GYP 配置文件的模板,有興趣的同學(xué)可以參考。GYP 和 CMake 分別代表了兩種迥異的「風(fēng)格」,至于孰優(yōu)孰劣,還得仁者見仁,智者見智。