代码改变世界

Scons使用教程

2021-09-30 09:45  dreamboy2000  阅读(2718)  评论(0编辑  收藏  举报

1. 简单编译

2. SConstruct文件

3. 编译多个源文件

4. 编译和链接库文件

5. 节点对象 

6. 依赖性

7. 环境


1.简单编译

    源文件:hello.cpp

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. cout << "Hello, World!" << endl;
  6. return 0;
  7. }

 用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就会删除合适的编译产生的文件。 

  1. $ scons //编译源文件(自动读取SConstruct中的内容)
  2. $ 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的注释:
  1. # Arrange to build the "hello" program.
  2. Program('hello.cpp') #"hello.cpp" is the source file.
  • 重要的一点是SConstruct文件并不完全像一个正常的Python脚本那样工作,其工作方式更像一个Makefile,那就是在SConstruct文件中SCons函数被调用的顺序并不影响SCons你实际想编译程序和目标文件的顺序。换句话说,当你调用Program方法,你并不是告诉SCons在调用这个方法的同时马上就编译这个程序,而是告诉SCons你想编译这个程序:
  1. print "Calling Program('hello.c')"
  2. Program('hello.c')
  3. print "Calling Program('goodbye.c')"
  4. Program('goodbye.c')
  5. print "Finished calling Program()"
  • 指定默认的目标文件
  1. Default(targets)
  2. env.Default(targets)

     指定了默认的target,如果在命令行中没有显示指定target,那么scons将编译默认的target,多次调用Default是合法的,实例:

  1. Default('foo', 'bar', 'baz')
  2. env.Default(['a', 'b', 'c'])
  3. hello = env.Program('hello', 'hello.c')
  4. 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'))

      或者:

  1. src_files=Split('main.cpp file1.cpp file2.cpp')
  2. Program('program', src_files)
  • SCons允许使用Python关键字参数来标识输出文件和输入文件。输出文件是target,输入文件是source,示例如下:
  1. src_files=Split('main.cpp file1.cpp file2.cpp')
  2. Program(target='program', source=src_files)

    或者:

  1. src_files=Split('main.cpp file1.cpp file2.cpp')
  2. Program(source=src_files, target='program')
  • 如果需要用同一个SConstruct文件编译多个程序,只需要调用Program方法多次:
  1. Program('foo.cpp')
  2. Program('bar', ['bar1.cpp', 'bar2.cpp'])
  • 多个程序之间共享源文件是很常见的代码重用方法。一种方式就是利用公共的源文件创建一个库文件,然后其他的程序可以链接这个库文件。另一个更直接,但是不够便利的方式就是在每个程序的源文件列表中包含公共的文件,示例如下:

  1. common=['common1.cpp', 'common2.cpp']
  2. foo_files=['foo.cpp'] + common
  3. bar_files=['bar1.cpp', 'bar2.cpp'] + common
  4. Program('foo', foo_files)
  5. 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指定存放库文件的目录:
  1. Library('foo', ['f1.cpp', 'f2.cpp', 'f3.cpp'])
  2. 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编译方法,如下所示:

  1. Object('hello.cpp', CCFLAGS='-DHELLO')
  2. Object('goodbye.cpp', CCFLAGS='-DGOODBYE')
  3. Program(['hello.o', 'goodbye.o'])

    这样指定字符串名字的问题就是我们的SConstruct文件不再是跨平台的了。因为在Windows里,目标文件成为了hello.obj和goodbye.obj。一个更好的解决方案就是将Object编译方法返回的目标列表赋值给变量,这些变量然后传递给Program编译方法: 

  1. hello_list = Object('hello.cpp', CCFLAGS='-DHELLO')
  2. goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE')
  3. Program(hello_list + goodbye_list)
  • 显示创建文件和目录节点

    在SCons里,表示文件的节点和表示目录的节点是有清晰区分的。SCons的File和Dir函数分别返回一个文件和目录节点:

  1. hello_c=File('hello.cpp')
  2. Program(hello_c)

    通常情况下,你不需要直接调用File或Dir,因为调用一个编译方法的时候,SCons会自动将字符串作为文件或目录的名字,以及将它们转换为节点对象。只有当你需要显示构造节点类型传递给编译方法或其他函数的时候,你才需要手动调用File和Dir函数。有时候,你需要引用文件系统中一个条目,同时你又不知道它是一个文件或一个目录,你可以调用Entry函数,它返回一个节点可以表示一个文件或一个目录:

