本文记录SCons的使用流程和使用方法。

Written by: Zhai Xiufeng

  1. 概述

SCons 是一个开源的、跨平台的自动化构建工具,类似于 Make,但使用 Python 脚本作为配置文件。它主要用于软件项目的编译、链接和构建。特别适用于自动化工程构建。

  1. Scons基本语句和用法

以下是 SCons 的主要函数、语句和用法,按功能分类,附带说明和示例。

2.1 环境创建与配置

SCons 的构建基于 Environment 对象,用于设置编译器、标志、路径等。

Environment(**kwargs)

创建一个新的构建环境。

用法:初始化编译器、标志、路径等。

参数:

CC:指定 C 编译器(如 'gcc'、'clang')。

CXX:指定 C++ 编译器。

CCFLAGS:编译器标志(如 '-Wall')。

CPPPATH:头文件搜索路径。

LIBPATH:库文件搜索路径。

LIBS:链接的库(如 ['m', 'pthread'])。

示例:

env = Environment(CC='gcc', CCFLAGS='-Wall', CPPPATH=['include'], LIBS=['m'])

env.Clone(**kwargs)

克隆现有环境并修改参数,避免影响原始环境。

用法:为不同目标创建独立的配置。

参数:

CC:指定 C 编译器(如 'gcc'、'clang')。

CXX:指定 C++ 编译器。

CCFLAGS:编译器标志(如 '-Wall')。

CPPPATH:头文件搜索路径。

LIBPATH:库文件搜索路径。

LIBS:链接的库(如 ['m', 'pthread'])。

示例:

env = Environment(CCFLAGS='-O2')

debug_env = env.Clone(CCFLAGS='-g')  # 创建带调试标志的环境

DefaultEnvironment(**kwargs)

获取或修改默认环境(全局环境)。

用法:设置全局默认配置。

参数:

CC:指定 C 编译器(如 'gcc'、'clang')。

CXX:指定 C++ 编译器。

CCFLAGS:编译器标志(如 '-Wall')。

CPPPATH:头文件搜索路径。

LIBPATH:库文件搜索路径。

LIBS:链接的库(如 ['m', 'pthread'])。

示例:

DefaultEnvironment(CCFLAGS='-Wall')

2.2 文件匹配与源文件处理

SCons 提供函数来匹配和处理源文件。

Glob(pattern)

匹配指定模式的文件(如 *.c)。

用法:自动收集源文件。

示例:

c_files = Glob('src/*.c')  # 匹配 src 目录下所有 .c 文件

env.Program('my_program', c_files)

Split(string)

将字符串拆分为文件列表。

用法:手动指定多个源文件。

示例:

sources = Split('main.c util.c')

env.Program('my_program', sources)

File(filename)

创建文件节点,显式指定单个文件。

用法:精确控制文件。

示例:

main_file = File('main.c')

env.Program('my_program', main_file)

Dir(dirname)

创建目录节点。

用法:指定目录路径。

示例:

src_dir = Dir('src')

2.3 构建目标(Builders)

SCons 使用 Builder 函数生成目标文件(如可执行文件、库)。

Program(target, source, **kwargs)

编译生成可执行文件。

参数:

target:输出文件名。

source:源文件列表。 示例:

env.Program(target='my_program', source=Glob('*.c'))

Object(target, source)

编译生成目标文件(.o 文件)。

用法:单独编译源文件。

示例:

env.Object('main.o', 'main.c')

Library(target, source)

生成静态库(.a 文件)。

示例:

env.Library('mylib', Glob('src/*.c'))

SharedLibrary(target, source)

生成动态库(.so 或 .dll 文件)。

示例:

env.SharedLibrary('mylib', Glob('src/*.c'))

Install(target_dir, source)

安装文件到指定目录。

用法:将生成的文件复制到目标路径。

示例:

env.Install('bin', 'my_program')

Alias(alias, targets)

为构建目标创建别名,方便命令行调用。

用法:简化复杂目标的构建。

示例:

env.Alias('build', 'my_program')

# 运行 `SCons build` 等价于构建 my_program

2.4 依赖管理

SCons 自动管理依赖,但也可以显式指定。

