1# -*- coding: utf-8 -*- 2""" 3Configuration management for RT-Thread build system. 4 5This module handles parsing and managing configuration from rtconfig.h files. 6""" 7 8import re 9import os 10from typing import Dict, List, Any, Optional, Union 11from dataclasses import dataclass 12from enum import Enum 13 14 15class ConfigType(Enum): 16 """Configuration value types.""" 17 BOOLEAN = "boolean" 18 INTEGER = "integer" 19 STRING = "string" 20 UNDEFINED = "undefined" 21 22 23@dataclass 24class ConfigOption: 25 """Configuration option with metadata.""" 26 name: str 27 value: Any 28 type: ConfigType 29 line_number: int = 0 30 comment: str = "" 31 32 def as_bool(self) -> bool: 33 """Get value as boolean.""" 34 if self.type == ConfigType.BOOLEAN: 35 return bool(self.value) 36 elif self.type == ConfigType.INTEGER: 37 return self.value != 0 38 elif self.type == ConfigType.STRING: 39 return bool(self.value) 40 return False 41 42 def as_int(self) -> int: 43 """Get value as integer.""" 44 if self.type == ConfigType.INTEGER: 45 return self.value 46 elif self.type == ConfigType.BOOLEAN: 47 return 1 if self.value else 0 48 elif self.type == ConfigType.STRING: 49 try: 50 return int(self.value) 51 except ValueError: 52 return 0 53 return 0 54 55 def as_str(self) -> str: 56 """Get value as string.""" 57 if self.type == ConfigType.STRING: 58 return self.value 59 return str(self.value) 60 61 62class ConfigParser: 63 """Parser for rtconfig.h files.""" 64 65 # Regular expressions for parsing 66 RE_DEFINE = re.compile(r'^\s*#\s*define\s+(\w+)(?:\s+(.*))?', re.MULTILINE) 67 RE_UNDEF = re.compile(r'^\s*#\s*undef\s+(\w+)', re.MULTILINE) 68 RE_IFDEF = re.compile(r'^\s*#\s*ifdef\s+(\w+)', re.MULTILINE) 69 RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+(\w+)', re.MULTILINE) 70 RE_ENDIF = re.compile(r'^\s*#\s*endif', re.MULTILINE) 71 RE_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL) 72 RE_LINE_COMMENT = re.compile(r'//.*$', re.MULTILINE) 73 74 def __init__(self): 75 self.options: Dict[str, ConfigOption] = {} 76 self.conditions: List[str] = [] 77 78 def parse_file(self, filepath: str) -> Dict[str, ConfigOption]: 79 """ 80 Parse configuration file. 81 82 Args: 83 filepath: Path to rtconfig.h 84 85 Returns: 86 Dictionary of configuration options 87 """ 88 if not os.path.exists(filepath): 89 raise FileNotFoundError(f"Configuration file not found: {filepath}") 90 91 with open(filepath, 'r', encoding='utf-8') as f: 92 content = f.read() 93 94 return self.parse_content(content) 95 96 def parse_content(self, content: str) -> Dict[str, ConfigOption]: 97 """ 98 Parse configuration content. 99 100 Args: 101 content: File content 102 103 Returns: 104 Dictionary of configuration options 105 """ 106 # Remove comments 107 content = self.RE_COMMENT.sub('', content) 108 content = self.RE_LINE_COMMENT.sub('', content) 109 110 # Parse line by line 111 lines = content.split('\n') 112 for i, line in enumerate(lines): 113 self._parse_line(line, i + 1) 114 115 return self.options 116 117 def _parse_line(self, line: str, line_number: int) -> None: 118 """Parse a single line.""" 119 # Check for #define 120 match = self.RE_DEFINE.match(line) 121 if match: 122 name = match.group(1) 123 value = match.group(2) if match.group(2) else '1' 124 125 # Parse value 126 parsed_value, value_type = self._parse_value(value.strip()) 127 128 # Create option 129 option = ConfigOption( 130 name=name, 131 value=parsed_value, 132 type=value_type, 133 line_number=line_number 134 ) 135 136 self.options[name] = option 137 return 138 139 # Check for #undef 140 match = self.RE_UNDEF.match(line) 141 if match: 142 name = match.group(1) 143 if name in self.options: 144 del self.options[name] 145 return 146 147 def _parse_value(self, value: str) -> tuple: 148 """ 149 Parse configuration value. 150 151 Returns: 152 Tuple of (parsed_value, ConfigType) 153 """ 154 if not value or value == '1': 155 return (True, ConfigType.BOOLEAN) 156 157 # Try integer 158 try: 159 return (int(value, 0), ConfigType.INTEGER) # Support hex/octal 160 except ValueError: 161 pass 162 163 # Try string (remove quotes) 164 if value.startswith('"') and value.endswith('"'): 165 return (value[1:-1], ConfigType.STRING) 166 167 # Default to string 168 return (value, ConfigType.STRING) 169 170 171class ConfigManager: 172 """ 173 Configuration manager for build system. 174 175 This class manages configuration options and provides dependency checking. 176 """ 177 178 def __init__(self): 179 self.parser = ConfigParser() 180 self.options: Dict[str, ConfigOption] = {} 181 self.cache: Dict[str, bool] = {} 182 183 def load_from_file(self, filepath: str) -> None: 184 """ 185 Load configuration from file. 186 187 Args: 188 filepath: Path to rtconfig.h 189 """ 190 self.options = self.parser.parse_file(filepath) 191 self.cache.clear() # Clear dependency cache 192 193 def get_option(self, name: str) -> Optional[ConfigOption]: 194 """ 195 Get configuration option. 196 197 Args: 198 name: Option name 199 200 Returns: 201 ConfigOption or None 202 """ 203 return self.options.get(name) 204 205 def get_value(self, name: str, default: Any = None) -> Any: 206 """ 207 Get configuration value. 208 209 Args: 210 name: Option name 211 default: Default value if not found 212 213 Returns: 214 Configuration value 215 """ 216 option = self.options.get(name) 217 if option: 218 return option.value 219 return default 220 221 def get_dependency(self, depend: Union[str, List[str]]) -> bool: 222 """ 223 Check if dependency is satisfied. 224 225 Args: 226 depend: Single dependency or list of dependencies 227 228 Returns: 229 True if all dependencies are satisfied 230 """ 231 # Handle empty dependency 232 if not depend: 233 return True 234 235 # Convert to list 236 if isinstance(depend, str): 237 depend = [depend] 238 239 # Check cache 240 cache_key = ','.join(sorted(depend)) 241 if cache_key in self.cache: 242 return self.cache[cache_key] 243 244 # Check all dependencies (AND logic) 245 result = all(self._check_single_dependency(d) for d in depend) 246 247 # Cache result 248 self.cache[cache_key] = result 249 return result 250 251 def _check_single_dependency(self, name: str) -> bool: 252 """Check a single dependency.""" 253 option = self.options.get(name) 254 if not option: 255 return False 256 257 # For RT-Thread, any defined macro is considered True 258 # except if explicitly set to 0 259 if option.type == ConfigType.INTEGER: 260 return option.value != 0 261 elif option.type == ConfigType.BOOLEAN: 262 return option.value 263 elif option.type == ConfigType.STRING: 264 return bool(option.value) 265 266 return True 267 268 def get_all_options(self) -> Dict[str, Any]: 269 """ 270 Get all configuration options as a simple dictionary. 271 272 Returns: 273 Dictionary of option names to values 274 """ 275 return {name: opt.value for name, opt in self.options.items()} 276 277 def validate(self) -> List[str]: 278 """ 279 Validate configuration. 280 281 Returns: 282 List of validation errors 283 """ 284 errors = [] 285 286 # Check for common issues 287 if 'RT_NAME_MAX' in self.options: 288 name_max = self.options['RT_NAME_MAX'].as_int() 289 if name_max < 4: 290 errors.append("RT_NAME_MAX should be at least 4") 291 292 if 'RT_THREAD_PRIORITY_MAX' in self.options: 293 prio_max = self.options['RT_THREAD_PRIORITY_MAX'].as_int() 294 if prio_max not in [8, 32, 256]: 295 errors.append("RT_THREAD_PRIORITY_MAX should be 8, 32, or 256") 296 297 return errors