diff --git a/tools/docs/package-json-support.md b/tools/docs/package-json-support.md new file mode 100644 index 0000000000..c27aed3648 --- /dev/null +++ b/tools/docs/package-json-support.md @@ -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构建系统完全集成,不支持独立构建 \ No newline at end of file diff --git a/tools/hello/package.json b/tools/hello/package.json index 1e390882b1..6fce0b9b00 100644 --- a/tools/hello/package.json +++ b/tools/hello/package.json @@ -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" + ] + } + ] } \ No newline at end of file diff --git a/tools/hello/src/helper.c b/tools/hello/src/helper.c new file mode 100644 index 0000000000..4367d076a2 --- /dev/null +++ b/tools/hello/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() +{ +} diff --git a/tools/hello/src/helper.h b/tools/hello/src/helper.h new file mode 100644 index 0000000000..bf8934217d --- /dev/null +++ b/tools/hello/src/helper.h @@ -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__ diff --git a/tools/ng/environment.py b/tools/ng/environment.py index 3646cfad7d..3423263683 100644 --- a/tools/ng/environment.py +++ b/tools/ng/environment.py @@ -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]: diff --git a/tools/package.py b/tools/package.py index f7a2ffa745..37a53f47bd 100644 --- a/tools/package.py +++ b/tools/package.py @@ -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)