http://scons.org/html
https://www.ibm.com/developerworks/cn/linux/l-cn-scons/index.htmlpython
後附:另外,WAF是一個基於scons的構建工具,並且是 Re-design of scons to improve its worst features。linux
在軟件項目開發過程當中,make 工具一般被用來建造程序。make 工具經過一個被稱爲 Makefile 的配置文件能夠自動的檢測文件之間的依賴關係,這對於建造複雜的項目很是有幫助,然而,編寫 Makefile 自己卻不是一件容易的事情。SCons 是一個用 Python 語言編寫的相似於 make 工具的程序。與 make 工具相比較,SCons 的配置文件更加簡單清晰明瞭,除此以外,它還有許多的優勢。本文將簡單介紹如何在軟件開發項目中使用 SCons,經過本文,讀者能夠學習到如何使用 SCons 來建造本身的程序項目。程序員
make 這個工具自上個世紀 70 年代 Stuart Feldman 在貝爾實驗室開發出以來,就一直是類 UNIX 程序員的最愛之一。經過檢查文件的修改時間,make 工具能夠知道編譯目標文件所要依賴的其餘文件。在複雜的項目中,若是隻有少數幾個文件修改過,make 工具知道僅僅須要對哪些文件從新編譯就能夠確保目標程序被正確的編譯連接。這樣作的好處就是在編譯中,不只能夠節省大量的重複輸入,還能夠確保程序能夠被正確的連接,縮短編譯的時間。雖然如此,可是爲 make 工具編寫建造規則卻不是一件容易的事。它複雜的配置規則,即便是有經驗的開發者也望而生畏。make 工具的許多替代品便所以而誕生,SCons 就是是其中之一。SCons 是一個用 Python 語言編寫的相似於 make 工具的程序。與 make 工具相比較,SCons 的配置文件更加簡單清晰明瞭,除此以外,它還有許多的優勢。編程
SCons 是一個開放源代碼、以 Python 語言編寫的下一代的程序建造工具。它最初的名字是 ScCons, 基於由 perl 語言編寫的 Cons 軟件開發而成,它在 2000 年 8 月得到了由 Software Carpentry 舉辦的 SC 建造比賽的大獎。如今 ScCons 已經被更名爲 SCons,目的是爲了表示再也不與 Software Carpentry 有聯繫,固然,還有一個目的,就是爲了更方便的輸入。vim
做爲下一代的軟件建造工具,SCons 的設計目標就是讓開發人員更容易、更可靠和更快速的建造軟件。與傳統的 make 工具比較,SCons 具備如下優勢:markdown
SCons 支持多種操做系統平臺,併爲各個系統製做了易於安裝的文件,所以在各個系統平臺上的安裝方法不盡相同,在 SCons 的官方網站上能夠查每一個平臺的具體安裝方法。 若是 SCons 沒有爲你的系統製做相應的安裝包,你也能夠下載 SCons 的源代碼,直接進行安裝。 首先,從 SCons 的網站上下載最新的 SCons 源代碼包(目前 SCons 的最新版本是 2.0.1)。 其次,解壓下載的源代碼。視下載的源代碼包的格式不一樣而有不一樣的方法,在 Windows 平臺上,但是使用 winzip 或者其餘相似的工具解壓。在 Linux 平臺上,對於 tar 包,使用 tar 命令進行解壓,如:app
$ tar -zxf scons-2.0.1.tar.gz
而後切換進入解壓後的目錄進行安裝,如ssh
$ cd scons-2.0.1 $ sudo python setup.py install
命令執行若是沒有錯誤,那麼 scons 就被安裝到系統上了。對於 Linux 來講,scons 會默認安裝到 /usr/loca/bin 目錄下,而在 Windows 平臺上,則會被安裝到 C:\Python25\Scripts 下。編程語言
在 SCons 安裝完成後,咱們就可使用 SCons 來建造咱們的程序或者項目了。像不少編程書籍那樣,在這裏咱們也經過一個簡單的 helloscons 例子來講明如何使用 SCons。例子 helloscons 包含兩個文件 :
$ ls helloscons helloscons.c SConstruct
其中 helloscons.c 是程序的源文件,SConstruct 是 scons 的配置文件,相似使用 make 工具時的 Makefile 文件,所以,爲了編譯你的項目,須要手工建立一個 SConstruct 文件(注意:文件名是大小寫敏感的)。不過,在編譯的時候不須要指定它。 要編譯這個例子,切換到 helloscons 的目錄下,運行 scons 命令,以下:
$ cd helloscons/ $ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o helloscons.o -c helloscons.c gcc -o helloscons helloscons.o scons: done building targets.
來查看一下運行 scons 命令後獲得的結果 :
$ ls helloscons helloscons.c helloscons.o SConstruct
建造結束後,獲得了二進制文件 helloscons 以及編譯的過程當中產生的一些以 .o 結尾的目標文件。試運行 helloscons 一下 , 會獲得 :
$ ./helloscons Hello, SCons!
如今讓咱們回過頭來解析一下 helloscons 這個例子 . helloscons.c 是這個例子裏的惟一一個源代碼文件,它所作的事就是在控制檯上輸出一行簡單的"Hello,SCons", 它的源代碼以下:
#include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { printf("Hello, SCons!\n"); return 0; }
做爲項目建造規則的配置文件 SConstruct 的內容以下 :
Program('helloscons.c')
你可能很驚訝 SConstruct 的內容只有一行,然而事實確實如此,它比傳統的 Makefile 簡單不少。SConstruct 以 Python 腳本的語法編寫,你能夠像編寫 Python 腳本同樣來編寫它。其中的 Program 是編譯的類型,說明你準備想要建造一個可執行的二進制程序,它由 helloscons.c 文件來生成。在這裏,沒有指定生成的可執行程序的名字。不過不用擔憂,SCons 會把源代碼文件名字的後綴去掉,用來做爲可執行文件的名字。在這裏,咱們甚至不須要像 Makefile 那樣指定清理的動做,就能夠執行清理任務。在 SCons 中,執行清理任務由參數 -c 指定,以下 :
$ scons -c scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed helloscons.o Removed helloscons scons: done cleaning targets. $ ls helloscons.c SConstruct
若是你不想直接編譯可執行的二進制文件,那也沒有關係。SCons 支持多種編譯類型,你能夠根據本身的須要,任意選用其中的一種。SCons 支持的編譯類型有:
這個簡單的 SConstruct 的配置文件從一個側面說明了使用 SCons 來建造程序是多麼的簡單。 在實際的項目開發中,程序的建造規則遠比 helloscons 這個例子複雜。不過,這些都不是問題,你能夠像擴展你本身的 Python 腳本文件那樣去擴展 SConstruct. 若是你不想使用 SConstruct 爲你設置的默承認執行文件的名字,而是選擇你本身喜歡的名字,如 myscons,能夠把 SConstruct 的內容修改成 :
Program('myscons, 'helloscons.c')
其中 myscons 就是你想要的可執行文件的名字,你能夠把它換成任意你喜歡的名字, 不過有點注意的是,這個名字必須放在第一位。 而後在 helloscons 目錄下運行 scons 命令,就會獲得 myscons 這個可執行文件,如 下:
$ scons -Q gcc -o helloscons.o -c helloscons.c gcc -o myscons helloscons.o
其中的 -Q 參數是減小編譯時的由 scons 產生的冗餘信息。 若是你的項目由多個源文件組成,並且你想指定一些編譯的宏定義,以及顯式的指定使用某些庫,這些對於 SCons 來講,都是很是簡單的事情。咱們的另一個例子 helloscons2 很好的說明這種狀況。 helloscons2 由 3 個源文件組成 , 它們是 helloscon2.c, file1.c, file2.c,另外指定了編譯的選項,同時還指定了使用哪些具體的庫。讓咱們來看一下 helloscons2 的 SConstruct 文件 :
Program('helloscons2', ['helloscons2.c', 'file1.c', 'file2.c'], LIBS = 'm', LIBPATH = ['/usr/lib', '/usr/local/lib'], CCFLAGS = '-DHELLOSCONS')
正如你想像的那樣,這樣一個配置文件並不複雜 . 該 SConstruct 文件指出,它將生成一個名叫 helloscons2 的可執行程序,該可執行程序由 helloscons2.c, file1.c 和 file2.c 組成。注意,多個源文件須要放在一個 Python 列表中。若是你的源程序代碼文件不少,有十幾個甚至上百個,那不要一個個的將他們都列出來,你可使用 glob('*.c') 來代替源代碼列表。以下 :
Program('helloscons2', Glob('*.c')
配置文件中 LIBS,LIBAPTH 和 CCFLAGS 是 SCons 內置的關鍵字,它們的做用以下:
運行 scons 命令的時候,能夠看到這些變量如何被使用的,讓咱們執行一下 scons 命令 :
$ 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
scons 命令的輸出顯示了可執行程序 helloscons2 如何由多個源文件而生成,以及在 SConstruct 中定義的 LIBS,LIBPATH 和 CCFLAGS 如何被使用。 可見,即便對於複雜的項目,SCons 的編譯配置文件也很簡單。除此以外,SCons 也提供了不少功能以適應不一樣的須要,若是讀者想更深刻的瞭解如何使用 SCons,能夠參考 SCons 的幫助手冊。
本文簡單介紹了 SCons 的特色,如何安裝 SCons,以及經過例子來講明如何在項目中使用 SCons。 做爲下一代的軟件建造工具,SCons 使用 Python 語言做爲配置文件,不但功能強大,並且簡單易用,對於跨平臺的項目,很是適合。 若是你厭煩了 make 工具的那種複雜的編寫規則,嘗試一下新鮮的 SCons 吧。
scons 交叉編譯的例子:
https://bitbucket.org/scons/scons/wiki/PlatformToolConfig#markdown-header-cross-compilation_1
https://stackoverflow.com/questions/23898584/how-can-i-use-a-cross-compiler-with-scons
https://stackoverflow.com/questions/31232955/using-scons-to-compile-c-file-with-std-c11-flag
簡單的例子:
(1)若是想要生成兩個編譯器版本的代碼,好比在PC機上的GCC編譯和ARM Linux gcc交叉編譯,而且在編譯的時候能夠選擇,SConstruct內容以下,源代碼測試文件仍是前一個hello world程序:
SConstruct:
src = Glob('*.c') platform = ARGUMENTS.get('platform','pc') if platform == 'arm': EXE_PATH = '/opt/arm-2007q1/bin' PREFIX = 'arm-none-linux-gnueabi-' ARMCC = PREFIX + 'gcc' ARMAS = PREFIX + 'gcc' ARMAR = PREFIX + 'ar' ARMLINK = PREFIX + 'gcc' ARMSIZE = PREFIX + 'size' ARMOBJDUMP = PREFIX + 'objdump' ARMOBJCPY = PREFIX + 'objcpy' env = Environment (AS = ARMAS, CC = ARMCC, AR = ARMAR, LINK = ARMLINK) env.PrependENVPath ('PATH',EXE_PATH) elif platform == 'pc': env = Environment() else: pass env.Program(source = src, target = 'hello_world')
第二條語句指定了若platform缺省時爲pc,故執行scons 與scons platform=pc效果同樣。運行以下:
$ ls main.c SConstruct $ scons platform=arm scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... arm-none-linux-gnueabi-gcc -o main.o -c main.c arm-none-linux-gnueabi-gcc -o hello_world main.o scons: done building targets. $ scons platform=pc scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o main.o -c main.c gcc -o hello_world main.o scons: done building targets. $ scons -c scons: Reading SConscript files ... scons: done reading SConscript files. scons: Cleaning targets ... Removed main.o Removed hello_world scons: done cleaning targets. $ scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o main.o -c main.c gcc -o hello_world main.o scons: done building targets.
(2)一樣,這個方法還能夠用來編譯器生成不一樣版本的代碼(如release版本和debug版本),以下:
> vim SConstruct src = Glob('*.c') debug = ARGUMENTS.get('debug',0) vars = Variables(None,ARGUMENTS) vars.Add('debug','Set to 1 to build for debug', 0) env = Environment(variables = vars) Help(vars.GenerateHelpText(env)) if int(debug) == 1: env.Append( CCFLAGS = '-g') env.Program(source = src, target = 'hello_world') > ls main.c SConstruct > scons debug=1 scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o main.o -c -g main.c gcc -o hello_world main.o scons: done building targets. > scons debug=0 scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... gcc -o main.o -c main.c gcc -o hello_world main.o scons: done building targets.
這樣,即可控制debug版本和release版本的生成,另外SConstruct 腳本中第三、四、五、6句用來產生幫助信息,能夠加上-h參數以運行使用:
> scons -h scons: Reading SConscript files ... scons: done reading SConscript files. debug: Set to 1 to build for debug default: 0 actual: 0 Use scons -H for help about command-line options.
這樣即可提示用戶使用命令。
(3)變量(如debug=1,platform=pc)還能夠從文件中引入,詳細在用戶手冊(10.2.3. Reading Build Variables From a File)一節,預編譯定義也可由這種方法定義。
如下是我本身的幾個 SConstruct 的實例:
# -*- coding: utf-8 -*-
cpp_defines = ['DSO_DLFCN', 'HAVE_DLFCN_H', 'NDEBUG', 'OPENSSL_NO_STATIC_ENGINE', 'OPENSSL_PIC']
#設置預編譯選項,至關於在實際的預編譯選項裏增長 -DL_ENDIAN, -DOPENSSL_USE_NODELETE
cpp_defines.append(['L_ENDIAN', 'OPENSSL_USE_NODELETE'])
# cpp_defines.append({'OPENSSLDIR': '\\"\\"'}) # 設置預編譯選項 OPENSSLDIR=\"\" , 不要用這種方式,這樣定義無效,用後面的方式
include_path = ['./include'] # 設置預編譯選項
include_path.append('.')
include_path.append('./include/x509v3')
env=Environment(CPPPATH=include_path) #這裏賦值會覆蓋之前定義的值,包括scons的默認搜索範圍
env.Append(CPPDEFINES=cpp_defines) #這裏是append,不會覆蓋原有的值,只是語法中的=號有點讓人模棱兩可
# 要定義這樣的 -DOPENSSLDIR=\"\" 這樣的預編譯指令,只能按照下面這種方式,不能用cpp_defines = ['DES_DEFAULT_OPTIONS=\"\"'] 這種方式
env.Append(CPPDEFINES={'OPENSSLDIR': '\"\"'})
env.Append(CCFLAGS='-Wall -m64 -fPIC')
env.Append(CCFLAGS='-g -ggdb -g3')
# FILES = env.Glob('*.c')
FILES = [f for f in Glob('*.c') if 'LPdir_unix.c' not in str(f)] # 過濾掉 LPdir_unix.c 文件,它不會被編譯
env.StaticLibrary('libcrypto.a', FILES)
# -*- coding: utf-8 -*- cpp_defines = ['LIBSSH_EXPORTS', '_LARGEFILE64_SOURCE'] include_path = ['./include'] include_path.append('.') cflags = [] # cflags.append(['-g', '-ggdb', '-g3', '-fprofile-arcs', '-ftest-coverage']) cflags.append(['-g', '-ggdb', '-g3']) cflags.append(['-std=gnu99', '-pedantic', '-pedantic-errors', '-Wall', '-Wextra', '-Wshadow', '-Wmissing-prototypes']) cflags.append(['-Wdeclaration-after-statement', '-Wunused', '-Wfloat-equal', '-Wpointer-arith', '-fvisibility=hidden']) cflags.append(['-Wwrite-strings', '-Wformat-security', '-Wmissing-format-attribute', '-fPIC', '-fstack-protector']) ld_path = [] ld_path.append('./libcrypto') libs = [] libs.append('crypto') libs.append('dl') ldflags = ['-rdynamic', '-fprofile-arcs', '-ftest-coverage', '--coverage'] ldflags.append('-g') ldflags.append('-ggdb') ldflags.append('-g3') ldflags.append(cflags) env=Environment(CPPPATH=include_path, LIBPATH=ld_path, LIBS=libs) env.Append(CPPDEFINES=cpp_defines) env.Append(CCFLAGS=cflags) env.Append(LINKFLAGS=ldflags) FILES = env.Glob('*.c') env.Program('ssh_server', FILES)