xyzzy=Entry('xyzzy')
  •  将一个节点的文件名当作一个字符串

    如果你不是想打印文件名,而是做一些其他的事情,你可以使用内置的Python的str函数。例如,你想使用Python的os.path.exists判断一个文件是否存在:

  1. import os.path
  2. program_list=Program('hello.cpp')
  3. program_name=str(program_list[0])
  4. if not os.path.exists(program_name):
  5. print program_name, "does not exist!"
  • GetBuildPath:从一个节点或字符串中获得路径

    env.GetBuildPath(file_or_list)返回一个节点或一个字符串表示的路径。它也可以接受一个节点或字符串列表,返回路径列表。如果传递单个节点,结果就和调用str(node)一样。路径可以是文件或目录,不需要一定存在:

  1. env=Environment(VAR="value")
  2. n=File("foo.cpp")
  3. print env.GetBuildPath([n, "sub/dir/$VAR"])

    将会打印输出如下: 

  1. $ scons -Q
  2. ['foo.cpp', 'sub/dir/value']
  3. scons: . is up to date.

6. 依赖性

  • 隐式依赖:$CPPPATH Construction变量
  1. #include <iostream>
  2. #include "hello.h"
  3. using namespace std;
  4. int main()
  5. {
  6. cout << "Hello, " << string << endl;
  7. return 0;
  8. }

    并且,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基于你系统中工具的一个变量集合来初始化每一个新的构造环境。当你初始化一个构造环境时,你可以设置环境的构造变量来控制一个是如何编译的。例如:

  1. import os
  2. env=Environment(CC='gcc', CCFLAGS='-O2')
  3. env.Program('foo.c')
  4. 或者
  5. env=Environment(CXX='/usr/local/bin/g++', CXXFLAGS='-02')
  6. env.Program('foo.cpp')
  • 从一个构造环境中获取值

    你可以使用访问Python字典的方法获取单个的构造变量: 

  1. env=Environment()
  2. print "CC is:", env['CC']
  3. print "CXX is:", env['CXX']

    一个构造环境实际上是一个拥有方法的对象。如果你想直接访问构造变量的字典,你可以使用Dictionary方法: 

  1. env=Environment(FOO='foo', BAR='bar')
  2. dict=env.Dictionary()
  3. for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
  4. print "key=%s, value=%s" % (key,dict[key])
  •  默认的构造环境:DefaultEnvironment函数

    你可以控制默认构造环境的设置,使用DefaultEnvironment函数:

DefaultEnvironment(CC='/usr/local/bin/gcc')

    这样配置以后,所有Program或者Object的调用都将使用/usr/local/bin/gcc编译目标文件。注意到DefaultEnvironment返回初始化了的默认构造环境对象,这个对象可以像其他构造环境一样被操作。所以如下的代码和上面的例子是等价的: 

  1. env=DefaultEnvironment()
  2. env['CC']='/usr/local/bin/gcc'
  • 多个构造环境 

    构造环境的真正优势是你可以创建你所需要的许多不同的构造环境,每一个构造环境对应了一种不同的方式去编译软件的一部分或其他文件。比如,如果我们需要用-O2编译一个程序,编译另一个用-g,我们可以如下做:

  1. opt=Environment(CCFLAGS='-O2')
  2. dbg=Environment(CCFLAGS='-g')
  3. opt.Program('foo','foo.cpp')
  4. dbg.Program('bar','bar.cpp')
  • 拷贝构造环境:Clone方法 

    有时候你想多于一个构造环境对于一个或多个变量共享相同的值。当你创建每一个构造环境的时候,不是重复设置所有共用的变量,你可以使用Clone方法创建一个构造环境的拷贝。Environment调用创建一个构造环境,Clone方法通过构造变量赋值,重载拷贝构造环境的值。例如,假设我们想使用gcc创建一个程序的三个版本,一个优化版,一个调试版,一个其他版本。我们可以创建一个基础构造环境设置$CC为gcc,然后创建两个拷贝:

  1. env=Environment(CC='gcc')
  2. opt=env.Clone(CCFLAGS='-O2')
  3. dbg=env.Clone(CCFLAGS='-g')
  4. env.Program('foo','foo.cpp')
  5. o=opt.Object('foo-opt','foo.cpp')
  6. opt.Program(o)
  7. d=dbg.Object('foo-dbg','foo.cpp')
  8. dbg.Program(d)
  • 替换值:Replace方法

    你可以使用Replace方法替换已经存在的构造变量:

  1. env=Environment(CCFLAGS='-DDEFINE1');
  2. env.Replace(CCFLAGS='-DDEFINE2');
  3. env.Program('foo.cpp')
  • 在没有定义的时候设置值:SetDefault方法

    有时候一个构造变量应该被设置为一个值仅仅在构造环境没有定义这个变量的情况下。你可以使用SetDefault方法,这有点类似于Python字典的set_default方法:

