C++少說(shuō)也用了十年了,從簡(jiǎn)單的Hello World到200萬(wàn)行的游戲項(xiàng)目,編譯和構(gòu)建的工具也經(jīng)歷了各種升級(jí)。最終的開發(fā)環(huán)境,選擇了Clang+GDB+CMake。當(dāng)然不斷改進(jìn)和升級(jí)開發(fā)工具的腳步尚未停止,只要能提高開發(fā)效率,怎樣折騰都是值得的。
期間經(jīng)歷了:
- 直接調(diào)用編譯和鏈接命令
- 使用Makefile
- 使用CMake
- 不斷嘗試其他構(gòu)建系統(tǒng),如:b2、WAF、SCons

對(duì)構(gòu)建系統(tǒng)的要求
由于C/C++本身的特性,如:跨平臺(tái)、高性能等、編寫復(fù)雜等,對(duì)構(gòu)建系統(tǒng)也是提出了一定的要求:
支持并行編譯:構(gòu)建系統(tǒng)能否支持并行編譯?對(duì)于編譯速度的要求,我給自己定的目標(biāo)是<10min,超過(guò)10min要么換機(jī)器,要么想辦法優(yōu)化代碼依賴。上百萬(wàn)行的代碼,并行編譯時(shí)必須的,否則一不小心改一行代碼等個(gè)把小時(shí),這樣開發(fā)時(shí)間白白浪費(fèi)在編譯上太不值得了。
自動(dòng)生成依賴:構(gòu)建系統(tǒng)是否僅僅編譯剛修改過(guò)的及其依賴的文件?代碼的依賴關(guān)系,要我們自己去手動(dòng)寫腳本(一般gcc/clang的話,使用
gcc -M xx.cpp)?跨平臺(tái):構(gòu)建系統(tǒng)能否僅寫一份構(gòu)建腳本,支持多種平臺(tái)?有些項(xiàng)目需要進(jìn)行交叉編譯,測(cè)試環(huán)境和運(yùn)行環(huán)境是在不同的平臺(tái)環(huán)境下。
支持自定義構(gòu)建目標(biāo): 構(gòu)建系統(tǒng)必須支持?jǐn)U展,支持自定義Target等。如:protobuf文件可以根據(jù)依賴規(guī)則自動(dòng)生成.h、.cpp;自定義一些用于打包或測(cè)試的命令(
make pack、make test)。
本文下面大概介紹一下剛提到的構(gòu)建系統(tǒng),具體用法不贅述,官方網(wǎng)站是最好的開始地方。若有必要會(huì)另起文章詳細(xì)講解如何使用及其工作原理。
基于make的
GNU Make
對(duì)于玩Linux的人來(lái)說(shuō),這是太熟悉不過(guò)的東西了。小規(guī)模的項(xiàng)目或僅自己玩的項(xiàng)目,手寫Makefile完全就足夠了。
GNU Make 是一個(gè)控制源碼生成可執(zhí)行文件或其他文件的工具。需要一個(gè)叫Makefile的文件來(lái)說(shuō)明構(gòu)建的目標(biāo)和規(guī)則。
最簡(jiǎn)單的規(guī)則大概是這樣的:
target: dependencies ...
commands
...
意思是:生成target,依賴于dependencies,如果dependencies有修改或者target不存在,就逐個(gè)執(zhí)行下面的commands去生成target。
下面貼一個(gè)復(fù)雜的Makefile感受下:
CXX = g++
CXXFLAGS = -g -I../proto.client -I../common
LDFLAGS = -L../common -L../proto.client/ -lproto.client -L/usr/local/lib -lzmq -lprotobuf -ltinyworld
OBJS = main.o
SRCS = $(OBJS:%.o=%.cpp)
DEPS = $(OBJS:%.o=.%.d)
TARGET=gateserver
.PHONY: all clean
all : $(TARGET)
include $(DEPS)
$(DEPS): $(SRCS)
@$(CXX) -M $(CXXFLAGS) $< > $@.$$$$; \\
sed 's,\\($*\\)\\.o[ :]*,\\1.o $@ : ,g' < $@.$$$$ >$@; \\
rm -f $@.$$$$
$(OBJS): %.o: %.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
$(TARGET): $(OBJS) ../common/libtinyworld.a
$(CXX) $(OBJS) -o $@ $(CXXFLAGS) $(LDFLAGS)
clean:
@rm -rf $(TARGET)
Microsoft NMake
在Windows下面做開發(fā),Visual Studio基本上完全勝任。微軟自己的IDE功能強(qiáng)大,對(duì)于項(xiàng)目構(gòu)建的管理IDE幫著你搞定了。VS的構(gòu)建的管理其實(shí)用的是微軟自己的Make,叫NMAKE。腳本還是IDE,各有千秋:IDE好處就是它什么都幫你干了,簡(jiǎn)單方便;壞處就是對(duì)構(gòu)建的方式和過(guò)程了解的比較淺,自由度沒(méi)那么大,遇到大型項(xiàng)目的特殊需求時(shí)要各種查資料。
MSDN上面的NMAKE腳本示例:
# Sample makefile
!include <win32.mak>
all: simple.exe challeng.exe
.c.obj:
$(cc) $(cdebug) $(cflags) $(cvars) $*.c
simple.exe: simple.obj
$(link) $(ldebug) $(conflags) -out:simple.exe simple.obj $(conlibs) lsapi32.lib
challeng.exe: challeng.obj md4c.obj
$(link) $(ldebug) $(conflags) -out:challeng.exe $** $(conlibs)
自動(dòng)生成make腳本的
手動(dòng)寫make腳本自由度大,為了自由度,它的設(shè)計(jì)比較簡(jiǎn)單,有許多上述對(duì)構(gòu)建系統(tǒng)的要求它沒(méi)法支持。如:GUN Make沒(méi)法自己知道代碼的依賴,需要借助編譯器來(lái)自己寫腳本;跨平臺(tái)就更不可能了。
還有一個(gè)重要的影響就是對(duì)于環(huán)境的自動(dòng)檢測(cè)。如果你的代碼發(fā)布出去,任何一個(gè)人下載下來(lái)需要進(jìn)行編譯,他的編譯器、操作系統(tǒng)環(huán)境、依賴的第三方庫(kù)的位置和版本都會(huì)有差異,如何進(jìn)行編譯?難到要下載你代碼的人去手動(dòng)修改你的Makefile嗎?當(dāng)然不是,這個(gè)時(shí)候在編譯之前還需要一步:檢測(cè)當(dāng)前編譯環(huán)境、操作系統(tǒng)環(huán)境、第三方庫(kù)的位置等,不滿足要求就直接報(bào)錯(cuò),檢測(cè)到所有依賴后再根據(jù)這些信息生成適合你當(dāng)前系統(tǒng)的Makefile,然后才能進(jìn)行編譯。
GNU Build System
認(rèn)識(shí)GNU Build System可以從兩個(gè)角度入手:使用者和開發(fā)者。主要包含三大模塊:
- Autoconf
- Automake
- Libtool
站在使用者的角度,GNU Build System為我們提供了源碼包編譯安裝的方式:
tar -xvzf package-name.version.tar.gz # tar -xvjf package-name.version.tar.bz2
cd package-name.version
./configure --prefix=xxx
make
make install
其中的configure就是檢測(cè)環(huán)境,生成Makefile的腳本。大概的過(guò)程如下:

