第三章 簡化構建過程
? 在本章節(jié)中兼雄,通過幾個簡單的SCons工程構建示例疑苔,向您展示無論是哪種編程語言规丽,亦或哪種系統(tǒng)平臺份企,軟件構建都將會是一件非常簡單的事情也榄。
3.1 指定編譯輸出文件
? 當調用Program構建方法時,它會構建和源代碼文件(多份則采取第一個文件)相同名稱的輸出文件司志,因此如果源代碼是hello.c甜紫,則構建出來的可執(zhí)行文件則為hello(POSIX平臺)降宅,或者hello.exe(WINDOWS平臺)。
Program('hello.c')
? 如果您想編譯出不同名稱的可執(zhí)行文件囚霸,則需要在源文件的前面腰根,指明輸出名稱即可(如new_hello):
Program('new_hello', 'hello.c')
? (Scons需要先指明目標文件名稱,后面才是源文件邮辽,這樣做的目的是和大多數(shù)的編程語言保持一致唠雕,python也是如此:“program = source files”)
? 現(xiàn)在執(zhí)行scons指令則會得到new_hello的編譯輸出:(POSIX平臺)
> scons -Q
cc -o hello.o -c hello.c
cc -o new_hello hello.o
? 得到new_hello.exe的編譯輸出:(WINDOWS平臺)
C:\> scons -Q
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:new_hello.exe hello.obj
embedManifestExeCheck(target, source, env)
3.2 編譯多個源文件
? 現(xiàn)在知道了如何編譯單個源文件,但是大多數(shù)情況下吨述,需要構建的工程岩睁,是由多個源文件構成,而非單一文件揣云,因此多源文件編譯時捕儒,需要將文件名放入一個python列表(list)中:
Program(['prog.c', ['file1.c', 'fil2.c'])
? 編譯輸出為:(POSIX平臺)
> scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o prog prog.o file1.o file2.o
? (WINDOWS平臺)
C:\>scons -Q
cl /Fofile1.obj /c file1.c /nologo
cl /Fofile2.obj /c file2.c /nologo
cl /Foprog.obj /c prog.c /nologo
link /nologo /OUT:program.exe prog.obj file1.obj file2.obj
embedManifestExeCheck(target, source, env)
3.3 采用Glob創(chuàng)建列表
? 您也可以采用Glob函數(shù)匹配所有符合條件的源文件,匹配原則采用標準腳本通配符“*”邓夕,“?”刘莹。[abc]將匹配a,b,c;[!abc]將匹配除abc意外的任意字符焚刚。這使得多個源文件的構建變得簡單了很多点弯。
Program('program', Glob('*.c'))
? SCons的幫助手冊有更多關于Glob函數(shù)的具體用法細節(jié),可以參考矿咕。
3.4 單一文件構建 VS 文件列表構建
? 構建工程目前有兩種形式抢肛,一種是采用文件列表,一種是采用單一文件直接構建的方式:
# 文件列表
Program('hello', ['file1.c', 'file2.c'])
# 單一文件
Program('hello', 'hello.c')
? 當然您也可以在文件列表中碳柱,只存儲一份文件:
Program('hello', ['hello.c'])
? 其實SCons內(nèi)部都是將源文件以列表的形式存儲捡絮,只是為了方便起見,允許用戶直接輸入單一文件莲镣。
重要提示
? 盡管SCons支持多種輸入方式福稳,但是python語言更為嚴格,因此在進行l(wèi)ist和string格式的文件相加則會產(chǎn)生python語法錯誤:
common_sources = ['file1.c', 'file2.c']
# 下述代碼將會報錯瑞侮, string和list相加
Program('program1', common_sources + 'program1.c')
# 下述代碼正確的圆, list與list相加
Program('program2', common_sources + ['program2.c'])
3.5 讓列表文件易于讀寫
? 對于文件列表而言,其缺點是每一個文件名稱都需要用引號包起來区岗,無論是單引號還是雙引號略板,當列表比較長時,看起來都比較麻煩慈缔。SCons提供了很多工具函數(shù)使得這一工作變得很簡單叮称。
? SCons提供了Split函數(shù)用于分割字符串文件中的一個或多個空格,亦或一個或多個tab,分割后以文件列表的形式存儲:
Program('program', Split('main.c file1.c file2.c'))
? (如果您熟悉python的話瓤檐,您會發(fā)現(xiàn)這和python標準字符串string模板的split()函數(shù)很相似赂韵,但是和split()不同的是,Split()不需要輸入一定為string挠蛉,其他輸入也可以(如列表)祭示,它會自動將其進行分割成列表,確保輸出格式的正確性谴古。)
? 直接將Split()函數(shù)嵌入到Program中质涛,顯得比較粗魯,可以進一步采用臨時變量增加代碼的可讀性:
src_files = Split('main.c file1.c file2.c')
Program('program', src_files)
? 最后掰担,Split函數(shù)不關系輸入中有多少個空格汇陆,因此您可以跨行編輯列表,進一步提高可讀性:
src_file = Split("""" main.c
file1.c
file2.c""")
Program('program', src_files)
? 請注意带饱,在此示例中毡代,我們使用了Python的“三重引用”語法,該語法允許一個字符串包含多行勺疼。三個引號可以是單引號或雙引號教寂。
3.6 關鍵字參數(shù)
? SCons同樣支持輸入或輸出文件的關鍵字定義。輸出文件的關鍵字是target执庐,輸入源文件的關鍵字是source:
src_files = Split('main.c file1.c file2.c')
Program(target='program', source=src_files)
? 因為關鍵字已經(jīng)指明了該參數(shù)的含義酪耕,因此關鍵字的順序不做要求:
src_files = Split('main.c file1.c file2.c')
Program(source=src_files, target='program')
? 是否選擇使用關鍵字,以及采用關鍵字時的順序指定轨淌,這些都是是個人喜好因妇,SCons不做特殊要求。
3.7 編譯多個工程
? 如果您想采用同一個SConstruct文件猿诸,構建多個工程,最簡單的方式是調用Program多次構建:
Program('foo.c')
Program('bar', ['bar1.c', 'bar2.c'])
? SCons則會輸出如下:
> scons -Q
cc -o bar1.o -c bar1.c
cc -o bar2.o -c bar2.c
cc -o bar bar1.o bar2.o
cc -o foo.o -c foo.c
cc -o foo foo.o
? 請注意狡忙,SCons不一定按照您在SConstruct文件中指定程序的順序來構建梳虽。 但是,SCons確實認識到必須先構建各個目標中間文件灾茁,然后才能構建最終輸出程序窜觉。
3.8 多工程編譯共享中間文件
? 代碼復用是常見的編程方式,創(chuàng)建庫文件(動態(tài)或靜態(tài)庫)是廣為人知的一種方式北专。具體庫文件的創(chuàng)建參見后續(xù)章節(jié)禀挫。
? 同一份源碼文件對應多個工程,最直接的方式是每個工程都加入該源碼:
Program(Split('foo.c common1.c common2.c'))
Program('bar', Split('bar1.c bar2.c common1.c common2.c'))
? 當采用這種方式構建時拓颓,SCons會意識到common1.c和common2.c的復用语婴,因此會將其僅僅編譯一份中間文件,盡管生成的目標文件分別鏈接到各自的中間文件:
% scons -Q
cc -o bar1.o -c bar1.c
cc -o bar2.o -c bar2.c
cc -o common1.o -c common1.c
cc -o common2.o -c common2.c
cc -o bar bar1.o bar2.o common1.o common2.o
cc -o foo.o -c foo.c
cc -o foo foo.o common1.o common2.o
? 如果兩個或多個程序共享許多公共源文件,則可以將公共源文件創(chuàng)建為一個列表砰左,以便維護和更改匿醒。同時采用python的+運算符進行其他列表的擴充:
common = ['common1.c', 'common2.c']
foo_files = ['foo.c'] + common
bar_files = ['bar1.c', 'bar2.c'] + common
Program('foo', foo_files)
Program('bar', bar_files)
? 這和前面的寫法是完全等價的。
3.9 構建方法參數(shù)的覆蓋
? 您也可以通過在構建方法(如Program等)中增加對應參數(shù)缠导,或關鍵字參數(shù)廉羔,來指定相關的參數(shù),最終參數(shù)會以此為準僻造。
env.Program('hello', 'hello.c', LIBS=['gl', 'glut'])
? 或者生成一個非標準后綴的動態(tài)庫:
env.SharedLibrary('word', 'word.cpp',
SHLIBSUFFIX='.ocx',
LIBSUFFIXES=['.ocx'])
? 您也可以采用parse_flags關鍵字將命令行樣式參數(shù)合并到適當?shù)臉嬙熳兞恐斜锼1纠龑ⅰ痠nclude‘增加到CPPPATH變量中, 將‘EBUG’加入到CPPDEFINES變量中,以及將'm'加入到LIBS變量中:
enc = Program('hello', 'hello.c', parse_flags='-Iinclude -DEBUG -lm')
? 在對構建方法的調用中髓削,不會克隆編譯環(huán)境竹挡,而是通過調用OverrideEnvironment(), 它會創(chuàng)建一個整個Environment()更輕便編譯環(huán)境。