这篇文章可能有点长,如果你想要学到一些知识,而不是简单入门,你要有耐心的看下去,当然在看的时候,也要自己思考,也许我写的有问题,这个时候你可以再去看看,官方文档.
这个教程由于内容太多,有些还没来得及写.
一般我这里不会太多的去,介绍它的历史,和它是干啥的,你可以自己去找度娘去了解.我这里只有逻辑和事例代码.

工具版本: 3.0.1
scons user guide

scons user guide

scons 普通简单的构建

1
2
3
4
5
6
7
8
9
// file name main.cpp
#include <iostream>
int main(int argc, char const *argv[])
{
using namespace std;
cout<< "helloworld" << endl;

return 0;
}
1
2
#!/usr/bin/env python
Program('main.cpp')

多个文件构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
# 构建多个文件
Program(['main.cpp','other.cpp'])

# 使用 匹配 的方式选择构建的的文件
# 可以使用 Glob()

Program(Glob('*.cpp'))

Glob() => 匹配的方式有个
- * 通配符
- ? 匹配任意一个字符
- [abc] 匹配任意一个符
- [!abc] 输了abc 之外的字符

Split函数 scons为了方便可以使用 分割函数一空格分割同样做到 多文件编译

Program("HelloWorld",Split("main.c other.c"))
Program("HelloWorld",Split("""
main.c
other.c"""))
允许使用 多行字符串

scons 也可以使用指定参数的形式调用

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')

Object 构建方法

1
2
3
4
Object('main.c')

在 POSIX 系统上产生 *.o 文件
在 windows 系统上产生 *.obj 文件

清楚构建对中产生的对象

1
scons -c

让scons 简介输出

1
scons -Q

设置可执行文件的名字

1
2
3
4
5
Program("HelloWorld","main.cpp")
在构建目录下产生 名字叫 HelloWorld.exe 的可执行文件
在 POSIX 系统上产生 HelloWorld 的可执行文件

如果不在 第一个参数 指定 可执行文件的名字 那么将会使用 第一个文件的名字 为可执行程序的名字

构建多个可执行程序

1
2
3
4
Program('H1',['main.c'])
Program('H2',['main1.c'])

只需要调用多遍 Program 这个构造器就行了

多个可执行程序 使用共同的源文件

1
2
3
4
5
6
common = ["common1.c", "common2.c"]
program1 = ["pro1.c"] + common
program2 = ["pro2.c"] + common

Program("pro1",program1)
Program("pro2",program2))

构建 Library(库)从源文件中

1
2
3
4
5
6
7
8
9
10
Library('haha',['main.c'])

你可以混合构建最终的库 scons 可以识别出来那些需要编译
这个只会编译 main.c
Library('haha',['main.c','common.o'])

构建静态库
StaticLibrary()
构建动态库
SharedLibrary()

链接库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LIBS => 指定要链接的库的名字
LIBPATH => 要连接库名字的路径

Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')
这里可以简写 如果是一个 直接写库名字 不需要写成使用 列表 包一层

Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS='foo', LIBPATH='.')

多个库路径可以使用 如下格式
['/home/libs','/home/gg/libs']
'/home/libs:/home/gg/libs'(POSIX系统上的冒号:)
'C:\\libs;C:\\gg\\libs'(Windows系统上的分号;)

构造其方法返回节点列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这么做的目的是让我们写的 构建文件是平台的
不同平台 编译后的文件后缀不一样(a.obj(Windows), a.o(Lunix)) 所以 scons 帮我们处理了
返回节点列表 soncs 去处理这些差异
hello_list = Object('hello.c', CCFLAGS='-DHELLO')
goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE')

Program(hello_list + goodbye_list)

打印节点列表
print(hello_list[0]) // hello.obj(Windows)

判断文件是否存在
if not os.path.exists(program_name):
print('不存在')

清晰区分 目录节点 和 文件节点

1
2
3
4
5
6
7
8
hello_c = File('hello.c')
Program(hello_c)

目录节点
Dir()

不知道是 目录还是文件
Entry()

从一个节点或字符串获取路径

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