Depends(target, dependency)

显式指定目标的依赖关系。

用法:强制依赖某些文件。

示例:

env.Depends('my_program', 'config.h')

Ignore(target, dependency)

忽略某些依赖。

用法:排除不必要的依赖检查。

示例:

env.Ignore('my_program', 'old_config.h')

SideEffect(filename, target)

指定构建过程中的副产物文件。

用法:管理中间文件。

示例:

env.SideEffect('temp.o', 'my_program')

2.5 子目录与模块化

SCons 支持通过 SConscript 文件管理子目录。

SConscript(files, [exports, variant_dir])

调用子构建脚本。

参数:

files:子脚本文件(如 'subdir/SConscript')。

exports:导出变量到子脚本。

variant_dir:指定构建输出目录。

示例:

# SConstruct文件

env = Environment()

SConscript('src/SConscript', exports='env')

# src/SConscript文件

Import('env')  # 导入导出的环境

env.Program('my_program', Glob('*.c'))

VariantDir(variant_dir, src_dir)

指定构建输出目录,保持源目录干净。

示例:

VariantDir('build', 'src')

SConscript('build/SConscript')

2.6 自定义工具与命令

SCons 允许自定义构建命令和工具。

Command(target, source, action)

执行自定义命令。

用法:运行任意命令(如脚本或工具)。

示例:

env.Command('output.txt', 'input.txt', 'cp $SOURCE $TARGET')

AddMethod(env, function, [name])

向环境添加自定义方法。

用法:扩展 SCons 功能。

示例:

def my_builder(env, target, source):

env.Command(target, source, 'echo Building $SOURCE > $TARGET')

env.AddMethod(my_builder, 'MyBuilder')

env.MyBuilder('out.txt', 'in.txt')

2.7 配置与检测

SCons 支持检测编译器、库和工具。

Configure(env, **kwargs)

创建配置上下文,用于检测环境。

用法:检查编译器、库或头文件是否存在。

示例:

conf = Configure(env)

if conf.CheckLib('m'):

print("Math library found")

env = conf.Finish()

CheckHeader(context, header, language)

检查头文件是否存在。

示例:

conf = Configure(env)

if conf.CheckHeader('math.h', language='C'):

print("math.h found")

env = conf.Finish()

2.8 构建控制

控制构建行为和流程。

Default(target)

指定默认构建目标。

用法:运行 SCons 时构建这些目标。

示例:

env.Program('my_program', 'main.c')

Default('my_program')

AlwaysBuild(target)

强制目标始终构建。

示例:

env.AlwaysBuild('my_program')

NoClean(target)

防止目标在 SCons -c 时被清理。

示例:

env.NoClean('my_program')

2.9 环境变量与标志

设置编译器和链接器选项。

env.Append(**kwargs)

向环境变量追加值(如标志、路径)。

示例:

env.Append(CCFLAGS='-g', CPPPATH=['include'])

env.Replace(**kwargs)

替换环境变量的值。

示例:

env.Replace(CC='clang')

env.Prepend(**kwargs)

在环境变量前添加值。

示例:

env.Prepend(CCFLAGS='-O3')

2.10 调试与信息输出

帮助调试构建过程。

env.Message(msg)

输出自定义消息。

示例:

env.Message("Building my_program...")

env.Progress(func)

显示构建进度。

示例:

env.Progress(lambda x: print(f"Processing {x}"))

  1. 工程示例

3.1 SConstruct文件

import os

# 描述:导入 Python 的 os 模块,提供文件和目录操作的功能。

# 意图:为路径操作、环境变量获取和目录检查等功能提供支持,例如构造路径或验证目录存在。



import sys

# 描述:导入 Python 的 sys 模块,用于操作 Python 运行时环境。

# 意图:允许修改模块搜索路径(sys.path),以便加载 RT-Thread 的工具模块。



import rtconfig

# 描述:导入 rtconfig 模块,包含 RT-Thread 项目的编译工具链和标志配置。

# 意图:提供编译器、链接器和标志的配置信息(如 rtconfig.CC、rtconfig.CFLAGS),用于设置构建环境。



