config.py 8.6 KB

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