1# -*- coding: utf-8 -*- 2""" 3Project and group management for RT-Thread build system. 4 5This module provides classes for managing project groups and their compilation. 6""" 7 8import os 9from typing import List, Dict, Any, Optional 10from dataclasses import dataclass, field 11from SCons.Script import * 12 13 14@dataclass 15class ProjectGroup: 16 """ 17 Represents a project group (component). 18 19 This class encapsulates the information from DefineGroup calls. 20 """ 21 name: str 22 sources: List[str] 23 dependencies: List[str] = field(default_factory=list) 24 environment: Any = None # SCons Environment 25 26 # Paths and defines 27 include_paths: List[str] = field(default_factory=list) 28 defines: Dict[str, str] = field(default_factory=dict) 29 30 # Compiler flags 31 cflags: str = "" 32 cxxflags: str = "" 33 asflags: str = "" 34 ldflags: str = "" 35 36 # Local options (only for this group) 37 local_cflags: str = "" 38 local_cxxflags: str = "" 39 local_include_paths: List[str] = field(default_factory=list) 40 local_defines: Dict[str, str] = field(default_factory=dict) 41 42 # Libraries 43 libs: List[str] = field(default_factory=list) 44 lib_paths: List[str] = field(default_factory=list) 45 46 # Build objects 47 objects: List[Any] = field(default_factory=list) 48 49 def build(self, env) -> List: 50 """ 51 Build the group and return objects. 52 53 Args: 54 env: SCons Environment 55 56 Returns: 57 List of build objects 58 """ 59 if not self.sources: 60 return [] 61 62 # Clone environment if we have local options 63 build_env = env 64 if self._has_local_options(): 65 build_env = env.Clone() 66 self._apply_local_options(build_env) 67 68 # Apply global options 69 self._apply_global_options(build_env) 70 71 # Build objects 72 self.objects = [] 73 for src in self.sources: 74 if isinstance(src, str): 75 # Build single file 76 obj = build_env.Object(src) 77 self.objects.extend(obj if isinstance(obj, list) else [obj]) 78 else: 79 # Already a Node 80 self.objects.append(src) 81 82 return self.objects 83 84 def _has_local_options(self) -> bool: 85 """Check if group has local options.""" 86 return bool( 87 self.local_cflags or 88 self.local_cxxflags or 89 self.local_include_paths or 90 self.local_defines 91 ) 92 93 def _apply_local_options(self, env) -> None: 94 """Apply local options to environment.""" 95 if self.local_cflags: 96 env.AppendUnique(CFLAGS=self.local_cflags.split()) 97 98 if self.local_cxxflags: 99 env.AppendUnique(CXXFLAGS=self.local_cxxflags.split()) 100 101 if self.local_include_paths: 102 paths = [os.path.abspath(p) for p in self.local_include_paths] 103 env.AppendUnique(CPPPATH=paths) 104 105 if self.local_defines: 106 env.AppendUnique(CPPDEFINES=self.local_defines) 107 108 def _apply_global_options(self, env) -> None: 109 """Apply global options to environment.""" 110 # These options affect dependent groups too 111 if self.include_paths: 112 paths = [os.path.abspath(p) for p in self.include_paths] 113 env.AppendUnique(CPPPATH=paths) 114 115 if self.defines: 116 env.AppendUnique(CPPDEFINES=self.defines) 117 118 if self.cflags and 'CFLAGS' not in env: 119 env['CFLAGS'] = self.cflags 120 121 if self.cxxflags and 'CXXFLAGS' not in env: 122 env['CXXFLAGS'] = self.cxxflags 123 124 if self.libs: 125 env.AppendUnique(LIBS=self.libs) 126 127 if self.lib_paths: 128 paths = [os.path.abspath(p) for p in self.lib_paths] 129 env.AppendUnique(LIBPATH=paths) 130 131 def get_info(self) -> Dict[str, Any]: 132 """ 133 Get group information for project generators. 134 135 Returns: 136 Dictionary with group information 137 """ 138 return { 139 'name': self.name, 140 'sources': self.sources, 141 'include_paths': self.include_paths + self.local_include_paths, 142 'defines': {**self.defines, **self.local_defines}, 143 'cflags': f"{self.cflags} {self.local_cflags}".strip(), 144 'cxxflags': f"{self.cxxflags} {self.local_cxxflags}".strip(), 145 'libs': self.libs, 146 'lib_paths': self.lib_paths 147 } 148 149 150class ProjectRegistry: 151 """ 152 Registry for all project groups. 153 154 This class manages all registered project groups and provides 155 methods for querying and merging them. 156 """ 157 158 def __init__(self): 159 self.groups: List[ProjectGroup] = [] 160 self._group_index: Dict[str, ProjectGroup] = {} 161 162 def register_group(self, group: ProjectGroup) -> None: 163 """ 164 Register a project group. 165 166 Args: 167 group: ProjectGroup instance 168 """ 169 self.groups.append(group) 170 self._group_index[group.name] = group 171 172 def get_group(self, name: str) -> Optional[ProjectGroup]: 173 """ 174 Get group by name. 175 176 Args: 177 name: Group name 178 179 Returns: 180 ProjectGroup or None 181 """ 182 return self._group_index.get(name) 183 184 def get_all_groups(self) -> List[ProjectGroup]: 185 """Get all registered groups.""" 186 return self.groups.copy() 187 188 def get_groups_by_dependency(self, dependency: str) -> List[ProjectGroup]: 189 """ 190 Get groups that depend on a specific macro. 191 192 Args: 193 dependency: Dependency name 194 195 Returns: 196 List of matching groups 197 """ 198 return [g for g in self.groups if dependency in g.dependencies] 199 200 def merge_groups(self, env) -> List: 201 """ 202 Merge all groups into a single list of objects. 203 204 Args: 205 env: SCons Environment 206 207 Returns: 208 List of all build objects 209 """ 210 all_objects = [] 211 212 for group in self.groups: 213 if group.objects: 214 all_objects.extend(group.objects) 215 216 return all_objects 217 218 def get_project_info(self) -> Dict[str, Any]: 219 """ 220 Get complete project information for generators. 221 222 Returns: 223 Dictionary with project information 224 """ 225 # Collect all unique values 226 all_sources = [] 227 all_includes = set() 228 all_defines = {} 229 all_libs = [] 230 all_lib_paths = set() 231 232 for group in self.groups: 233 info = group.get_info() 234 235 # Sources 236 all_sources.extend(info['sources']) 237 238 # Include paths 239 all_includes.update(info['include_paths']) 240 241 # Defines 242 all_defines.update(info['defines']) 243 244 # Libraries 245 all_libs.extend(info['libs']) 246 all_lib_paths.update(info['lib_paths']) 247 248 return { 249 'groups': [g.get_info() for g in self.groups], 250 'all_sources': all_sources, 251 'all_includes': sorted(list(all_includes)), 252 'all_defines': all_defines, 253 'all_libs': all_libs, 254 'all_lib_paths': sorted(list(all_lib_paths)) 255 } 256 257 def clear(self) -> None: 258 """Clear all registered groups.""" 259 self.groups.clear() 260 self._group_index.clear()