env.SetDefault(SPECIAL_FLAG='-extra-option')
  • 控制目标文件的路径:env.Install方法 
  1. test = env.Program('test.cpp')
  2. env.Install('bin', 'test.exe') #表示要将test.exe 放到bin目录下
  • 执行SConscript脚本文件
  1. SConscript(scripts, [exports, variant_dir, duplicate])
  2. env.SConscript(scripts, [exports, variant_dir, duplicate])
  3. SConscript(dirs=subdirs, [name=script, exports, variant_dir, duplicate])
  4. env.SConscript(dirs=subdirs, [name=script, exports, variant_dir, duplicate])

    调用该SConscript函数有两种方法:  

    第一种方法是明确指定一个或多个 scripts 作为第一个参数。可以将单个脚本指定为字符串; 多个脚本则必须指定为列表(显式或由函数创建 Split),例子:

  1. SConscript('SConscript') #在当前目录中运行SConscript
  2. SConscript('src / SConscript') #在src目录中运行SConscript
  3. SConscript(['src / SConscript''doc / SConscript'])# 执行多个脚本

  第二种方法是将(子)目录名称列表指定为 dirs=subdirs 参数。在这种情况下,scons将 在每个指定目录中执行名为SConscript的辅助配置文件 。您可以 通过提供可选的name = keyword参数来指定除了名为SConscript以外script。例子:

  1. SConscript(dirs ='.') #在当前目录中运行SConscript
  2. SConscript(dirs ='src') #在src目录中运行SConscript
  3. SConscript(dirs = ['src''doc'])
  4. SConscript(dirs = [' sub1''sub2'],name ='MySConscript'

   可选exports参数提供变量名称列表或要导出到script的命名值字典。这些变量仅在本地导出到指定 script(s)的变量,并且不会影响Export函数使用的全局变量池 。子脚本Script必须使用Import函数来导入变量。例子:

  1. foo = SConscript('sub/SConscript'exports ='env'
  2. SConscript('dir/SConscript'exports = ['env''variable'])
  3. SConscript(dirs ='subdir'exports ='env variable'
  4. SConscript(dirs = ['one''two''three'],exports ='shared_info'

     如果提供variant_dir参数,Sconscript位于源码目录之下,就像位于variant_dir目录下一样,例子一:

  1. SConscript('src/SConscript', variant_dir = 'build')
  2. 等价于:
  3. VariantDir('build', 'src') # 指定obj文件的目录
  4. SConscript('build/SConscript')

  例子二:

  1. SConscript('SConscript', variant_dir = 'build')
  2. 等价于:
  3. VariantDir('build', '.')
  4. SConscript('build/SConscript')

     如果没有提供variant_dir参数,那么参数duplicate参数将会被忽略,这个参数表示是否备份目标文件。  

 (3)执行环境

    一个执行环境是SCons在执行一个外部命令编译一个或多个目标文件时设置的一些值。这和外部环境是不同的。

  • 控制命令的执行环境

    当SCons编译一个目标文件的时候,它不会使用你用来执行SCons的同样的外部环境来执行一些命令。它会使用$ENV构造变量作为外部环境来执行命令。这个行为最重要的体现就是PATH环境变量,它决定了操作系统将去哪里查找命令和工具,与你调用SCons使用的外部环境的不一样。这就意味着SCons将不能找到你在命令行里执行的所有工具。PATH环境变量的默认值是/usr/local/bin:/bin:/usr/bin。如果你想执行任何命令不在这些默认地方,你需要在你的构造环境中的$ENV字典中设置PATH,最简单的方式就是当你创建构造环境的时候初始化这些值:

  1. path=['/usr/local/bin', '/usr/bin']
  2. 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变量:

  1. import os
  2. env=Environment(ENV={'PATH':os.environ['PATH']})

    你设置可以设置整个的外部环境:

  1. import os
  2. env=Environment(ENV=os.environ)
  • 在执行环境里增加PATH的值

    常见的一个需求就是增加一个或多个自定义的目录到PATH变量中:

  1. env=Environment(ENV=os.environ)
  2. env.PrependENVPath('PATH','/usr/local/bin')
  3. env.AppendENVPath('LIB','/usr/local/lib')

本文参考:https://blog.csdn.net/andyelvis/article/category/948141