scons学习

在gem5和NVMain中,都使用了scons工具进行编译。学习scons必须提上日程!

编译文件的基本步骤

参考阅读

在此之前,还需要复习一下编译文件的基本知识。编译文件大致可以分为预处理=>编译=>汇编=>链接四个步骤。

  • 预处理:将给定的文件进行展开。展开宏定义以及include的文件。
  • 编译过程:进行词法分析,语法分析,语义分析处理,生成中间代码,优化,然后生成汇编代码。
  • 汇编:将汇编代码生成机器可执行的代码(即二进制),生成可重定位目标程序的.o文件。
  • 链接:使用静态链接或者动态链接的方法加入程序执行需要依赖库的文件,最后生成一个可执行文件。

创建一个简单的helloworld.cpp文件。

使用g++ -E helloworld.cpp -o helloworld.ii生成预处理文件(比源文件大非常多,因为iostream是一个非常大的header文件)。

使用g++ -S helloworld.ii -o helloworld.S生成汇编代码。

-c是执行汇编操作。

简单编译

创建一个Sconstruct文件,创建一个helloworld.cpp文件。对于scons工具来说,Sconstruct文件就相当于Makefile的作用一样,scons通过读取Sconstruct的内容来控制构建程序的流程。当然,Sconstruct与Makfile的区别在于前者使用的是Python脚本,而后者使用的是命令行。

使用Program函数能够生成可执行文件。

1
2
3
Program('helloworld.cpp')
Program('newhello', 'helloworld.cpp') # 可以重命名可执行文件
Program('new_program', ['file1.c', 'file2.c', 'file3.c']) # 编译多个文件

在第三个示例中,相当于执行:

1
2
3
4
5
$ scons
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o file3.o -c file3.c
cc -o new_program file1.o file2.o fil3.o

当然,可以使用Glob函数指定规则匹配所有需要的文件。

在Program函数中可以指定target,source。

1
2
3
4
5
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')

Program中还有LIBPATH,scons会从指定的目录中寻找库文件。

使用

1
Object('helloworld.cpp')

可以生成一个可重定位目标程序文件。

除了Program, Object,还有Library、StaticLibrary和SharedLibrary分别编译库文件、静态库以及动态库。

1
2
3
4
5
6
7
8
C:\>scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cl /Fohello.obj /c hello.c /nologo
link /nologo /OUT:hello.exe hello.obj
embedManifestExeCheck(target, source, env)
scons: done building targets.

可以看到,scons首先会读取所有的配置文件(主要指的是SConscript文件),然后执行它们,然后构建所有的target files。

节点对象

所有的builder方法都会返回一系列的node object,这些object可能是target file或者是file。

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

这样一般默认是在Linux系统下使用scons。

或者

1
2
3
hello_list = Object('hello.cpp', CCFLAGS='-DHELLO') # 使用一个hello_list节点
goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE')
Program(hello_list + goodbye_list) # 利用节点编译

hello_listgoodbye_list就是target object的节点。除了Object,还有File、Dir和Entry节点。

1
2
3
4
object_list = Object('hello.c')
program_list = Program(object_list)
print("The object file is: %s"%object_list[0])
print("The program file is: %s"%program_list[0])

可以使用该方法打印出相应的文件名。

环境

可以声明环境变量指定编译环境:

1
2
3
4
5
6
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')

有时候一个编译文件需要另一个文件构建的环境,这时候就需要使用ImportExport进行指定变量的输出。

编译选项

使用AddOption函数进行选择。

1
2
3
4
5
AddOption('--verbose', dest='verbose', action='store_true',
help='Show full compiler command line')
AddOption('--build-type', dest='build_type', type='choice',
choices=["debug","fast","prof"],
help='Type of build. Determines compiler flags')