【c++】Scons|scons对比make
目录
简略说明
(摘自:https://www.cnblogs.com/m-zhang-yang/p/13228299.html)
Scons命令
scons:进行代码文件编译
scons -Q:进行代码文件编译,不显示Scons内部操作打印的信息,只显示编译信息
scons -c:清除编译中间文件和可执行文件
Scons编译脚本
Scons对应的编译脚本名称为SConstruct,就如同make对应的编译脚本为makefile
SConstruct脚本编写
编译函数
Program():执行编译操作,生成可执行文件
Library():执行编译操作,生成静态库
StaticLibrary():执行编译操作,生成静态库
SharedLibrary():执行编译操作,生成动态库
Environment():编译环境
编译参数
target,生成的执行文件名字
source,编译文件
LIBS,依赖库
LIBPATH,依赖库路径,有环境变量的可不添加,针对用户库或第三方库
CPPPATH,头文件路径
CCFLAGS,编译参数
其他函数
Split():将字符串分隔为列表
Glob('*.cpp'):加入所有文件
编译参数可以以任意顺序加入编译函数,编译函数依据编译参数对代码文件进行编译。
一个简单例子
将文件放在当前目录,头文件放在上级目录。
str = Split('main.cpp display.cpp') Program('main',str,CPPPATH = '../'i,LIBS = 'pthread')
一个复杂项目的例子
大多项目都可以套用的格式,Program内参数多个文件需要以python列表形式作为入参,这里可以用Split对文件字符串分隔为列表输入。直接用Glob('*.cpp')包含当前目录所有需要编译的cpp文件。
TARGETNAME = 'server' LIB = Split('jsoncpp logger ACE basetool tinyxml pthread dl rt') FILELIST = Glob('*.cpp') HEADFILEPATH = Split('../../common/include ../../3rd/ACE_wrappers ../../3rd/include/') LIB_DIR = Split('../../3rd/lib ../../common/lib') Program(target = TARGETNAME,source = FILELIST,LIBS = LIB,CPPPATH = HEADFILEPATH,LIBPATH = LIB_DIR)
src:
| SConstruct
| libstlport.a
| test.cpp
| include(目录):
| foo.h
| mA(目录):
| SConscript
| func.cpp
什么是Scons
make
make 工具可以增量编译,缩短编译的时间,但 make 规则复杂,即使老鸟也望而生畏。
Scons是它的同类产品,做的事情跟make一样,但更简单,更容易。
Scons
Scons是一个开源、Python编写的自动化构建工具,并且scons是跨平台的,只要scons脚本写的好,可以在Linux和Windows下随意编译。
SCons 的设计目标就是让开发人员更容易、更可靠和更快速的建造软件。
遗留疑问:Scons 和cmake 对比如何?
待研究。
SCons, CMake 算是第三代构建工具的两个代表。
第一代 DSL 型构建语言 Makefile,是描述构建的规则和依赖关系的绝对王者。
第二代 autotools(automake) 系列,几乎是是整个 Linux 的构建工具,能完成对环境依赖项的检测。但是不是能够轻松驾驭的。如果用 -j 用得比较多,肯定会遇到:出现链接错误,再次运行就能正确的情形。(gn 有 link pool 的支持优雅解决了这个问题)。
SCons, CMake 算是第三代构建工具的两个代表了。
链接:https://www.zhihu.com/question/276415476/answer/557715322
《构建工具之shell,configure,make,cmake,scons,xmake简析总结》:
https://blog.csdn.net/shixin_0125/article/details/78424747
scons对比make
与传统的 make 工具比较,SCons 具有以下优点:
1、 移植性:python能运行的地方,就能运行scons
2、 扩展性:理论上scons只是提供了python的类,scons使用者可以在这个类的基础上做所有python能做的事情。比如想把一个已经使用了Makefile大型工程切换到scons,就可以保留原来的Makefile,并用python解析Makefile中的编译选项、源/目标文件等,作为参数传递给scons,完成编译。
3、 智能:Scons继承了autoconf/automake的功能,自动解析系统的include路径、typedef等;“以全局的观点来看所有的依赖关系”
- 使用 Python 脚本做为配置文件
- 对于 C,C++ 和 Fortran, 内建支持可靠自动依赖分析 . 不用像 make 工具那样需要 执行"make depends"和"make clean"就可以获得所有的依赖关系。
- 内建支持 C, C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG 以及 Tex/Latex。 用户还可以根据自己的需要进行扩展以获得对需要编程语言的支持。
- 支持 make -j 风格的并行建造。相比 make -j, SCons 可以同时运行 N 个工作,而 不用担心代码的层次结构。
- 使用 Autoconf 风格查找头文件,函数库,函数和类型定义。
- 良好的夸平台性。SCons 可以运行在 Linux, AIX, BSD, HP/UX, IRIX, Solaris, Windows, Mac OS X 和 OS/2 上。
Scons使用
安装
在 SCons 的官方网站上可以查每个平台的具体安装方法。
Win平台的下载和安装就不说了。
Linux下
方法1:
pip3 install scons
方法2:
下载tar包,执行以下命令即可:
tar -zxf scons-2.0.1.tar.gz
cd scons-2.0.1
sudo python setup.py install
对于 Linux 来说,scons 会默认安装到 /usr/loca/bin 目录下,而在 Windows 平台上,则会被安装到 C:\Python25\Scripts 下。
使用
这里通过一个简单的示例来说明。
一个hello.c源文件,一个SConstruct文件,后面再解释。
SConstruct
Program("hello.c")
hello.c
#include <stdio.h>
int main(void)
{
printf("hello, world!\n");return 0;
}
运行Scons,得到如下:
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c hello.c
gcc -o hello hello.o
scons: done building targets.$ ls
hello hello.c hello.o SConstruct$ ./hello
hello, world!
就是这样,程序编译完成了。到这里Scons似乎与make没有什么两样,同样的操作,只是把scons换成make,SConstruct换成Makefile。
对,不同的地方就在于,下面的展示:
$ cat SConstruct
Program("hello.c")
你没看错,就这一行,编译就完成了!!!
事实确实如此,它比传统的 Makefile 简单很多。SConstruct 以 Python 脚本的语法编写,你可以像编写 Python 脚本一样来编写它。
其中的 Program 是编译的类型,说明你准备想要建造一个可执行的二进制程序,它由 hello.c 文件来生成。
在这里,没有指定生成的可执行程序的名字,SCons 会把源代码文件名字的后缀去掉,用来作为可执行文件的名字。
我们甚至不需要像 Makefile 那样指定清理的动作,就可以执行清理任务。在 SCons 中,执行清理任务由参数 -c 指定,如下 :
$ scons -c
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed hello.o
Removed hello
scons: done cleaning targets.$ ls
hello.c SConstruct
相关命令
SCons 支持的编译(目标文件)类型有:
- Program: Program("hello.c")编译成可执行程序(在 Windows 平台上即是 exe 文件),这是最常用的一种编译类型。
- Object:Object("hello.c") 只编译成目标文件。使用这种类型,编译结束后,只会产生目标文件。在 POSIX 系统中,目标文件以 .o 结尾,在 Windows 平台上以 .OBJ 结尾。
- Library: Library("hello.c") 编译成库文件。SCons 默认编译的库是指静态链接库。
- StaticLibrary: StaticLibrary("hello.c") 显示的编译成静态链接库,与上面的 Library 效果一样。
- SharedLibrary: SharedLibrary("hello.c") 在 POSIX 系统上编译动态链接库,在 Windows 平台上编译 DLL。
如果不想使用 SConstruct 为你设置的默认可执行文件的名字,而是选择你自己喜欢的名字,如 myhello,可以把 SConstruct 的内容修改为 :
Program('myhello', 'helloscons.c')
其中 myhello就是你想要的可执行文件的名字.
$ scons -Q
gcc -o hello.o -c hello.c
gcc -o myhello hello.o
Scons入门简介(https://blog.csdn.net/guotianqing/article/details/92003258)
其中,-Q 参数是减少编译时的由 scons 产生的冗余信息。
显式的指定使用某些库:
指定一些编译的宏定义,显式的指定使用某些库:
例子: helloscons2 由 3 个源文件生成 : helloscon2.c, file1.c, file2.c,指定编译选项,同时指定使用哪些具体的库,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 组成。
如果源程序代码文件很多,glob(’*.c’) 来代替源代码列表,如下 :
Program('helloscons2', Glob('*.c'))
配置文件中 LIBS,LIBAPTH 和 CCFLAGS 是 SCons 内置的关键字,它们的作用如下:
LIBS: 显示的指明要在链接过程中使用的库,如果有多个库,应该把它们放在一个列表里面。这个例子里,我们使用一个称为 m 的库。
LIBPATH: 链接库的搜索路径,多个搜索路径放在一个列表中。这个例子里,库的搜索路径是 /usr/lib 和 /usr/local/lib。
CCFLAGS: 编译选项,可以指定需要的任意编译选项,如果有多个选项,应该放在一个列表中。这个例子里,编译选项是通过 -D 这个 gcc 的选项定义了一个宏 HELLOSCONS。
CPPPATH:指定头文件的路径
运行 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
更多内容
以上只是入门,还有更多知识等待学习。
如,通过Environment指定编译环境,env = Environment(CC = 'g++')
,指定使用g++编译程序。
示例如下:
env = Environment(CC = 'g++')
env.Program("client", "client.c", LIBS = 'm', CPPPATH = '../include', CCFLAGS = '-std=c++11')
摘抄自:https://blog.csdn.net/guotianqing/article/details/92003258
scons文件和命令
二、scons文件
scons中可能出现的文件:
SConstruct,Sconstruct,sconstruct,SConscript
scons将在当前目录以下次序 SConstruct,Sconstruct,sconstruct 来搜索配置文件,从读取的第一个文件中读取相关配置。
在配置文件SConstruct中可以使用函数SConscript()函数来定附属的配置文件。按惯例,这些附属配置文件被命名为”SConscript”,当然也可以使用任意其它名字。
三、scons的命令行参数
scons: 执行SConstruct中脚本
scons -c clean
scons -Q 只显示编译信息,去除多余的打印信息
scons -Q --implicit-cache hello 保存依赖关系
--implicit-deps-changed 强制更新依赖关系
--implicit-deps-unchanged 强制使用原先的依赖关系,即使已经改变
四、SConstruct提供的方法
1、Program:生成可执行文件
Program('hello.c') 编译hello.c可执行文件,根据系统自动生成(hello.exe on Windows; hello on POSIX)
Program('hello','hello.c') 指定Output文件名(hello.exe on Windows; hello on POSIX)
Program(['hello.c', 'file1.c', 'file2.c']) 编译多个文件,Output文件名以第一个文件命名
Program(source = "hello.c",target = "hello")
Program(target = "hello" , source = "hello.c")
Program('hello', Split('hello.c file1.c file2.c')) 编译多个文件Program(Glob("*.c"))
src = ["hello.c","foo.c"];Program(src)
2、Object:生成目标文件
Object('hello.c') 编译hello.c目标文件,根据系统自动生成(hello.obj on Windows; hello.o on POSIX)
3、Library:生成静态/动态库文件
Library('foo', ['f1.c', 'f2.c', 'f3.c']) 编译library
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) 编译 shared library
StaticLibrary('bar', ['f4.c', 'f5.c', 'f6.c']) 编译 static library
库的使用:
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.') 连接库,不需加后缀或是前缀
4、SourceSignatures:判断源文件是否修改
SourceSignatures('MD5') 根据内容是否改变,默认方式
SourceSignatures('timestamp') 根据修改时间
5、TargetSignatures:判断目标文件是否改变
TargetSignatures('build') 根据编译结果
TargetSignatures('content') 根据文件内容,如果只是加了句注释,将不会被重新编译
6、Ignore:忽略依赖关系
Ignore(hello, 'hello.h') 忽略某个依赖关系
7、Depends:明确依赖关系
Depends(hello, 'other_file') 明确依赖关系
8、SConscript:scons的配置文件。
源文件的目录结构如下:
src:
| SConstruct
| test.cpp
| mA(目录):
| SConscript
| func.cpp
其中test.cpp为主文件,中调用func.cpp中定义的函数
SConstruct内容如下:
subobj = SConscript(['mA/SConscript'])
obj = subobj + Object(Glob("*.cpp"))
Program("test",list(obj))
SConscript内容 :
obj = Object(Glob("*.cpp"))
Return("obj")
上例中,在主目录中执行 scons就可以编译整个"工程"。SConstruct编译主目录中的test.cpp,并通过SConscript编译mA目录下的源文件,并最终生成可执行文件;SConscript用于编译mA中的func.cpp并把生成的func.o传递给主目录的SConstruct。
10.env:环境变量
环境变量用于设置在编译过程中的各种参数,可以用下面的SConstruct打印环境变量的所有信息(实际上env就是一个python字典)
可以使用如下的SConstruct查看环境变量的内容:
env = Environment()
dict = env.Dictionary()
keys = dict.keys()
keys.sort()
for key in keys:
print "construction variable = '%s', value = '%s'" % (key, dict[key])
环境变量的使用:
env = Environment() #创建默认的环境变量,默认scons会按编译器的默认选项来进行编译
import os
env = Environment(CC = 'gcc',CCFLAGS = '-O2') #创建并设置环境 变量
env.Program('foo.c')
环境变量的复制:
env = Environment(CC = 'gcc')
opt = env.Clone(CCFLAGS = '-O2')
dbg = env.Clone(CCFLAGS = '-g')
环境变量的替换:
env = Environment(CCFLAGS = '-DDEFINE1')
env.Replace(CCFLAGS = '-DDEFINE2')
env.Program('foo.c')
环境变量的输入输出:用于统一多目录源文件的编译选项,如:
src:
| SConstruct
| libstlport.a
| test.cpp
| include(目录):
| foo.h
| mA(目录):
| SConscript
| func.cpp
test.cpp和mA/func.cpp都引用了include/foo.h,test.cpp调用了mA/func.cpp的功能函数,其中include/foo.h中定义了一个包含string类型的类。
SConstruct如下:
env = Environment()
flags = env.ParseFlags(['-pthread -I/usr/include/stlport ',' -L .'])
env.MergeFlags(class_flags)
subobj = SConscript(['mA/SConscript'])
obj = subobj + env.Object(Glob("*.cpp"))
env.Program("test",list(obj),LIBS = ['libstlport.a'])
mA/SConscrip如下:
obj = Object(Glob("*.cpp"))
Return("obj")
不出意外的话上边的工程编译可以通过,但是运行的时候会Aborted。因为test.cpp,mA/func.cpp都使用了包含string类型的那个类,但是由于编译环境的不同,test.cpp认为string变量的大小是24字节, mA/func.cpp认为string变量的大小是4个字节(libstlport.a捣的鬼)。
解决问题的办法就是环境变量输出,修改SConstruct和mA/SConscript如下:
SConstruct:
env = Environment()
flags = env.ParseFlags(['-pthread -I/usr/include/stlport ',' -L .'])
env.MergeFlags(class_flags)
Export('env')
subobj = SConscript(['mA/SConscript'],exports = 'env')
obj = subobj + env.Object(Glob("*.cpp"))
env.Program("test",list(obj),LIBS = ['libstlport.a'])
mA/SConscript:
Import('env')
obj = env.Object(Glob("*.cpp"))
Return("obj")