if os.getenv('RTT_ROOT'):

# 描述:检查环境变量 RTT_ROOT 是否存在,使用 os.getenv() 获取其值。

# 意图:确定 RT-Thread 根目录的路径,优先使用用户设置的环境变量以提高灵活性。



RTT_ROOT = os.getenv('RTT_ROOT')

# 描述:将环境变量 RTT_ROOT 的值赋给变量 RTT_ROOT。

# 意图:记录 RT-Thread 根目录路径,确保后续路径构造使用正确的根目录。



else:

RTT_ROOT = os.path.normpath(os.getcwd() + '/../../..')

# 描述:计算 RT-Thread 根目录路径,通过 os.getcwd() 获取当前目录并向上回溯三级,再用 os.path.normpath 规范化路径。

# 意图:为没有设置 RTT_ROOT 环境变量的情况提供默认根目录路径,确保脚本在不同环境下可运行。



sys.path = sys.path + [os.path.join(RTT_ROOT, 'tools')]

# 描述:将 RT-Thread 的 tools 目录路径追加到 Python 的模块搜索路径 sys.path 中,使用 os.path.join 构造路径。

# 意图:确保 Python 能找到 RT-Thread 的工具模块(如 building.py),支持后续导入构建辅助函数。



try:

from building import *

# 描述:尝试从 tools 目录的 building 模块导入所有内容,包含 RT-Thread 的构建辅助函数。

# 意图:加载 PrepareBuilding 和 DoBuilding 等函数,为项目的编译和链接提供支持。



except:

print('Cannot find RT-Thread root directory, please check RTT_ROOT')

# 描述:如果导入 building 模块失败,打印错误信息,提示无法找到 RT-Thread 根目录。

# 意图:帮助用户调试问题,明确错误原因是 RTT_ROOT 路径不正确。



print(RTT_ROOT)

# 描述:打印当前 RTT_ROOT 变量的值。

# 意图:提供 RTT_ROOT 路径的实际值,方便用户检查路径是否正确。



exit(-1)

# 描述:调用 exit(-1) 退出程序,返回错误码 -1。

# 意图:终止构建过程,表明由于找不到 RT-Thread 根目录,脚本无法继续执行。



TARGET = 'rt-thread.' + rtconfig.TARGET_EXT

# 描述:构造目标文件名,拼接字符串 'rt-thread.' 和 rtconfig.TARGET_EXT(目标文件扩展名,如 'out')。

# 意图:定义最终生成的目标文件名称,例如 'rt-thread.out',用于后续编译和链接。



DefaultEnvironment(tools=[])

# 描述:创建 SCons 的默认环境,并通过 tools=[] 禁用所有默认工具。

# 意图:避免 SCons 加载默认工具链(如 gcc),为自定义工具链配置留出空间。



env = Environment(tools=['mingw'],

# 描述:创建 SCons 构建环境,指定 tools=['mingw'] 使用 MinGW 工具链。

# 意图:初始化一个自定义的编译环境,适配 Windows 上的 GCC 编译器或类似工具链。



AS=rtconfig.AS, ASFLAGS=rtconfig.AFLAGS,

# 描述:设置汇编器(AS)和汇编标志(ASFLAGS),从 rtconfig 模块获取相应值。

# 意图:配置汇编工具和标志,确保汇编代码按 RT-Thread 的要求编译。



CC=rtconfig.CC, CFLAGS=rtconfig.CFLAGS,

# 描述:设置 C 编译器(CC)和 C 编译标志(CFLAGS),从 rtconfig 模块获取值。

# 意图:定义 C 代码的编译工具和标志,确保与 RT-Thread 的配置一致。



AR=rtconfig.AR, ARFLAGS='-rc',

# 描述:设置归档工具(AR)为 rtconfig.AR,归档标志(ARFLAGS)固定为 '-rc'。

# 意图:配置静态库生成工具,确保生成 .a 文件时使用正确的归档参数。



CXX=rtconfig.CXX, CXXFLAGS=rtconfig.CXXFLAGS,

# 描述:设置 C++ 编译器(CXX)和 C++ 编译标志(CXXFLAGS),从 rtconfig 模块获取值。

# 意图:支持 C++ 代码的编译,适配 RT-Thread 项目中的 C++ 部分(如果有)。



LINK=rtconfig.LINK, LINKFLAGS=rtconfig.LFLAGS)

