1# -*- coding: utf-8 -*- 2""" 3Environment extensions for RT-Thread build system. 4 5This module provides methods that are injected into the SCons Environment object 6to provide RT-Thread-specific functionality. 7""" 8 9import os 10from typing import List, Union, Dict, Any, Optional 11from SCons.Script import * 12 13from .core import BuildContext 14from .project import ProjectGroup 15 16 17class RTEnv: 18 """ 19 RT-Thread environment extensions (RTEnv). 20 21 This class provides methods that are added to the SCons Environment object. 22 """ 23 24 @staticmethod 25 def inject_methods(env): 26 """ 27 Inject RT-Thread methods into SCons Environment. 28 29 Args: 30 env: SCons Environment object 31 """ 32 # Core build methods 33 env.AddMethod(RTEnv.DefineGroup, 'DefineGroup') 34 env.AddMethod(RTEnv.GetDepend, 'GetDepend') 35 env.AddMethod(RTEnv.SrcRemove, 'SrcRemove') 36 env.AddMethod(RTEnv.GetCurrentDir, 'GetCurrentDir') 37 env.AddMethod(RTEnv.BuildPackage, 'BuildPackage') 38 39 # Utility methods 40 env.AddMethod(RTEnv.Glob, 'GlobFiles') 41 env.AddMethod(RTEnv.GetBuildOptions, 'GetBuildOptions') 42 env.AddMethod(RTEnv.GetContext, 'GetContext') 43 44 # Path utilities 45 env.AddMethod(RTEnv.GetRTTRoot, 'GetRTTRoot') 46 env.AddMethod(RTEnv.GetBSPRoot, 'GetBSPRoot') 47 48 @staticmethod 49 def DefineGroup(env, name: str, src: List[str], depend: Any = None, **kwargs) -> List: 50 """ 51 Define a component group. 52 53 This method maintains compatibility with the original DefineGroup function 54 while using the new object-oriented implementation. 55 56 Args: 57 env: SCons Environment 58 name: Group name 59 src: Source file list 60 depend: Dependency conditions 61 **kwargs: Additional parameters (CPPPATH, CPPDEFINES, etc.) 62 63 Returns: 64 List of build objects 65 """ 66 context = BuildContext.get_current() 67 if not context: 68 raise RuntimeError("BuildContext not initialized") 69 70 # Check dependencies 71 if depend and not env.GetDepend(depend): 72 return [] 73 74 # Process source files 75 if isinstance(src, str): 76 src = [src] 77 78 # Create project group 79 group = ProjectGroup( 80 name=name, 81 sources=src, 82 dependencies=depend if isinstance(depend, list) else [depend] if depend else [], 83 environment=env 84 ) 85 86 # Process parameters 87 group.include_paths = kwargs.get('CPPPATH', []) 88 group.defines = kwargs.get('CPPDEFINES', {}) 89 group.cflags = kwargs.get('CFLAGS', '') 90 group.cxxflags = kwargs.get('CXXFLAGS', '') 91 group.local_cflags = kwargs.get('LOCAL_CFLAGS', '') 92 group.local_cxxflags = kwargs.get('LOCAL_CXXFLAGS', '') 93 group.local_include_paths = kwargs.get('LOCAL_CPPPATH', []) 94 group.local_defines = kwargs.get('LOCAL_CPPDEFINES', {}) 95 group.libs = kwargs.get('LIBS', []) 96 group.lib_paths = kwargs.get('LIBPATH', []) 97 98 # Build objects 99 objects = group.build(env) 100 101 # Register group 102 context.register_project_group(group) 103 104 return objects 105 106 @staticmethod 107 def GetDepend(env, depend: Any) -> bool: 108 """ 109 Check if dependency is satisfied. 110 111 Args: 112 env: SCons Environment 113 depend: Dependency name or list of names 114 115 Returns: 116 True if dependency is satisfied 117 """ 118 context = BuildContext.get_current() 119 if not context: 120 # Fallback to checking environment variables 121 if isinstance(depend, str): 122 return env.get(depend, False) 123 elif isinstance(depend, list): 124 return all(env.get(d, False) for d in depend) 125 return False 126 127 return context.get_dependency(depend) 128 129 @staticmethod 130 def SrcRemove(env, src: List[str], remove: List[str]) -> None: 131 """ 132 Remove files from source list. 133 134 Args: 135 env: SCons Environment 136 src: Source file list (modified in place) 137 remove: Files to remove 138 """ 139 if not isinstance(remove, list): 140 remove = [remove] 141 142 for item in remove: 143 # Handle both exact matches and pattern matches 144 if item in src: 145 src.remove(item) 146 else: 147 # Try pattern matching 148 import fnmatch 149 to_remove = [f for f in src if fnmatch.fnmatch(f, item)] 150 for f in to_remove: 151 src.remove(f) 152 153 @staticmethod 154 def GetCurrentDir(env) -> str: 155 """ 156 Get current directory. 157 158 Args: 159 env: SCons Environment 160 161 Returns: 162 Current directory path 163 """ 164 return Dir('.').abspath 165 166 @staticmethod 167 def BuildPackage(env, package_path: str = None) -> List: 168 """ 169 Build package from package.json. 170 171 Args: 172 env: SCons Environment 173 package_path: Path to package.json. If None, looks for package.json in current directory. 174 175 Returns: 176 List of build objects 177 """ 178 # Import the existing package module 179 import sys 180 import os 181 182 # Get the building module path 183 building_path = os.path.dirname(os.path.abspath(__file__)) 184 tools_path = os.path.dirname(building_path) 185 186 # Add to path if not already there 187 if tools_path not in sys.path: 188 sys.path.insert(0, tools_path) 189 190 # Import and use the existing BuildPackage 191 try: 192 from package import BuildPackage as build_package_func 193 194 # BuildPackage uses global functions, so we need to set up the context 195 # Save current directory 196 current_dir = os.getcwd() 197 198 # Change to the directory where we want to build 199 if package_path is None: 200 work_dir = env.GetCurrentDir() 201 elif os.path.isdir(package_path): 202 work_dir = package_path 203 else: 204 work_dir = os.path.dirname(package_path) 205 206 os.chdir(work_dir) 207 208 try: 209 # Call the original BuildPackage 210 result = build_package_func(package_path) 211 finally: 212 # Restore directory 213 os.chdir(current_dir) 214 215 return result 216 217 except ImportError: 218 # Fallback if import fails 219 context = BuildContext.get_current() 220 if context: 221 context.logger.error("Failed to import package module") 222 return [] 223 224 @staticmethod 225 def Glob(env, pattern: str) -> List[str]: 226 """ 227 Enhanced glob with better error handling. 228 229 Args: 230 env: SCons Environment 231 pattern: File pattern 232 233 Returns: 234 List of matching files 235 """ 236 try: 237 files = Glob(pattern, strings=True) 238 return sorted(files) # Sort for consistent ordering 239 except Exception as e: 240 context = BuildContext.get_current() 241 if context: 242 context.logger.warning(f"Glob pattern '{pattern}' failed: {e}") 243 return [] 244 245 @staticmethod 246 def GetBuildOptions(env) -> Dict[str, Any]: 247 """ 248 Get build options. 249 250 Args: 251 env: SCons Environment 252 253 Returns: 254 Dictionary of build options 255 """ 256 context = BuildContext.get_current() 257 if context: 258 return context.build_options 259 return {} 260 261 @staticmethod 262 def GetContext(env) -> Optional[BuildContext]: 263 """ 264 Get current build context. 265 266 Args: 267 env: SCons Environment 268 269 Returns: 270 BuildContext instance or None 271 """ 272 return BuildContext.get_current() 273 274 @staticmethod 275 def GetRTTRoot(env) -> str: 276 """ 277 Get RT-Thread root directory. 278 279 Args: 280 env: SCons Environment 281 282 Returns: 283 RT-Thread root path 284 """ 285 return env.get('RTT_ROOT', '') 286 287 @staticmethod 288 def GetBSPRoot(env) -> str: 289 """ 290 Get BSP root directory. 291 292 Args: 293 env: SCons Environment 294 295 Returns: 296 BSP root path 297 """ 298 return env.get('BSP_ROOT', '')