mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-05-31 20:05:59 +08:00
[Feature][Tools] Add support for package.json, refactor BuildPackage function to handle new format.
This commit is contained in:
@@ -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构建系统完全集成,不支持独立构建
|
||||||
+22
-11
@@ -1,20 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "hello",
|
"name": "hello",
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Hello World component for RT-Thread",
|
"description": "Hello World component for RT-Thread",
|
||||||
"author": "RT-Thread Development Team",
|
"type": "rt-thread-component",
|
||||||
"license": "Apache-2.0",
|
"dependencies": [],
|
||||||
"type": "rt-package",
|
"defines": [
|
||||||
"source_files": [
|
"DEFINE_HELLO"
|
||||||
"hello.c"
|
|
||||||
],
|
],
|
||||||
"CPPPATH": [
|
"sources": [
|
||||||
|
{
|
||||||
|
"dependencies": [],
|
||||||
|
"includes": [
|
||||||
"."
|
"."
|
||||||
],
|
],
|
||||||
"CPPDEFINES": [
|
"files": [
|
||||||
"HELLO"
|
"hello.c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": [
|
||||||
|
"HELLO_USING_HELPER"
|
||||||
],
|
],
|
||||||
"depends": [
|
"includes": [
|
||||||
""
|
"src"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"src/helper.c"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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__
|
||||||
+40
-46
@@ -170,63 +170,57 @@ class RTEnv:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
env: SCons Environment
|
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:
|
Returns:
|
||||||
List of build objects
|
List of build objects
|
||||||
"""
|
"""
|
||||||
import json
|
# Import the existing package module
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
# Find package.json
|
# Get the building module path
|
||||||
|
building_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
tools_path = os.path.dirname(building_path)
|
||||||
|
|
||||||
|
# Add to path if not already there
|
||||||
|
if tools_path not in sys.path:
|
||||||
|
sys.path.insert(0, tools_path)
|
||||||
|
|
||||||
|
# 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:
|
if package_path is None:
|
||||||
package_path = 'package.json'
|
work_dir = env.GetCurrentDir()
|
||||||
elif os.path.isdir(package_path):
|
elif os.path.isdir(package_path):
|
||||||
package_path = os.path.join(package_path, 'package.json')
|
work_dir = package_path
|
||||||
|
|
||||||
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:
|
else:
|
||||||
main_depend = list(dependencies.keys())[0]
|
work_dir = os.path.dirname(package_path)
|
||||||
|
|
||||||
if not env.GetDepend(main_depend):
|
os.chdir(work_dir)
|
||||||
|
|
||||||
|
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 []
|
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
|
@staticmethod
|
||||||
def Glob(env, pattern: str) -> List[str]:
|
def Glob(env, pattern: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+69
-21
@@ -41,41 +41,89 @@ def ExtendPackageVar(package, var):
|
|||||||
def BuildPackage(package = None):
|
def BuildPackage(package = None):
|
||||||
if package is None:
|
if package is None:
|
||||||
package = os.path.join(GetCurrentDir(), 'package.json')
|
package = os.path.join(GetCurrentDir(), 'package.json')
|
||||||
|
elif os.path.isdir(package):
|
||||||
if not os.path.isfile(package):
|
# support directory path
|
||||||
print("%s/package.json not found" % GetCurrentDir())
|
package = os.path.join(package, 'package.json')
|
||||||
return []
|
|
||||||
|
|
||||||
f = open(package)
|
|
||||||
package_json = f.read()
|
|
||||||
|
|
||||||
# get package.json path
|
# get package.json path
|
||||||
cwd = os.path.dirname(package)
|
cwd = os.path.dirname(os.path.abspath(package))
|
||||||
|
|
||||||
|
if not os.path.isfile(package):
|
||||||
|
# silent return for conditional usage
|
||||||
|
return []
|
||||||
|
|
||||||
|
with open(package, 'r') as f:
|
||||||
|
package_json = f.read()
|
||||||
package = json.loads(package_json)
|
package = json.loads(package_json)
|
||||||
|
|
||||||
# check package name
|
# check package name
|
||||||
if 'name' not in package or 'type' not in package or package['type'] != 'rt-package':
|
if 'name' not in package or 'type' not in package or package['type'] != 'rt-thread-component':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# get depends
|
# get depends
|
||||||
depend = ExtendPackageVar(package, 'depends')
|
depend = []
|
||||||
|
if 'dependencies' in package:
|
||||||
|
depend = ExtendPackageVar(package, 'dependencies')
|
||||||
|
|
||||||
|
# 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 = []
|
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)
|
|
||||||
|
|
||||||
CPPPATH = []
|
CPPPATH = []
|
||||||
if 'CPPPATH' in package:
|
if 'sources' in package:
|
||||||
for path in package['CPPPATH']:
|
src_depend = []
|
||||||
if path.startswith('/') and os.path.isdir(path):
|
src_CPPPATH = []
|
||||||
CPPPATH = CPPPATH + [path]
|
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:
|
else:
|
||||||
CPPPATH = CPPPATH + [os.path.join(cwd, path)]
|
path = os.path.abspath(os.path.join(cwd, include))
|
||||||
|
src_CPPPATH = src_CPPPATH + [path]
|
||||||
|
|
||||||
CPPDEFINES = ExtendPackageVar(package, 'CPPDEFINES')
|
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)
|
objs = DefineGroup(package['name'], src, depend = depend, CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user