# 描述:设置链接器(LINK)和链接标志(LINKFLAGS),从 rtconfig 模块获取值。

# 意图:配置链接工具和参数,确保生成目标文件时使用正确的链接脚本和选项。



env.PrependENVPath('PATH', rtconfig.EXEC_PATH)

# 描述:将 rtconfig.EXEC_PATH(工具链的可执行文件路径)添加到环境变量 PATH 的开头。

# 意图:确保 SCons 能找到编译器、链接器等工具,优先使用 RT-Thread 指定的工具链路径。



if rtconfig.PLATFORM in ['iccarm']:

# 描述:检查 rtconfig.PLATFORM 是否为 'iccarm',判断是否使用 IAR 编译器。

# 意图:为 IAR 编译器平台提供特定的配置,适配其独特的编译和链接要求。



env.Replace(CCCOM=['$CC $CFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -o $TARGET $SOURCES'])

# 描述:替换 C 编译命令(CCCOM),使用 IAR 特定的命令模板,包含编译器、标志和输出选项。

# 意图:确保 IAR 编译器按照正确的参数编译 C 代码,生成目标文件。



env.Replace(ARFLAGS=[''])

# 描述:清空归档标志(ARFLAGS),将其设置为空列表。

# 意图:适配 IAR 编译器的归档工具,移除默认的 '-rc' 标志以避免冲突。



env.Replace(LINKCOM=env["LINKCOM"] + ' --map rt-thread.map')

# 描述:修改链接命令(LINKCOM),在原有命令后追加 '--map rt-thread.map' 参数。

# 意图:为 IAR 链接器生成映射文件 rt-thread.map,记录符号表和内存布局。



Export('RTT_ROOT')

# 描述:使用 SCons 的 Export 函数将 RTT_ROOT 变量导出到子脚本。

# 意图:允许子 SConscript 脚本通过 Import('RTT_ROOT') 访问 RT-Thread 根目录路径。



Export('rtconfig')

# 描述:使用 Export 函数将 rtconfig 模块导出到子脚本。

# 意图:使子脚本能够访问编译器和标志配置,确保一致的构建环境。



SDK_ROOT = os.path.abspath('./')

# 描述:使用 os.path.abspath 获取当前工作目录的绝对路径,存储在 SDK_ROOT 变量中。

# 意图:记录项目根目录的绝对路径,方便后续构造库或驱动的路径。



if os.path.exists(SDK_ROOT + '/libraries'):

# 描述:检查 SDK_ROOT/libraries 目录是否存在。

# 意图:确定 libraries 目录的位置,优先使用项目内的 libraries 路径。



libraries_path_prefix = SDK_ROOT + '/libraries'

# 描述:如果 libraries 目录存在,将其路径赋给 libraries_path_prefix。

# 意图:设置 libraries 目录的路径,用于查找 STM32 HAL 库和驱动。



else:

libraries_path_prefix = os.path.dirname(SDK_ROOT) + '/libraries'

# 描述:如果 libraries 目录不存在,构造父目录下的 libraries 路径并赋给 libraries_path_prefix。

# 意图:提供备用路径,确保即使 libraries 不在项目内也能找到库文件。



SDK_LIB = libraries_path_prefix

# 描述:将 libraries_path_prefix 的值赋给 SDK_LIB 变量。

# 意图:统一库路径的变量名,便于后续导出和使用。



Export('SDK_LIB')

# 描述:使用 Export 函数将 SDK_LIB 变量导出到子脚本。

# 意图:允许子 SConscript 脚本访问库路径,方便加载 STM32 库和驱动。



objs = PrepareBuilding(env, RTT_ROOT, has_libcpu=False)

# 描述:调用 PrepareBuilding 函数,准备构建环境,返回构建对象列表,传入 env、RTT_ROOT 和 has_libcpu=False 参数。

# 意图:初始化 RT-Thread 项目的编译环境,收集核心源文件和各级SConscript的构建对象,禁用 libcpu 相关代码。