站在開發(fā)者的角度,GNU Build System 為我們廣大程序員提供了編寫構(gòu)建規(guī)則和檢查安裝環(huán)境的功能。

要發(fā)布自己的源碼,首先需要一個(gè)Autoconf的configure.ac,最簡(jiǎn)單的長(zhǎng)這樣:
AC_INIT([hello], [1.0])
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS(config.h)
AC_PROG_CC
AC_CONFIG_FILES(Makefile)
AC_PROG_INSTALL
AC_OUTPUT
其次還需要一個(gè)Automake的Makefile.am來(lái)描述構(gòu)建規(guī)則,看起來(lái)這這樣的:
AC_INIT([hello], [1.0])
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE
AC_PROG_CC
AC_CONFIG_FILES(Makefile)
AC_PROG_INSTALL
AC_OUTPUT
定義好檢查環(huán)境和配置的configure.ac和描述構(gòu)建規(guī)則的Makefile.am,生成一個(gè)可以發(fā)布的源碼包大概過(guò)程如下:
aclocal
autoconf
autoheader
touch NEWS README AUTHORS ChangeLog
automake -a
./configure
make
make dist
CMake

CMake是一個(gè)跨平臺(tái)的安裝(編譯)工具,可以用簡(jiǎn)單的語(yǔ)句來(lái)描述所有平臺(tái)的安裝(編譯過(guò)程)。他能夠輸出各種各樣的Makefile或者project文件,能檢查編譯器所支持的C++特性,類似UNIX下的automake。CMake 并不直接建構(gòu)出最終的軟件,而是產(chǎn)生標(biāo)準(zhǔn)的建構(gòu)腳本(如Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再使用相應(yīng)的工具進(jìn)行編譯。
CMak的特點(diǎn)主要有:
- 開放源代碼, 使用類 BSD 許可發(fā)布。 http://cmake.org/HTML/Copyright. html
- 跨平臺(tái), 并可生成 native 編譯配置文件, 在 Linux/Unix 平臺(tái), 生成 makefile, 在蘋果平臺(tái), 可以生成 xcode, 在 Windows 平臺(tái), 可以生成 MSVC 的工程文件。
- 能夠管理大型項(xiàng)目, KDE4 就是最好的證明。
- 簡(jiǎn)化編譯構(gòu)建過(guò)程和編譯過(guò)程。 CMake 的工具鏈非常簡(jiǎn)單:
cmake+make。 - 可擴(kuò)展, 可以為 cmake 編寫特定功能的模塊, 擴(kuò)充 cmake 功能。
其實(shí)CMake工具包不僅僅提供了編譯,還有:支持單元測(cè)試的CTest,支持不同平臺(tái)打包的CPack,自動(dòng)化測(cè)試及其展示的CDash。有興趣的訪問(wèn)官方網(wǎng)站學(xué)習(xí):https://cmake.org/
一般,在每個(gè)源碼目錄下都有一個(gè) CMakeLists.txt,看起來(lái)是這樣的:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
使用的時(shí)候:
第一步:根據(jù)CMakeLists.txt生成Makefile,命令如下:
mkdir path-to-build
cd path-tob-build
cmake path-to-source
cmake的過(guò)程可以分為配置和生成過(guò)程。配置的時(shí)候優(yōu)先從CMakeCache.txt中讀取設(shè)置,然后再掃一遍CMakeList.txt中的設(shè)置,該步驟會(huì)檢查第三方庫(kù)和構(gòu)建過(guò)程的變量;生成步驟則根據(jù)當(dāng)前的環(huán)境和平臺(tái),生成不同的構(gòu)建腳本,如Linux的Makefile,Windows的VC工程文件。

第二步:編譯。沒(méi)啥好說(shuō)的,Linux下直接make -jxx,其他的操作系統(tǒng)的IDE直接打開點(diǎn)一下build按鈕即可。
非基于make的
非基于make的構(gòu)建系統(tǒng)五花八門,這里只大概介紹一下我所知的幾個(gè)。
SCons
SCons 是一個(gè)開放源代碼、以 Python 語(yǔ)言編寫的下一代的程序建造工具。作為下一代的軟件建造工具,SCons 的設(shè)計(jì)目標(biāo)就是讓開發(fā)人員更容易、更可靠和更快速的建造軟件。與傳統(tǒng)的 make 工具比較,SCons 具有以下優(yōu)點(diǎn):
- 使用 Python 腳本做為配置文件。
- 對(duì)于 C,C++,Fortran,內(nèi)建支持可靠自動(dòng)依賴分析 。不用像 make 工具那樣需要 執(zhí)行"make depends"和"make clean"就可以獲得所有的依賴關(guān)系。
- 內(nèi)建支持 C, C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG 以及 Tex/Latex。 用戶還可以根據(jù)自己的需要進(jìn)行擴(kuò)展以獲得對(duì)需要編程語(yǔ)言的支持。
- 支持 make -j 風(fēng)格的并行建造。相比 make -j, SCons 可以同時(shí)運(yùn)行 N 個(gè)工作,而 不用擔(dān)心代碼的層次結(jié)構(gòu)。
- 使用 Autoconf 風(fēng)格查找頭文件,函數(shù)庫(kù),函數(shù)和類型定義。
- 良好的夸平臺(tái)性。SCons 可以運(yùn)行在 Linux, AIX, BSD, HP/UX, IRIX, Solaris, Windows, Mac OS X 和 OS/2 上。
SCons架構(gòu):

SCons的腳本名為SConstruct, 內(nèi)容看起來(lái)是這樣的:
Program('helloscons2', ['helloscons2.c', 'file1.c', 'file2.c'],
LIBS = 'm',
LIBPATH = ['/usr/lib', '/usr/local/lib'],
CCFLAGS = '-DHELLOSCONS')
其中,
- LIBS: 顯示的指明要在鏈接過(guò)程中使用的庫(kù),如果有多個(gè)庫(kù),應(yīng)該把它們放在一個(gè)列表里面。這個(gè)例子里,我們使用一個(gè)稱為 m 的庫(kù)。
- LIBPATH: 鏈接庫(kù)的搜索路徑,多個(gè)搜索路徑放在一個(gè)列表中。這個(gè)例子里,庫(kù)的搜索路徑是 /usr/lib 和 /usr/local/lib。
- CCFLAGS: 編譯選項(xiàng),可以指定需要的任意編譯選項(xiàng),如果有多個(gè)選項(xiàng),應(yīng)該放在一個(gè)列表中。這個(gè)例子里,編譯選項(xiàng)是通過(guò) -D 這個(gè) gcc 的選項(xiàng)定義了一個(gè)宏 HELLOSCONS。
編譯命令:
$ scons -Q
gcc -o file1.o -c -DHELLOSCONS file1.c
gcc -o file2.o -c -DHELLOSCONS file2.c
gcc -o helloscons2.o -c -DHELLOSCONS helloscons2.c
gcc -o helloscons2 helloscons2.o file1.o file2.o -L/usr/lib -L/usr/local/lib -lm
Waf
SCons項(xiàng)目小的話還好,規(guī)模一大,依賴分析速度急速下降,而且自動(dòng)配置功能很弱 (跨平臺(tái)構(gòu)建能力不足),Waf嘗試去解決SCons所暴露的問(wèn)題。Waf也是基于Python的配置、編譯、安裝程序。主要特性:
- 構(gòu)建順序自動(dòng)化:輸入輸出文件的構(gòu)建順序自動(dòng)化識(shí)別。
- 依賴自動(dòng)分析:根據(jù)文件或命令自動(dòng)進(jìn)行依賴分析。
- 性能:任務(wù)都是并發(fā)執(zhí)行的。
- 靈活性:可以方便地通過(guò)添加新的子類創(chuàng)建新的命令或任務(wù),特定構(gòu)建過(guò)程中的瓶頸可以動(dòng)過(guò)方法的動(dòng)態(tài)重載來(lái)消除。
- 可擴(kuò)展性:默認(rèn)支持多種編程語(yǔ)言和編譯器,有需求新加的也可以通過(guò)插件進(jìn)行支持。
- IDE支持:Eclipse, Visual Studio and Xcode project generators (waflib/extras/)
- 文檔詳細(xì):入門到深入可以閱讀:《Waf Book》
- Python兼容:cPython 2.5 to 3.4, Jython 2.5, IronPython, and Pypy
一個(gè)簡(jiǎn)單的C++構(gòu)建腳本wscript,先睹為快:
#! /usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2006-2010 (ita)
# the following two variables are used by the target "waf dist"
VERSION='0.0.1'
APPNAME='cxx_test'
# these variables are mandatory ('/' are converted automatically)
top = '.'
out = 'build'
def options(opt):
opt.load('compiler_cxx')
def configure(conf):
conf.load('compiler_cxx')
conf.check(header_name='stdio.h', features='cxx cxxprogram', mandatory=False)
def build(bld):
bld.shlib(source='a.cpp', target='mylib', vnum='9.8.7')
bld.shlib(source='a.cpp', target='mylib2', vnum='9.8.7', cnum='9.8')
bld.shlib(source='a.cpp', target='mylib3')
bld.program(source='main.cpp', target='app', use='mylib')
bld.stlib(target='foo', source='b.cpp')
# just a test to check if the .c is compiled as c++ when no c compiler is found
bld.program(features='cxx cxxprogram', source='main.c', target='app2')
if bld.cmd != 'clean':
from waflib import Logs
bld.logger = Logs.make_logger('test.log', 'build') # just to get a clean output
bld.check(header_name='sadlib.h', features='cxx cxxprogram', mandatory=False)
bld.logger = None
Boost.Build(b2)
在編譯Boost庫(kù)的時(shí)候,會(huì)用到b2命令,其實(shí)就是Boost.Build的縮寫。編譯C++/C代碼時(shí),只需要指定要編譯那些可執(zhí)行文件或庫(kù),然后列出相關(guān)的源碼,Boost.Build幫你搞定其他事情,支持Windows、OSX、Linux和商業(yè)的Unix系統(tǒng)。
HelloWorld項(xiàng)目的jamroot.jam腳本(Jamfiles,一種不同于Makefile的構(gòu)建腳本,有興趣自google):
exe hello : hello.cpp ;
Boost.Build是一個(gè)高級(jí)編譯系統(tǒng),它能盡可能容易的管理C++項(xiàng)目集。其思想是在配置文件中指定編譯程序的要素。例如,它不需要告訴Boost.Build如何使用某個(gè)編譯器。Boost.Build支持多個(gè)編譯程序,并知道如何使用它們。如果你創(chuàng)建一個(gè)配置文件,你只需要告訴Boost.Build在何處尋找源文件,調(diào)用哪些可執(zhí)行文件,Boost.Build使用哪個(gè)編譯器。然后,Boost.Build將嘗試查找編譯器并自動(dòng)生成程序。
Boost.Build支持許多不包含任何編譯器特定選項(xiàng)的編譯器的配置文件。配置文件完全是編譯器獨(dú)立的。當(dāng)然,可以設(shè)置選項(xiàng)是否應(yīng)該優(yōu)化代碼。這些選項(xiàng)都是boost.build語(yǔ)言寫的。一旦選擇編譯器去編譯程序, Boost.Build會(huì)將配置文件中的選項(xiàng)翻譯成相應(yīng)編譯器的命令行選項(xiàng)。這樣就有可能寫一次配置文件,在不同的平臺(tái)上用不同的編譯器構(gòu)建程序。
Boost.Build只支持C++和C項(xiàng)目。它是為在不同平臺(tái)上用不同編譯器編譯和安裝Boost C++庫(kù)而創(chuàng)造的。
小結(jié)
各種構(gòu)建系統(tǒng)各有優(yōu)缺點(diǎn),需要深入研究和使用才能了解。沒(méi)有那個(gè)是最好的,只有最適合的。一般:
- 一兩個(gè)源文件的C++代碼,完全沒(méi)必要用構(gòu)建系統(tǒng),直接使用編譯器命令直接搞定;
- 自己用的小項(xiàng)目,直接手動(dòng)寫Makefile即可
- 大型C++項(xiàng)目建議使用CMake,GNU Build System比較年齡大了,規(guī)則有些復(fù)雜,寫起來(lái)沒(méi)有CMake那么舒服,跨平臺(tái)的話就根本沒(méi)戲。
- 偶爾突破一下,想嘗試一下新鮮的構(gòu)建系統(tǒng),SCons、Waf、B2等等,等著你玩。
- 某天感覺(jué)構(gòu)建系統(tǒng)也不過(guò)如此,閑的無(wú)聊你也可以嘗試寫一個(gè),這不500行代碼搞定:http://www.aosabook.org/en/500L/contingent-a-fully-dynamic-build-system.html