当前位置: 首页 > news >正文

SConscript 脚本入门教程

第一章:什么是 SCons 和 SConscript?

核心概念

SCons 是一个现代化的构建工具,用于自动化软件构建过程,类似于 Make 但功能更强大、语法更简洁。

  • SConstruct:是 SCons 的主配置文件,通常在项目根目录,相当于 Makefile
  • SConscript:是子配置文件,用于组织大型项目,可被 SConstruct 或其他 SConscript 包含
  • 构建过程:将源代码转换为可执行文件的过程(编译、链接等)

SCons 使用 Python 语法,因此如果你熟悉 Python,学习 SCons 会更容易。

第一个示例

创建一个最简单的 SConscript:

# 编译hello.c并生成可执行文件hello
Program('hello.c')

这个简单的脚本告诉 SCons:编译 hello.c 文件并生成同名可执行文件。

练习题

  1. 什么是 SCons?它的主要作用是什么?
  2. SConstruct 和 SConscript 有什么区别?
  3. 写出一个 SConscript 脚本,用于编译 main.c 生成名为 app 的可执行文件。

答案详解

1.SCons 是一个构建工具,主要作用是自动化软件的编译、链接等构建过程,简化项目管理,提高开发效率。与传统的 Make 相比,它语法更简洁,功能更强大,且跨平台性更好。

2.区别在于:

  • SConstruct 是主配置文件,是 SCons 的入口点
  • SConscript 是子配置文件,用于组织大型项目
  • 一个项目通常有一个 SConstruct,可能有多个 SConscript
  • SConstruct 可以包含并执行 SConscript

3.实现代码:

# 编译main.c生成名为app的可执行文件
Program('app', ['main.c'])

这里Program是 SCons 的一个构建器 (builder),第一个参数是输出文件名,第二个参数是源文件列表。

第二章:SConscript 基本语法

核心概念

SConscript 使用 Python 语法,同时提供了一些 SCons 特有的函数和变量:

  • 构建器 (Builder):如Program(生成可执行文件)、Library(生成库文件) 等
  • 路径处理GetCurrentDir()获取当前目录
  • 文件匹配Glob()匹配符合模式的文件
  • 依赖管理:处理文件间的依赖关系

常用函数

Program(target, sources):编译源文件生成可执行文件

Program('myapp', ['main.c', 'utils.c'])

Library(target, sources):生成库文件

Library('mylib', ['func1.c', 'func2.c'])

GetCurrentDir():获取当前目录路径

current_dir = GetCurrentDir()
print(f"当前目录: {current_dir}")

Glob(pattern):匹配文件

# 获取所有.c文件
c_files = Glob('*.c')
# 获取src目录下所有.c文件
src_files = Glob('src/*.c')

Return(value):返回值给父脚本

src_files = Glob('*.c')
Return('src_files')  # 将src_files返回给包含此脚本的父脚本

练习题

1.请写出一段 SConscript 代码,实现以下功能:

获取当前目录下所有的.c 文件

获取 src 目录下所有的.c 文件

将这两部分文件合并到一个列表中

打印出收集到的所有文件路径

2.

编写一个完整的 SConscript 脚本,实现以下功能:

编译 math 目录下所有的.c 文件生成名为 libmath 的静态库

编译 main.c 文件,并链接 libmath 库生成名为 calculator 的可执行文件

确保编译器能找到 math 目录下的头文件

答案详解

1. 实现代码:

# 获取当前目录下所有的.c文件
current_c_files = Glob('*.c')# 获取src目录下所有的.c文件
src_c_files = Glob('src/*.c')# 合并两个文件列表
all_c_files = current_c_files + src_c_files# 打印收集到的文件路径
print("收集到的C源文件:")
for file in all_c_files:print(f"- {file}")

