[Tools] Add documents for tools script; Add NG for tools

This commit is contained in:
bernard 2025-08-01 21:33:50 +08:00 committed by R b b666
parent 5347500f33
commit a65efe648c
16 changed files with 5686 additions and 0 deletions

162
tools/docs/README.md Normal file
View File

@ -0,0 +1,162 @@
# RT-Thread 构建系统文档
欢迎使用RT-Thread构建系统文档。本文档集详细介绍了RT-Thread基于SCons的构建系统的使用方法和技术原理。
## 文档目录
### 📚 用户指南
1. **[构建系统使用指南](构建系统使用指南.md)**
- 快速开始
- 命令行选项详解
- 工具链配置
- 项目生成
- 软件包管理
- 高级功能
- 常见问题解答
2. **[SConscript编写指南](SConscript编写指南.md)**
- 基础语法
- 常用模式
- 高级技巧
- 最佳实践
- 示例集合
### 🔧 技术文档
3. **[构建系统技术原理](构建系统技术原理.md)**
- 系统架构设计
- 核心模块分析
- 构建流程详解
- 依赖管理机制
- 工具链适配层
- 项目生成器架构
- 扩展机制
## 快速导航
### 常用命令
```bash
# 基础编译
scons # 默认编译
scons -j8 # 8线程并行编译
scons -c # 清理编译产物
# 配置管理
menuconfig # 图形化配置
scons --pyconfig # Python脚本配置
# 项目生成
scons --target=mdk5 # 生成Keil MDK5项目
scons --target=iar # 生成IAR项目
scons --target=vsc # 生成VS Code项目
scons --target=cmake # 生成CMake项目
# 软件包管理
pkgs --update # 更新软件包
pkgs --list # 列出已安装包
```
### 核心概念
- **SConstruct**: BSP根目录的主构建脚本
- **SConscript**: 各个组件/目录的构建脚本
- **rtconfig.py**: 工具链和平台配置
- **rtconfig.h**: RT-Thread功能配置
- **DefineGroup**: 定义组件的核心函数
- **GetDepend**: 检查依赖的核心函数
## 构建系统架构图
```
┌─────────────────────────────────────┐
│ 用户命令 (scons) │
└──────────────┬──────────────────────┘
┌──────────────▼──────────────────────┐
│ SConstruct (主脚本) │
│ ┌─────────────────────┐ │
│ │ PrepareBuilding() │ │
│ │ 环境初始化 │ │
│ └──────────┬──────────┘ │
└────────────────┼───────────────────┘
┌────────────────▼────────────────────┐
│ building.py │
│ ┌──────────┬──────────┐ │
│ │ 组件收集 │ 依赖处理 │ │
│ └──────────┴──────────┘ │
└─────────────────────────────────────┘
┌────────┴────────┐
│ │
┌───────▼──────┐ ┌────────▼────────┐
│ SConscript │ │ rtconfig.h │
│ 组件脚本 │ │ 功能配置 │
└──────────────┘ └─────────────────┘
```
## 主要特性
✅ **多工具链支持**
- GCC (ARM/RISC-V/x86)
- Keil MDK (ARMCC/ARMClang)
- IAR
- Visual Studio
✅ **灵活的配置系统**
- Kconfig图形配置
- 条件编译支持
- 本地编译选项
✅ **丰富的项目生成器**
- IDE项目文件生成
- CMake支持
- Makefile生成
- VS Code配置
✅ **模块化设计**
- 组件独立构建
- 清晰的依赖管理
- 可扩展架构
## 开发工作流
```mermaid
graph LR
A[配置系统] --> B[编写代码]
B --> C[构建项目]
C --> D[调试运行]
D --> E{是否完成?}
E -->|否| B
E -->|是| F[发布]
A1[menuconfig] -.-> A
C1[scons] -.-> C
C2[IDE项目] -.-> C
```
## 相关链接
- [RT-Thread官网](https://www.rt-thread.org)
- [RT-Thread GitHub](https://github.com/RT-Thread/rt-thread)
- [SCons官方文档](https://scons.org/documentation.html)
## 贡献指南
如果您发现文档中的错误或有改进建议,欢迎:
1. 在GitHub上提交Issue
2. 提交Pull Request
3. 在RT-Thread社区论坛反馈
## 版本信息
- 文档版本1.0.0
- 更新日期2024-01
- 适用版本RT-Thread 4.1.0+
---
**注意**本文档基于RT-Thread最新版本编写部分功能可能需要特定版本支持。使用前请确认您的RT-Thread版本。

View File

@ -0,0 +1,948 @@
# RT-Thread SConscript 编写指南
## 目录
1. [概述](#概述)
2. [基础语法](#基础语法)
3. [常用模式](#常用模式)
4. [高级技巧](#高级技巧)
5. [最佳实践](#最佳实践)
6. [示例集合](#示例集合)
7. [常见问题](#常见问题)
## 概述
SConscript是RT-Thread构建系统中的模块构建脚本每个组件或目录都可以有自己的SConscript文件。本指南将详细介绍如何编写高质量的SConscript文件。
### SConscript在构建系统中的位置
```
项目根目录/
├── SConstruct # 主构建脚本
├── applications/
│ └── SConscript # 应用层构建脚本
├── drivers/
│ └── SConscript # 驱动层构建脚本
└── components/
├── SConscript # 组件主脚本
└── finsh/
└── SConscript # 子组件脚本
```
## 基础语法
### 1. 基本结构
```python
# 导入构建模块
from building import *
# 获取当前目录
cwd = GetCurrentDir()
# 定义源文件
src = ['main.c', 'app.c']
# 定义头文件路径
CPPPATH = [cwd]
# 定义组
group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)
# 返回组对象
Return('group')
```
### 2. 导入building模块
```python
from building import *
```
这行代码导入了RT-Thread构建系统的所有函数包括
- `GetCurrentDir()` - 获取当前目录
- `DefineGroup()` - 定义组件组
- `GetDepend()` - 检查依赖
- `Glob()` - 文件通配符匹配
- `SrcRemove()` - 移除源文件
- `DoBuilding()` - 执行构建
### 3. 源文件定义
#### 手动指定文件列表
```python
src = ['file1.c', 'file2.c', 'file3.c']
```
#### 使用通配符
```python
src = Glob('*.c') # 当前目录所有.c文件
src = Glob('src/*.c') # src子目录所有.c文件
src = Glob('**/*.c') # 递归所有子目录的.c文件
```
#### 混合使用
```python
src = ['main.c'] + Glob('drivers/*.c')
```
### 4. 条件编译
#### 基于宏定义
```python
src = ['common.c']
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if GetDepend('RT_USING_I2C'):
src += ['i2c.c']
```
#### 基于平台
```python
import rtconfig
if rtconfig.PLATFORM == 'gcc':
src += ['gcc_specific.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['keil_specific.c']
```
### 5. 移除文件
```python
src = Glob('*.c')
SrcRemove(src, ['test.c', 'debug.c']) # 移除不需要的文件
```
## 常用模式
### 1. 简单组件模式
最基础的SConscript模式
```python
from building import *
src = Glob('*.c')
CPPPATH = [GetCurrentDir()]
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'],
CPPPATH = CPPPATH)
Return('group')
```
### 2. 条件编译模式
根据配置选项包含不同的源文件:
```python
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')
```
### 3. 多目录组织模式
处理复杂的目录结构:
```python
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')
```
### 4. 递归子目录模式
自动处理所有子目录的SConscript
```python
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')
```
### 5. 库文件链接模式
链接预编译的库文件:
```python
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')
```
## 高级技巧
### 1. 本地编译选项
为特定模块设置独立的编译选项:
```python
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')
```
### 2. 动态源文件生成
在构建时生成源文件:
```python
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')
```
### 3. 复杂依赖处理
处理复杂的依赖关系:
```python
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')
```
### 4. 平台特定实现
根据不同平台包含不同实现:
```python
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')
```
### 5. 第三方库集成
集成第三方库的模板:
```python
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')
```
### 6. 条件导出符号
根据配置导出不同的API
```python
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')
```
## 最佳实践
### 1. 文件组织原则
```
component/
├── SConscript # 主构建脚本
├── Kconfig # 配置选项
├── README.md # 组件说明
├── include/ # 公开头文件
│ └── component.h
├── src/ # 源文件
│ ├── core.c
│ └── utils.c
├── port/ # 平台相关代码
│ ├── cortex-m/
│ └── risc-v/
└── examples/ # 示例代码
└── example.c
```
### 2. 依赖管理最佳实践
```python
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')
```
### 3. 错误处理
```python
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')
```
### 4. 性能优化
```python
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')
```
### 5. 文档化
```python
"""
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 *
# ... 构建脚本内容 ...
```
## 示例集合
### 示例1设备驱动SConscript
```python
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')
```
### 示例2网络组件SConscript
```python
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')
```
### 示例3文件系统SConscript
```python
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')
```
### 示例4使用package.json的SConscript
```python
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')
```
## 常见问题
### Q1: 如何调试SConscript
```python
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'))
```
### Q2: 如何处理可选的依赖?
```python
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')
```
### Q3: 如何支持多个工具链?
```python
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')
```
### Q4: 如何处理生成的代码?
```python
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')
```
### Q5: 如何组织大型项目?
```python
# 主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需要
1. **清晰的结构**:合理组织源文件和目录
2. **正确的依赖**:准确声明和检查依赖关系
3. **平台兼容**:处理不同工具链和平台的差异
4. **性能考虑**:避免不必要的文件扫描
5. **错误处理**:优雅处理各种异常情况
6. **文档完善**:添加必要的注释和说明
通过遵循本指南的建议和最佳实践可以编写出易维护、可扩展的构建脚本为RT-Thread项目的构建提供坚实的基础。

View File

@ -0,0 +1,643 @@
# RT-Thread 构建系统使用指南
## 目录
1. [概述](#概述)
2. [快速开始](#快速开始)
3. [命令行选项详解](#命令行选项详解)
4. [工具链配置](#工具链配置)
5. [项目生成](#项目生成)
6. [软件包管理](#软件包管理)
7. [高级功能](#高级功能)
8. [常见问题](#常见问题)
## 概述
RT-Thread使用基于SCons的构建系统提供了统一的跨平台构建体验。构建系统支持
- 多种编译器和IDEGCC、Keil、IAR、VS Code等
- 模块化的组件管理
- 灵活的配置系统
- 自动化的依赖处理
- 软件包管理功能
### 系统架构图
```
┌─────────────────────────────────────────────────────────────┐
│ 用户命令 │
│ (scons, scons --target=xxx) │
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────▼───────────────────────────────────────┐
│ SConstruct │
│ (BSP根目录构建脚本) │
└─────────────────────┬───────────────────────────────────────┘
┌─────────────────────▼───────────────────────────────────────┐
│ building.py │
│ (核心构建引擎和函数库) │
├─────────────────────┬───────────────────────────────────────┤
│ PrepareBuilding() │ DefineGroup() │ DoBuilding() │
│ 环境初始化 │ 组件定义 │ 执行构建 │
└─────────────────────┴─────────┬─────────────────────────────┘
┌───────────────────────┼───────────────────────┐
│ │ │
┌───────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
│ SConscript │ │ rtconfig.py │ │ rtconfig.h │
│ (组件脚本) │ │ (工具链配置) │ │ (功能配置) │
└────────────────┘ └─────────────────┘ └─────────────────┘
```
## 快速开始
### 基本编译流程
1. **进入BSP目录**
```bash
cd bsp/stm32/stm32f103-blue-pill
```
2. **配置系统**(可选)
```bash
menuconfig # 图形化配置
```
3. **编译项目**
```bash
scons # 默认编译
scons -j8 # 多线程编译
```
4. **生成IDE项目**
```bash
scons --target=mdk5 # 生成Keil MDK5项目
scons --target=iar # 生成IAR项目
scons --target=vsc # 生成VS Code项目
```
### 清理和重建
```bash
scons -c # 清理编译产物
scons -c --target=mdk5 # 清理MDK5项目文件
scons --dist # 生成分发包
```
## 命令行选项详解
### 基础编译选项
| 选项 | 说明 | 示例 |
|------|------|------|
| `-j N` | 多线程编译N为线程数 | `scons -j8` |
| `-c` | 清理编译产物 | `scons -c` |
| `-s` | 静默模式,不显示命令 | `scons -s` |
| `--verbose` | 详细输出模式 | `scons --verbose` |
### 项目生成选项
| 选项 | 说明 | 生成的文件 |
|------|------|------------|
| `--target=mdk4` | Keil MDK4项目 | project.uvproj |
| `--target=mdk5` | Keil MDK5项目 | project.uvprojx |
| `--target=iar` | IAR工作区 | project.eww |
| `--target=vs2012` | Visual Studio项目 | project.vcxproj |
| `--target=vsc` | VS Code配置 | .vscode/目录 |
| `--target=eclipse` | Eclipse CDT项目 | .project, .cproject |
| `--target=cmake` | CMake项目 | CMakeLists.txt |
| `--target=makefile` | 通用Makefile | Makefile |
### 配置管理选项
| 选项 | 说明 | 使用场景 |
|------|------|----------|
| `--menuconfig` | 启动图形配置界面 | 修改功能配置 |
| `--pyconfig` | 通过Python脚本配置 | 自动化配置 |
| `--pyconfig-silent` | 静默Python配置 | CI/CD环境 |
| `--genconfig` | 从rtconfig.h生成.config | 配置迁移 |
| `--useconfig=xxx` | 使用指定配置文件 | 切换配置 |
### 工具链选项
| 选项 | 说明 | 示例 |
|------|------|------|
| `--exec-path=PATH` | 指定工具链路径 | `--exec-path=/opt/gcc-arm/bin` |
| `--exec-prefix=PREFIX` | 指定工具链前缀 | `--exec-prefix=arm-none-eabi-` |
| `--strict` | 严格编译模式 | 开启-Werror |
### 分发和调试选项
| 选项 | 说明 | 用途 |
|------|------|------|
| `--dist` | 生成分发包 | 项目发布 |
| `--dist-strip` | 生成精简分发包 | 最小化项目 |
| `--dist-ide` | 生成RT-Thread Studio项目 | Studio开发 |
| `--cscope` | 生成cscope数据库 | 代码导航 |
| `--clang-analyzer` | 运行Clang静态分析 | 代码质量检查 |
## 工具链配置
### rtconfig.py配置文件
每个BSP都有一个`rtconfig.py`文件,定义了工具链配置:
```python
import os
# 工具链定义
CROSS_TOOL = 'gcc' # 工具链类型: gcc/keil/iar
PLATFORM = 'armcc' # 平台标识
# 编译器路径
if os.getenv('RTT_EXEC_PATH'):
EXEC_PATH = os.getenv('RTT_EXEC_PATH')
else:
EXEC_PATH = r'C:/Keil_v5/ARM/ARMCC/bin'
# 编译器前缀GCC工具链
PREFIX = 'arm-none-eabi-'
# 编译器定义
CC = PREFIX + 'gcc'
CXX = PREFIX + 'g++'
AS = PREFIX + 'gcc'
AR = PREFIX + 'ar'
LINK = PREFIX + 'gcc'
SIZE = PREFIX + 'size'
OBJDUMP = PREFIX + 'objdump'
OBJCPY = PREFIX + 'objcopy'
# 设备相关参数
DEVICE = ' -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections'
# 编译标志
CFLAGS = DEVICE + ' -Dgcc'
AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -Wa,-mimplicit-it=thumb '
LFLAGS = DEVICE + ' -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,Reset_Handler -T link.lds'
# 路径定义
CPATH = ''
LPATH = ''
# 链接脚本
LINK_SCRIPT = 'link.lds'
# 后处理命令
POST_ACTION = OBJCPY + ' -O binary $TARGET rtthread.bin\n' + SIZE + ' $TARGET \n'
```
### 支持的工具链
1. **GCC工具链**
```python
CROSS_TOOL = 'gcc'
PREFIX = 'arm-none-eabi-'
```
2. **Keil MDK**
```python
CROSS_TOOL = 'keil'
PLATFORM = 'armcc' # ARM Compiler 5
# 或
PLATFORM = 'armclang' # ARM Compiler 6
```
3. **IAR**
```python
CROSS_TOOL = 'iar'
PLATFORM = 'iccarm'
```
4. **RISC-V GCC**
```python
CROSS_TOOL = 'gcc'
PREFIX = 'riscv64-unknown-elf-'
```
### 环境变量支持
构建系统支持通过环境变量覆盖配置:
```bash
# 设置工具链路径
export RTT_EXEC_PATH=/opt/gcc-arm-none-eabi-10-2020-q4-major/bin
# 设置工具链前缀
export RTT_CC_PREFIX=arm-none-eabi-
# 设置工具链类型
export RTT_CC=gcc
```
## 项目生成
### VS Code项目配置
使用`scons --target=vsc`生成VS Code项目会创建以下配置文件
**.vscode/c_cpp_properties.json** - IntelliSense配置
```json
{
"configurations": [
{
"name": "RT-Thread",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../../components/finsh",
"${workspaceFolder}/../../include"
],
"defines": [
"RT_USING_FINSH",
"RT_USING_SERIAL",
"__GNUC__"
],
"compilerPath": "/opt/gcc-arm/bin/arm-none-eabi-gcc",
"cStandard": "c99",
"cppStandard": "c++11"
}
]
}
```
**.vscode/tasks.json** - 构建任务配置
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "scons",
"problemMatcher": "$gcc",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
```
### CMake项目生成
使用`scons --target=cmake`生成CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.10)
# 工具链设置
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m3)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
project(rtthread C ASM)
# 编译选项
add_compile_options(
-mcpu=cortex-m3
-mthumb
-ffunction-sections
-fdata-sections
-Wall
-O0
-g
)
# 头文件路径
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../include
)
# 源文件
set(SOURCES
applications/main.c
../../src/clock.c
../../src/components.c
)
# 生成可执行文件
add_executable(${PROJECT_NAME}.elf ${SOURCES})
# 链接选项
target_link_options(${PROJECT_NAME}.elf PRIVATE
-T${CMAKE_CURRENT_SOURCE_DIR}/link.lds
-Wl,-Map=${PROJECT_NAME}.map,--cref
-Wl,--gc-sections
)
```
## 软件包管理
### 使用package.json定义组件
RT-Thread支持使用`package.json`文件定义软件包:
```json
{
"name": "my-driver",
"version": "1.0.0",
"type": "rt-thread-component",
"license": "Apache-2.0",
"dependencies": {
"RT_USING_DEVICE": "latest"
},
"sources": {
"common": {
"source_files": ["src/*.c"],
"header_files": ["inc/*.h"],
"header_path": ["inc"]
},
"cortex-m": {
"condition": "defined(ARCH_ARM_CORTEX_M)",
"source_files": ["port/cortex-m/*.c"]
}
}
}
```
### 在SConscript中使用BuildPackage
```python
from building import *
import os
# 使用package.json构建
objs = BuildPackage('package.json')
# 或者手动指定包路径
pkg_path = os.path.join(GetCurrentDir(), 'package.json')
objs = BuildPackage(pkg_path)
Return('objs')
```
## 高级功能
### 1. 条件编译和依赖管理
**基于宏定义的条件编译**
```python
src = ['common.c']
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if GetDepend(['RT_USING_SPI', 'RT_USING_SFUD']):
src += ['spi_flash.c']
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'])
```
**复杂依赖表达式**
```python
# 依赖可以是列表AND关系
depend = ['RT_USING_LWIP', 'RT_USING_NETDEV']
# 或者使用GetDepend进行复杂判断
if GetDepend('RT_USING_LWIP') and not GetDepend('RT_USING_SAL'):
print('配置错误LWIP需要SAL支持')
```
### 2. 本地编译选项
为特定模块设置独立的编译选项:
```python
# 全局编译选项
CPPPATH = [GetCurrentDir()]
CPPDEFINES = ['MODULE_VERSION=1']
# 本地编译选项仅对当前group有效
LOCAL_CFLAGS = '-O3 -funroll-loops'
LOCAL_CPPPATH = ['./private']
LOCAL_CPPDEFINES = {'BUFFER_SIZE': 1024}
group = DefineGroup('Module', src, depend = [''],
CPPPATH = CPPPATH,
CPPDEFINES = CPPDEFINES,
LOCAL_CFLAGS = LOCAL_CFLAGS,
LOCAL_CPPPATH = LOCAL_CPPPATH,
LOCAL_CPPDEFINES = LOCAL_CPPDEFINES
)
```
### 3. 递归构建子目录
自动扫描并构建子目录:
```python
import os
from building import *
objs = []
cwd = GetCurrentDir()
dirs = os.listdir(cwd)
# 黑名单目录
skip_dirs = ['test', 'doc', 'example']
for d in dirs:
if d in skip_dirs:
continue
path = os.path.join(cwd, d)
if os.path.isdir(path):
sconscript = os.path.join(path, 'SConscript')
if os.path.isfile(sconscript):
objs += SConscript(sconscript)
Return('objs')
```
### 4. 自定义构建动作
添加构建前后的自定义动作:
```python
from building import *
def pre_build_action(target, source, env):
print('开始构建:', target[0])
# 执行预处理操作
def post_build_action(target, source, env):
print('构建完成:', target[0])
# 生成额外文件如hex文件
import subprocess
subprocess.call(['arm-none-eabi-objcopy', '-O', 'ihex',
str(target[0]), str(target[0]) + '.hex'])
# 注册构建动作
if GetOption('target') == None:
rtconfig.POST_ACTION = post_build_action
```
### 5. 分发包定制
创建自定义分发包:
```python
# 在BSP的SConstruct中添加
def dist_handle(BSP_ROOT, dist_dir):
import shutil
# 复制必要文件
src_files = ['applications', 'board', 'rtconfig.py', 'SConstruct']
for src in src_files:
src_path = os.path.join(BSP_ROOT, src)
dst_path = os.path.join(dist_dir, src)
if os.path.isdir(src_path):
shutil.copytree(src_path, dst_path)
else:
shutil.copy2(src_path, dst_path)
# 创建README
with open(os.path.join(dist_dir, 'README.md'), 'w') as f:
f.write('# RT-Thread BSP 分发包\n')
f.write('构建时间: ' + time.strftime('%Y-%m-%d %H:%M:%S\n'))
# 注册分发处理函数
AddOption('--dist-handle',
dest = 'dist-handle',
action = 'store_true',
default = False,
help = 'Enable dist handle')
if GetOption('dist-handle'):
dist_handle(BSP_ROOT, dist_dir)
```
### 6. 代码分析集成
**Clang静态分析**
```bash
scons --clang-analyzer
```
**生成compile_commands.json**
```bash
scons --target=cmake # CMake项目会包含compile_commands.json
# 或使用
scons --compile-commands
```
**生成Cscope数据库**
```bash
scons --cscope
```
## 常见问题
### Q1: 如何添加新的源文件?
在相应目录的SConscript中添加
```python
src = Glob('*.c') # 自动包含所有.c文件
# 或
src = ['file1.c', 'file2.c'] # 手动指定
```
### Q2: 如何排除特定文件?
```python
src = Glob('*.c')
SrcRemove(src, ['test.c', 'debug.c'])
```
### Q3: 如何处理不同配置下的源文件?
```python
src = ['common.c']
if rtconfig.PLATFORM == 'gcc':
src += ['gcc_specific.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['keil_specific.c']
```
### Q4: 如何调试构建问题?
1. 使用详细输出模式:
```bash
scons --verbose
```
2. 查看预处理结果:
```bash
scons --target=mdk5 --verbose # 查看生成的项目配置
```
3. 检查依赖关系:
```python
# 在SConscript中添加调试输出
print('GetDepend result:', GetDepend('RT_USING_XXX'))
```
### Q5: 如何加快编译速度?
1. 使用多线程编译:
```bash
scons -j$(nproc) # Linux/macOS
scons -j8 # Windows
```
2. 使用ccacheGCC
```python
# 在rtconfig.py中
CC = 'ccache ' + PREFIX + 'gcc'
```
3. 优化依赖关系,避免不必要的重编译
### Q6: 如何处理第三方库?
1. **作为源码包含**
```python
# libraries/foo/SConscript
src = Glob('src/*.c')
CPPPATH = [GetCurrentDir() + '/include']
group = DefineGroup('foo', src, depend = ['RT_USING_FOO'],
CPPPATH = CPPPATH)
```
2. **作为预编译库**
```python
# 添加库文件
LIBS = ['foo']
LIBPATH = [GetCurrentDir() + '/lib']
group = DefineGroup('foo', [], depend = ['RT_USING_FOO'],
LIBS = LIBS, LIBPATH = LIBPATH)
```
### Q7: 如何自定义链接脚本?
在rtconfig.py中指定
```python
# GCC工具链
LINK_SCRIPT = 'board/link.lds'
# Keil MDK
LINK_SCRIPT = 'board/link.sct'
# IAR
LINK_SCRIPT = 'board/link.icf'
```
## 最佳实践
1. **模块化设计**每个功能模块使用独立的SConscript
2. **依赖管理**正确设置depend参数避免编译不需要的代码
3. **路径处理**使用GetCurrentDir()获取当前路径,避免硬编码
4. **条件编译**合理使用GetDepend进行条件判断
5. **编译选项**全局选项放在rtconfig.py局部选项使用LOCAL_xxx
6. **文档维护**在SConscript中添加必要的注释说明
## 总结
RT-Thread的构建系统提供了强大而灵活的项目管理能力。通过合理使用各种构建选项和功能可以高效地进行嵌入式软件开发。建议开发者深入理解构建系统的工作原理以便更好地利用其功能。

View File

@ -0,0 +1,912 @@
# RT-Thread 构建系统技术原理
## 目录
1. [系统架构设计](#系统架构设计)
2. [核心模块分析](#核心模块分析)
3. [构建流程详解](#构建流程详解)
4. [依赖管理机制](#依赖管理机制)
5. [工具链适配层](#工具链适配层)
6. [项目生成器架构](#项目生成器架构)
7. [配置系统实现](#配置系统实现)
8. [扩展机制](#扩展机制)
## 系统架构设计
### 整体架构图
```
┌────────────────────────────────────────────────────────────────────┐
│ 用户接口层 │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐│
│ │ 命令行接口 │ │ menuconfig │ │ 环境变量 │ │ 配置文件 ││
│ │ (scons) │ │ (Kconfig) │ │ (RTT_xxx) │ │ (.config)││
│ └──────┬──────┘ └──────┬───────┘ └──────┬──────┘ └─────┬────┘│
└─────────┼─────────────────┼──────────────────┼───────────────┼─────┘
│ │ │ │
┌─────────▼─────────────────▼──────────────────▼───────────────▼─────┐
│ 构建引擎层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ building.py │ │
│ ├─────────────┬───────────────┬─────────────┬────────────────┤ │
│ │ 环境准备 │ 组件收集 │ 依赖处理 │ 构建执行 │ │
│ │ Prepare │ DefineGroup │ GetDepend │ DoBuilding │ │
│ └─────────────┴───────────────┴─────────────┴────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
│ │ │ │
┌─────────▼─────────────────▼──────────────────▼───────────────▼─────┐
│ 工具支撑层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────┐│
│ │ utils.py │ │options.py│ │package.py│ │mkdist.py│ │预处理││
│ │ 工具函数 │ │命令选项 │ │包管理 │ │分发打包 │ │器 ││
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ └──────┘│
└────────────────────────────────────────────────────────────────────┘
│ │ │ │
┌─────────▼─────────────────▼──────────────────▼───────────────▼─────┐
│ 目标生成器层 │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────────┐ │
│ │keil.py │ │iar.py │ │gcc.py │ │vsc.py │ │cmake.py等 │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ └────────────┘ │
└────────────────────────────────────────────────────────────────────┘
```
### 设计原则
1. **模块化设计**:每个功能模块独立,通过明确的接口交互
2. **可扩展性**:易于添加新的工具链支持和目标生成器
3. **跨平台兼容**:统一的抽象层处理平台差异
4. **配置驱动**:通过配置文件控制构建行为
## 核心模块分析
### 1. building.py - 构建引擎核心
#### 1.1 全局变量管理
```python
BuildOptions = {} # 存储从rtconfig.h解析的宏定义
Projects = [] # 存储所有的组件对象
Rtt_Root = '' # RT-Thread根目录
Env = None # SCons环境对象
```
#### 1.2 PrepareBuilding 函数实现
```python
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = []):
"""
准备构建环境
参数:
env: SCons环境对象
root_directory: RT-Thread根目录
has_libcpu: 是否包含libcpu
remove_components: 需要移除的组件列表
"""
# 1. 添加命令行选项
AddOptions()
# 2. 设置全局环境变量
global Env, Rtt_Root
Env = env
Rtt_Root = os.path.abspath(root_directory)
# 3. 配置日志系统
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('rt-scons')
Env['log'] = logger
# 4. 工具链检测和配置
if not utils.CmdExists(os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)):
# 尝试自动检测工具链
try:
envm = utils.ImportModule('env_utility')
exec_path = envm.GetSDKPath(rtconfig.CC)
if exec_path:
rtconfig.EXEC_PATH = exec_path
except:
pass
# 5. 解析rtconfig.h配置
PreProcessor = create_preprocessor_instance()
with open('rtconfig.h', 'r') as f:
PreProcessor.process_contents(f.read())
BuildOptions = PreProcessor.cpp_namespace
# 6. 处理目标平台
if GetOption('target'):
# 根据目标设置工具链
rtconfig.CROSS_TOOL, rtconfig.PLATFORM = tgt_dict[tgt_name]
return objs
```
#### 1.3 DefineGroup 函数实现
```python
def DefineGroup(name, src, depend, **parameters):
"""
定义一个组件组
参数:
name: 组名称
src: 源文件列表
depend: 依赖条件
**parameters: 编译参数(CPPPATH, CPPDEFINES, LIBS等)
返回:
组对象列表
"""
# 1. 检查依赖条件
if not GetDepend(depend):
return []
# 2. 处理源文件
if isinstance(src, list):
# 过滤掉被移除的文件
src = [s for s in src if s not in removed_src]
# 3. 创建组对象
group = {}
group['name'] = name
group['src'] = src
# 4. 处理编译参数
# 全局参数
if 'CPPPATH' in parameters:
group['CPPPATH'] = parameters['CPPPATH']
# 本地参数(仅对当前组有效)
if 'LOCAL_CPPPATH' in parameters:
paths = parameters['LOCAL_CPPPATH']
group['LOCAL_CPPPATH'] = [os.path.abspath(p) for p in paths]
# 5. 注册到全局项目列表
Projects.append(group)
# 6. 返回SCons对象
if src:
objs = Env.Object(src)
else:
objs = []
return objs
```
### 2. 依赖管理机制
#### 2.1 GetDepend 实现
```python
def GetDepend(depend):
"""
检查依赖条件是否满足
参数:
depend: 字符串或字符串列表
返回:
True: 依赖满足
False: 依赖不满足
"""
# 1. 处理空依赖
if not depend:
return True
# 2. 处理字符串依赖
if isinstance(depend, str):
return _CheckSingleDepend(depend)
# 3. 处理列表依赖(AND关系)
if isinstance(depend, list):
for d in depend:
if not _CheckSingleDepend(d):
return False
return True
return False
def _CheckSingleDepend(depend):
"""检查单个依赖"""
# 1. 检查是否在BuildOptions中定义
if depend in BuildOptions:
# 2. 检查值是否为真
return BuildOptions[depend] != '0'
return False
```
#### 2.2 依赖表达式支持
```python
# 支持的依赖表达式
depend = 'RT_USING_SERIAL' # 单个依赖
depend = ['RT_USING_LWIP', 'SAL'] # AND关系
depend = '' # 无条件包含
# 高级用法 - 在SConscript中
if GetDepend('RT_USING_LWIP'):
if GetDepend('RT_USING_LWIP_TCP'):
src += ['tcp.c']
if GetDepend('RT_USING_LWIP_UDP'):
src += ['udp.c']
```
### 3. 配置解析系统
#### 3.1 预处理器实现
```python
class PreProcessor:
"""
C预处理器实现用于解析rtconfig.h
"""
def __init__(self):
self.cpp_namespace = {}
self.defines = {}
def process_contents(self, contents):
"""处理文件内容"""
lines = contents.split('\n')
for line in lines:
# 处理 #define 指令
if line.startswith('#define'):
self._process_define(line)
# 处理 #ifdef 等条件编译
elif line.startswith('#ifdef'):
self._process_ifdef(line)
def _process_define(self, line):
"""处理宏定义"""
# #define RT_NAME_MAX 8
parts = line.split(None, 2)
if len(parts) >= 2:
name = parts[1]
value = parts[2] if len(parts) > 2 else '1'
self.cpp_namespace[name] = value
```
#### 3.2 配置文件格式
**rtconfig.h 示例**
```c
/* RT-Thread 配置文件 */
#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__
/* 内核配置 */
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 100
#define RT_USING_TIMER_SOFT
/* 组件配置 */
#define RT_USING_DEVICE
#define RT_USING_SERIAL
#define RT_SERIAL_RB_BUFSZ 64
/* 条件配置 */
#ifdef RT_USING_SERIAL
#define RT_SERIAL_USING_DMA
#endif
#endif /* RT_CONFIG_H__ */
```
### 4. 工具链适配层
#### 4.1 工具链抽象接口
```python
class ToolchainBase:
"""工具链基类"""
def __init__(self):
self.name = ''
self.prefix = ''
self.suffix = ''
def get_cc(self):
"""获取C编译器"""
raise NotImplementedError
def get_cflags(self):
"""获取C编译选项"""
raise NotImplementedError
def get_linkflags(self):
"""获取链接选项"""
raise NotImplementedError
```
#### 4.2 GCC工具链实现
```python
class GccToolchain(ToolchainBase):
def __init__(self, prefix=''):
self.name = 'gcc'
self.prefix = prefix
self.suffix = ''
def get_cc(self):
return self.prefix + 'gcc'
def get_cflags(self):
flags = []
# 基础选项
flags += ['-Wall', '-g']
# 优化选项
if GetOption('optimization') == 'size':
flags += ['-Os']
else:
flags += ['-O0']
# 架构选项
flags += ['-mcpu=cortex-m3', '-mthumb']
return ' '.join(flags)
```
#### 4.3 Keil MDK适配
```python
class KeilToolchain(ToolchainBase):
def __init__(self):
self.name = 'keil'
def setup_environment(self, env):
"""设置Keil特定的环境变量"""
# 修改文件扩展名
env['OBJSUFFIX'] = '.o'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
# 设置编译命令
env['CC'] = 'armcc'
env['AS'] = 'armasm'
env['AR'] = 'armar'
env['LINK'] = 'armlink'
# 设置命令格式
env['ARCOM'] = '$AR --create $TARGET $SOURCES'
```
### 5. 项目生成器架构
#### 5.1 生成器基类
```python
class ProjectGenerator:
"""项目生成器基类"""
def __init__(self, env, project):
self.env = env
self.project = project
self.template_dir = ''
def generate(self):
"""生成项目文件"""
self._prepare()
self._generate_project_file()
self._generate_workspace_file()
self._copy_template_files()
self._post_process()
def _collect_source_files(self):
"""收集源文件"""
sources = []
for group in self.project:
sources.extend(group['src'])
return sources
def _collect_include_paths(self):
"""收集头文件路径"""
paths = []
for group in self.project:
if 'CPPPATH' in group:
paths.extend(group['CPPPATH'])
return list(set(paths)) # 去重
```
#### 5.2 VS Code生成器实现
```python
class VSCodeGenerator(ProjectGenerator):
"""VS Code项目生成器"""
def _generate_project_file(self):
"""生成VS Code配置文件"""
# 创建.vscode目录
vscode_dir = os.path.join(self.env['BSP_ROOT'], '.vscode')
if not os.path.exists(vscode_dir):
os.makedirs(vscode_dir)
# 生成c_cpp_properties.json
self._generate_cpp_properties()
# 生成tasks.json
self._generate_tasks()
# 生成launch.json
self._generate_launch()
def _generate_cpp_properties(self):
"""生成IntelliSense配置"""
config = {
"configurations": [{
"name": "RT-Thread",
"includePath": self._collect_include_paths(),
"defines": self._collect_defines(),
"compilerPath": self._get_compiler_path(),
"cStandard": "c99",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-arm"
}],
"version": 4
}
# 写入文件
file_path = os.path.join('.vscode', 'c_cpp_properties.json')
with open(file_path, 'w') as f:
json.dump(config, f, indent=4)
```
#### 5.3 Keil MDK5生成器
```python
class MDK5Generator(ProjectGenerator):
"""Keil MDK5项目生成器"""
def _generate_project_file(self):
"""生成uvprojx文件"""
# 加载XML模板
tree = etree.parse(self.template_file)
root = tree.getroot()
# 更新目标配置
self._update_target_options(root)
# 添加文件组
groups_node = root.find('.//Groups')
for group in self.project:
self._add_file_group(groups_node, group)
# 保存项目文件
tree.write('project.uvprojx', encoding='utf-8',
xml_declaration=True)
def _add_file_group(self, parent, group):
"""添加文件组"""
group_elem = etree.SubElement(parent, 'Group')
# 组名
name_elem = etree.SubElement(group_elem, 'GroupName')
name_elem.text = group['name']
# 文件列表
files_elem = etree.SubElement(group_elem, 'Files')
for src in group['src']:
self._add_file(files_elem, src)
```
### 6. 编译数据库生成
#### 6.1 compile_commands.json生成
```python
def generate_compile_commands(env, project):
"""
生成compile_commands.json用于代码分析工具
"""
commands = []
for group in project:
for src in group['src']:
if src.endswith('.c') or src.endswith('.cpp'):
cmd = {
"directory": env['BSP_ROOT'],
"file": os.path.abspath(src),
"command": _generate_compile_command(env, src, group)
}
commands.append(cmd)
# 写入文件
with open('compile_commands.json', 'w') as f:
json.dump(commands, f, indent=2)
def _generate_compile_command(env, src, group):
"""生成单个文件的编译命令"""
cmd = []
# 编译器
cmd.append(env['CC'])
# 编译选项
cmd.extend(env['CFLAGS'].split())
# 头文件路径
for path in group.get('CPPPATH', []):
cmd.append('-I' + path)
# 宏定义
for define in group.get('CPPDEFINES', []):
if isinstance(define, tuple):
cmd.append('-D{}={}'.format(define[0], define[1]))
else:
cmd.append('-D' + define)
# 源文件
cmd.append(src)
return ' '.join(cmd)
```
### 7. 分发系统实现
#### 7.1 分发包生成流程
```python
def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project):
"""生成分发包"""
# 1. 创建分发目录
dist_name = os.path.basename(BSP_ROOT)
dist_dir = os.path.join(BSP_ROOT, 'dist', dist_name)
# 2. 复制RT-Thread内核
print('=> copy RT-Thread kernel')
copytree(os.path.join(RTT_ROOT, 'src'),
os.path.join(dist_dir, 'rt-thread', 'src'))
copytree(os.path.join(RTT_ROOT, 'include'),
os.path.join(dist_dir, 'rt-thread', 'include'))
# 3. 复制使用的组件
print('=> copy components')
for group in project:
_copy_group_files(group, dist_dir)
# 4. 生成Kconfig文件
_generate_kconfig(dist_dir, project)
# 5. 打包
make_zip(dist_dir, dist_name + '.zip')
```
#### 7.2 精简分发包生成
```python
def MkDist_Strip(program, BSP_ROOT, RTT_ROOT, Env):
"""
基于compile_commands.json生成精简分发包
只包含实际使用的文件
"""
# 1. 解析compile_commands.json
with open('compile_commands.json', 'r') as f:
commands = json.load(f)
# 2. 提取使用的文件
used_files = set()
for cmd in commands:
# 源文件
used_files.add(cmd['file'])
# 解析包含的头文件
includes = _parse_includes(cmd['file'], cmd['command'])
used_files.update(includes)
# 3. 复制文件
for file in used_files:
_copy_with_structure(file, dist_dir)
```
## 构建流程详解
### 完整构建流程图
```
┌──────────────┐
│ 用户输入 │
│ scons │
└──────┬───────┘
┌──────▼───────┐
│ SConstruct │ ← 读取rtconfig.py
│ 主脚本 │ ← 调用PrepareBuilding
└──────┬───────┘
┌──────▼───────┐
│ 环境初始化 │
│ ·设置路径 │
│ ·检测工具链 │
│ ·解析配置 │
└──────┬───────┘
┌──────▼───────┐
│ 递归处理 │
│ SConscript │ ← 调用DefineGroup
│ ·收集源文件 │ ← 检查依赖GetDepend
│ ·设置参数 │
└──────┬───────┘
┌──────▼───────┐
│ 构建执行 │
│ DoBuilding │
│ ·合并对象 │
│ ·链接程序 │
└──────┬───────┘
┌──────▼───────┐
│ 后处理 │
│ ·生成bin文件 │
│ ·显示大小 │
│ ·自定义动作 │
└──────────────┘
```
### 依赖解析流程
```python
def dependency_resolution_flow():
"""
依赖解析流程示例
"""
# 1. 从rtconfig.h读取所有宏定义
macros = parse_rtconfig_h()
# 例: {'RT_USING_SERIAL': '1', 'RT_USING_PIN': '1'}
# 2. 处理单个组件
for component in components:
# 3. 检查依赖条件
if check_dependencies(component.depends, macros):
# 4. 包含组件
include_component(component)
else:
# 5. 跳过组件
skip_component(component)
# 6. 递归处理子依赖
resolve_sub_dependencies()
```
## 扩展机制
### 1. 添加新的工具链支持
```python
# 1. 在tgt_dict中添加映射
tgt_dict['mycc'] = ('mycc', 'mycc')
# 2. 创建tools/mycc.py
import os
from building import *
def generate_project(env, project):
"""生成项目文件"""
print("Generating MyCC project...")
# 收集信息
info = ProjectInfo(env, project)
# 生成项目文件
# ...
# 3. 在rtconfig.py中配置
CROSS_TOOL = 'mycc'
PLATFORM = 'mycc'
```
### 2. 添加自定义构建步骤
```python
# 在SConstruct或SConscript中
def custom_builder(target, source, env):
"""自定义构建器"""
# 执行自定义操作
cmd = 'custom_tool -o {} {}'.format(target[0], source[0])
os.system(cmd)
# 注册构建器
env['BUILDERS']['CustomBuild'] = Builder(action=custom_builder,
suffix='.out',
src_suffix='.in')
# 使用构建器
custom_out = env.CustomBuild('output.out', 'input.in')
```
### 3. 扩展配置解析器
```python
class ExtendedPreProcessor(PreProcessor):
"""扩展的预处理器"""
def __init__(self):
super().__init__()
self.custom_handlers = {}
def register_handler(self, directive, handler):
"""注册自定义指令处理器"""
self.custom_handlers[directive] = handler
def process_line(self, line):
"""处理单行"""
# 检查自定义指令
for directive, handler in self.custom_handlers.items():
if line.startswith(directive):
return handler(line)
# 默认处理
return super().process_line(line)
```
### 4. 插件系统实现
```python
class BuildPlugin:
"""构建插件基类"""
def __init__(self, name):
self.name = name
def pre_build(self, env, project):
"""构建前钩子"""
pass
def post_build(self, env, project):
"""构建后钩子"""
pass
def configure(self, env):
"""配置环境"""
pass
# 插件管理器
class PluginManager:
def __init__(self):
self.plugins = []
def register(self, plugin):
self.plugins.append(plugin)
def run_pre_build(self, env, project):
for plugin in self.plugins:
plugin.pre_build(env, project)
```
## 性能优化
### 1. 构建缓存机制
```python
class BuildCache:
"""构建缓存"""
def __init__(self, cache_dir='.scache'):
self.cache_dir = cache_dir
self.cache_db = os.path.join(cache_dir, 'cache.db')
def get_hash(self, file):
"""计算文件哈希"""
import hashlib
with open(file, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def is_cached(self, source, target):
"""检查是否已缓存"""
# 检查目标文件是否存在
if not os.path.exists(target):
return False
# 检查源文件是否更新
source_hash = self.get_hash(source)
cached_hash = self.load_hash(source)
return source_hash == cached_hash
```
### 2. 并行构建优化
```python
def optimize_parallel_build(env, project):
"""优化并行构建"""
# 1. 分析依赖关系
dep_graph = analyze_dependencies(project)
# 2. 计算最优构建顺序
build_order = topological_sort(dep_graph)
# 3. 分组独立任务
parallel_groups = []
for level in build_order:
# 同一层级可以并行
parallel_groups.append(level)
# 4. 设置并行度
import multiprocessing
num_jobs = multiprocessing.cpu_count()
env.SetOption('num_jobs', num_jobs)
return parallel_groups
```
## 调试技巧
### 1. 构建日志分析
```python
# 启用详细日志
def enable_build_logging():
# 设置SCons日志
env.SetOption('debug', 'explain')
# 自定义日志
class BuildLogger:
def __init__(self, logfile):
self.logfile = logfile
def __call__(self, msg, *args):
with open(self.logfile, 'a') as f:
f.write(msg % args + '\n')
logger = BuildLogger('build.log')
env['PRINT_CMD_LINE_FUNC'] = logger
```
### 2. 依赖关系可视化
```python
def visualize_dependencies(project):
"""生成依赖关系图"""
import graphviz
dot = graphviz.Digraph(comment='Dependencies')
# 添加节点
for group in project:
dot.node(group['name'])
# 添加边
for group in project:
for dep in group.get('depends', []):
if find_group(dep):
dot.edge(dep, group['name'])
# 渲染
dot.render('dependencies', format='png')
```
## 最佳实践
### 1. 模块化设计原则
- 每个功能模块独立的SConscript
- 明确的依赖关系声明
- 避免循环依赖
- 使用统一的命名规范
### 2. 性能优化建议
- 使用Glob谨慎大目录下性能差
- 合理设置并行编译数
- 使用增量编译
- 避免重复的文件扫描
### 3. 可维护性建议
- 添加充分的注释
- 使用有意义的变量名
- 遵循Python PEP8规范
- 定期清理无用代码
### 4. 跨平台兼容性
- 使用os.path处理路径
- 避免平台特定的命令
- 测试多平台构建
- 处理路径分隔符差异
## 总结
RT-Thread的构建系统是一个精心设计的模块化系统通过清晰的架构和灵活的扩展机制为嵌入式开发提供了强大的构建能力。理解其内部原理有助于
1. 更好地使用和优化构建流程
2. 快速定位和解决构建问题
3. 扩展支持新的工具链和平台
4. 为项目定制构建流程
构建系统的核心价值在于将复杂的嵌入式构建过程标准化和自动化,让开发者能够专注于功能开发而不是构建配置。

345
tools/ng/README.md Normal file
View File

@ -0,0 +1,345 @@
# RT-Thread Next Generation Build System
## 概述
RT-Thread NGNext Generation构建系统是对现有构建系统的面向对象重构在保持完全向后兼容的同时提供了更清晰的架构和更强的可扩展性。
## 特性
- ✅ **完全向后兼容**现有的SConscript无需修改
- ✅ **面向对象设计**:清晰的类层次结构和职责分离
- ✅ **SCons最佳实践**充分利用SCons的Environment对象
- ✅ **可扩展架构**:易于添加新的工具链和项目生成器
- ✅ **类型安全**:更好的类型提示和错误处理
## 架构设计
### 核心模块
```
ng/
├── __init__.py # 包初始化
├── core.py # 核心类BuildContext
├── environment.py # 环境扩展RTEnv类注入到SCons Environment
├── config.py # 配置管理解析rtconfig.h
├── project.py # 项目管理ProjectGroup和Registry
├── toolchain.py # 工具链抽象GCC、Keil、IAR等
├── generator.py # 项目生成器VS Code、CMake等
├── utils.py # 工具函数:路径、版本等
├── adapter.py # 适配器与building.py集成
└── building_ng.py # 示例最小化修改的building.py
```
### 类图
```mermaid
classDiagram
class BuildContext {
+root_directory: str
+config_manager: ConfigManager
+project_registry: ProjectRegistry
+toolchain_manager: ToolchainManager
+prepare_environment(env)
+get_dependency(depend): bool
}
class ConfigManager {
+load_from_file(filepath)
+get_dependency(depend): bool
+get_option(name): ConfigOption
}
class ProjectGroup {
+name: str
+sources: List[str]
+dependencies: List[str]
+build(env): List[Object]
}
class Toolchain {
<<abstract>>
+get_name(): str
+detect(): bool
+configure_environment(env)
}
class ProjectGenerator {
<<abstract>>
+generate(context, info): bool
+clean(): bool
}
BuildContext --> ConfigManager
BuildContext --> ProjectRegistry
BuildContext --> ToolchainManager
ProjectRegistry --> ProjectGroup
ToolchainManager --> Toolchain
```
## 使用方法
### 1. 最小化集成(推荐)
在building.py中添加少量代码即可集成新系统
```python
# 在building.py的开头添加
try:
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig
)
USE_NG = True
except ImportError:
USE_NG = False
# 在PrepareBuilding函数中添加
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components=[]):
# ... 原有代码 ...
# 集成新系统
if USE_NG:
context = init_build_context(root_directory)
inject_environment_methods(env)
ng_load_rtconfig('rtconfig.h')
# ... 继续原有代码 ...
```
### 2. 使用新的环境方法
集成后SCons Environment对象会自动获得新方法
```python
# 在SConscript中使用新方法
Import('env')
# 使用环境方法(推荐)
src = env.GlobFiles('*.c')
group = env.DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
# 也可以使用传统方式(保持兼容)
from building import *
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
```
### 3. 新的项目生成器
新系统提供了改进的项目生成器:
```bash
# 生成VS Code项目
scons --target=vscode
# 生成CMake项目
scons --target=cmake
```
## API参考
### 环境方法
所有方法都被注入到SCons Environment对象中
#### env.DefineGroup(name, src, depend, **kwargs)
定义一个组件组。
**参数:**
- `name`: 组名称
- `src`: 源文件列表
- `depend`: 依赖条件(字符串或列表)
- `**kwargs`: 额外参数
- `CPPPATH`: 头文件路径
- `CPPDEFINES`: 宏定义
- `CFLAGS`/`CXXFLAGS`: 编译选项
- `LOCAL_CFLAGS`/`LOCAL_CPPPATH`: 仅对当前组有效的选项
- `LIBS`/`LIBPATH`: 库配置
**返回:** 构建对象列表
**示例:**
```python
src = ['driver.c', 'hal.c']
group = env.DefineGroup('Driver',
src,
depend=['RT_USING_DEVICE'],
CPPPATH=[env.GetCurrentDir()],
LOCAL_CFLAGS='-O3'
)
```
#### env.GetDepend(depend)
检查依赖是否满足。
**参数:**
- `depend`: 依赖名称或列表
**返回:** True如果依赖满足
**示例:**
```python
if env.GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if env.GetDepend(['RT_USING_SERIAL', 'RT_SERIAL_USING_DMA']):
src += ['serial_dma.c']
```
#### env.SrcRemove(src, remove)
从源文件列表中移除文件。
**参数:**
- `src`: 源文件列表(就地修改)
- `remove`: 要移除的文件
**示例:**
```python
src = env.GlobFiles('*.c')
env.SrcRemove(src, ['test.c', 'debug.c'])
```
#### env.BuildPackage(package_path)
从package.json构建软件包。
**参数:**
- `package_path`: package.json路径
**返回:** 构建对象列表
**示例:**
```python
objs = env.BuildPackage('package.json')
```
#### env.GetContext()
获取当前构建上下文。
**返回:** BuildContext实例
**示例:**
```python
context = env.GetContext()
if context:
context.logger.info("Building component...")
```
## 高级特性
### 1. 自定义工具链
创建自定义工具链:
```python
from ng.toolchain import Toolchain
class MyToolchain(Toolchain):
def get_name(self):
return "mycc"
def detect(self):
# 检测工具链
return shutil.which("mycc") is not None
def configure_environment(self, env):
env['CC'] = 'mycc'
env['CFLAGS'] = '-O2 -Wall'
# 注册工具链
context = env.GetContext()
context.toolchain_manager.register_toolchain('mycc', MyToolchain())
```
### 2. 自定义项目生成器
创建自定义项目生成器:
```python
from ng.generator import ProjectGenerator
class MyGenerator(ProjectGenerator):
def get_name(self):
return "myide"
def generate(self, context, project_info):
# 生成项目文件
self._ensure_output_dir()
# ... 生成逻辑 ...
return True
# 注册生成器
context.generator_registry.register('myide', MyGenerator)
```
### 3. 构建钩子
使用构建上下文添加钩子:
```python
context = env.GetContext()
# 添加日志
context.logger.info("Starting build...")
# 访问配置
if context.config_manager.get_option('RT_THREAD_PRIORITY_MAX'):
print("Max priority:", context.config_manager.get_value('RT_THREAD_PRIORITY_MAX'))
# 获取项目信息
info = context.project_registry.get_project_info()
print(f"Total sources: {len(info['all_sources'])}")
```
## 迁移指南
### 从旧版本迁移
1. **无需修改**现有的SConscript文件无需任何修改即可工作
2. **可选升级**:可以逐步将`DefineGroup`调用改为`env.DefineGroup`
3. **新功能**:可以开始使用新的特性如`env.BuildPackage`
### 最佳实践
1. **使用环境方法**:优先使用`env.DefineGroup`而不是全局函数
2. **类型提示**在Python 3.5+中使用类型提示
3. **错误处理**使用context.logger记录错误和警告
4. **路径处理**使用PathService处理跨平台路径
## 性能优化
新系统包含多项性能优化:
1. **配置缓存**:依赖检查结果会被缓存
2. **延迟加载**:工具链和生成器按需加载
3. **并行支持**:项目生成可以并行执行
## 测试
运行测试套件:
```bash
cd tools/ng
python -m pytest tests/
```
## 贡献
欢迎贡献代码!请遵循以下准则:
1. 保持向后兼容性
2. 添加类型提示
3. 编写单元测试
4. 更新文档
## 路线图
- [ ] 完整的测试覆盖
- [ ] 性能基准测试
- [ ] 插件系统
- [ ] 更多项目生成器Eclipse、Qt Creator等
- [ ] 构建缓存系统
- [ ] 分布式构建支持
## 许可证
本项目遵循RT-Thread的Apache License 2.0许可证。

25
tools/ng/__init__.py Normal file
View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
RT-Thread Next Generation Build System
This module provides an object-oriented implementation of the RT-Thread build system
while maintaining backward compatibility with the existing building.py interface.
"""
from .core import BuildContext
from .environment import RTEnv
from .config import ConfigManager
from .project import ProjectRegistry, ProjectGroup
from .toolchain import ToolchainManager
from .generator import GeneratorRegistry
__version__ = "1.0.0"
__all__ = [
'BuildContext',
'RTEnv',
'ConfigManager',
'ProjectRegistry',
'ProjectGroup',
'ToolchainManager',
'GeneratorRegistry'
]

218
tools/ng/adapter.py Normal file
View File

@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
"""
Adapter module to integrate new OOP implementation with existing building.py.
This module provides the bridge between the legacy function-based API and the new
object-oriented implementation, ensuring backward compatibility.
"""
import os
from typing import List, Dict, Any, Optional
from .core import BuildContext
from .environment import RTEnv
from .generator import GeneratorConfig, GeneratorRegistry
# Global variables for compatibility
_context: Optional[BuildContext] = None
def init_build_context(root_directory: str) -> BuildContext:
"""
Initialize the build context.
This function should be called early in PrepareBuilding.
Args:
root_directory: RT-Thread root directory
Returns:
BuildContext instance
"""
global _context
_context = BuildContext(root_directory)
return _context
def get_build_context() -> Optional[BuildContext]:
"""Get the current build context."""
return _context
def inject_environment_methods(env) -> None:
"""
Inject RT-Thread methods into SCons Environment.
This should be called in PrepareBuilding after environment setup.
Args:
env: SCons Environment object
"""
RTEnv.inject_methods(env)
# Also set the environment in context
if _context:
_context.prepare_environment(env)
def load_rtconfig(config_file: str = 'rtconfig.h') -> Dict[str, Any]:
"""
Load configuration from rtconfig.h.
Args:
config_file: Configuration file name
Returns:
Dictionary of build options
"""
if _context:
_context.load_configuration(config_file)
return _context.build_options
return {}
def DefineGroup(name: str, src: List[str], depend: Any = None, **kwargs) -> List:
"""
Legacy DefineGroup function for backward compatibility.
This function delegates to the environment method.
Args:
name: Group name
src: Source files
depend: Dependencies
**kwargs: Additional parameters
Returns:
List of build objects
"""
if _context and _context.environment:
return _context.environment.DefineGroup(name, src, depend, **kwargs)
else:
# Fallback behavior
print(f"Warning: DefineGroup called before environment setup for group '{name}'")
return []
def GetDepend(depend: Any) -> bool:
"""
Legacy GetDepend function for backward compatibility.
Args:
depend: Dependency to check
Returns:
True if dependency is satisfied
"""
if _context:
return _context.get_dependency(depend)
return False
def GetCurrentDir() -> str:
"""
Get current directory.
Returns:
Current directory path
"""
return os.path.abspath('.')
def SrcRemove(src: List[str], remove: List[str]) -> None:
"""
Remove files from source list.
Args:
src: Source list (modified in place)
remove: Files to remove
"""
if not isinstance(remove, list):
remove = [remove]
for item in remove:
if item in src:
src.remove(item)
def GetBuildOptions() -> Dict[str, Any]:
"""
Get build options.
Returns:
Dictionary of build options
"""
if _context:
return _context.build_options
return {}
def MergeGroups() -> List:
"""
Merge all registered groups.
Returns:
List of all build objects
"""
if _context:
return _context.merge_groups()
return []
def GenerateProject(target: str, env, projects: List) -> None:
"""
Generate IDE project files.
Args:
target: Target type (mdk5, iar, vscode, etc.)
env: SCons Environment
projects: Project list
"""
if not _context:
print("Error: Build context not initialized")
return
# Get project info from registry
project_info = _context.project_registry.get_project_info()
# Create generator config
config = GeneratorConfig(
output_dir=os.getcwd(),
project_name=os.path.basename(os.getcwd()),
target_name="rtthread.elf"
)
# Create and run generator
try:
generator = _context.generator_registry.create_generator(target, config)
if generator.generate(_context, project_info):
print(f"Successfully generated {target} project files")
else:
print(f"Failed to generate {target} project files")
except Exception as e:
print(f"Error generating {target} project: {e}")
def PrepareModuleBuilding(env, root_directory, bsp_directory) -> None:
"""
Prepare for building a module.
This is a simplified version of PrepareBuilding for module compilation.
Args:
env: SCons Environment
root_directory: RT-Thread root directory
bsp_directory: BSP directory
"""
# Initialize context
context = init_build_context(root_directory)
context.bsp_directory = bsp_directory
# Inject methods
inject_environment_methods(env)
# Load configuration
config_path = os.path.join(bsp_directory, 'rtconfig.h')
if os.path.exists(config_path):
load_rtconfig(config_path)

115
tools/ng/building_ng.py Normal file
View File

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""
Next Generation building.py with minimal modifications.
This file shows how to integrate the new OOP system with minimal changes to building.py.
The actual implementation would modify the original building.py file.
"""
# Import everything from original building.py
import sys
import os
# Add parent directory to path to import original building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from building import *
# Import new OOP modules
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig,
GenerateProject as ng_GenerateProject
)
# Override PrepareBuilding to integrate new system
_original_PrepareBuilding = PrepareBuilding
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components=[]):
"""
Enhanced PrepareBuilding that integrates the new OOP system.
This function wraps the original PrepareBuilding and adds OOP functionality.
"""
# Initialize new build context
context = init_build_context(root_directory)
# Call original PrepareBuilding
result = _original_PrepareBuilding(env, root_directory, has_libcpu, remove_components)
# Inject new methods into environment
inject_environment_methods(env)
# Load configuration into new system
ng_load_rtconfig('rtconfig.h')
# Store context in environment for access
env['_BuildContext'] = context
return result
# Override DefineGroup to use new implementation
_original_DefineGroup = DefineGroup
def DefineGroup(name, src, depend, **parameters):
"""
Enhanced DefineGroup that uses the new OOP implementation.
This maintains backward compatibility while using the new system internally.
"""
# Get environment from global Env
global Env
if Env and hasattr(Env, 'DefineGroup'):
# Use new method if available
return Env.DefineGroup(name, src, depend, **parameters)
else:
# Fallback to original
return _original_DefineGroup(name, src, depend, **parameters)
# Override GetDepend to use new implementation
_original_GetDepend = GetDepend
def GetDepend(depend):
"""
Enhanced GetDepend that uses the new OOP implementation.
"""
global Env
if Env and hasattr(Env, 'GetDepend'):
# Use new method if available
return Env.GetDepend(depend)
else:
# Fallback to original
return _original_GetDepend(depend)
# Override DoBuilding to integrate project generation
_original_DoBuilding = DoBuilding
def DoBuilding(target, objects):
"""
Enhanced DoBuilding that integrates new project generation.
"""
# Call original DoBuilding
_original_DoBuilding(target, objects)
# Handle project generation with new system
if GetOption('target'):
target_name = GetOption('target')
global Env, Projects
# Use new generator if available
try:
ng_GenerateProject(target_name, Env, Projects)
except Exception as e:
print(f"Falling back to original generator: {e}")
# Call original GenTargetProject
from building import GenTargetProject
GenTargetProject(Projects, program=target)
# Export enhanced functions
__all__ = ['PrepareBuilding', 'DefineGroup', 'GetDepend', 'DoBuilding'] + \
[name for name in dir(sys.modules['building']) if not name.startswith('_')]

297
tools/ng/config.py Normal file
View File

@ -0,0 +1,297 @@
# -*- coding: utf-8 -*-
"""
Configuration management for RT-Thread build system.
This module handles parsing and managing configuration from rtconfig.h files.
"""
import re
import os
from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass
from enum import Enum
class ConfigType(Enum):
"""Configuration value types."""
BOOLEAN = "boolean"
INTEGER = "integer"
STRING = "string"
UNDEFINED = "undefined"
@dataclass
class ConfigOption:
"""Configuration option with metadata."""
name: str
value: Any
type: ConfigType
line_number: int = 0
comment: str = ""
def as_bool(self) -> bool:
"""Get value as boolean."""
if self.type == ConfigType.BOOLEAN:
return bool(self.value)
elif self.type == ConfigType.INTEGER:
return self.value != 0
elif self.type == ConfigType.STRING:
return bool(self.value)
return False
def as_int(self) -> int:
"""Get value as integer."""
if self.type == ConfigType.INTEGER:
return self.value
elif self.type == ConfigType.BOOLEAN:
return 1 if self.value else 0
elif self.type == ConfigType.STRING:
try:
return int(self.value)
except ValueError:
return 0
return 0
def as_str(self) -> str:
"""Get value as string."""
if self.type == ConfigType.STRING:
return self.value
return str(self.value)
class ConfigParser:
"""Parser for rtconfig.h files."""
# Regular expressions for parsing
RE_DEFINE = re.compile(r'^\s*#\s*define\s+(\w+)(?:\s+(.*))?', re.MULTILINE)
RE_UNDEF = re.compile(r'^\s*#\s*undef\s+(\w+)', re.MULTILINE)
RE_IFDEF = re.compile(r'^\s*#\s*ifdef\s+(\w+)', re.MULTILINE)
RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+(\w+)', re.MULTILINE)
RE_ENDIF = re.compile(r'^\s*#\s*endif', re.MULTILINE)
RE_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL)
RE_LINE_COMMENT = re.compile(r'//.*$', re.MULTILINE)
def __init__(self):
self.options: Dict[str, ConfigOption] = {}
self.conditions: List[str] = []
def parse_file(self, filepath: str) -> Dict[str, ConfigOption]:
"""
Parse configuration file.
Args:
filepath: Path to rtconfig.h
Returns:
Dictionary of configuration options
"""
if not os.path.exists(filepath):
raise FileNotFoundError(f"Configuration file not found: {filepath}")
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
return self.parse_content(content)
def parse_content(self, content: str) -> Dict[str, ConfigOption]:
"""
Parse configuration content.
Args:
content: File content
Returns:
Dictionary of configuration options
"""
# Remove comments
content = self.RE_COMMENT.sub('', content)
content = self.RE_LINE_COMMENT.sub('', content)
# Parse line by line
lines = content.split('\n')
for i, line in enumerate(lines):
self._parse_line(line, i + 1)
return self.options
def _parse_line(self, line: str, line_number: int) -> None:
"""Parse a single line."""
# Check for #define
match = self.RE_DEFINE.match(line)
if match:
name = match.group(1)
value = match.group(2) if match.group(2) else '1'
# Parse value
parsed_value, value_type = self._parse_value(value.strip())
# Create option
option = ConfigOption(
name=name,
value=parsed_value,
type=value_type,
line_number=line_number
)
self.options[name] = option
return
# Check for #undef
match = self.RE_UNDEF.match(line)
if match:
name = match.group(1)
if name in self.options:
del self.options[name]
return
def _parse_value(self, value: str) -> tuple:
"""
Parse configuration value.
Returns:
Tuple of (parsed_value, ConfigType)
"""
if not value or value == '1':
return (True, ConfigType.BOOLEAN)
# Try integer
try:
return (int(value, 0), ConfigType.INTEGER) # Support hex/octal
except ValueError:
pass
# Try string (remove quotes)
if value.startswith('"') and value.endswith('"'):
return (value[1:-1], ConfigType.STRING)
# Default to string
return (value, ConfigType.STRING)
class ConfigManager:
"""
Configuration manager for build system.
This class manages configuration options and provides dependency checking.
"""
def __init__(self):
self.parser = ConfigParser()
self.options: Dict[str, ConfigOption] = {}
self.cache: Dict[str, bool] = {}
def load_from_file(self, filepath: str) -> None:
"""
Load configuration from file.
Args:
filepath: Path to rtconfig.h
"""
self.options = self.parser.parse_file(filepath)
self.cache.clear() # Clear dependency cache
def get_option(self, name: str) -> Optional[ConfigOption]:
"""
Get configuration option.
Args:
name: Option name
Returns:
ConfigOption or None
"""
return self.options.get(name)
def get_value(self, name: str, default: Any = None) -> Any:
"""
Get configuration value.
Args:
name: Option name
default: Default value if not found
Returns:
Configuration value
"""
option = self.options.get(name)
if option:
return option.value
return default
def get_dependency(self, depend: Union[str, List[str]]) -> bool:
"""
Check if dependency is satisfied.
Args:
depend: Single dependency or list of dependencies
Returns:
True if all dependencies are satisfied
"""
# Handle empty dependency
if not depend:
return True
# Convert to list
if isinstance(depend, str):
depend = [depend]
# Check cache
cache_key = ','.join(sorted(depend))
if cache_key in self.cache:
return self.cache[cache_key]
# Check all dependencies (AND logic)
result = all(self._check_single_dependency(d) for d in depend)
# Cache result
self.cache[cache_key] = result
return result
def _check_single_dependency(self, name: str) -> bool:
"""Check a single dependency."""
option = self.options.get(name)
if not option:
return False
# For RT-Thread, any defined macro is considered True
# except if explicitly set to 0
if option.type == ConfigType.INTEGER:
return option.value != 0
elif option.type == ConfigType.BOOLEAN:
return option.value
elif option.type == ConfigType.STRING:
return bool(option.value)
return True
def get_all_options(self) -> Dict[str, Any]:
"""
Get all configuration options as a simple dictionary.
Returns:
Dictionary of option names to values
"""
return {name: opt.value for name, opt in self.options.items()}
def validate(self) -> List[str]:
"""
Validate configuration.
Returns:
List of validation errors
"""
errors = []
# Check for common issues
if 'RT_NAME_MAX' in self.options:
name_max = self.options['RT_NAME_MAX'].as_int()
if name_max < 4:
errors.append("RT_NAME_MAX should be at least 4")
if 'RT_THREAD_PRIORITY_MAX' in self.options:
prio_max = self.options['RT_THREAD_PRIORITY_MAX'].as_int()
if prio_max not in [8, 32, 256]:
errors.append("RT_THREAD_PRIORITY_MAX should be 8, 32, or 256")
return errors

176
tools/ng/core.py Normal file
View File

@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""
Core module for RT-Thread build system.
This module provides the central BuildContext class that manages the build state
and coordinates between different components.
"""
import os
import logging
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from .config import ConfigManager
from .project import ProjectRegistry
from .toolchain import ToolchainManager
from .generator import GeneratorRegistry
from .utils import PathService
class BuildContext:
"""
Central build context that manages all build-related state.
This class replaces the global variables in building.py with a proper
object-oriented design while maintaining compatibility.
"""
# Class variable to store the current context (for backward compatibility)
_current_context: Optional['BuildContext'] = None
def __init__(self, root_directory: str):
"""
Initialize build context.
Args:
root_directory: RT-Thread root directory path
"""
self.root_directory = os.path.abspath(root_directory)
self.bsp_directory = os.getcwd()
# Initialize managers
self.config_manager = ConfigManager()
self.project_registry = ProjectRegistry()
self.toolchain_manager = ToolchainManager()
self.generator_registry = GeneratorRegistry()
self.path_service = PathService(self.bsp_directory)
# Build environment
self.environment = None
self.build_options = {}
# Logging
self.logger = self._setup_logger()
# Set as current context
BuildContext._current_context = self
@classmethod
def get_current(cls) -> Optional['BuildContext']:
"""Get the current build context."""
return cls._current_context
@classmethod
def set_current(cls, context: Optional['BuildContext']) -> None:
"""Set the current build context."""
cls._current_context = context
def _setup_logger(self) -> logging.Logger:
"""Setup logger for build system."""
logger = logging.getLogger('rtthread.build')
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('[%(levelname)s] %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
def prepare_environment(self, env) -> None:
"""
Prepare the build environment.
Args:
env: SCons Environment object
"""
self.environment = env
# Set environment variables
env['RTT_ROOT'] = self.root_directory
env['BSP_ROOT'] = self.bsp_directory
# Add to Python path
import sys
tools_path = os.path.join(self.root_directory, 'tools')
if tools_path not in sys.path:
sys.path.insert(0, tools_path)
self.logger.debug(f"Prepared environment with RTT_ROOT={self.root_directory}")
def load_configuration(self, config_file: str = 'rtconfig.h') -> None:
"""
Load configuration from rtconfig.h.
Args:
config_file: Path to configuration file
"""
config_path = os.path.join(self.bsp_directory, config_file)
if os.path.exists(config_path):
self.config_manager.load_from_file(config_path)
self.build_options = self.config_manager.get_all_options()
self.logger.info(f"Loaded configuration from {config_file}")
else:
self.logger.warning(f"Configuration file {config_file} not found")
def get_dependency(self, depend: Any) -> bool:
"""
Check if dependency is satisfied.
Args:
depend: Dependency name or list of names
Returns:
True if dependency is satisfied
"""
return self.config_manager.get_dependency(depend)
def register_project_group(self, group) -> None:
"""
Register a project group.
Args:
group: ProjectGroup instance
"""
self.project_registry.register_group(group)
def merge_groups(self) -> List:
"""
Merge all registered project groups.
Returns:
List of build objects
"""
return self.project_registry.merge_groups(self.environment)
@dataclass
class BuildOptions:
"""Build options container."""
verbose: bool = False
strict: bool = False
target: Optional[str] = None
jobs: int = 1
clean: bool = False
@dataclass
class ProjectInfo:
"""Project information for generators."""
name: str = "rtthread"
target_name: str = "rtthread.elf"
# File collections
source_files: List[str] = field(default_factory=list)
include_paths: List[str] = field(default_factory=list)
defines: Dict[str, str] = field(default_factory=dict)
# Compiler options
cflags: str = ""
cxxflags: str = ""
asflags: str = ""
ldflags: str = ""
# Libraries
libs: List[str] = field(default_factory=list)
lib_paths: List[str] = field(default_factory=list)

304
tools/ng/environment.py Normal file
View File

@ -0,0 +1,304 @@
# -*- coding: utf-8 -*-
"""
Environment extensions for RT-Thread build system.
This module provides methods that are injected into the SCons Environment object
to provide RT-Thread-specific functionality.
"""
import os
from typing import List, Union, Dict, Any, Optional
from SCons.Script import *
from .core import BuildContext
from .project import ProjectGroup
class RTEnv:
"""
RT-Thread environment extensions (RTEnv).
This class provides methods that are added to the SCons Environment object.
"""
@staticmethod
def inject_methods(env):
"""
Inject RT-Thread methods into SCons Environment.
Args:
env: SCons Environment object
"""
# Core build methods
env.AddMethod(RTEnv.DefineGroup, 'DefineGroup')
env.AddMethod(RTEnv.GetDepend, 'GetDepend')
env.AddMethod(RTEnv.SrcRemove, 'SrcRemove')
env.AddMethod(RTEnv.GetCurrentDir, 'GetCurrentDir')
env.AddMethod(RTEnv.BuildPackage, 'BuildPackage')
# Utility methods
env.AddMethod(RTEnv.Glob, 'GlobFiles')
env.AddMethod(RTEnv.GetBuildOptions, 'GetBuildOptions')
env.AddMethod(RTEnv.GetContext, 'GetContext')
# Path utilities
env.AddMethod(RTEnv.GetRTTRoot, 'GetRTTRoot')
env.AddMethod(RTEnv.GetBSPRoot, 'GetBSPRoot')
@staticmethod
def DefineGroup(env, name: str, src: List[str], depend: Any = None, **kwargs) -> List:
"""
Define a component group.
This method maintains compatibility with the original DefineGroup function
while using the new object-oriented implementation.
Args:
env: SCons Environment
name: Group name
src: Source file list
depend: Dependency conditions
**kwargs: Additional parameters (CPPPATH, CPPDEFINES, etc.)
Returns:
List of build objects
"""
context = BuildContext.get_current()
if not context:
raise RuntimeError("BuildContext not initialized")
# Check dependencies
if depend and not env.GetDepend(depend):
return []
# Process source files
if isinstance(src, str):
src = [src]
# Create project group
group = ProjectGroup(
name=name,
sources=src,
dependencies=depend if isinstance(depend, list) else [depend] if depend else [],
environment=env
)
# Process parameters
group.include_paths = kwargs.get('CPPPATH', [])
group.defines = kwargs.get('CPPDEFINES', {})
group.cflags = kwargs.get('CFLAGS', '')
group.cxxflags = kwargs.get('CXXFLAGS', '')
group.local_cflags = kwargs.get('LOCAL_CFLAGS', '')
group.local_cxxflags = kwargs.get('LOCAL_CXXFLAGS', '')
group.local_include_paths = kwargs.get('LOCAL_CPPPATH', [])
group.local_defines = kwargs.get('LOCAL_CPPDEFINES', {})
group.libs = kwargs.get('LIBS', [])
group.lib_paths = kwargs.get('LIBPATH', [])
# Build objects
objects = group.build(env)
# Register group
context.register_project_group(group)
return objects
@staticmethod
def GetDepend(env, depend: Any) -> bool:
"""
Check if dependency is satisfied.
Args:
env: SCons Environment
depend: Dependency name or list of names
Returns:
True if dependency is satisfied
"""
context = BuildContext.get_current()
if not context:
# Fallback to checking environment variables
if isinstance(depend, str):
return env.get(depend, False)
elif isinstance(depend, list):
return all(env.get(d, False) for d in depend)
return False
return context.get_dependency(depend)
@staticmethod
def SrcRemove(env, src: List[str], remove: List[str]) -> None:
"""
Remove files from source list.
Args:
env: SCons Environment
src: Source file list (modified in place)
remove: Files to remove
"""
if not isinstance(remove, list):
remove = [remove]
for item in remove:
# Handle both exact matches and pattern matches
if item in src:
src.remove(item)
else:
# Try pattern matching
import fnmatch
to_remove = [f for f in src if fnmatch.fnmatch(f, item)]
for f in to_remove:
src.remove(f)
@staticmethod
def GetCurrentDir(env) -> str:
"""
Get current directory.
Args:
env: SCons Environment
Returns:
Current directory path
"""
return Dir('.').abspath
@staticmethod
def BuildPackage(env, package_path: str = None) -> List:
"""
Build package from package.json.
Args:
env: SCons Environment
package_path: Path to package.json or directory containing it
Returns:
List of build objects
"""
import json
# Find package.json
if package_path is None:
package_path = 'package.json'
elif os.path.isdir(package_path):
package_path = os.path.join(package_path, 'package.json')
if not os.path.exists(package_path):
env.GetContext().logger.error(f"Package file not found: {package_path}")
return []
# Load package definition
with open(package_path, 'r') as f:
package = json.load(f)
# Process package
name = package.get('name', 'unnamed')
dependencies = package.get('dependencies', {})
# Check main dependency
if 'RT_USING_' + name.upper() not in dependencies:
main_depend = 'RT_USING_' + name.upper().replace('-', '_')
else:
main_depend = list(dependencies.keys())[0]
if not env.GetDepend(main_depend):
return []
# Collect sources
src = []
include_paths = []
sources = package.get('sources', {})
for category, config in sources.items():
# Check condition
condition = config.get('condition')
if condition and not eval(condition, {'env': env, 'GetDepend': env.GetDepend}):
continue
# Add source files
source_files = config.get('source_files', [])
for pattern in source_files:
src.extend(env.Glob(pattern))
# Add header paths
header_path = config.get('header_path', [])
include_paths.extend(header_path)
# Create group
return env.DefineGroup(name, src, depend=main_depend, CPPPATH=include_paths)
@staticmethod
def Glob(env, pattern: str) -> List[str]:
"""
Enhanced glob with better error handling.
Args:
env: SCons Environment
pattern: File pattern
Returns:
List of matching files
"""
try:
files = Glob(pattern, strings=True)
return sorted(files) # Sort for consistent ordering
except Exception as e:
context = BuildContext.get_current()
if context:
context.logger.warning(f"Glob pattern '{pattern}' failed: {e}")
return []
@staticmethod
def GetBuildOptions(env) -> Dict[str, Any]:
"""
Get build options.
Args:
env: SCons Environment
Returns:
Dictionary of build options
"""
context = BuildContext.get_current()
if context:
return context.build_options
return {}
@staticmethod
def GetContext(env) -> Optional[BuildContext]:
"""
Get current build context.
Args:
env: SCons Environment
Returns:
BuildContext instance or None
"""
return BuildContext.get_current()
@staticmethod
def GetRTTRoot(env) -> str:
"""
Get RT-Thread root directory.
Args:
env: SCons Environment
Returns:
RT-Thread root path
"""
return env.get('RTT_ROOT', '')
@staticmethod
def GetBSPRoot(env) -> str:
"""
Get BSP root directory.
Args:
env: SCons Environment
Returns:
BSP root path
"""
return env.get('BSP_ROOT', '')

368
tools/ng/generator.py Normal file
View File

@ -0,0 +1,368 @@
# -*- coding: utf-8 -*-
"""
Project generator framework for RT-Thread build system.
This module provides the base classes for project generators (MDK, IAR, VS Code, etc.).
"""
import os
import shutil
import json
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from .utils import PathService
@dataclass
class GeneratorConfig:
"""Configuration for project generators."""
output_dir: str
project_name: str = "rtthread"
target_name: str = "rtthread.elf"
class ProjectGenerator(ABC):
"""Abstract base class for project generators."""
def __init__(self, config: GeneratorConfig):
self.config = config
self.path_service = PathService(os.getcwd())
@abstractmethod
def get_name(self) -> str:
"""Get generator name."""
pass
@abstractmethod
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""
Generate project files.
Args:
context: BuildContext instance
project_info: Project information from registry
Returns:
True if successful
"""
pass
@abstractmethod
def clean(self) -> bool:
"""
Clean generated files.
Returns:
True if successful
"""
pass
def _ensure_output_dir(self) -> None:
"""Ensure output directory exists."""
os.makedirs(self.config.output_dir, exist_ok=True)
def _copy_template(self, template_name: str, output_name: str = None) -> str:
"""
Copy template file to output directory.
Args:
template_name: Template file name
output_name: Output file name (defaults to template_name)
Returns:
Output file path
"""
if output_name is None:
output_name = template_name
template_dir = os.path.join(os.path.dirname(__file__), '..', 'targets')
template_path = os.path.join(template_dir, template_name)
output_path = os.path.join(self.config.output_dir, output_name)
if os.path.exists(template_path):
shutil.copy2(template_path, output_path)
return output_path
else:
raise FileNotFoundError(f"Template not found: {template_path}")
class VscodeGenerator(ProjectGenerator):
"""Visual Studio Code project generator."""
def get_name(self) -> str:
return "vscode"
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""Generate VS Code project files."""
self._ensure_output_dir()
# Create .vscode directory
vscode_dir = os.path.join(self.config.output_dir, '.vscode')
os.makedirs(vscode_dir, exist_ok=True)
# Generate c_cpp_properties.json
self._generate_cpp_properties(vscode_dir, context, project_info)
# Generate tasks.json
self._generate_tasks(vscode_dir, context)
# Generate launch.json
self._generate_launch(vscode_dir, context)
# Generate settings.json
self._generate_settings(vscode_dir)
return True
def clean(self) -> bool:
"""Clean VS Code files."""
vscode_dir = os.path.join(self.config.output_dir, '.vscode')
if os.path.exists(vscode_dir):
shutil.rmtree(vscode_dir)
return True
def _generate_cpp_properties(self, vscode_dir: str, context, project_info: Dict) -> None:
"""Generate c_cpp_properties.json."""
# Get toolchain info
toolchain = context.toolchain_manager.get_current()
compiler_path = ""
if toolchain and toolchain.info:
if toolchain.get_name() == "gcc":
compiler_path = os.path.join(toolchain.info.path, toolchain.info.prefix + "gcc")
config = {
"configurations": [
{
"name": "RT-Thread",
"includePath": [
"${workspaceFolder}/**"
] + project_info.get('all_includes', []),
"defines": [f"{k}={v}" if v != '1' else k
for k, v in project_info.get('all_defines', {}).items()],
"compilerPath": compiler_path,
"cStandard": "c99",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-arm" if "arm" in compiler_path else "gcc-x64"
}
],
"version": 4
}
output_path = os.path.join(vscode_dir, 'c_cpp_properties.json')
with open(output_path, 'w') as f:
json.dump(config, f, indent=4)
def _generate_tasks(self, vscode_dir: str, context) -> None:
"""Generate tasks.json."""
tasks = {
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "scons",
"problemMatcher": "$gcc",
"group": {
"kind": "build",
"isDefault": True
}
},
{
"label": "clean",
"type": "shell",
"command": "scons -c",
"problemMatcher": "$gcc"
},
{
"label": "rebuild",
"type": "shell",
"command": "scons -c && scons",
"problemMatcher": "$gcc"
}
]
}
output_path = os.path.join(vscode_dir, 'tasks.json')
with open(output_path, 'w') as f:
json.dump(tasks, f, indent=4)
def _generate_launch(self, vscode_dir: str, context) -> None:
"""Generate launch.json."""
launch = {
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/" + self.config.target_name,
"device": "STM32F103C8",
"configFiles": [
"interface/stlink-v2.cfg",
"target/stm32f1x.cfg"
]
}
]
}
output_path = os.path.join(vscode_dir, 'launch.json')
with open(output_path, 'w') as f:
json.dump(launch, f, indent=4)
def _generate_settings(self, vscode_dir: str) -> None:
"""Generate settings.json."""
settings = {
"files.associations": {
"*.h": "c",
"*.c": "c",
"*.cpp": "cpp",
"*.cc": "cpp",
"*.cxx": "cpp"
},
"C_Cpp.errorSquiggles": "Enabled"
}
output_path = os.path.join(vscode_dir, 'settings.json')
with open(output_path, 'w') as f:
json.dump(settings, f, indent=4)
class CMakeGenerator(ProjectGenerator):
"""CMake project generator."""
def get_name(self) -> str:
return "cmake"
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""Generate CMakeLists.txt."""
self._ensure_output_dir()
# Get toolchain info
toolchain = context.toolchain_manager.get_current()
lines = [
"cmake_minimum_required(VERSION 3.10)",
"",
"# RT-Thread CMake Project",
f"project({self.config.project_name} C CXX ASM)",
"",
"# C Standard",
"set(CMAKE_C_STANDARD 99)",
"set(CMAKE_CXX_STANDARD 11)",
""
]
# Toolchain configuration
if toolchain and toolchain.get_name() == "gcc":
lines.extend([
"# Toolchain",
f"set(CMAKE_C_COMPILER {toolchain.info.prefix}gcc)",
f"set(CMAKE_CXX_COMPILER {toolchain.info.prefix}g++)",
f"set(CMAKE_ASM_COMPILER {toolchain.info.prefix}gcc)",
""
])
# Include directories
lines.extend([
"# Include directories",
"include_directories("
])
for inc in project_info.get('all_includes', []):
lines.append(f" {inc}")
lines.extend([")", ""])
# Definitions
lines.extend([
"# Definitions",
"add_definitions("
])
for k, v in project_info.get('all_defines', {}).items():
if v == '1':
lines.append(f" -D{k}")
else:
lines.append(f" -D{k}={v}")
lines.extend([")", ""])
# Source files
lines.extend([
"# Source files",
"set(SOURCES"
])
for src in project_info.get('all_sources', []):
lines.append(f" {src}")
lines.extend([")", ""])
# Executable
lines.extend([
"# Executable",
f"add_executable(${{PROJECT_NAME}} ${{SOURCES}})",
""
])
# Libraries
if project_info.get('all_libs'):
lines.extend([
"# Libraries",
f"target_link_libraries(${{PROJECT_NAME}}"
])
for lib in project_info['all_libs']:
lines.append(f" {lib}")
lines.extend([")", ""])
# Write file
output_path = os.path.join(self.config.output_dir, 'CMakeLists.txt')
with open(output_path, 'w') as f:
f.write('\n'.join(lines))
return True
def clean(self) -> bool:
"""Clean CMake files."""
files_to_remove = ['CMakeLists.txt', 'CMakeCache.txt']
dirs_to_remove = ['CMakeFiles']
for file in files_to_remove:
file_path = os.path.join(self.config.output_dir, file)
if os.path.exists(file_path):
os.remove(file_path)
for dir in dirs_to_remove:
dir_path = os.path.join(self.config.output_dir, dir)
if os.path.exists(dir_path):
shutil.rmtree(dir_path)
return True
class GeneratorRegistry:
"""Registry for project generators."""
def __init__(self):
self.generators: Dict[str, type] = {}
self._register_default_generators()
def _register_default_generators(self) -> None:
"""Register default generators."""
self.register("vscode", VscodeGenerator)
self.register("vsc", VscodeGenerator) # Alias
self.register("cmake", CMakeGenerator)
def register(self, name: str, generator_class: type) -> None:
"""Register a generator class."""
self.generators[name] = generator_class
def create_generator(self, name: str, config: GeneratorConfig) -> ProjectGenerator:
"""Create a generator instance."""
if name not in self.generators:
raise ValueError(f"Unknown generator: {name}")
return self.generators[name](config)
def list_generators(self) -> List[str]:
"""List available generators."""
return list(self.generators.keys())

View File

@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
"""
Example of minimal changes needed in building.py to integrate the new OOP system.
This file shows the exact changes that would be made to the original building.py.
"""
# =============================================================================
# CHANGES TO ADD AT THE BEGINNING OF building.py
# =============================================================================
"""
# Add after the imports section in building.py (around line 45)
# Try to import new OOP system
try:
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig,
MergeGroups as ng_MergeGroups
)
NG_AVAILABLE = True
except ImportError:
NG_AVAILABLE = False
"""
# =============================================================================
# CHANGES IN PrepareBuilding FUNCTION
# =============================================================================
"""
# Add these lines in PrepareBuilding function after setting up Env (around line 70)
# Initialize new OOP system if available
if NG_AVAILABLE:
# Initialize build context
ng_context = init_build_context(Rtt_Root)
# Inject methods into environment
inject_environment_methods(Env)
# Store context reference
Env['__NG_Context'] = ng_context
"""
# =============================================================================
# CHANGES AFTER PARSING rtconfig.h
# =============================================================================
"""
# Add after parsing rtconfig.h (around line 430)
# Load configuration into new system
if NG_AVAILABLE and 'rtconfig.h' in os.listdir(Bsp_Root):
ng_load_rtconfig('rtconfig.h')
"""
# =============================================================================
# ENHANCED DefineGroup FUNCTION
# =============================================================================
"""
# Replace the original DefineGroup function (around line 565) with:
def DefineGroup(name, src, depend, **parameters):
global Env
if Env is None:
return []
# Try to use new implementation if available
if NG_AVAILABLE and hasattr(Env, 'DefineGroup'):
return Env.DefineGroup(name, src, depend, **parameters)
# Original implementation continues below...
# [Keep all the original DefineGroup code here]
"""
# =============================================================================
# ENHANCED GetDepend FUNCTION
# =============================================================================
"""
# Replace the original GetDepend function (around line 655) with:
def GetDepend(depend):
global Env
# Try to use new implementation if available
if NG_AVAILABLE and Env and hasattr(Env, 'GetDepend'):
return Env.GetDepend(depend)
# Original implementation continues below...
# [Keep all the original GetDepend code here]
"""
# =============================================================================
# ENHANCED MergeGroup FUNCTION
# =============================================================================
"""
# Replace the original MergeGroup function (around line 700) with:
def MergeGroup(src_group, group):
# Try to use new implementation if available
if NG_AVAILABLE and Env and hasattr(Env, '__NG_Context'):
context = Env['__NG_Context']
if context:
# Register groups with new system
from ng.project import ProjectGroup
for g in group:
if 'name' in g:
pg = ProjectGroup(
name=g['name'],
sources=g.get('src', []),
dependencies=[],
environment=Env
)
context.register_project_group(pg)
# Original implementation continues below...
# [Keep all the original MergeGroup code here]
"""
# =============================================================================
# EXAMPLE USAGE IN SCONSCRIPT
# =============================================================================
def example_sconscript():
"""
Example of how to use the new features in a SConscript file.
"""
sconscript_content = '''
from building import *
# Get environment
env = GetEnvironment()
# Method 1: Use new environment methods (if available)
if hasattr(env, 'DefineGroup'):
# New OOP style
src = env.GlobFiles('*.c')
group = env.DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
else:
# Fallback to traditional style
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
# Method 2: Always compatible style
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
Return('group')
'''
return sconscript_content
# =============================================================================
# MINIMAL CHANGES SUMMARY
# =============================================================================
"""
Summary of changes needed in building.py:
1. Add imports at the beginning (5 lines)
2. Add initialization in PrepareBuilding (6 lines)
3. Add config loading after rtconfig.h parsing (3 lines)
4. Modify DefineGroup to check for new method (3 lines)
5. Modify GetDepend to check for new method (3 lines)
6. Enhance MergeGroup to register with new system (15 lines)
Total: ~35 lines of code added/modified in building.py
Benefits:
- Fully backward compatible
- Opt-in design (works even if ng module is not present)
- Gradual migration path
- No changes needed in existing SConscript files
"""

260
tools/ng/project.py Normal file
View File

@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
"""
Project and group management for RT-Thread build system.
This module provides classes for managing project groups and their compilation.
"""
import os
from typing import List, Dict, Any, Optional
from dataclasses import dataclass, field
from SCons.Script import *
@dataclass
class ProjectGroup:
"""
Represents a project group (component).
This class encapsulates the information from DefineGroup calls.
"""
name: str
sources: List[str]
dependencies: List[str] = field(default_factory=list)
environment: Any = None # SCons Environment
# Paths and defines
include_paths: List[str] = field(default_factory=list)
defines: Dict[str, str] = field(default_factory=dict)
# Compiler flags
cflags: str = ""
cxxflags: str = ""
asflags: str = ""
ldflags: str = ""
# Local options (only for this group)
local_cflags: str = ""
local_cxxflags: str = ""
local_include_paths: List[str] = field(default_factory=list)
local_defines: Dict[str, str] = field(default_factory=dict)
# Libraries
libs: List[str] = field(default_factory=list)
lib_paths: List[str] = field(default_factory=list)
# Build objects
objects: List[Any] = field(default_factory=list)
def build(self, env) -> List:
"""
Build the group and return objects.
Args:
env: SCons Environment
Returns:
List of build objects
"""
if not self.sources:
return []
# Clone environment if we have local options
build_env = env
if self._has_local_options():
build_env = env.Clone()
self._apply_local_options(build_env)
# Apply global options
self._apply_global_options(build_env)
# Build objects
self.objects = []
for src in self.sources:
if isinstance(src, str):
# Build single file
obj = build_env.Object(src)
self.objects.extend(obj if isinstance(obj, list) else [obj])
else:
# Already a Node
self.objects.append(src)
return self.objects
def _has_local_options(self) -> bool:
"""Check if group has local options."""
return bool(
self.local_cflags or
self.local_cxxflags or
self.local_include_paths or
self.local_defines
)
def _apply_local_options(self, env) -> None:
"""Apply local options to environment."""
if self.local_cflags:
env.AppendUnique(CFLAGS=self.local_cflags.split())
if self.local_cxxflags:
env.AppendUnique(CXXFLAGS=self.local_cxxflags.split())
if self.local_include_paths:
paths = [os.path.abspath(p) for p in self.local_include_paths]
env.AppendUnique(CPPPATH=paths)
if self.local_defines:
env.AppendUnique(CPPDEFINES=self.local_defines)
def _apply_global_options(self, env) -> None:
"""Apply global options to environment."""
# These options affect dependent groups too
if self.include_paths:
paths = [os.path.abspath(p) for p in self.include_paths]
env.AppendUnique(CPPPATH=paths)
if self.defines:
env.AppendUnique(CPPDEFINES=self.defines)
if self.cflags and 'CFLAGS' not in env:
env['CFLAGS'] = self.cflags
if self.cxxflags and 'CXXFLAGS' not in env:
env['CXXFLAGS'] = self.cxxflags
if self.libs:
env.AppendUnique(LIBS=self.libs)
if self.lib_paths:
paths = [os.path.abspath(p) for p in self.lib_paths]
env.AppendUnique(LIBPATH=paths)
def get_info(self) -> Dict[str, Any]:
"""
Get group information for project generators.
Returns:
Dictionary with group information
"""
return {
'name': self.name,
'sources': self.sources,
'include_paths': self.include_paths + self.local_include_paths,
'defines': {**self.defines, **self.local_defines},
'cflags': f"{self.cflags} {self.local_cflags}".strip(),
'cxxflags': f"{self.cxxflags} {self.local_cxxflags}".strip(),
'libs': self.libs,
'lib_paths': self.lib_paths
}
class ProjectRegistry:
"""
Registry for all project groups.
This class manages all registered project groups and provides
methods for querying and merging them.
"""
def __init__(self):
self.groups: List[ProjectGroup] = []
self._group_index: Dict[str, ProjectGroup] = {}
def register_group(self, group: ProjectGroup) -> None:
"""
Register a project group.
Args:
group: ProjectGroup instance
"""
self.groups.append(group)
self._group_index[group.name] = group
def get_group(self, name: str) -> Optional[ProjectGroup]:
"""
Get group by name.
Args:
name: Group name
Returns:
ProjectGroup or None
"""
return self._group_index.get(name)
def get_all_groups(self) -> List[ProjectGroup]:
"""Get all registered groups."""
return self.groups.copy()
def get_groups_by_dependency(self, dependency: str) -> List[ProjectGroup]:
"""
Get groups that depend on a specific macro.
Args:
dependency: Dependency name
Returns:
List of matching groups
"""
return [g for g in self.groups if dependency in g.dependencies]
def merge_groups(self, env) -> List:
"""
Merge all groups into a single list of objects.
Args:
env: SCons Environment
Returns:
List of all build objects
"""
all_objects = []
for group in self.groups:
if group.objects:
all_objects.extend(group.objects)
return all_objects
def get_project_info(self) -> Dict[str, Any]:
"""
Get complete project information for generators.
Returns:
Dictionary with project information
"""
# Collect all unique values
all_sources = []
all_includes = set()
all_defines = {}
all_libs = []
all_lib_paths = set()
for group in self.groups:
info = group.get_info()
# Sources
all_sources.extend(info['sources'])
# Include paths
all_includes.update(info['include_paths'])
# Defines
all_defines.update(info['defines'])
# Libraries
all_libs.extend(info['libs'])
all_lib_paths.update(info['lib_paths'])
return {
'groups': [g.get_info() for g in self.groups],
'all_sources': all_sources,
'all_includes': sorted(list(all_includes)),
'all_defines': all_defines,
'all_libs': all_libs,
'all_lib_paths': sorted(list(all_lib_paths))
}
def clear(self) -> None:
"""Clear all registered groups."""
self.groups.clear()
self._group_index.clear()

396
tools/ng/toolchain.py Normal file
View File

@ -0,0 +1,396 @@
# -*- coding: utf-8 -*-
"""
Toolchain management for RT-Thread build system.
This module provides abstraction for different toolchains (GCC, Keil, IAR, etc.).
"""
import os
import shutil
import subprocess
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
@dataclass
class ToolchainInfo:
"""Toolchain information."""
name: str
version: str
path: str
prefix: str = ""
suffix: str = ""
class Toolchain(ABC):
"""Abstract base class for toolchains."""
def __init__(self):
self.info = None
@abstractmethod
def get_name(self) -> str:
"""Get toolchain name."""
pass
@abstractmethod
def detect(self) -> bool:
"""Detect if toolchain is available."""
pass
@abstractmethod
def configure_environment(self, env) -> None:
"""Configure SCons environment for this toolchain."""
pass
@abstractmethod
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get compilation flags for target CPU."""
pass
def get_version(self) -> Optional[str]:
"""Get toolchain version."""
return self.info.version if self.info else None
def _run_command(self, cmd: List[str]) -> Tuple[int, str, str]:
"""Run command and return (returncode, stdout, stderr)."""
try:
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode, result.stdout, result.stderr
except Exception as e:
return -1, "", str(e)
class GccToolchain(Toolchain):
"""GCC toolchain implementation."""
def __init__(self, prefix: str = ""):
super().__init__()
self.prefix = prefix or "arm-none-eabi-"
def get_name(self) -> str:
return "gcc"
def detect(self) -> bool:
"""Detect GCC toolchain."""
gcc_path = shutil.which(self.prefix + "gcc")
if not gcc_path:
return False
# Get version
ret, stdout, _ = self._run_command([gcc_path, "--version"])
if ret == 0:
lines = stdout.split('\n')
if lines:
version = lines[0].split()[-1]
self.info = ToolchainInfo(
name="gcc",
version=version,
path=os.path.dirname(gcc_path),
prefix=self.prefix
)
return True
return False
def configure_environment(self, env) -> None:
"""Configure environment for GCC."""
env['CC'] = self.prefix + 'gcc'
env['CXX'] = self.prefix + 'g++'
env['AS'] = self.prefix + 'gcc'
env['AR'] = self.prefix + 'ar'
env['LINK'] = self.prefix + 'gcc'
env['SIZE'] = self.prefix + 'size'
env['OBJDUMP'] = self.prefix + 'objdump'
env['OBJCPY'] = self.prefix + 'objcopy'
# Set default flags
env['ARFLAGS'] = '-rc'
env['ASFLAGS'] = '-x assembler-with-cpp'
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get GCC compilation flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU flags
cpu_flags = {
'cortex-m0': '-mcpu=cortex-m0 -mthumb',
'cortex-m0+': '-mcpu=cortex-m0plus -mthumb',
'cortex-m3': '-mcpu=cortex-m3 -mthumb',
'cortex-m4': '-mcpu=cortex-m4 -mthumb',
'cortex-m7': '-mcpu=cortex-m7 -mthumb',
'cortex-m23': '-mcpu=cortex-m23 -mthumb',
'cortex-m33': '-mcpu=cortex-m33 -mthumb',
'cortex-a7': '-mcpu=cortex-a7',
'cortex-a9': '-mcpu=cortex-a9'
}
if cpu in cpu_flags:
base_flags = cpu_flags[cpu]
for key in ['CFLAGS', 'CXXFLAGS', 'ASFLAGS']:
flags[key].append(base_flags)
# FPU flags
if fpu:
fpu_flag = f'-mfpu={fpu}'
for key in ['CFLAGS', 'CXXFLAGS']:
flags[key].append(fpu_flag)
# Float ABI
if float_abi:
abi_flag = f'-mfloat-abi={float_abi}'
for key in ['CFLAGS', 'CXXFLAGS']:
flags[key].append(abi_flag)
# Common flags
common_flags = ['-ffunction-sections', '-fdata-sections']
flags['CFLAGS'].extend(common_flags)
flags['CXXFLAGS'].extend(common_flags)
# Linker flags
flags['LDFLAGS'].extend(['-Wl,--gc-sections'])
# Convert lists to strings
return {k: ' '.join(v) for k, v in flags.items()}
class ArmccToolchain(Toolchain):
"""ARM Compiler (Keil) toolchain implementation."""
def get_name(self) -> str:
return "armcc"
def detect(self) -> bool:
"""Detect ARM Compiler toolchain."""
armcc_path = shutil.which("armcc")
if not armcc_path:
# Try common Keil installation paths
keil_paths = [
r"C:\Keil_v5\ARM\ARMCC\bin",
r"C:\Keil\ARM\ARMCC\bin",
"/opt/arm/bin"
]
for path in keil_paths:
test_path = os.path.join(path, "armcc")
if os.path.exists(test_path):
armcc_path = test_path
break
if not armcc_path:
return False
# Get version
ret, stdout, _ = self._run_command([armcc_path, "--version"])
if ret == 0:
lines = stdout.split('\n')
for line in lines:
if "ARM Compiler" in line:
version = line.split()[-1]
self.info = ToolchainInfo(
name="armcc",
version=version,
path=os.path.dirname(armcc_path)
)
return True
return False
def configure_environment(self, env) -> None:
"""Configure environment for ARM Compiler."""
env['CC'] = 'armcc'
env['CXX'] = 'armcc'
env['AS'] = 'armasm'
env['AR'] = 'armar'
env['LINK'] = 'armlink'
# ARM Compiler specific settings
env['ARCOM'] = '$AR --create $TARGET $SOURCES'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
env['LIBLINKPREFIX'] = ''
env['LIBLINKSUFFIX'] = '.lib'
env['LIBDIRPREFIX'] = '--userlibpath '
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get ARM Compiler flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU selection
cpu_map = {
'cortex-m0': '--cpu Cortex-M0',
'cortex-m0+': '--cpu Cortex-M0+',
'cortex-m3': '--cpu Cortex-M3',
'cortex-m4': '--cpu Cortex-M4',
'cortex-m7': '--cpu Cortex-M7'
}
if cpu in cpu_map:
cpu_flag = cpu_map[cpu]
for key in flags:
flags[key].append(cpu_flag)
# Common flags
flags['CFLAGS'].extend(['--c99', '--gnu'])
flags['CXXFLAGS'].extend(['--cpp', '--gnu'])
return {k: ' '.join(v) for k, v in flags.items()}
class IarToolchain(Toolchain):
"""IAR toolchain implementation."""
def get_name(self) -> str:
return "iar"
def detect(self) -> bool:
"""Detect IAR toolchain."""
iccarm_path = shutil.which("iccarm")
if not iccarm_path:
# Try common IAR installation paths
iar_paths = [
r"C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\bin",
r"C:\Program Files\IAR Systems\Embedded Workbench 8.0\arm\bin",
"/opt/iar/bin"
]
for path in iar_paths:
test_path = os.path.join(path, "iccarm.exe" if os.name == 'nt' else "iccarm")
if os.path.exists(test_path):
iccarm_path = test_path
break
if not iccarm_path:
return False
self.info = ToolchainInfo(
name="iar",
version="8.x", # IAR version detection is complex
path=os.path.dirname(iccarm_path)
)
return True
def configure_environment(self, env) -> None:
"""Configure environment for IAR."""
env['CC'] = 'iccarm'
env['CXX'] = 'iccarm'
env['AS'] = 'iasmarm'
env['AR'] = 'iarchive'
env['LINK'] = 'ilinkarm'
# IAR specific settings
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.a'
env['LIBLINKPREFIX'] = ''
env['LIBLINKSUFFIX'] = '.a'
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get IAR flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU selection
cpu_map = {
'cortex-m0': '--cpu=Cortex-M0',
'cortex-m0+': '--cpu=Cortex-M0+',
'cortex-m3': '--cpu=Cortex-M3',
'cortex-m4': '--cpu=Cortex-M4',
'cortex-m7': '--cpu=Cortex-M7'
}
if cpu in cpu_map:
cpu_flag = cpu_map[cpu]
flags['CFLAGS'].append(cpu_flag)
flags['CXXFLAGS'].append(cpu_flag)
# Common flags
flags['CFLAGS'].extend(['-e', '--dlib_config', 'DLib_Config_Normal.h'])
return {k: ' '.join(v) for k, v in flags.items()}
class ToolchainManager:
"""Manager for toolchain selection and configuration."""
def __init__(self):
self.toolchains: Dict[str, Toolchain] = {}
self.current_toolchain: Optional[Toolchain] = None
self._register_default_toolchains()
def _register_default_toolchains(self) -> None:
"""Register default toolchains."""
# Try to detect available toolchains
toolchain_classes = [
(GccToolchain, ['arm-none-eabi-', 'riscv32-unknown-elf-', 'riscv64-unknown-elf-']),
(ArmccToolchain, ['']),
(IarToolchain, [''])
]
for toolchain_class, prefixes in toolchain_classes:
for prefix in prefixes:
if toolchain_class == GccToolchain:
tc = toolchain_class(prefix)
else:
tc = toolchain_class()
if tc.detect():
name = f"{tc.get_name()}-{prefix}" if prefix else tc.get_name()
self.register_toolchain(name, tc)
def register_toolchain(self, name: str, toolchain: Toolchain) -> None:
"""Register a toolchain."""
self.toolchains[name] = toolchain
def select_toolchain(self, name: str) -> Toolchain:
"""Select a toolchain by name."""
if name not in self.toolchains:
# Try to create it
if name == 'gcc':
tc = GccToolchain()
elif name == 'armcc' or name == 'keil':
tc = ArmccToolchain()
elif name == 'iar':
tc = IarToolchain()
else:
raise ValueError(f"Unknown toolchain: {name}")
if tc.detect():
self.register_toolchain(name, tc)
else:
raise RuntimeError(f"Toolchain '{name}' not found")
self.current_toolchain = self.toolchains[name]
return self.current_toolchain
def get_current(self) -> Optional[Toolchain]:
"""Get current toolchain."""
return self.current_toolchain
def list_toolchains(self) -> List[str]:
"""List available toolchains."""
return list(self.toolchains.keys())

339
tools/ng/utils.py Normal file
View File

@ -0,0 +1,339 @@
# -*- coding: utf-8 -*-
"""
Utility functions for RT-Thread build system.
This module provides common utility functions used throughout the build system.
"""
import os
import sys
import platform
from typing import List, Tuple, Optional
class PathService:
"""Service for path manipulation and normalization."""
def __init__(self, base_path: str = None):
self.base_path = base_path or os.getcwd()
def normalize_path(self, path: str) -> str:
"""
Normalize path for cross-platform compatibility.
Args:
path: Path to normalize
Returns:
Normalized path
"""
# Convert to absolute path if relative
if not os.path.isabs(path):
path = os.path.abspath(os.path.join(self.base_path, path))
# Normalize separators
path = os.path.normpath(path)
# Convert to forward slashes for consistency
if platform.system() == 'Windows':
path = path.replace('\\', '/')
return path
def make_relative(self, path: str, base: str = None) -> str:
"""
Make path relative to base.
Args:
path: Path to make relative
base: Base path (defaults to self.base_path)
Returns:
Relative path
"""
if base is None:
base = self.base_path
path = self.normalize_path(path)
base = self.normalize_path(base)
try:
rel_path = os.path.relpath(path, base)
# Convert to forward slashes
if platform.system() == 'Windows':
rel_path = rel_path.replace('\\', '/')
return rel_path
except ValueError:
# Different drives on Windows
return path
def split_path(self, path: str) -> List[str]:
"""
Split path into components.
Args:
path: Path to split
Returns:
List of path components
"""
path = self.normalize_path(path)
parts = []
while True:
head, tail = os.path.split(path)
if tail:
parts.insert(0, tail)
if head == path: # Reached root
if head:
parts.insert(0, head)
break
path = head
return parts
def common_prefix(self, paths: List[str]) -> str:
"""
Find common prefix of multiple paths.
Args:
paths: List of paths
Returns:
Common prefix path
"""
if not paths:
return ""
# Normalize all paths
normalized = [self.normalize_path(p) for p in paths]
# Find common prefix
prefix = os.path.commonpath(normalized)
return self.normalize_path(prefix)
class PlatformInfo:
"""Platform and system information."""
@staticmethod
def get_platform() -> str:
"""Get platform name (Windows, Linux, Darwin)."""
return platform.system()
@staticmethod
def get_architecture() -> str:
"""Get system architecture."""
return platform.machine()
@staticmethod
def is_windows() -> bool:
"""Check if running on Windows."""
return platform.system() == 'Windows'
@staticmethod
def is_linux() -> bool:
"""Check if running on Linux."""
return platform.system() == 'Linux'
@staticmethod
def is_macos() -> bool:
"""Check if running on macOS."""
return platform.system() == 'Darwin'
@staticmethod
def get_python_version() -> Tuple[int, int, int]:
"""Get Python version tuple."""
return sys.version_info[:3]
@staticmethod
def check_python_version(min_version: Tuple[int, int]) -> bool:
"""
Check if Python version meets minimum requirement.
Args:
min_version: Minimum version tuple (major, minor)
Returns:
True if version is sufficient
"""
current = sys.version_info[:2]
return current >= min_version
class FileUtils:
"""File operation utilities."""
@staticmethod
def read_file(filepath: str, encoding: str = 'utf-8') -> str:
"""
Read file content.
Args:
filepath: File path
encoding: File encoding
Returns:
File content
"""
with open(filepath, 'r', encoding=encoding) as f:
return f.read()
@staticmethod
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> None:
"""
Write content to file.
Args:
filepath: File path
content: Content to write
encoding: File encoding
"""
# Ensure directory exists
directory = os.path.dirname(filepath)
if directory:
os.makedirs(directory, exist_ok=True)
with open(filepath, 'w', encoding=encoding) as f:
f.write(content)
@staticmethod
def copy_file(src: str, dst: str) -> None:
"""
Copy file from src to dst.
Args:
src: Source file path
dst: Destination file path
"""
import shutil
# Ensure destination directory exists
dst_dir = os.path.dirname(dst)
if dst_dir:
os.makedirs(dst_dir, exist_ok=True)
shutil.copy2(src, dst)
@staticmethod
def find_files(directory: str, pattern: str, recursive: bool = True) -> List[str]:
"""
Find files matching pattern.
Args:
directory: Directory to search
pattern: File pattern (supports wildcards)
recursive: Search recursively
Returns:
List of matching file paths
"""
import fnmatch
matches = []
if recursive:
for root, dirnames, filenames in os.walk(directory):
for filename in filenames:
if fnmatch.fnmatch(filename, pattern):
matches.append(os.path.join(root, filename))
else:
try:
filenames = os.listdir(directory)
for filename in filenames:
if fnmatch.fnmatch(filename, pattern):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
matches.append(filepath)
except OSError:
pass
return sorted(matches)
class VersionUtils:
"""Version comparison utilities."""
@staticmethod
def parse_version(version_str: str) -> Tuple[int, ...]:
"""
Parse version string to tuple.
Args:
version_str: Version string (e.g., "1.2.3")
Returns:
Version tuple
"""
try:
parts = version_str.split('.')
return tuple(int(p) for p in parts if p.isdigit())
except (ValueError, AttributeError):
return (0,)
@staticmethod
def compare_versions(v1: str, v2: str) -> int:
"""
Compare two version strings.
Args:
v1: First version
v2: Second version
Returns:
-1 if v1 < v2, 0 if equal, 1 if v1 > v2
"""
t1 = VersionUtils.parse_version(v1)
t2 = VersionUtils.parse_version(v2)
# Pad shorter version with zeros
if len(t1) < len(t2):
t1 = t1 + (0,) * (len(t2) - len(t1))
elif len(t2) < len(t1):
t2 = t2 + (0,) * (len(t1) - len(t2))
if t1 < t2:
return -1
elif t1 > t2:
return 1
else:
return 0
@staticmethod
def version_satisfies(version: str, requirement: str) -> bool:
"""
Check if version satisfies requirement.
Args:
version: Version string
requirement: Requirement string (e.g., ">=1.2.0")
Returns:
True if satisfied
"""
import re
# Parse requirement
match = re.match(r'([<>=]+)\s*(.+)', requirement)
if not match:
# Exact match required
return version == requirement
op, req_version = match.groups()
cmp = VersionUtils.compare_versions(version, req_version)
if op == '>=':
return cmp >= 0
elif op == '<=':
return cmp <= 0
elif op == '>':
return cmp > 0
elif op == '<':
return cmp < 0
elif op == '==':
return cmp == 0
elif op == '!=':
return cmp != 0
else:
return False