SConscript是RT-Thread构建系统中的模块构建脚本,每个组件或目录都可以有自己的SConscript文件。本指南将详细介绍如何编写高质量的SConscript文件。
项目根目录/
├── SConstruct # 主构建脚本
├── applications/
│ └── SConscript # 应用层构建脚本
├── drivers/
│ └── SConscript # 驱动层构建脚本
└── components/
├── SConscript # 组件主脚本
└── finsh/
└── SConscript # 子组件脚本
# 导入构建模块
from building import *
# 获取当前目录
cwd = GetCurrentDir()
# 定义源文件
src = ['main.c', 'app.c']
# 定义头文件路径
CPPPATH = [cwd]
# 定义组
group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)
# 返回组对象
Return('group')
from building import *
这行代码导入了RT-Thread构建系统的所有函数,包括:
GetCurrentDir()
- 获取当前目录DefineGroup()
- 定义组件组GetDepend()
- 检查依赖Glob()
- 文件通配符匹配SrcRemove()
- 移除源文件DoBuilding()
- 执行构建src = ['file1.c', 'file2.c', 'file3.c']
src = Glob('*.c') # 当前目录所有.c文件
src = Glob('src/*.c') # src子目录所有.c文件
src = Glob('**/*.c') # 递归所有子目录的.c文件
src = ['main.c'] + Glob('drivers/*.c')
src = ['common.c']
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if GetDepend('RT_USING_I2C'):
src += ['i2c.c']
import rtconfig
if rtconfig.PLATFORM == 'gcc':
src += ['gcc_specific.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['keil_specific.c']
src = Glob('*.c')
SrcRemove(src, ['test.c', 'debug.c']) # 移除不需要的文件
最基础的SConscript模式:
from building import *
src = Glob('*.c')
CPPPATH = [GetCurrentDir()]
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'],
CPPPATH = CPPPATH)
Return('group')
根据配置选项包含不同的源文件:
from building import *
src = ['core.c']
CPPPATH = [GetCurrentDir()]
# 功能模块条件编译
if GetDepend('MYCOMPONENT_USING_FEATURE_A'):
src += ['feature_a.c']
if GetDepend('MYCOMPONENT_USING_FEATURE_B'):
src += ['feature_b.c']
# 平台相关代码
if rtconfig.PLATFORM == 'gcc':
src += ['port_gcc.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['port_keil.c']
elif rtconfig.PLATFORM == 'iccarm':
src += ['port_iar.c']
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'],
CPPPATH = CPPPATH)
Return('group')
处理复杂的目录结构:
from building import *
cwd = GetCurrentDir()
# 源文件来自多个目录
src = Glob('src/*.c')
src += Glob('port/*.c')
src += Glob('hal/*.c')
# 多个头文件路径
CPPPATH = [
cwd,
cwd + '/include',
cwd + '/internal',
cwd + '/port'
]
# 根据配置添加特定目录
if GetDepend('RT_USING_LWIP'):
src += Glob('lwip/*.c')
CPPPATH += [cwd + '/lwip']
group = DefineGroup('Network', src, depend = ['RT_USING_NETWORK'],
CPPPATH = CPPPATH)
Return('group')
自动处理所有子目录的SConscript:
import os
from building import *
objs = []
cwd = GetCurrentDir()
# 获取所有子目录
list = os.listdir(cwd)
# 需要跳过的目录
skip_dirs = ['test', 'doc', 'examples', '.git']
for d in list:
path = os.path.join(cwd, d)
# 检查是否是目录且包含SConscript
if os.path.isdir(path) and d not in skip_dirs:
if os.path.isfile(os.path.join(path, 'SConscript')):
objs = objs + SConscript(os.path.join(d, 'SConscript'))
Return('objs')
链接预编译的库文件:
from building import *
import os
cwd = GetCurrentDir()
# 只包含必要的接口文件
src = ['lib_interface.c']
CPPPATH = [cwd + '/include']
# 库文件配置
LIBS = []
LIBPATH = []
# 根据架构选择库文件
import rtconfig
if rtconfig.ARCH == 'arm':
if rtconfig.CPU == 'cortex-m4':
LIBS += ['mylib_cm4']
LIBPATH += [cwd + '/lib/cortex-m4']
elif rtconfig.CPU == 'cortex-m3':
LIBS += ['mylib_cm3']
LIBPATH += [cwd + '/lib/cortex-m3']
group = DefineGroup('MyLib', src, depend = ['RT_USING_MYLIB'],
CPPPATH = CPPPATH, LIBS = LIBS, LIBPATH = LIBPATH)
Return('group')
为特定模块设置独立的编译选项:
from building import *
src = Glob('*.c')
CPPPATH = [GetCurrentDir()]
# 全局编译选项(影响依赖此组件的其他组件)
CPPDEFINES = ['GLOBAL_DEFINE']
# 本地编译选项(仅影响当前组件)
LOCAL_CFLAGS = ''
LOCAL_CPPDEFINES = ['LOCAL_DEFINE']
LOCAL_CPPPATH = ['./private']
# 根据编译器设置优化选项
import rtconfig
if rtconfig.PLATFORM == 'gcc':
LOCAL_CFLAGS += ' -O3 -funroll-loops'
elif rtconfig.PLATFORM == 'armcc':
LOCAL_CFLAGS += ' -O3 --loop_optimization_level=2'
group = DefineGroup('HighPerf', src, depend = ['RT_USING_HIGHPERF'],
CPPPATH = CPPPATH,
CPPDEFINES = CPPDEFINES,
LOCAL_CFLAGS = LOCAL_CFLAGS,
LOCAL_CPPDEFINES = LOCAL_CPPDEFINES,
LOCAL_CPPPATH = LOCAL_CPPPATH
)
Return('group')
在构建时生成源文件:
from building import *
import time
def generate_version_file():
"""生成版本信息文件"""
version_c = '''
/* Auto-generated file, do not edit! */
#include "version.h"
const char *build_time = "%s";
const char *version = "%s";
''' % (time.strftime('%Y-%m-%d %H:%M:%S'), '1.0.0')
with open('version_gen.c', 'w') as f:
f.write(version_c)
# 生成文件
generate_version_file()
src = ['main.c', 'version_gen.c']
CPPPATH = [GetCurrentDir()]
group = DefineGroup('App', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
处理复杂的依赖关系:
from building import *
src = []
CPPPATH = [GetCurrentDir()]
CPPDEFINES = []
# 基础功能
if GetDepend('RT_USING_DEVICE'):
src += ['device_core.c']
# 串口驱动(依赖设备框架)
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
# 串口DMA(依赖串口驱动)
if GetDepend('RT_SERIAL_USING_DMA'):
src += ['serial_dma.c']
CPPDEFINES += ['SERIAL_USING_DMA']
# SPI驱动(依赖设备框架)
if GetDepend('RT_USING_SPI'):
src += ['spi.c']
# SPI DMA(依赖SPI驱动)
if GetDepend('RT_SPI_USING_DMA'):
src += ['spi_dma.c']
# 错误检查
if GetDepend('RT_SERIAL_USING_DMA') and not GetDepend('RT_USING_SERIAL'):
print('Error: RT_SERIAL_USING_DMA requires RT_USING_SERIAL!')
exit(1)
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
根据不同平台包含不同实现:
from building import *
import rtconfig
cwd = GetCurrentDir()
src = ['common.c']
CPPPATH = [cwd, cwd + '/include']
# 架构相关目录映射
arch_map = {
'arm': {
'cortex-m3': 'arm/cortex-m3',
'cortex-m4': 'arm/cortex-m4',
'cortex-m7': 'arm/cortex-m7',
'cortex-a': 'arm/cortex-a'
},
'risc-v': {
'rv32': 'riscv/rv32',
'rv64': 'riscv/rv64'
},
'x86': {
'i386': 'x86/i386',
'x86_64': 'x86/x86_64'
}
}
# 根据架构和CPU选择实现
if hasattr(rtconfig, 'ARCH') and hasattr(rtconfig, 'CPU'):
arch = rtconfig.ARCH
cpu = rtconfig.CPU
if arch in arch_map and cpu in arch_map[arch]:
port_dir = arch_map[arch][cpu]
port_src = Glob(port_dir + '/*.c')
port_src += Glob(port_dir + '/*.S')
src += port_src
CPPPATH += [cwd + '/' + port_dir]
else:
print('Warning: No port for %s - %s' % (arch, cpu))
group = DefineGroup('MyDriver', src, depend = ['RT_USING_MYDRIVER'],
CPPPATH = CPPPATH)
Return('group')
集成第三方库的模板:
from building import *
import os
cwd = GetCurrentDir()
# 第三方库路径
lib_path = cwd + '/3rdparty/libfoo'
# 检查库是否存在
if not os.path.exists(lib_path):
print('Error: libfoo not found at', lib_path)
print('Please run: git submodule update --init')
Return('group')
# 库源文件
src = Glob(lib_path + '/src/*.c')
# 移除不需要的文件
SrcRemove(src, [
lib_path + '/src/test.c',
lib_path + '/src/example.c'
])
# 头文件路径
CPPPATH = [
lib_path + '/include',
cwd + '/port' # 移植层头文件
]
# 添加移植层
src += Glob('port/*.c')
# 配置宏定义
CPPDEFINES = ['LIBFOO_RTOS_RTTHREAD']
# 根据配置启用功能
if GetDepend('LIBFOO_ENABLE_FLOAT'):
CPPDEFINES += ['LIBFOO_USE_FLOAT']
if GetDepend('LIBFOO_ENABLE_STDIO'):
CPPDEFINES += ['LIBFOO_USE_STDIO']
group = DefineGroup('libfoo', src, depend = ['RT_USING_LIBFOO'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
根据配置导出不同的API:
from building import *
src = ['core.c']
CPPPATH = [GetCurrentDir()]
# API版本控制
if GetDepend('RT_USING_MODULE_API_V2'):
src += ['api_v2.c']
CPPDEFINES = ['MODULE_API_VERSION=2']
else:
src += ['api_v1.c']
CPPDEFINES = ['MODULE_API_VERSION=1']
# 根据配置级别导出不同功能
if GetDepend('MODULE_EXPERT_MODE'):
src += ['expert_api.c']
CPPDEFINES += ['EXPORT_EXPERT_API']
group = DefineGroup('Module', src, depend = ['RT_USING_MODULE'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
component/
├── SConscript # 主构建脚本
├── Kconfig # 配置选项
├── README.md # 组件说明
├── include/ # 公开头文件
│ └── component.h
├── src/ # 源文件
│ ├── core.c
│ └── utils.c
├── port/ # 平台相关代码
│ ├── cortex-m/
│ └── risc-v/
└── examples/ # 示例代码
└── example.c
from building import *
# 1. 明确声明依赖
REQUIRED_DEPS = ['RT_USING_DEVICE', 'RT_USING_HEAP']
# 2. 检查必要依赖
for dep in REQUIRED_DEPS:
if not GetDepend(dep):
print('Error: %s requires %s' % ('MyComponent', dep))
Return('group')
# 3. 可选依赖
src = ['core.c']
# 可选功能
OPTIONAL_FEATURES = {
'RT_MYCOMPONENT_USING_DMA': 'dma.c',
'RT_MYCOMPONENT_USING_INTERRUPT': 'interrupt.c',
'RT_MYCOMPONENT_USING_STATS': 'statistics.c'
}
for macro, file in OPTIONAL_FEATURES.items():
if GetDepend(macro):
src += [file]
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
from building import *
import os
# 检查关键文件
critical_files = ['config.h', 'version.h']
for f in critical_files:
if not os.path.exists(f):
print('Error: Missing required file:', f)
# 返回空组,不中断整体构建
group = DefineGroup('MyComponent', [], depend = [''])
Return('group')
# 检查工具链
import rtconfig
supported_toolchains = ['gcc', 'armcc', 'iar']
if rtconfig.PLATFORM not in supported_toolchains:
print('Warning: Toolchain %s not tested' % rtconfig.PLATFORM)
# 正常构建流程
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
from building import *
import os
# 1. 缓存文件列表(避免重复扫描)
_file_cache = {}
def cached_glob(pattern):
if pattern not in _file_cache:
_file_cache[pattern] = Glob(pattern)
return _file_cache[pattern]
# 2. 延迟加载(仅在需要时扫描)
src = []
if GetDepend('RT_USING_MYCOMPONENT'):
src = cached_glob('*.c')
if GetDepend('RT_MYCOMPONENT_USING_EXTRA'):
src += cached_glob('extra/*.c')
# 3. 避免深度递归(使用显式路径)
# 不好的做法
# src = Glob('**/*.c') # 递归所有子目录
# 好的做法
src = Glob('*.c')
src += Glob('core/*.c')
src += Glob('hal/*.c')
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
"""
SConscript for MyComponent
This component provides [功能描述]
Configuration:
RT_USING_MYCOMPONENT - Enable this component
RT_MYCOMPONENT_USING_DMA - Enable DMA support
RT_MYCOMPONENT_BUFFER_SIZE - Buffer size (default: 256)
Dependencies:
- RT_USING_DEVICE (required)
- RT_USING_DMA (optional, for DMA support)
"""
from building import *
# ... 构建脚本内容 ...
from building import *
cwd = GetCurrentDir()
# 驱动源文件
src = []
CPPPATH = [cwd + '/../inc']
# I2C驱动
if GetDepend('RT_USING_I2C'):
src += ['drv_i2c.c']
# SPI驱动
if GetDepend('RT_USING_SPI'):
src += ['drv_spi.c']
# QSPI支持
if GetDepend('RT_USING_QSPI'):
src += ['drv_qspi.c']
# USB驱动
if GetDepend('RT_USING_USB'):
src += ['drv_usb.c']
if GetDepend('RT_USING_USB_HOST'):
src += ['drv_usbh.c']
if GetDepend('RT_USING_USB_DEVICE'):
src += ['drv_usbd.c']
# SDIO驱动
if GetDepend('RT_USING_SDIO'):
src += ['drv_sdio.c']
group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd]
# 网络接口层
if GetDepend('RT_USING_NETDEV'):
src += Glob('netdev/*.c')
CPPPATH += [cwd + '/netdev']
# SAL套接字抽象层
if GetDepend('RT_USING_SAL'):
src += Glob('sal/src/*.c')
src += Glob('sal/socket/*.c')
CPPPATH += [cwd + '/sal/include']
# AT指令支持
if GetDepend('SAL_USING_AT'):
src += Glob('sal/impl/af_inet_at.c')
# LwIP支持
if GetDepend('SAL_USING_LWIP'):
src += Glob('sal/impl/af_inet_lwip.c')
# AT指令框架
if GetDepend('RT_USING_AT'):
src += Glob('at/src/*.c')
CPPPATH += [cwd + '/at/include']
# AT Socket
if GetDepend('AT_USING_SOCKET'):
src += Glob('at/at_socket/*.c')
group = DefineGroup('Network', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd + '/include']
# DFS框架
if GetDepend('RT_USING_DFS'):
src += Glob('src/*.c')
# ELM FatFS
if GetDepend('RT_USING_DFS_ELMFAT'):
src += Glob('filesystems/elmfat/*.c')
# FatFS版本选择
if GetDepend('RT_DFS_ELM_USE_LFN'):
src += ['filesystems/elmfat/ffunicode.c']
# ROMFS
if GetDepend('RT_USING_DFS_ROMFS'):
src += ['filesystems/romfs/dfs_romfs.c']
# DevFS
if GetDepend('RT_USING_DFS_DEVFS'):
src += ['filesystems/devfs/devfs.c']
# NFS
if GetDepend('RT_USING_DFS_NFS'):
src += Glob('filesystems/nfs/*.c')
CPPPATH += [cwd + '/filesystems/nfs']
group = DefineGroup('Filesystem', src, depend = ['RT_USING_DFS'],
CPPPATH = CPPPATH)
Return('group')
from building import *
import os
import json
cwd = GetCurrentDir()
# 尝试使用package.json
package_file = os.path.join(cwd, 'package.json')
if os.path.exists(package_file):
# 使用自动构建
objs = BuildPackage(package_file)
else:
# 手动构建
src = Glob('src/*.c')
CPPPATH = [cwd + '/include']
# 读取配置
config_file = os.path.join(cwd, 'config.json')
if os.path.exists(config_file):
with open(config_file, 'r') as f:
config = json.load(f)
# 根据配置添加源文件
for feature in config.get('features', []):
if GetDepend('RT_USING_' + feature.upper()):
src += Glob('src/%s/*.c' % feature)
objs = DefineGroup('MyPackage', src, depend = ['RT_USING_MYPACKAGE'],
CPPPATH = CPPPATH)
Return('objs')
from building import *
# 1. 打印调试信息
print('Current directory:', GetCurrentDir())
print('GetDepend RT_USING_XXX:', GetDepend('RT_USING_XXX'))
# 2. 打印源文件列表
src = Glob('*.c')
print('Source files:', src)
# 3. 条件调试输出
if GetOption('verbose'):
print('Detailed debug info...')
# 4. 检查环境变量
import os
print('RTT_ROOT:', os.getenv('RTT_ROOT'))
from building import *
src = ['core.c']
# 可选依赖处理
optional_deps = {
'RT_USING_SERIAL': ['serial.c', 'serial_ops.c'],
'RT_USING_CAN': ['can.c', 'can_ops.c'],
'RT_USING_I2C': ['i2c.c', 'i2c_ops.c']
}
for dep, files in optional_deps.items():
if GetDepend(dep):
src += files
# 检查组合依赖
if GetDepend('RT_USING_SERIAL') and GetDepend('RT_USING_DMA'):
src += ['serial_dma.c']
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'])
Return('group')
from building import *
import rtconfig
src = ['common.c']
# 工具链特定文件
toolchain_files = {
'gcc': ['gcc_startup.S', 'gcc_specific.c'],
'armcc': ['keil_startup.s', 'keil_specific.c'],
'iccarm': ['iar_startup.s', 'iar_specific.c']
}
if rtconfig.PLATFORM in toolchain_files:
src += toolchain_files[rtconfig.PLATFORM]
else:
print('Warning: Unknown toolchain', rtconfig.PLATFORM)
# 工具链特定编译选项
LOCAL_CFLAGS = ''
if rtconfig.PLATFORM == 'gcc':
LOCAL_CFLAGS = '-Wno-unused-function'
elif rtconfig.PLATFORM == 'armcc':
LOCAL_CFLAGS = '--diag_suppress=177'
group = DefineGroup('MyComponent', src, depend = [''],
LOCAL_CFLAGS = LOCAL_CFLAGS)
Return('group')
from building import *
import subprocess
def generate_code():
"""生成代码"""
# 运行代码生成器
cmd = ['python', 'codegen.py', '-o', 'generated.c']
subprocess.check_call(cmd)
# 确保生成代码
if GetDepend('RT_USING_CODEGEN'):
generate_code()
src = ['generated.c']
else:
src = ['default.c']
group = DefineGroup('Generated', src, depend = [''])
Return('group')
# 主SConscript
from building import *
objs = []
# 子模块列表
modules = [
'core',
'drivers',
'network',
'filesystem',
'gui'
]
# 根据配置包含模块
for module in modules:
# 检查模块是否启用
if GetDepend('RT_USING_' + module.upper()):
# 构建子模块
objs += SConscript(module + '/SConscript')
Return('objs')
编写高质量的SConscript需要:
通过遵循本指南的建议和最佳实践,可以编写出易维护、可扩展的构建脚本,为RT-Thread项目的构建提供坚实的基础。