解析

  • Glob(pattern)是 SCons 提供的文件匹配函数,用于查找符合模式的文件
  • *.c表示匹配当前目录下所有以.c 结尾的文件
  • src/*.c表示匹配 src 子目录下所有以.c 结尾的文件
  • +运算符用于合并两个列表(Python 语法)
  • 通过 for 循环遍历并打印所有文件路径,方便查看收集结果

2.实现代码:

# 编译math目录下所有.c文件生成libmath静态库
# Library是SCons的库文件构建器,第一个参数是库名,第二个是源文件
math_library = Library('math', Glob('math/*.c'))# 编译main.c并链接libmath库生成calculator可执行文件
# Program是SCons的可执行文件构建器
# CPPPATH指定头文件搜索路径,确保编译器能找到math目录下的头文件
# LIBS指定要链接的库文件
Program(target='calculator',          # 输出的可执行文件名source=['main.c'],            # 源文件CPPPATH=['math'],             # 头文件搜索路径LIBS=['math']                 # 要链接的库
)

解析

  • Library('math', Glob('math/*.c')):生成名为 libmath 的静态库(在 Linux 下实际生成 libmath.a,Windows 下生成 math.lib)
  • Glob('math/*.c'):自动收集 math 目录下所有的 C 源文件
  • CPPPATH=['math']:告诉编译器在 math 目录中查找头文件(相当于 gcc 的 - I 选项)
  • LIBS=['math']:指定链接名为 math 的库(SCons 会自动处理库文件的路径和命名规则)
  • 这种分离编译的好处是:如果 math 目录下的文件没有修改,再次构建时不会重新编译,提高构建效率

第三章:变量和配置

核心概念

在 SConscript 中,变量用于存储配置信息、文件列表、编译选项等:

  • 路径变量:存储目录路径,如源文件目录、头文件目录
  • 文件列表变量:存储源文件列表
  • 编译选项变量:如CPPPATH(头文件搜索路径)、CFLAGS(C 编译选项)
  • 条件变量:根据不同平台或配置定义不同的变量值

示例代码

# 定义变量
cwd = GetCurrentDir()  # 当前目录
src_files = []         # 源文件列表
inc_paths = [cwd, cwd + '/include']  # 头文件路径# 添加源文件
src_files += Glob('*.c')
src_files += Glob('src/*.c')# 设置编译选项
CPPPATH = inc_paths  # 头文件搜索路径
CFLAGS = '-Wall -O2'  # C编译选项# 根据条件修改变量
import os
if os.name == 'nt':  # Windows系统CFLAGS += ' -DWIN32'
else:  # 类Unix系统CFLAGS += ' -DUNIX'# 使用变量
Program('myapp', src_files, CPPPATH=CPPPATH, CFLAGS=CFLAGS)

练习题

1.解释以下变量的含义和作用:CPPPATHCFLAGSLIBSLIBPATH

2.编写一个 SConscript 脚本,实现以下功能:

  • 定义一个变量存储当前目录路径
  • 定义源文件列表,包含当前目录和 src 子目录下的所有.c 文件
  • 定义头文件搜索路径,包含当前目录、include 目录和 src/include 目录
  • 为 GCC 编译器设置编译选项:开启所有警告、将警告视为错误、优化级别为 O2
  • 为 Windows 系统添加宏定义_WIN32,为 Linux 系统添加宏定义_LINUX
  • 编译生成名为 myapp 的可执行文件

答案详解

1.各变量含义和作用:

CPPPATH:C 预处理器的头文件搜索路径列表。告诉编译器去哪里查找#include指令引用的头文件。
示例:CPPPATH=['include', 'src/include']相当于 gcc 的-Iinclude -Isrc/include选项。

CFLAGS:C 编译器的编译选项。用于设置警告级别、优化级别、宏定义等。
示例:CFLAGS='-Wall -O2'表示开启所有警告并使用 O2 级优化。

LIBS:需要链接的库文件列表。指定程序运行时依赖的库。
示例:LIBS=['m', 'pthread']表示链接数学库和线程库。

LIBPATH:库文件的搜索路径列表。告诉链接器去哪里查找需要链接的库文件。
示例:LIBPATH=['lib', '/usr/local/lib']相当于 gcc 的-Llib -L/usr/local/lib选项。

2. 实现代码:

# 导入os模块用于判断操作系统类型
import os# 定义当前目录路径变量
current_dir = GetCurrentDir()# 定义源文件列表:当前目录和src子目录下的所有.c文件
source_files = Glob('*.c') + Glob('src/*.c')# 定义头文件搜索路径
include_paths = [current_dir,           # 当前目录current_dir + '/include',  # include目录current_dir + '/src/include'  # src/include目录
]# 初始化编译选项
compile_flags = '-Wall -Werror -O2'  # 开启所有警告、警告视为错误、O2优化# 根据操作系统添加不同的宏定义
if os.name == 'nt':# Windows系统,添加_WIN32宏定义compile_flags += ' -D_WIN32'
else:# Linux或类Unix系统,添加_LINUX宏定义compile_flags += ' -D_LINUX'# 编译生成myapp可执行文件
Program(target='myapp',        # 目标可执行文件名source=source_files,   # 源文件列表CPPPATH=include_paths, # 头文件搜索路径CFLAGS=compile_flags   # 编译选项
)

解析

  • GetCurrentDir()是 SCons 提供的函数,用于获取当前脚本所在的目录路径
  • Glob('*.c')Glob('src/*.c')分别获取当前目录和 src 目录下的所有.c 文件,+运算符将两个列表合并
  • include_paths列表包含了所有需要搜索头文件的目录,确保编译器能找到所有#include的文件
  • compile_flags变量集合了所有编译选项,-Wall开启所有警告,-Werror将警告视为错误,-O2设置优化级别
  • -D选项用于定义宏,在预处理阶段生效,代码中可以通过#ifdef _WIN32等条件编译指令实现跨平台逻辑
  • os.name是 Python 的 os 模块提供的变量,用于判断操作系统类型('nt' 表示 Windows,'posix' 表示 Linux/Unix 等)

第四章:条件判断和依赖管理

核心概念

  • 条件判断:根据不同平台、架构或配置选项执行不同的构建逻辑
  • 依赖管理:处理代码中的依赖关系,如特定功能依赖于某个宏定义
  • 平台相关配置:为不同 CPU 架构、操作系统定制构建选项

常用函数和语法

GetDepend(dependencies):检查是否存在特定的依赖项

# 检查是否定义了RT_USING_SMP宏
if GetDepend(['RT_USING_SMP']):print("启用了SMP支持")

字典用于存储平台 / 架构相关配置

# 支持的架构和CPU
support_arch = {"arm": ["cortex-m3", "cortex-m4"],"risc-v": ["na900"]
}

条件性添加源文件

src = []
# 根据架构添加不同的源文件
if arch == "arm":src += Glob('arch/arm/*.c')
elif arch == "risc-v":src += Glob('arch/risc-v/*.c')

示例代码

# 导入必要的模块和配置
import os
from building import *# 获取平台和架构信息
platform = rtconfig.PLATFORM
arch = rtconfig.ARCH
cpu = rtconfig.CPU# 初始化变量
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd, cwd + '/include']# 定义支持的架构和CPU
support_arch = {"arm": ["cortex-m3", "cortex-m4"],"risc-v": ["na900"]
}# 根据CPU类型设置不同的源文件
if arch in support_arch.keys() and cpu in support_arch[arch]:# 添加对应架构和CPU的源文件src += Glob('arch/' + arch + '/' + cpu + '/*.c')# 添加公共架构代码src += Glob('arch/' + arch + '/common/*.c')# 添加通用代码src += Glob('*.c')# 设置头文件路径CPPPATH.append(cwd + '/arch/' + arch + '/' + cpu)# 根据配置选项移除不需要的文件
if not GetDepend('RT_USING_MEMORY_PROTECTION'):# 移除内存保护相关文件SrcRemove(src, ['mpu.c'])# 定义编译选项
LOCAL_CFLAGS = '-Wall'# 创建构建组并返回
group = DefineGroup('core', src, depend = ['RT_USING_CORE'], CPPPATH = CPPPATH, LOCAL_CFLAGS = LOCAL_CFLAGS)Return('group')

答案详解

1. GetDepend()函数详解:

作用:检查是否存在指定的依赖项,主要用于判断是否定义了特定的宏或配置选项。

参数格式:接受一个列表作为参数,列表中包含要检查的依赖项名称(字符串类型)。
示例:GetDepend(['RT_USING_SMP', 'RT_USING_MMU'])

返回值:返回一个布尔值(True 或 False)。如果所有指定的依赖项都存在,则返回 True;否则返回 False。

使用场景

根据宏定义决定是否包含某些源文件

启用或禁用特定功能模块

为不同配置提供不同的编译选项
示例代码:

# 检查是否定义了RT_USING_MEMORY_PROTECTION宏
if GetDepend(['RT_USING_MEMORY_PROTECTION']):# 如果定义了,则添加内存保护相关的源文件src += ['mpu.c', 'memory_protect.c']
else:# 如果未定义,则添加普通内存管理文件src += ['memory.c']

这段代码根据是否启用内存保护功能,选择不同的源文件进行编译,实现了条件性构建。

实现代码:

# 定义不同编译器对应的启动文件
startup_files = {'gcc': 'startup_gcc.s','armcc': 'startup_armcc.s','iccarm': 'startup_iccarm.s'
}# 假设这些变量是从配置中获取的
platform = rtconfig.PLATFORM  # 当前使用的编译器
arch = rtconfig.ARCH          # 当前架构# 初始化源文件列表
src = []# 选择默认启动文件
if platform in startup_files.keys():selected_startup = startup_files[platform]# 如果是ARM架构且启用了SMP,则使用带smp的启动文件if arch == "arm" and GetDepend(['RT_USING_SMP']):# 替换文件名,添加_smp后缀(如startup_gcc.s -> startup_gcc_smp.s)selected_startup = selected_startup.replace('.s', '_smp.s')# 将选择的启动文件添加到源文件列表src.append(selected_startup)
else:# 如果编译器不被支持,打印警告信息print(f"警告:不支持的编译器 {platform},未添加启动文件")# 可以继续添加其他源文件
src += Glob('*.c')

解析

  • startup_files字典使用键值对存储不同编译器对应的启动文件,便于根据编译器类型快速查找
  • platform in startup_files.keys()用于检查当前编译器是否在支持的列表中,避免使用未定义的启动文件
  • GetDepend(['RT_USING_SMP'])检查是否启用了 SMP(对称多处理)功能,这是嵌入式系统中常见的配置选项
  • selected_startup.replace('.s', '_smp.s')通过字符串替换生成带 smp 的启动文件名,避免重复编写条件判断
  • src.append(selected_startup)将选择好的启动文件添加到源文件列表,参与后续的编译过程
  • 最后的src += Glob('*.c')将其他 C 源文件添加到列表中,完成源文件的收集

第五章:项目实战与综合应用

核心概念

综合前面所学的知识,我们可以理解和编写更复杂的 SConscript 脚本,主要包括:

  • 项目结构组织
  • 多平台 / 架构支持
  • 条件编译和配置管理
  • 构建组定义和返回

示例解析

让我们解析你提供的示例脚本,理解其工作原理:

from building import *
import os# 获取平台、架构和CPU信息
platform = rtconfig.PLATFORM
arch     = rtconfig.ARCH
cpu      = rtconfig.CPU# 初始化变量
cwd     = GetCurrentDir()
src     = []
CPPPATH = [cwd]# 定义支持的架构和CPU
support_arch  = {"arm": ["cortex-m3", "cortex-m4", "cortex-m7", "cortex-a", "cortex-r5", "cortex-r52", "cortex-m33"],"aarch64":["cortex-a"],"risc-v": ["na900"],"arc": ["em"],"arch_tricore": ["arch_tc3", "arch_tc4"],"RH850": ["rh850g3kh","rh850g4mh"],
}# 定义不同编译器对应的汇编文件
platform_file = {'armcc': 'rvds.S', 'gcc': 'gcc.S', 'iccarm': 'iar.S', 'mw': 'mw_gcc.S', 'armclang': 'rvds.S', 'ghs':'osa_ghs.S'
}# 根据CPU类型和配置修改汇编文件
if cpu == "cortex-m4":if GetDepend(['RT_USING_SMP']):platform_file[platform] = 'gcc_smp.S'else:platform_file[platform] = 'gcc.S'if cpu == "cortex-m3":if GetDepend(['RT_USING_SMP']):platform_file[platform] = 'iar_smp.S'else:platform_file[platform] = 'iar.S'if cpu == "cortex-m33":if GetDepend(['RT_USING_SMP']):platform_file[platform] = 'gcc_smp.S'else:platform_file[platform] = 'gcc.S'# 处理risc-v架构的CPU
if arch == 'risc-v':rv64 = ['virt64', 'c906']if cpu in rv64:cpu = 'rv64'# 根据平台和架构添加源文件
if platform in platform_file.keys():  # 检查是否支持当前平台if arch in support_arch.keys() and cpu in support_arch[arch]:# 汇编文件路径asm_path = 'arch/' + arch + '/' + cpu + '/*' + platform_file[platform]# 公共架构代码路径arch_common = 'arch/' + arch + '/' + 'common/*.c'# 添加所有源文件src += Glob('*.c') + Glob(asm_path) + Glob(arch_common)src += Glob('arch/' + arch + '/' + cpu + '/*.c')# 设置头文件路径CPPPATH = [cwd, cwd + '/arch/' + arch + '/' + cpu, cwd + '/include']# 处理特殊架构
if arch == "arch_tricore":src = ['arch/arch_tricore/osa_tricore.c']src += Glob('*.c')CPPPATH = [cwd, cwd + '/include']# 移除不需要的文件
if not GetDepend('RT_USING_MEMORY_PROTECTION'):SrcRemove(src, ['osa_mpu.c'])# 设置编译选项
LOCAL_CFLAGS = ''
if rtconfig.PLATFORM in ['gcc']:  # 仅对GCC设置LOCAL_CFLAGS = ' -Wall -Werror'# 定义构建组并返回
group = DefineGroup('osa', src, depend = ['RT_USING_OSA'], CPPPATH = CPPPATH, LOCAL_CFLAGS = LOCAL_CFLAGS)Return('group')

这个脚本的主要功能:

  1. 根据不同的 CPU 架构、类型和编译器选择合适的源文件
  2. 处理特殊的架构配置
  3. 根据是否启用内存保护功能决定是否包含相关文件
  4. 为 GCC 编译器设置特定的编译选项
  5. 定义一个名为 'osa' 的构建组并返回

练习题

1.在提供的示例脚本中,有这样一段代码:

if not GetDepend('RT_USING_MEMORY_PROTECTION'):SrcRemove(src, ['osa_mpu.c'])

请详细解释这段代码的作用、使用场景和工作原理。

2.如何修改示例脚本,使其支持一种新的 CPU 架构 "risc-v" 的 "c910" 型号?需要修改哪些部分?为什么?

3.解释DefineGroup()函数的各个参数的含义,并说明为什么在示例脚本的最后要使用Return('group')

答案详解

1. 代码解析:

if not GetDepend('RT_USING_MEMORY_PROTECTION'):SrcRemove(src, ['osa_mpu.c'])

作用:当未启用内存保护功能时,从源文件列表中移除内存保护相关的文件osa_mpu.c

使用场景:这是条件编译的典型应用,用于根据不同的功能配置包含或排除特定文件。在嵌入式系统中,内存保护(MPU)通常是可选功能,不是所有硬件平台都支持,也不是所有项目都需要。

工作原理

GetDepend('RT_USING_MEMORY_PROTECTION')检查是否定义了RT_USING_MEMORY_PROTECTION宏(通常在配置文件中定义)

not表示取反,如果未定义该宏(即不启用内存保护),则执行下面的语句

SrcRemove(src, ['osa_mpu.c'])是 SCons 提供的函数,用于从src列表中移除osa_mpu.c文件

这样,在后续的编译过程中,osa_mpu.c就不会被编译,避免了不必要的代码和可能的编译错误

好处:通过这种方式,可以用一个代码库支持不同的功能配置,无需为不同配置维护多个代码分支,提高了代码的复用性和可维护性。

2. 支持新 CPU 架构 "risc-v" 的 "c910" 型号的修改:

需要修改以下几个部分:

# 1. 在support_arch字典中添加c910支持
support_arch  = {# ... 其他架构保持不变"risc-v": ["na900", "c910"],  # 添加c910到risc-v支持列表# ... 其他架构保持不变
}# 2. 在risc-v架构处理部分添加c910的支持
if arch == 'risc-v':rv64 = ['virt64', 'c906', 'c910']  # 添加c910到rv64列表if cpu in rv64:cpu = 'rv64'  # 将c910归类为rv64架构# 3. 为c910添加特定的汇编文件配置(如果需要)
if arch == 'risc-v' and cpu == 'c910':# 根据是否启用SMP选择不同的汇编文件if GetDepend(['RT_USING_SMP']):platform_file[platform] = 'gcc_riscv_c910_smp.S'else:platform_file[platform] = 'gcc_riscv_c910.S'

修改原因

  1. 第 1 处修改:support_arch字典定义了脚本支持的架构和 CPU 型号,添加 c910 才能让脚本识别并处理这个 CPU 型号
  2. 第 2 处修改:risc-v 架构有 32 位和 64 位之分,c910 是 64 位 CPU,需要归类到 rv64 中以使用正确的编译选项
  3. 第 3 处修改:不同的 CPU 可能需要不同的汇编启动文件或硬件相关代码,这部分修改确保为 c910 选择正确的汇编文件

3. DefineGroup()函数及Return('group')解析:

DefineGroup()函数用于定义一个构建组,其参数含义如下:

group = DefineGroup('osa',               # 第一个参数:构建组的名称,用于标识这个模块src,                 # 第二个参数:源文件列表,包含该模块所有需要编译的文件depend = ['RT_USING_OSA'],  # depend参数:构建依赖条件,只有定义了RT_USING_OSA才会构建这个组CPPPATH = CPPPATH,   # CPPPATH参数:头文件搜索路径,供编译器查找头文件LOCAL_CFLAGS = LOCAL_CFLAGS  # LOCAL_CFLAGS参数:该模块专用的编译选项
)

'osa':构建组的名称,通常与模块名一致,便于在构建系统中识别和引用

src:该模块所有源文件的列表,包含 C 文件、汇编文件等

depend = ['RT_USING_OSA']:指定构建该模块的前提条件,只有当RT_USING_OSA宏被定义时,才会编译这个模块

CPPPATH:指定该模块所需的头文件路径,确保编译器能找到所有需要的头文件

LOCAL_CFLAGS:该模块专用的编译选项,不会影响其他模块

Return('group')的作用:

  • SConscript 作为子脚本,需要将定义好的构建组返回给调用它的父脚本(通常是 SConstruct 或其他 SConscript)
  • Return('group')表示将group变量的值传递给父脚本
  • 父脚本可以通过env.SConscript('path/to/SConscript')获取这个返回值,并将其整合到整个项目的构建流程中
  • 这种机制实现了模块化的构建配置,每个子模块负责定义自己的构建信息,再由父脚本统一组织,使大型项目的构建配置更加清晰和可维护

http://www.dtcms.com/a/338986.html

相关文章:

  • ChatBI驱动的智能商业决策:奥威BI的深度实践
  • 图像增强——灰度变换增强(线性,对数,指数)、空间滤波增强、频域增强、主成分/彩色合成增强(原理解释和代码示例)
  • 源码编译安装LAMP架构并部署WordPress(CentOS 7)
  • Unity音频管理:打造沉浸式游戏音效
  • BCT8937A Class T Audio Amplifier
  • 小程序开发APP
  • 车载以太网gPTP时间同步:从协议到工程实践
  • 如何用20%计算成本训练LLaMA4级大模型?MoE稀疏化架构实战指南
  • 第14章 现代React特性
  • 使用 MySQL 为 SpreadJS 协同服务器提供存储支持
  • 前后端联合实现文件下载,实现 SQL Server image 类型文件下载
  • PHP如何使用JpGraph生成折线图?
  • 如何在 uBlock Origin 中忽略指定网站的某一个拦截规则
  • 【数据可视化-94】2025 亚洲杯总决赛数据可视化分析:澳大利亚队 vs 中国队
  • HarmonyOS中AppStorageV2信息储存使用案例
  • 【git】 撤销revert一次commit中的某几个文件
  • HarmonyOS 实战:学会在鸿蒙中使用第三方 JavaScript 库(附完整 Demo)
  • CalMAN简介
  • ECharts漏斗图-漏斗图1,附视频讲解与代码下载
  • 程序员之电工基础-万用表、开关电源
  • Visual Studio中的常用调试功能(下)
  • peewee中db.create_tables(tables, safe=True),safe=True作用
  • 【Rust嵌入式键值对数据库redb】第一课 第一次写入
  • Matplotlib数据可视化实战:Matplotlib图表注释与美化入门
  • Kubernetes 的 YAML 配置文件-kind
  • 8.19作业
  • STM32 定时器(中断源)
  • Vibe 编码技巧与建议(Vibe Coding Tips and Tricks)
  • K近邻算法(knn)
  • SpringAIAlibaba之基础功能和基础类源码解析(2)