Browse Source

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

bernard 1 month ago
parent
commit
fbdab95299

+ 128 - 0
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构建系统完全集成,不支持独立构建

+ 28 - 17
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"
+      ]
+    }
+  ]
 }

+ 15 - 0
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()
+{
+}

+ 16 - 0
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__

+ 39 - 45
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]
+        # 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
             
-        if not env.GetDepend(main_depend):
-            return []
+            # BuildPackage uses global functions, so we need to set up the context
+            # Save current directory
+            current_dir = os.getcwd()
             
-        # 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
+            # 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))
+            os.chdir(work_dir)
+            
+            try:
+                # Call the original BuildPackage
+                result = build_package_func(package_path)
+            finally:
+                # Restore directory
+                os.chdir(current_dir)
                 
-            # Add header paths
-            header_path = config.get('header_path', [])
-            include_paths.extend(header_path)
+            return result
             
-        # Create group
-        return env.DefineGroup(name, src, depend=main_depend, CPPPATH=include_paths)
+        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]:

+ 78 - 30
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)
-
-    package = json.loads(package_json)
+    cwd = os.path.dirname(os.path.abspath(package))
 
-    # 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')
-
-    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 = []
-    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)]
-
-    CPPDEFINES = ExtendPackageVar(package, 'CPPDEFINES')
+    with open(package, 'r') as f:
+        package_json = f.read()
+        package = json.loads(package_json)
+
+        # check package name
+        if 'name' not in package or 'type' not in package or package['type'] != 'rt-thread-component':
+            return []
+
+        # get 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 = []
+        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)