stm32_library = 'STM32F4xx_HAL'

# 描述:定义变量 stm32_library,赋值为 'STM32F4xx_HAL',表示 STM32F4xx 硬件抽象层库。

# 意图:指定项目使用的 STM32 库名称,供后续路径构造和配置使用。



rtconfig.BSP_LIBRARY_TYPE = stm32_library

# 描述:将 stm32_library 的值赋给 rtconfig.BSP_LIBRARY_TYPE。

# 意图:记录板级支持包(BSP)使用的库类型,确保 RT-Thread 配置与 STM32 库一致。



objs.extend(SConscript(os.path.join(libraries_path_prefix, stm32_library, 'SConscript')))

# 描述:调用 STM32F4xx_HAL 库的 SConscript 脚本,获取其构建对象并追加到 objs 列表。

# 意图:将 STM32 HAL 库的编译结果(.o 文件)纳入构建过程,支持硬件抽象层功能。



objs.extend(SConscript(os.path.join(libraries_path_prefix, 'HAL_Drivers', 'SConscript')))

# 描述:调用 HAL_Drivers 目录的 SConscript 脚本,获取驱动相关的构建对象并追加到 objs 列表。

# 意图:将 STM32 驱动代码的编译结果纳入构建过程,提供硬件驱动支持。



DoBuilding(TARGET, objs)

# 描述:调用 DoBuilding 函数,传入目标文件名 TARGET 和构建对象列表 objs,执行最终编译和链接。

# 意图:生成最终的目标文件(如 rt-thread.elf),完成项目的构建过程。

3.2 SConstruct文件

import os

# 描述:导入 Python 的 os 模块,提供文件和目录操作功能。

# 意图:为后续的目录遍历和文件检查提供必要的工具,例如列出子目录或构造路径。



Import('remove_components')

# 描述:使用 SCons 的 Import 函数从父脚本导入 remove_components 变量。

# 意图:获取父脚本中定义的排除模块列表,以便动态控制哪些子模块不被编译。



from building import *

# 描述:从 building 模块导入所有内容,通常包含 RT-Thread 的构建辅助函数。

# 意图:为脚本提供 RT-Thread 特定的构建工具,如 PrepareBuilding 或 DoBuilding,尽管本脚本未直接使用。



objs = []

# 描述:初始化一个空的列表 objs,用于存储子模块的构建对象。

# 意图:创建一个容器,收集所有子模块的编译结果(如 .o 文件),供父脚本使用。



cwd = GetCurrentDir()

# 描述:调用 SCons 的 GetCurrentDir 函数,获取当前 SConscript 脚本所在目录的路径。

# 意图:记录当前工作目录,以便构造子目录路径,用于后续遍历和文件检查。



list = os.listdir(cwd)

# 描述:使用 os.listdir 函数获取当前目录下的所有文件和子目录名称列表。

# 意图:生成一个包含潜在子模块的列表,为后续遍历子目录提供基础数据。



for item in list:

# 描述:使用 for 循环遍历 list 中的每个条目,item 表示文件或目录的名称。

# 意图:逐一检查当前目录下的每个条目,识别哪些是需要编译的子模块。



if item in remove_components:

continue

# 描述:检查当前条目 item 是否在 remove_components 列表中,若是则跳过(continue)。

# 意图:排除不需要编译的模块,实现动态配置,允许根据项目需求禁用特定模块。



if os.path.isfile(os.path.join(cwd, item, 'SConscript')):

# 描述:使用 os.path.isfile 检查子目录中是否存在 SConscript 文件,路径由 os.path.join 构造。

# 意图:确认当前子目录是否是一个有效的模块(包含 SConscript 文件),以决定是否需要编译。



objs = objs + SConscript(os.path.join(item, 'SConscript'))

# 描述:调用子目录中的 SConscript 脚本,并将其返回的构建对象追加到 objs 列表。

# 意图:执行子模块的构建逻辑,收集其编译结果(如 .o 文件),汇总到 objs 用于最终链接。



Return('objs')

