07 SCons依赖

SCons通过DeciderFunction确定文件是否需要重新编译,包括内容签名、时间戳等多种策略。它能自动处理隐式依赖,并支持缓存机制以提高效率。程序员还可以自定义决策方法或使用AlwaysBuild方法确保特定文件总被编译。

SCons依赖


前面已经知道,SCons如何第一次去编译源文件,当我们需要重新编译源文件时,SCons需要知道哪些文件需要重新编译,哪些文件不需要编译。而不是每次编译都重新编译所有文件,这样会浪费掉很多时间。实际上这些就是文件依赖规则来决定的。
依赖规则方法 (Decider Function),SCons内置了几种Decider Function,我们还可以定义自己的 Decider Function。下面看看几种Decider 方法

1.Decider方法做出决策当输入的文件发生变化

我们不希望去重新编译那些不需要编译的文件,SCons默认使用内容签名的方式判断文件是否改变需要重新编译,当然我们也可以替换成时间戳方式来判断文件是否需要重新编译,甚至可以写自己的判断方法。

1.1使用文件内容签名决策方法(content signatures)

这种方式是默认方式,SCons使用哈希加密来判断文件是否改变。

如果你想显示的申明使用内容签名方式来判断文件是否改变,可以按照下面方式操作。

Program('hello.c')
Decider('content')
1.2使用时间戳决策方法(time stamps)

这种方式是判断源文件的修改时间比目标文件更新来判断源文件是否需要重新编译,使用下面方式:

Object('hello.c')
Decider('timestamp-newer') or Decider('make')

使用这种方式的一个缺点是,当我们从源码备份硬盘中复制一些老的源代码时,因为这些源代码时间都比我们目标文件的时间老,所以不会被编译。

不过这种缺点是可以避免的,我们只需要使用Decider('timestamp-match') 代替 Decider('timestamp-newer')即可。

这种方式SCons会在编译的时候准确的记录源文的时间信息。

1.3使用签名和时间戳决策方法(MD Signatures and Time Stamps)

这种方式是增强型的,SCons只有在源文件时间戳改变时才会读取内容,方式如下

Program('hello.c')
Decider('content-timestamp')

这种方式中的内容签名规则仍然遵守前面说过的Decider('content')规则。

这种方式的缺点是:修改源文件的时间不能再1秒内,也就是说,如果你在1秒内修改源文件并且执行编译,那么SCons将不会重新编译源文件。

工程师不可能会做到,但是自动构建工具有可能会在1秒内做到。

1.4自定义决策方法

这种方式是我们使用python编写自己的决策方法,这里不过多叙述,看下模板样式:

Program('hello.c')
def decide_if_changed(dependency, target, prev_ni, repo_node=None):
    if dependency.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)
1.5混合决策方法的方式

假如我们编译多个不同目标文件,想指定不同的决策方法,SCons也是支持的。(虽然这种情况我们很少使用)

将使用env.Decider方式,我们需要创建不同的环境env,如下:

# 环境env1
env1 = Environment(CPPPATH = ['.'])
# 从env1克隆 出env2
env2 = env1.Clone()

# env2 设置决策方式
# env1 没有设置,使用的就是默认 Decider('content')
env2.Decider('timestamp-match')

# 在env1环境下 编译 program1.c 生成 prog-content 程序
env1.Program('prog-content' , 'program1.c')
#在env2环境下 编译 program2.c生成 prog-timestamp 程序
env2.Program('prog-timestamp' , 'program2.c')

2.隐式依赖:$CPPPATH构造变量

隐式依赖就是不需要我们指定依赖关系,SCons能够自己识别的依赖关系。

比如我们的例子hello.c 一般还会包含一个hello.h文件,hello.c如下:

#include <hello.h>
int
main()
{
    printf("Hello, %s!\n", string);
}

hello.h
#define string    "world

如果hello.h的内容改变,那么我们希望SCons能够重新编译hello.c,语法如下:

# CPPPATH 环境变量指定源文件和头文件路径
Program('hello.c', CPPPATH='.')

CPPPATH和LIBPATH 一样都支持列表类型,如下:

Program('hello.c', CPPPATH = ['include', '/home/project/inc'])

3.缓存隐式依赖

SCons编译时会自动扫描源文件中包含的#include 行,以便于他能知道隐式依赖的文件是否改变。

在构建一个大型项目时,SCons扫描文件所花费的时间占总的构造项目的时间是很少的比例。假如如果你还觉的自动扫描会浪费不必要的时间,那么SCons可以使用缓存隐式依赖,语法如下:

SetOption('implicit_cache' ,1)
# 或在命令行窗口执行
scons -Q --implicit-cache hello

注意:如果开启了缓存依赖,那么SCons每次从缓存中查找依赖,即使源文件有改变,SCons也不会重新编译。

其实这样做的意义,对于我们编译一些比较稳定的、不需要修改的源代码意义很大。比如我们引用了第三方的库,只要重新扫描一次缓存隐式的依赖。

3.1 --implicit-deps-changed 选项

那么如上面的参数设置,假如我们缓存的隐式依赖的源文件有变化,如何更新缓存呢?语法如下:

scons -Q --implicit-deps-changed hello

3.2 --implicit-deps-unchanged 选项

scons -Q --implicit-deps-unchanged 命令行告诉SCons强制使用缓存的隐式依赖。


4.显示依赖:Depends 方法

当一个文件依赖另一个文件,但是SCons无法检测这种依赖,那么我们可以显式的指定依赖关系,如下:

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

5.依赖外部文件:ParseDepends 方法

有时候SCons扫描器扫描隐式依赖失败,比如下面代码:

#define FOO_HEADER <foo.h>
#include FOO_HEADER
int main() {
    return FOO;
}

