1.簡單編譯
源文件:hello.cpp
-
#include<iostream>
-
using
namespace
std;
-
-
int main()
-
{
-
cout <<
"Hello, World!" <<
endl;
-
return
0;
-
}
用SCons編譯它,需要在一個名為SConstruct的文件:
Program('hello.cpp')
這個短小的配置文件給了SCons兩條信息:你想編譯什么(一個可執行程序),你編譯的輸入文件(hello.cpp)。Program是一個編譯器方法(builder_method),一個Python調用告訴SCons,你想編譯一個可執行程序。 Program編譯方法是SCons提供的許多編譯方法中一個。另一個是Object編譯方法,告訴SCons從指定的源文件編譯出一個目標文件,在SConstruct中為:
Object('hello.cpp')
使用SCons,編譯之后想要清除不需要增加特殊的命令或目標名。你調用SCons的時候,使用-c或--clean選項,SCons就會刪除合適的編譯產生的文件。
-
$ scons //編譯源文件(自動讀取SConstruct中的內容)
-
$ scons -c //清除scons編譯的文件
當你調用Program編譯方法的的時候,它編譯出來的程序名字是和源文件名是一樣的。下面的從hello.cpp源文件編譯一個可執行程序的調用將會在POSIX系統里編譯出一個名為hello的可執行程序,在windows系統里會編譯出一個名為hello.exe的可執行程序。如果你想編譯出來的程序的名字與源文件名字不一樣,你只需要在源文件名的左邊聲明一個目標文件的名字就可以了:
Program('new_hello','hello.cpp')
2. SConstruct文件
如果你使用過Make編譯系統,你應該可以推斷出SConstruct文件就相當於Make系統中的Makefile。SCons讀取SConstruct文件來控制程序的編譯。
- SConstruct文件實際上就是一個Python腳本。你可以在你的SConstruct文件中使用Python的注釋:
-
# Arrange to build the "hello" program.
-
Program(
'hello.cpp')
#"hello.cpp" is the source file.
- 重要的一點是SConstruct文件並不完全像一個正常的Python腳本那樣工作,其工作方式更像一個Makefile,那就是在SConstruct文件中SCons函數被調用的順序並不影響SCons你實際想編譯程序和目標文件的順序。換句話說,當你調用Program方法,你並不是告訴SCons在調用這個方法的同時馬上就編譯這個程序,而是告訴SCons你想編譯這個程序:
-
print
"Calling Program('hello.c')"
-
Program(
'hello.c')
-
print
"Calling Program('goodbye.c')"
-
Program(
'goodbye.c')
-
print
"Finished calling Program()"
- 指定默認的目標文件
-
Default(targets)
-
env.
Default(targets)
指定了默認的target,如果在命令行中沒有顯示指定target,那么scons將編譯默認的target,多次調用Default是合法的,實例:
-
Default(
'foo',
'bar',
'baz')
-
env.
Default([
'a',
'b',
'c'])
-
hello = env.
Program(
'hello',
'hello.c')
-
env.
Default(hello)
如果在Default中傳入參數None,那么將會清楚所有默認的target:
Default(None)
3. 編譯多個源文件
- 通常情況下,你需要使用多個輸入源文件編譯一個程序。在SCons里,只需要就多個源文件放到一個Python列表中就行了,如下所示:
Program('program',['prog.cpp','file1.cpp','file2.cpp'])
- 你可以使用Glob函數,定義一個匹配規則來指定源文件列表,比如*,?以及[abc]等標准的shell模式。如下所示:
Program('program', Glob('*.cpp'))
- 為了更容易處理文件名長列表,SCons提供了一個Split函數,這個Split函數可以將一個用引號引起來,並且以空格或其他空白字符分隔開的字符串分割成一個文件名列表,示例如下:
Program('program', Split('main.cpp file1.cpp file2.cpp'))
或者:
-
src_files=Split('main.cpp file1.cpp file2.cpp')
-
Program('program', src_files)
- SCons允許使用Python關鍵字參數來標識輸出文件和輸入文件。輸出文件是target,輸入文件是source,示例如下:
-
src_files=Split('main.cpp file1.cpp file2.cpp')
-
Program(
target='program', source=src_files)
或者:
-
src_files=Split('main.cpp file1.cpp file2.cpp')
-
Program(
source=src_files, target='program')
- 如果需要用同一個SConstruct文件編譯多個程序,只需要調用Program方法多次:
-
Program(
'foo.cpp')
-
Program(
'bar', [
'bar1.cpp',
'bar2.cpp'])
-
多個程序之間共享源文件是很常見的代碼重用方法。一種方式就是利用公共的源文件創建一個庫文件,然后其他的程序可以鏈接這個庫文件。另一個更直接,但是不夠便利的方式就是在每個程序的源文件列表中包含公共的文件,示例如下:
-
common=[
'common1.cpp',
'common2.cpp']
-
foo_files=[
'foo.cpp'] + common
-
bar_files=[
'bar1.cpp',
'bar2.cpp'] + common
-
Program(
'foo', foo_files)
-
Program(
'bar', bar_files)
4. 編譯和鏈接庫文件
(1)編譯靜態庫:
- 你可以使用Library方法來編譯庫文件:
Library('foo', ['f1.cpp', 'f2.cpp', 'f3.cpp'])
- 除了使用源文件外,Library也可以使用目標文件
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
- 你甚至可以在文件List里混用源文件和目標文件
Library('foo', ['f1.cpp', 'f2.o', 'f3.c', 'f4.o'])
- 使用StaticLibrary顯示編譯靜態庫
StaticLibrary('foo', ['f1.cpp', 'f2.cpp', 'f3.cpp'])
(2)編譯動態庫:
如果想編譯動態庫(在POSIX系統里)或DLL文件(Windows系統),可以使用SharedLibrary:
SharedLibrary('foo', ['f1.cpp', 'f2.cpp', 'f3.cpp'])
(3)鏈接庫文件:
- 鏈接庫文件的時候,使用$LIBS變量指定庫文件,使用$LIBPATH指定存放庫文件的目錄:
-
Library(
'foo', [
'f1.cpp',
'f2.cpp',
'f3.cpp'])
-
Program(
'prog', LIBS=[
'foo',
'bar'], LIBPATH=
'.')
注意到,你不需要指定庫文件的前綴(比如lib)或后綴(比如.a或.lib),SCons會自動匹配。
- 默認情況下,鏈接器只會在系統默認的庫目錄中尋找庫文件。SCons也會去$LIBPATH指定的目錄中去尋找庫文件。$LIBPATH由一個目錄列表組成,如下所示:
Program('prog', LIBS='m', LIBPATH=['/usr/lib', '/usr/local/lib'])
5. 節點對象
- 編譯方法返回目標節點列表
所有編譯方法會返回一個節點對象列表,這些節點對象標識了那些將要被編譯的目標文件。這些返回出來的節點可以作為參數傳遞給其他的編譯方法。例如,假設我們想編譯兩個目標文件,這兩個目標有不同的編譯選項,並且最終組成一個完整的程序。這意味着對每一個目標文件調用Object編譯方法,如下所示:
-
Object(
'hello.cpp', CCFLAGS=
'-DHELLO')
-
Object(
'goodbye.cpp', CCFLAGS=
'-DGOODBYE')
-
Program([
'hello.o',
'goodbye.o'])
這樣指定字符串名字的問題就是我們的SConstruct文件不再是跨平台的了。因為在Windows里,目標文件成為了hello.obj和goodbye.obj。一個更好的解決方案就是將Object編譯方法返回的目標列表賦值給變量,這些變量然后傳遞給Program編譯方法:
-
hello_list = Object('hello.cpp', CCFLAGS='-DHELLO')
-
goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE')
-
Program(
hello_list + goodbye_list)
- 顯示創建文件和目錄節點
在SCons里,表示文件的節點和表示目錄的節點是有清晰區分的。SCons的File和Dir函數分別返回一個文件和目錄節點:
-
hello_c=File('hello.cpp')
-
Program(
hello_c)
通常情況下,你不需要直接調用File或Dir,因為調用一個編譯方法的時候,SCons會自動將字符串作為文件或目錄的名字,以及將它們轉換為節點對象。只有當你需要顯示構造節點類型傳遞給編譯方法或其他函數的時候,你才需要手動調用File和Dir函數。有時候,你需要引用文件系統中一個條目,同時你又不知道它是一個文件或一個目錄,你可以調用Entry函數,它返回一個節點可以表示一個文件或一個目錄:
xyzzy=Entry('xyzzy')
- 將一個節點的文件名當作一個字符串
如果你不是想打印文件名,而是做一些其他的事情,你可以使用內置的Python的str函數。例如,你想使用Python的os.path.exists判斷一個文件是否存在:
-
import
os.
path
-
program_list=Program(
'hello.cpp')
-
program_name=str(program_list[
0])
-
if
not
os.
path.exists(program_name):
-
print program_name,
"does not exist!"
- GetBuildPath:從一個節點或字符串中獲得路徑
env.GetBuildPath(file_or_list)返回一個節點或一個字符串表示的路徑。它也可以接受一個節點或字符串列表,返回路徑列表。如果傳遞單個節點,結果就和調用str(node)一樣。路徑可以是文件或目錄,不需要一定存在:
-
env=Environment(VAR=
"value")
-
n=File(
"foo.cpp")
-
print env.GetBuildPath([n,
"sub/dir/$VAR"])
將會打印輸出如下:
-
$ scons -Q
-
[
'foo.cpp',
'sub/dir/value']
-
scons: .
is up
to date.
6. 依賴性
- 隱式依賴:$CPPPATH Construction變量
-
#include <iostream>
-
#include "hello.h"
-
using
namespace
std;
-
-
int main()
-
{
-
cout <<
"Hello, " <<
string <<
endl;
-
return
0;
-
}
並且,hello.h文件如下:
#define string "world"
在這種情況下,我們希望SCons能夠認識到,如果hello.h文件的內容發生改變,那么hello程序必須重新編譯。我們需要修改SConstruct文件如下:
Program('hello.cpp', CPPPATH='.') #CPPPATH告訴SCons去當前目錄('.')查看那些被C源文件(.c或.h文件)包含的文件。
就像$LIBPATH變量,$CPPPATH也可能是一個目錄列表,或者一個被系統特定路徑分隔符分隔的字符串。
Program('hello.cpp', CPPPATH=['include', '/home/project/inc'])
7. 環境
(1)外部環境
外部環境指的是在用戶運行SCons的時候,用戶環境中的變量的集合。這些變量在SConscript文件中通過Python的os.environ字典可以獲得。你想使用外部環境的SConscript文件需要增加一個import os語句。
(2)構造環境
一個構造環境是在一個SConscript文件中創建的一個唯一的對象,這個對象包含了一些值可以影響SCons編譯一個目標的時候做什么動作,以及決定從那一個源中編譯出目標文件。SCons一個強大的功能就是可以創建多個構造環境,包括從一個存在的構造環境中克隆一個新的自定義的構造環境。
- 創建一個構造環境:Environment函數
默認情況下,SCons基於你系統中工具的一個變量集合來初始化每一個新的構造環境。當你初始化一個構造環境時,你可以設置環境的構造變量來控制一個是如何編譯的。例如:
-
import os
-
env=Environment(CC=
'gcc', CCFLAGS=
'-O2')
-
env.
Program(
'foo.c')
-
或者
-
env=Environment(CXX=
'/usr/local/bin/g++', CXXFLAGS=
'-02')
-
env.
Program(
'foo.cpp')
- 從一個構造環境中獲取值
你可以使用訪問Python字典的方法獲取單個的構造變量:
-
env=Environment()
-
print
"CC is:", env[
'CC']
-
print
"CXX is:", env[
'CXX']
一個構造環境實際上是一個擁有方法的對象。如果你想直接訪問構造變量的字典,你可以使用Dictionary方法:
-
env=Environment(FOO=
'foo', BAR=
'bar')
-
dict=env.Dictionary()
-
for key in [
'OBJSUFFIX',
'LIBSUFFIX',
'PROGSUFFIX']:
-
print
"key=%s, value=%s" % (key,dict[key])
- 默認的構造環境:DefaultEnvironment函數
你可以控制默認構造環境的設置,使用DefaultEnvironment函數:
DefaultEnvironment(CC='/usr/local/bin/gcc')
這樣配置以后,所有Program或者Object的調用都將使用/usr/local/bin/gcc編譯目標文件。注意到DefaultEnvironment返回初始化了的默認構造環境對象,這個對象可以像其他構造環境一樣被操作。所以如下的代碼和上面的例子是等價的:
-
env=DefaultEnvironment()
-
env[
'CC']=
'/usr/local/bin/gcc'
- 多個構造環境
構造環境的真正優勢是你可以創建你所需要的許多不同的構造環境,每一個構造環境對應了一種不同的方式去編譯軟件的一部分或其他文件。比如,如果我們需要用-O2編譯一個程序,編譯另一個用-g,我們可以如下做:
-
opt=Environment(CCFLAGS=
'-O2')
-
dbg=Environment(CCFLAGS=
'-g')
-
opt.
Program(
'foo',
'foo.cpp')
-
dbg.
Program(
'bar',
'bar.cpp')
- 拷貝構造環境:Clone方法
有時候你想多於一個構造環境對於一個或多個變量共享相同的值。當你創建每一個構造環境的時候,不是重復設置所有共用的變量,你可以使用Clone方法創建一個構造環境的拷貝。Environment調用創建一個構造環境,Clone方法通過構造變量賦值,重載拷貝構造環境的值。例如,假設我們想使用gcc創建一個程序的三個版本,一個優化版,一個調試版,一個其他版本。我們可以創建一個基礎構造環境設置$CC為gcc,然后創建兩個拷貝:
-
env=Environment(CC=
'gcc')
-
opt=env.Clone(CCFLAGS=
'-O2')
-
dbg=env.Clone(CCFLAGS=
'-g')
-
env.
Program(
'foo',
'foo.cpp')
-
o=opt.
Object(
'foo-opt',
'foo.cpp')
-
opt.
Program(o)
-
d=dbg.
Object(
'foo-dbg',
'foo.cpp')
-
dbg.
Program(d)
- 替換值:Replace方法
你可以使用Replace方法替換已經存在的構造變量:
-
env=Environment(CCFLAGS=
'-DDEFINE1');
-
env.Replace(CCFLAGS=
'-DDEFINE2');
-
env.
Program(
'foo.cpp')
- 在沒有定義的時候設置值:SetDefault方法
有時候一個構造變量應該被設置為一個值僅僅在構造環境沒有定義這個變量的情況下。你可以使用SetDefault方法,這有點類似於Python字典的set_default方法:
env.SetDefault(SPECIAL_FLAG='-extra-option')
- 控制目標文件的路徑:env.Install方法
-
test = env.Program(
'test.cpp')
-
env.Install(
'bin',
'test.exe')
#表示要將test.exe 放到bin目錄下
- 執行SConscript腳本文件
-
SConscript(scripts, [
exports, variant_dir, duplicate])
-
env.SConscript(scripts, [
exports, variant_dir, duplicate])
-
SConscript(dirs=subdirs, [
name=script,
exports, variant_dir, duplicate])
-
env.SConscript(dirs=subdirs, [
name=script,
exports, variant_dir, duplicate])
調用該SConscript函數有兩種方法:
第一種方法是明確指定一個或多個 scripts
作為第一個參數。可以將單個腳本指定為字符串; 多個腳本則必須指定為列表(顯式或由函數創建 Split),
例子:
-
SConscript(
'SConscript') #在當前目錄中運行SConscript
-
SConscript(
'src / SConscript') #在src目錄中運行SConscript
-
SConscript([
'src / SConscript',
'doc / SConscript'])
# 執行多個腳本
第二種方法是將(子)目錄名稱列表指定為 dirs=subdirs 參數。在這種情況下,scons將 在每個指定目錄中執行名為SConscript的輔助配置文件 。您可以 通過提供可選的name = keyword參數來指定除了名為SConscript以外script
。例子:
-
SConscript(
dirs =
'.') #在當前目錄中運行SConscript
-
SConscript(
dirs =
'src') #在src目錄中運行SConscript
-
SConscript(
dirs = [
'src',
'doc'])
-
SConscript(
dirs = [
' sub1',
'sub2'],name =
'MySConscript')
可選exports參數提供變量名稱列表或要導出到script的命名值字典。這些變量僅在本地導出到指定 script(s)
的變量,並且不會影響Export
函數使用的全局變量池 。子腳本Script必須使用Import函數來導入變量。例子:
-
foo = SConscript(
'sub/SConscript',
exports =
'env')
-
SConscript(
'dir/SConscript',
exports = [
'env',
'variable'])
-
SConscript(dirs =
'subdir',
exports =
'env variable' )
-
SConscript(dirs = [
'one',
'two',
'three'],
exports =
'shared_info')
如果提供variant_dir參數,Sconscript位於源碼目錄之下,就像位於variant_dir目錄下一樣,例子一:
-
SConscript(
'src/SConscript', variant_dir =
'build')
-
等價於:
-
VariantDir(
'build',
'src') # 指定
obj文件的目錄
-
SConscript(
'build/SConscript')
例子二:
-
SConscript(
'SConscript', variant_dir =
'build')
-
等價於:
-
VariantDir(
'build',
'.')
-
SConscript(
'build/SConscript')
如果沒有提供variant_dir參數,那么參數duplicate參數將會被忽略,這個參數表示是否備份目標文件。
(3)執行環境
一個執行環境是SCons在執行一個外部命令編譯一個或多個目標文件時設置的一些值。這和外部環境是不同的。
- 控制命令的執行環境
當SCons編譯一個目標文件的時候,它不會使用你用來執行SCons的同樣的外部環境來執行一些命令。它會使用$ENV構造變量作為外部環境來執行命令。這個行為最重要的體現就是PATH環境變量,它決定了操作系統將去哪里查找命令和工具,與你調用SCons使用的外部環境的不一樣。這就意味着SCons將不能找到你在命令行里執行的所有工具。PATH環境變量的默認值是/usr/local/bin:/bin:/usr/bin。如果你想執行任何命令不在這些默認地方,你需要在你的構造環境中的$ENV字典中設置PATH,最簡單的方式就是當你創建構造環境的時候初始化這些值:
-
path=[
'/usr/local/bin',
'/usr/bin']
-
env=Environment(ENV={
'PATH':PATH})
以這種方式將一個字典賦值給$ENV構造變量完全重置了外部環境,所以當外部命令執行的時候,設置的變量僅僅是PATH的值。如果你想使用$ENV中其余的值,僅僅只是設置PATH的值,你可以這樣做:
env['ENV']['PATH']=['/usr/local/bin','/bin','/usr/bin']
注意SCons允許你用一個字符串定義PATH中的目錄,路徑用路徑分隔符分隔:
env['ENV']['PATH']='/usr/local/bin:/bin:/usr/bin'
- 從外部環境獲得PATH值
你可能想獲得外部的PATH來作為命令的執行環境。你可以使用來自os.environ的PATH值來初始化PATH變量:
-
import os
-
env=Environment(
ENV={'PATH'
:os.environ['PATH']})
你設置可以設置整個的外部環境:
-
import
os
-
env=
Environment(ENV=os.environ)
- 在執行環境里增加PATH的值
常見的一個需求就是增加一個或多個自定義的目錄到PATH變量中:
-
env=Environment(ENV=
os.environ)
-
env.PrependENVPath(
'PATH',
'/usr/local/bin')
-
env.AppendENVPath(
'LIB',
'/usr/local/lib')
本文參考:https://blog.csdn.net/andyelvis/article/category/948141