[Feature][Tools] Add support for package.json, refactor BuildPackage function to handle new format.

This commit is contained in:
bernard
2025-08-03 03:57:54 +08:00
committed by R b b666
parent 5c568f0280
commit fbdab95299
6 changed files with 303 additions and 91 deletions

View File

@@ -0,0 +1,128 @@
# RT-Thread package.json 构建支持
## 概述
RT-Thread支持使用package.json来定义组件的构建配置作为传统SConscript的简化替代方案。
## 现有支持
### package.json格式
```json
{
"name": "hello",
"description": "Hello World component for RT-Thread",
"type": "rt-thread-component",
"dependencies": ["RT_USING_HELLO"],
"defines": [],
"sources": [{
"name": "src",
"dependencies": [],
"includes": ["."],
"files": ["hello.c"]
}]
}
```
### 字段说明
- **name**: 组件名称(必需)
- **type**: 必须为"rt-thread-component"(必需)
- **description**: 组件描述
- **dependencies**: 全局依赖,数组形式的宏定义
- **defines**: 全局宏定义
- **sources**: 源文件组数组,每组可包含:
- **name**: 源组名称
- **dependencies**: 源组特定依赖
- **includes**: 头文件搜索路径
- **files**: 源文件列表(支持通配符)
## 使用方式
### 1. 在SConscript中使用
方式一使用PackageSConscript推荐
```python
from building import *
objs = PackageSConscript('package.json')
Return('objs')
```
方式二直接调用BuildPackage
```python
Import('env')
from package import BuildPackage
objs = BuildPackage('package.json')
Return('objs')
```
### 2. 目录结构示例
```
mycomponent/
├── SConscript
├── package.json
├── mycomponent.c
├── mycomponent.h
└── src/
└── helper.c
```
### 3. 完整示例
package.json:
```json
{
"name": "mycomponent",
"description": "My RT-Thread component",
"type": "rt-thread-component",
"dependencies": ["RT_USING_MYCOMPONENT"],
"defines": ["MY_VERSION=1"],
"sources": [
{
"name": "main",
"dependencies": [],
"includes": ["."],
"files": ["mycomponent.c"]
},
{
"name": "helper",
"dependencies": ["RT_USING_MYCOMPONENT_HELPER"],
"includes": ["src"],
"files": ["src/*.c"]
}
]
}
```
## 工作原理
1. **依赖检查**首先检查全局dependencies如果不满足则跳过整个组件
2. **源组处理**遍历sources数组每个源组独立检查dependencies
3. **路径处理**includes相对路径基于package.json所在目录
4. **文件匹配**使用SCons的Glob函数处理文件通配符
5. **构建调用**最终调用DefineGroup创建构建组
## 与DefineGroup的对比
| 特性 | package.json | DefineGroup |
|------|--------------|-------------|
| 配置方式 | JSON文件 | Python代码 |
| 依赖管理 | 结构化 | 函数参数 |
| 源文件组织 | 分组管理 | 单一列表 |
| 条件编译 | 源组级别 | 整体级别 |
| 灵活性 | 中等 | 高 |
| 易用性 | 高 | 中等 |
## 最佳实践
1. **简单组件优先使用package.json**:配置清晰,易于维护
2. **复杂逻辑使用SConscript**:需要动态逻辑时使用传统方式
3. **混合使用**:可以在同一项目中混用两种方式
## 注意事项
1. package.json必须是有效的JSON格式
2. type字段必须为"rt-thread-component"
3. 文件路径相对于package.json所在目录
4. 依赖不满足时会静默跳过,不会报错
5. 与RT-Thread构建系统完全集成不支持独立构建

View File

@@ -1,20 +1,31 @@
{
"name": "hello",
"version": "1.0.0",
"description": "Hello World component for RT-Thread",
"author": "RT-Thread Development Team",
"license": "Apache-2.0",
"type": "rt-package",
"source_files": [
"hello.c"
],
"CPPPATH": [
"name": "hello",
"description": "Hello World component for RT-Thread",
"type": "rt-thread-component",
"dependencies": [],
"defines": [
"DEFINE_HELLO"
],
"sources": [
{
"dependencies": [],
"includes": [
"."
],
"CPPDEFINES": [
"HELLO"
],
"depends": [
""
]
],
"files": [
"hello.c"
]
},
{
"dependencies": [
"HELLO_USING_HELPER"
],
"includes": [
"src"
],
"files": [
"src/helper.c"
]
}
]
}

15
tools/hello/src/helper.c Normal file
View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-08-03 Bernard First version
*/
#include "helper.h"
void hello_helper()
{
}