扫描枪将无法扫描到foo.h头文件,这时需要:

ParseDepends()方法

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

这种方式,只有在扫描器扫描不出依赖关系时才推荐使用。


6.忽略依赖: Ignore 方法

有时我们希望忽略依赖,即使依赖的文件有改变,也不希望SCons重新编译,语法如下:

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

7.顺序依赖(Order-Only): Requires 方法

有时我们的源文件a.c包含了另外一个源文件b.c,当b.c改变时我们不希望a.c被重新编译。

&#x20;比如hello.c 包含version.c,version.c的内容每次在编译的时候都会改变,比如我们使用python代码生成version.c的内容,

内容包括版本号,编译日期,等等信息。

hello.c #include "version.h"文件,hello.c只要访问version.c里面的数组地址就可以获取版本,日期等信息。而不必关心具体内容。

但是根据隐式依赖关系,每次verison.c的内容改变时,hello.c都会被重新编译。

看下编译文件:

import time
version_c_text = """
char *date = "%s";
""" % time.ctime(time.time())
open('version.c', 'w').write(version_c_text)
hello = Program(['hello.c', 'version.c'])

改进后的编译文件:

import time
version_c_text = """
char *date = "%s";
""" % time.ctime(time.time())
open('version.c', 'w').write(version_c_text)
version_obj = Object('version.c')

# 将version.c的目标文件和hello目标文件链接在一起
# 这样 hello.c就不需要每次重复编译了
hello = Program('hello.c',
                LINKFLAGS = str(version_obj[0]))
                
# 指定 hello文件需要version_obj文件
Requires(hello, version_obj)

8.AlwayBuild 方法

这个方法意思并不是字面意思,他是在需要编译的时候总是编译。语法如下:

hello = Program('hello.c')
AlwaysBuild(hello)
SCons 是一个基于 Python 的开源软件构建工具,其核心特性是使用 Python 脚本作为构建配置文件,从而提供高度灵活和可扩展的构建流程控制。SCons 能够自动检测依赖关系,并支持跨平台构建,适用于 C/C++、Java、Fortran 等多种语言的项目构建。 ### 基本配置方法 SCons 的配置主要依赖于 `SConstruct` 文件,该文件是项目构建的入口点,类似于 Make 中的 `Makefile`。在项目根目录下创建 `SConstruct` 文件后,SCons 会自动读取并执行其中的指令。 以下是一个简单的 `SConstruct` 文件示例,用于构建一个 C 程序: ```python Program('hello.c') ``` 上述代码会告诉 SCons 编译 `hello.c` 并生成一个可执行文件(在 Unix-like 系统上为 `hello`,在 Windows 上为 `hello.exe`)[^1]。 ### 构建多个源文件 如果程序由多个源文件组成,可以使用列表形式指定: ```python Program(['main.c', 'utils.c']) ``` ### 自定义目标名称 默认情况下,生成的可执行文件名称与源文件相同。若需自定义目标名称,可以使用如下方式: ```python Program(target='myapp', source=['main.c', 'utils.c']) ``` 此时生成的可执行文件名称为 `myapp`。 ### 指定编译器和编译选项 SCons 支持通过环境变量来设置编译器及其选项。例如,使用 `Environment` 来定义编译器和编译标志: ```python env = Environment(CC='gcc', CCFLAGS='-Wall') env.Program(target='myapp', source=['main.c', 'utils.c']) ``` ### 构建库文件 SCons 也支持构建静态库和共享库。构建静态库的示例如下: ```python StaticLibrary('libutils', ['utils.c']) ``` 构建共享库的示例如下: ```python SharedLibrary('libutils', ['utils.c']) ``` ### 构建子目录项目 若项目结构较复杂,包含多个子目录,可以使用 `SConscript` 文件来组织构建逻辑。主 `SConstruct` 文件中可以调用子目录中的 `SConscript` 文件: ```python SConscript('src/SConscript') ``` 在 `src/SConscript` 文件中可以编写: ```python Program('main.c') ``` ### 清理构建产物 与 `make clean` 类似,SCons 提供了清理构建产物的功能: ```bash scons -c ``` 该命令会删除所有由 SCons 生成的文件。 ### 其他常用选项 - **并行构建**:使用 `-j` 参数指定并行线程数,提升构建速度: ```bash scons -j4 ``` - **显示详细信息**:使用 `-Q` 参数减少输出冗余信息: ```bash scons -Q ``` - **指定构建目标**:可以直接指定要构建的目标: ```bash scons myapp ``` ### 自定义构建步骤 SCons 允许用户通过 `Command` 和 `Action` 定义自定义的构建步骤。例如,执行一个脚本生成配置文件: ```python config_file = Command('config.h', 'generate_config.py', 'python generate_config.py') ``` ### 配置环境变量 SCons 提供了 `Environment` 对象来管理构建过程中的各种配置参数,包括编译器、链接器、库路径等。可以复制现有环境并进行修改: ```python env = Environment() env.Append(CCFLAGS=['-g', '-O2']) env.Program('debug_app', 'main.c') ``` ### 使用第三方模块 SCons 支持通过 `Tools` 扩展功能,用户可以加载特定工具模块以支持更多语言或构建需求: ```python env = Environment(tools=['default', 'qt']) ``` ### 构建安装目标 SCons 支持定义安装目标,通常用于将构建产物复制到指定目录: ```python install = env.Install('/usr/local/bin', 'myapp') env.Alias('install', install) ``` 使用时执行: ```bash scons install ``` ### 构建文档 SCons 也支持构建文档,例如使用 `PDF` 构建目标生成 PDF 文档: ```python env.PDF('report.tex') ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搏哥聊技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值