# 描述:使用 SCons 的 Return 函数将 objs 列表返回给调用该脚本的父脚本。

# 意图:将所有子模块的构建对象传递给父脚本,以便进行进一步的编译或链接(如生成可执行文件)。

3.3 自建SCons工程验证

3.3.1  SCons工程和C代码工程
1. 工程验证环境搭建
搭建Python环境
搭建GCC编译环境
搭建SCONS编译环境

2. 写个C语言demo
H文件


C文件

3. 添加SConstruct文件和SConscript文件

SConstruct文件

import os

from SCons.Script import ARGUMENTS, Environment

# 定义 MinGW 的编译器路径

# 用于后续构建中明确指定 gcc/g++ 所在的位置

MINGW_PATH = r'D:\tools\WinGcc\mingw64\bin'

# 创建一个构建环境,指定使用 MinGW 工具链

# 避免 SCons 默认选择 MSVC 编译器造成参数不兼容

env = Environment(

tools=['mingw'],

)

# 将 MinGW 路径添加到环境变量 PATH 中

# 确保 SCons 在执行 gcc/g++ 时能正确找到工具路径

env.PrependENVPath('PATH', MINGW_PATH)

# 手动设置编译器与链接器路径及可执行文件后缀

# 避免使用 SCons 自动探测的工具路径,确保调用的是我们指定的 MinGW 版本

env.Replace(

CC   = os.path.join(MINGW_PATH, 'gcc.exe'),     # 设置 C 编译器为 MinGW 的 gcc

CXX  = os.path.join(MINGW_PATH, 'g++.exe'),     # 设置 C++ 编译器为 MinGW 的 g++

LINK = os.path.join(MINGW_PATH, 'gcc.exe'),     # 设置链接器为 gcc(适用于 C 项目)

PROGSUFFIX = '.exe'                             # 指定生成程序使用 .exe 后缀(Windows 下的标准格式)

)

# 获取构建模式参数,默认为 debug

# 允许通过命令行传参切换 debug 或 release 模式(如:SCons build=release)

variant = ARGUMENTS.get('build', 'debug')

# 设置 debug 模式下的编译和链接参数

# 开启调试信息,不进行优化,并显示所有警告

if variant == 'debug':

env.Append(CCFLAGS=['-g', '-O0', '-Wall'], LINKFLAGS=['-g'])

# 设置 release 模式下的参数

# 启用优化并保留警告提示,适合发布版本

else:

env.Append(CCFLAGS=['-O2', '-Wall'])

# 构建输出目录,按构建模式区分(如 build/debug)

# 实现不同构建模式的中间文件和输出文件隔离

out_dir = f'build/{variant}'

# 加载子构建脚本,返回中间编译产物(object 文件列表)

# 将构建任务分离到子目录中,保持结构清晰

objs = SConscript('project/SConscript',

exports='env',              # 向子脚本传递构建环境变量

variant_dir=out_dir,        # 指定输出目录

duplicate=0)                # 不复制源文件,只生成构建结果

# 链接生成最终的可执行文件,命名为 output.exe

# 使用返回的 object 文件完成链接步骤,输出至对应目录

env.Program(f'{out_dir}/output.exe', objs)

SConscript

# 导入从主构建脚本(SConstruct)传进来的构建环境变量 env

# 保证子构建脚本与主构建脚本使用相同的编译器和参数设置

Import('env')

# 使用通配符获取当前目录下所有 .c 源文件,保存在 sources 变量中

# 自动收集所有 C 源文件,避免手动一个个列出,方便管理

sources = Glob('*.c')

# 使用传入的构建环境将所有源文件编译为 .o 对象文件,存入 objs

# 预编译阶段生成目标文件,供后续链接生成可执行文件使用

objs = env.Object(sources)

# 将编译生成的对象文件列表返回给调用者(SConstruct)

# 主构建脚本需要用这些对象文件来进行最终的链接操作

Return('objs')

  1.  编译
    打开CMD 输入SCons命令

  1.  执行写的示例

3.3.2 附录测试demo

链接: https://pan.baidu.com/s/1znPwPG6EJ8d2H7BY_P0_dA?pwd=4uhd 提取码: 4uhd

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