重复构建 scons 可以识别出来 会出现 scons: `.’ is up to date.

指出已经是最新的了

自定义 Decider(‘MD5’)

自定义 重构规则 Decider(‘MD5’) 默认是使用 md5 记录一个文件是够需要从新构建甚至 可以使用 ‘content’ 代表同名词 md5 的缺陷是 如果修改了注释 scons 也认为 没有修改
Decider(‘make’) 只要文件的修改时间改变就从新构建 同义词 ‘timestamp-newer’(判定规则是 比上次构建的时间戳新 就从新构建 但是有的使用会碰到 文件还原 这样就会出现 之前构建的时候 比 现在的 新 下面的 记录规则 可以解决这个问题)
‘timestamp-match’ 匹配时间戳是否与构建的时候一样,
‘MD5-timestamp’ 判定规则是 MD5 和 时间错都改变的时候 在重新构建 (如果在SCons构建文件的最后一秒内修改了源文件 这种情况可能不合适, 但是基本可能不存在)

编写自定义Decider函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Program('hello.c')
def decide_if_changed(dependency, target, prev_ni):
if self.get_timestamp() != prev_ni.timestamp:
dep = str(dependency)
tgt = str(target)
if specific_part_of_file_has_changed(dep, tgt):
return True
return False
Decider(decide_if_changed)

展开第三个参数
params :
prev_ni :
.csig The content signature, or MD5 checksum, of the contents of the dependency file the list time the target was built.
.size The size in bytes of the dependency file the list time the target was built.
.timestamp The modification time of the dependency file the list time the target was built.

当第一次构建的时候 这个参数 prev_ni 不存在

读这些东西的时候需要静心观看 不然就可能看不懂呦

env = Environment()

def config_file_decider(dependency, target, prev_ni):
import os.path

# We always have to init the .csig value...
# 获取依赖文件的签名信息
dep_csig = dependency.get_csig()
# .csig may not exist, because no target was built yet...
if 'csig' not in dir(prev_ni):
return True
# Target file may not exist yet
if not os.path.exists(str(target.abspath)):
return True
if dep_csig != prev_ni.csig:
# Some change on source file => update installed one
return True
return False

def update_file():
f = open("test.txt","a")
f.write("some line\n")
f.close()

update_file()

# Activate our own decider function
env.Decider(config_file_decider)

env.Install("install","test.txt")


不同的环境使用不同 判定规则

env1 = Environment(CPPPATH = ['.'])
env2 = env1.Clone()
env2.Decider('timestamp-match')
env1.Program('prog-MD5', 'program1.c')
env2.Program('prog-timestamp', 'program2.c')

隐式构造变量

1
2
3
4
5
6
7
8
9
/* main.c */
#include <hello.h>
int main()
{
printf("Hello, %s!\n", string);
}

/* hello.h */
#define string "world"
1
2
3
4
5
6
# Sconsturct
Program('hello.c', CPPPATH = '.')
也可以是多个 路径 也可以像 LIBPATH 那样
Program('hello.c', CPPPATH = ['include', '/home/project/inc'])
Window 分割符是 分号 (;) Linux 分隔符是 冒号(:)
Program('hello.c', CPPPATH = 'include:/home/project/inc')

缓存隐式依赖关系

1
SetOption('implicit_cache'1

或则是使用命令行

scons -Q –implicit-cache

scons -Q –implicit-deps-changed 通知 scons 跟新隐式缓存 依赖的文件改变时

scons -Q –implicit-deps-unchanged 强制及时依赖的文件改变时 仍然使用 缓存依赖

显示依赖关系

有的时候 scons 没有扫描(detected)到依赖关系的时候 scons 允许你 显示指定 依赖关系

1
2
3
4
5
6
7
8
hello = Program('hello.c')
Depends(hello, 'other_file')

Depends 的第二个参数 也可以是节点 列表

hello = Program('hello.c')
goodbye = Program('goodbye.c')
Depends(hello, goodbye)

外部文件的依赖关系:ParseDepends 功能

1
2
3
4
5
6
#define FOO_HEADER <foo.h>
#include FOO_HEADER

int main() {
return FOO;
}

上面这种情况 scons 可能无法 知道依赖关系

1
2
3
4
obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.')
SideEffect('hello.d', obj)
ParseDepends('hello.d')
Program('hello', obj)

借助编译器 对宏的展开 并生成一个 .d 的文件 第一次 scons 无法检测依赖 第二次 scons 可以根据生成的 .d的文件确定依赖关系

忽略依赖项

1
2
3
4
5
6
7
8
9
10
11
12
13
hello_obj=Object('hello.c')
hello = Program(hello_obj)
Ignore(hello_obj, 'hello.h')

上面的例子可能很难想象 它依赖的头文件 改变了 不从新编译
有一种情况 比如多个系统 使用同一个 头文件 每个系统的头文件 会有略微的差异
此时依赖的头文件改变了 但是不需要重新编译
hello = Program('hello.c', CPPPATH=['/usr/include'])
Ignore(hello, '/usr/include/stdio.h')

hello_obj=Object('hello.c')
hello = Program(hello_obj)
Ignore('.',[hello,hello_obj])

AlwaysBuild

1
2
hello = Program('hello.c')
AlwaysBuild(hello)

构造环境feature的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
创建新的 构造环境
env = Environment()

一个简单的例子 设置 c编译器
env = Environment(CC = 'gcc', CCFLAGS = '-O2')
env.Program('foo.c')

访问构造环境的各个变量 (它是个具有关联方法的对象)
env = Environment()
print("CC is: %s"%env['CC'])

如果你仅仅是访问他的构造变量,你可以这样做 调用构造环境的 Dictionary 方法
env = Environment(FOO = 'foo', BAR = 'bar')
dict = env.Dictionary()
for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
print("key = %s, value = %s" % (key, dict[key]))

on Windows > OBJSUFFIX => .obj LIBSUFFIX => .lib PROGSUFFIX => .exe
on Linux > OBJSUFFIX => .o LIBSUFFIX => .a PROGSUFFIX =>

获取构造环境中的变量 方法2
使用 subst() 方法 使用 $ + 变量字段 的方式访问
使用这个方法的好处是 它可以将构造变量展开
env = Environment()
print("CC is: %s"%env.subst('$CC'))
# $CCCOM

处理 变量 展开带来的问题
如果访问一个不存在的变量是 scons 并不会报错
如果想要报错你可以这样做
AllowSubstExceptions() 开始 使用 subst 函数异常的功能 这是个全局变量

除了定义默认的异常外 还可以定义 其他异常如 零除异常
AllowSubstExceptions(IndexError, NameError, ZeroDivisionError)
env = Environment()
print("value is: %s"%env.subst( '->${1 / 0}<-' ))

创建默认的 构造环境 可以设置 目标编译器
DefaultEnvironment(CC = '/usr/local/bin/gcc')

当然也可以这样做
env = DefaultEnvironment()
env['CC'] = '/usr/local/bin/gcc'

使用 DefaultEnvironment() 可以加快你初始化构造变量
在初始化时 scons 会在 系统的环境变量中搜索 可用的 编译器 连接器等 你可以指定 搜索的 工具 加快scons的初始化速度 你可以这样做

直接指定 编译工具 链接工具 指定 c编译器 为gcc
env = DefaultEnvironment(tools = ['gcc', 'gnulink'],
CC = '/usr/local/bin/gcc')

多构造环境 你可以这样做 不同的构造环境 使用不同的配置
opt = Environment(CCFLAGS = '-O2')
dbg = Environment(CCFLAGS = '-g')

opt.Program('foo', 'foo.c')
dbg.Program('foo', 'foo.c')
--------------------------------------
产生编译产物 *.obj(on Windows)
opt = Environment(CCFLAGS = '-O2')
dbg = Environment(CCFLAGS = '-g')

o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)

d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)

克隆当前的环境变量
克隆一份一模一样的构造环境
env = Environment(CC = 'gcc')
opt = env.Clone(CCFLAGS = '-O2')
dbg = env.Clone(CCFLAGS = '-g')
env.Program('foo', 'foo.c')
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)

替换构造环境
env = Environment(CCFLAGS = '-DDEFINE1')
env.Replace(CCFLAGS = '-DDEFINE2')
env.Program('foo.c')

仅在尚未定义的情况下设置值
env.SetDefault(SPECIAL_FLAG = '-extra-option')

附加到构造变量末尾
env = Environment(CCFLAGS = ['-DMY_VALUE'])
env.Append(CCFLAGS = ['-DLAST'])
env.Program('foo.c')

附加唯一值
Some times it's useful to add a new value only if the existing construction variable doesn't already contain the value. This can be done using the AppendUnique method:
官方解释: 有时 它是有用的 在这个构造变量没有包含这个变量的时候添加一个新值 , 这样可以使使用这个 方法做
env.AppendUnique(CCFLAGS=['-g'])

In the above example, the -g would be added only if the $CCFLAGS variable does not already contain a -g value.
解释: 在上面这个例子, -g 标识将被添加 仅仅这个 $CCFLAGS 变量曾没有包含这个 -g 的标识

附加到值的开头
env = Environment(CCFLAGS = ['-DMY_VALUE'])
env.Prepend(CCFLAGS = ['-DFIRST'])
env.Program('foo.c')

附加唯一值到开头 和上面在结尾附加值 的 逻辑是一样的
env.PrependUnique(CCFLAGS = [ ' - G'])

最后更新: 2019年08月14日 11:22

原始链接: https://leng521.top/posts/79065537/