SCons 第三章 简化构建过程

第三章 简化构建过程

​ 在本章节中,通过几个简单的SCons工程构建示例,向您展示无论是哪种编程语言,亦或哪种系统平台,软件构建都将会是一件非常简单的事情。

3.1 指定编译输出文件

​ 当调用Program构建方法时,它会构建和源代码文件(多份则采取第一个文件)相同名称的输出文件,因此如果源代码是hello.c,则构建出来的可执行文件则为hello(POSIX平台),或者hello.exe(WINDOWS平台)。

Program('hello.c')

​ 如果您想编译出不同名称的可执行文件,则需要在源文件的前面,指明输出名称即可(如new_hello):

Program('new_hello', 'hello.c')

​ (Scons需要先指明目标文件名称,后面才是源文件,这样做的目的是和大多数的编程语言保持一致,python也是如此:“program = source files”)

​ 现在执行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 编译多个源文件

​ 现在知道了如何编译单个源文件,但是大多数情况下,需要构建的工程,是由多个源文件构成,而非单一文件,因此多源文件编译时,需要将文件名放入一个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创建列表

​ 您也可以采用Glob函数匹配所有符合条件的源文件,匹配原则采用标准脚本通配符“*”,“?”。[abc]将匹配a,b,c;[!abc]将匹配除abc意外的任意字符。这使得多个源文件的构建变得简单了很多。

Program('program', Glob('*.c'))

​ SCons的帮助手册有更多关于Glob函数的具体用法细节,可以参考。

3.4 单一文件构建 VS 文件列表构建

​ 构建工程目前有两种形式,一种是采用文件列表,一种是采用单一文件直接构建的方式:

# 文件列表
Program('hello', ['file1.c', 'file2.c'])

# 单一文件
Program('hello', 'hello.c')

​ 当然您也可以在文件列表中,只存储一份文件:

Program('hello', ['hello.c'])

​ 其实SCons内部都是将源文件以列表的形式存储,只是为了方便起见,允许用户直接输入单一文件。

重要提示

​ 尽管SCons支持多种输入方式,但是python语言更为严格,因此在进行list和string格式的文件相加则会产生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提供了很多工具函数使得这一工作变得很简单。

​ SCons提供了Split函数用于分割字符串文件中的一个或多个空格,亦或一个或多个tab,分割后以文件列表的形式存储:

Program('program', Split('main.c file1.c file2.c'))

​ (如果您熟悉python的话,您会发现这和python标准字符串string模板的split()函数很相似,但是和split()不同的是,Split()不需要输入一定为string,其他输入也可以(如列表),它会自动将其进行分割成列表,确保输出格式的正确性。)

​ 直接将Split()函数嵌入到Program中,显得比较粗鲁,可以进一步采用临时变量增加代码的可读性:

src_files = Split('main.c file1.c file2.c')
Program('program', src_files)

​ 最后,Split函数不关系输入中有多少个空格,因此您可以跨行编辑列表,进一步提高可读性:

src_file = Split("""" main.c
                      file1.c 
                      file2.c""")
Program('program', src_files)

​ 请注意,在此示例中,我们使用了Python的“三重引用”语法,该语法允许一个字符串包含多行。三个引号可以是单引号或双引号。

3.6 关键字参数

​ SCons同样支持输入或输出文件的关键字定义。输出文件的关键字是target,输入源文件的关键字是source:

src_files = Split('main.c file1.c file2.c')
Program(target='program', source=src_files)

​ 因为关键字已经指明了该参数的含义,因此关键字的顺序不做要求:

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 多工程编译共享中间文件

​ 代码复用是常见的编程方式,创建库文件(动态或静态库)是广为人知的一种方式。具体库文件的创建参见后续章节。

​ 同一份源码文件对应多个工程,最直接的方式是每个工程都加入该源码:

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

​ 如果两个或多个程序共享许多公共源文件,则可以将公共源文件创建为一个列表,以便维护和更改。同时采用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 构建方法参数的覆盖

​ 您也可以通过在构建方法(如Program等)中增加对应参数,或关键字参数,来指定相关的参数,最终参数会以此为准。

env.Program('hello', 'hello.c', LIBS=['gl', 'glut'])

​ 或者生成一个非标准后缀的动态库:

env.SharedLibrary('word', 'word.cpp',
SHLIBSUFFIX='.ocx',
LIBSUFFIXES=['.ocx'])

​ 您也可以采用parse_flags关键字将命令行样式参数合并到适当的构造变量中。本例将’include‘增加到CPPPATH变量中, 将‘EBUG’加入到CPPDEFINES变量中,以及将'm'加入到LIBS变量中:

enc = Program('hello', 'hello.c', parse_flags='-Iinclude -DEBUG -lm')

​ 在对构建方法的调用中,不会克隆编译环境,而是通过调用OverrideEnvironment(), 它会创建一个整个Environment()更轻便编译环境。

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 2,901评论 0 2
  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    IT诸葛亮阅读 5,031评论 1 118
  • Python语言特性 1 Python的函数参数传递 看两个如下例子,分析运行结果: 代码一: a = 1 def...
    时光清浅03阅读 110评论 0 0
  • Python语言特性 1 Python的函数参数传递 看两个如下例子,分析运行结果: 代码一: a = 1 def...
    清清子衿木子水心阅读 209评论 0 3
  • Python语言特性 1 Python的函数参数传递 看两个如下例子,分析运行结果: 代码一: a = 1 def...
    伊森H阅读 1,402评论 0 15