16
tools/hello/src/helper.h Normal file
View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-08-03 Bernard First version
*/
#ifndef __HELPER__H__
#define __HELPER__H__
void hello_helper();
#endif //!__HELPER__H__

View File

@@ -170,62 +170,56 @@ class RTEnv:
Args:
env: SCons Environment
package_path: Path to package.json or directory containing it
package_path: Path to package.json. If None, looks for package.json in current directory.
Returns:
List of build objects
"""
import json
# Import the existing package module
import sys
import os
# 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', {})
# Get the building module path
building_path = os.path.dirname(os.path.abspath(__file__))
tools_path = os.path.dirname(building_path)
# 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 = []
# Add to path if not already there
if tools_path not in sys.path:
sys.path.insert(0, tools_path)
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
# Import and use the existing BuildPackage
try:
from package import BuildPackage as build_package_func
# BuildPackage uses global functions, so we need to set up the context
# Save current directory
current_dir = os.getcwd()
# Change to the directory where we want to build
if package_path is None:
work_dir = env.GetCurrentDir()
elif os.path.isdir(package_path):
work_dir = package_path
else:
work_dir = os.path.dirname(package_path)
# 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)
os.chdir(work_dir)
# Create group
return env.DefineGroup(name, src, depend=main_depend, CPPPATH=include_paths)
try:
# Call the original BuildPackage
result = build_package_func(package_path)
finally:
# Restore directory
os.chdir(current_dir)
return result
except ImportError:
# Fallback if import fails
context = BuildContext.get_current()
if context:
context.logger.error("Failed to import package module")
return []
@staticmethod
def Glob(env, pattern: str) -> List[str]:

View File

@@ -41,41 +41,89 @@ def ExtendPackageVar(package, var):
def BuildPackage(package = None):
if package is None:
package = os.path.join(GetCurrentDir(), 'package.json')
if not os.path.isfile(package):
print("%s/package.json not found" % GetCurrentDir())
return []
f = open(package)
package_json = f.read()
elif os.path.isdir(package):
# support directory path
package = os.path.join(package, 'package.json')
# get package.json path
cwd = os.path.dirname(package)
cwd = os.path.dirname(os.path.abspath(package))
package = json.loads(package_json)
# check package name
if 'name' not in package or 'type' not in package or package['type'] != 'rt-package':
if not os.path.isfile(package):
# silent return for conditional usage
return []
# get depends
depend = ExtendPackageVar(package, 'depends')
with open(package, 'r') as f:
package_json = f.read()
package = json.loads(package_json)
src = []
if 'source_files' in package:
for src_file in package['source_files']:
src_file = os.path.join(cwd, src_file)
src += Glob(src_file)
# check package name
if 'name' not in package or 'type' not in package or package['type'] != 'rt-thread-component':
return []
CPPPATH = []
if 'CPPPATH' in package:
for path in package['CPPPATH']:
if path.startswith('/') and os.path.isdir(path):
CPPPATH = CPPPATH + [path]
else:
CPPPATH = CPPPATH + [os.path.join(cwd, path)]
# get depends
depend = []
if 'dependencies' in package:
depend = ExtendPackageVar(package, 'dependencies')
CPPDEFINES = ExtendPackageVar(package, 'CPPDEFINES')
# check dependencies
if depend:
group_enable = False
for item in depend:
if GetDepend(item):
group_enable = True
break
if not group_enable:
return []
CPPDEFINES = []
if 'defines' in package:
CPPDEFINES = ExtendPackageVar(package, 'defines')
src = []
CPPPATH = []
if 'sources' in package:
src_depend = []
src_CPPPATH = []
for item in package['sources']:
if 'includes' in item:
includes = item['includes']
for include in includes:
if include.startswith('/') and os.path.isdir(include):
src_CPPPATH = src_CPPPATH + [include]
else:
path = os.path.abspath(os.path.join(cwd, include))
src_CPPPATH = src_CPPPATH + [path]
if 'dependencies' in item:
src_depend = src_depend + ExtendPackageVar(item, 'dependencies')
src_enable = False
if src_depend == []:
src_enable = True
else:
for d in src_depend:
if GetDepend(d):
src_enable = True
break
if src_enable:
files = []
src_files = []
if 'files' in item:
files += ExtendPackageVar(item, 'files')
for item in files:
# handle glob patterns relative to package.json directory
old_dir = os.getcwd()
os.chdir(cwd)
try:
src_files += Glob(item)
finally:
os.chdir(old_dir)
src += src_files
CPPPATH += src_CPPPATH
objs = DefineGroup(package['name'], src, depend = depend, CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)