[Tools] Add documents for tools script; Add NG for tools
This commit is contained in:
parent
5347500f33
commit
a65efe648c
|
|
@ -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版本。
|
||||
|
|
@ -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项目的构建提供坚实的基础。
|
||||
|
|
@ -0,0 +1,643 @@
|
|||
# RT-Thread 构建系统使用指南
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [快速开始](#快速开始)
|
||||
3. [命令行选项详解](#命令行选项详解)
|
||||
4. [工具链配置](#工具链配置)
|
||||
5. [项目生成](#项目生成)
|
||||
6. [软件包管理](#软件包管理)
|
||||
7. [高级功能](#高级功能)
|
||||
8. [常见问题](#常见问题)
|
||||
|
||||
## 概述
|
||||
|
||||
RT-Thread使用基于SCons的构建系统,提供了统一的跨平台构建体验。构建系统支持:
|
||||
|
||||
- 多种编译器和IDE(GCC、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. 使用ccache(GCC):
|
||||
```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的构建系统提供了强大而灵活的项目管理能力。通过合理使用各种构建选项和功能,可以高效地进行嵌入式软件开发。建议开发者深入理解构建系统的工作原理,以便更好地利用其功能。
|
||||
|
|
@ -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. 为项目定制构建流程
|
||||
|
||||
构建系统的核心价值在于将复杂的嵌入式构建过程标准化和自动化,让开发者能够专注于功能开发而不是构建配置。
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
# RT-Thread Next Generation Build System
|
||||
|
||||
## 概述
|
||||
|
||||
RT-Thread NG(Next 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许可证。
|
||||
|
|
@ -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'
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
@ -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('_')]
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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', '')
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
"""
|
||||
|
|
@ -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()
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue