| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- # -*- coding: utf-8 -*-
- """
- Configuration management for RT-Thread build system.
- This module handles parsing and managing configuration from rtconfig.h files.
- """
- import re
- import os
- from typing import Dict, List, Any, Optional, Union
- from dataclasses import dataclass
- from enum import Enum
- class ConfigType(Enum):
- """Configuration value types."""
- BOOLEAN = "boolean"
- INTEGER = "integer"
- STRING = "string"
- UNDEFINED = "undefined"
- @dataclass
- class ConfigOption:
- """Configuration option with metadata."""
- name: str
- value: Any
- type: ConfigType
- line_number: int = 0
- comment: str = ""
-
- def as_bool(self) -> bool:
- """Get value as boolean."""
- if self.type == ConfigType.BOOLEAN:
- return bool(self.value)
- elif self.type == ConfigType.INTEGER:
- return self.value != 0
- elif self.type == ConfigType.STRING:
- return bool(self.value)
- return False
-
- def as_int(self) -> int:
- """Get value as integer."""
- if self.type == ConfigType.INTEGER:
- return self.value
- elif self.type == ConfigType.BOOLEAN:
- return 1 if self.value else 0
- elif self.type == ConfigType.STRING:
- try:
- return int(self.value)
- except ValueError:
- return 0
- return 0
-
- def as_str(self) -> str:
- """Get value as string."""
- if self.type == ConfigType.STRING:
- return self.value
- return str(self.value)
- class ConfigParser:
- """Parser for rtconfig.h files."""
-
- # Regular expressions for parsing
- RE_DEFINE = re.compile(r'^\s*#\s*define\s+(\w+)(?:\s+(.*))?', re.MULTILINE)
- RE_UNDEF = re.compile(r'^\s*#\s*undef\s+(\w+)', re.MULTILINE)
- RE_IFDEF = re.compile(r'^\s*#\s*ifdef\s+(\w+)', re.MULTILINE)
- RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+(\w+)', re.MULTILINE)
- RE_ENDIF = re.compile(r'^\s*#\s*endif', re.MULTILINE)
- RE_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL)
- RE_LINE_COMMENT = re.compile(r'//.*$', re.MULTILINE)
-
- def __init__(self):
- self.options: Dict[str, ConfigOption] = {}
- self.conditions: List[str] = []
-
- def parse_file(self, filepath: str) -> Dict[str, ConfigOption]:
- """
- Parse configuration file.
-
- Args:
- filepath: Path to rtconfig.h
-
- Returns:
- Dictionary of configuration options
- """
- if not os.path.exists(filepath):
- raise FileNotFoundError(f"Configuration file not found: {filepath}")
-
- with open(filepath, 'r', encoding='utf-8') as f:
- content = f.read()
-
- return self.parse_content(content)
-
- def parse_content(self, content: str) -> Dict[str, ConfigOption]:
- """
- Parse configuration content.
-
- Args:
- content: File content
-
- Returns:
- Dictionary of configuration options
- """
- # Remove comments
- content = self.RE_COMMENT.sub('', content)
- content = self.RE_LINE_COMMENT.sub('', content)
-
- # Parse line by line
- lines = content.split('\n')
- for i, line in enumerate(lines):
- self._parse_line(line, i + 1)
-
- return self.options
-
- def _parse_line(self, line: str, line_number: int) -> None:
- """Parse a single line."""
- # Check for #define
- match = self.RE_DEFINE.match(line)
- if match:
- name = match.group(1)
- value = match.group(2) if match.group(2) else '1'
-
- # Parse value
- parsed_value, value_type = self._parse_value(value.strip())
-
- # Create option
- option = ConfigOption(
- name=name,
- value=parsed_value,
- type=value_type,
- line_number=line_number
- )
-
- self.options[name] = option
- return
-
- # Check for #undef
- match = self.RE_UNDEF.match(line)
- if match:
- name = match.group(1)
- if name in self.options:
- del self.options[name]
- return
-
- def _parse_value(self, value: str) -> tuple:
- """
- Parse configuration value.
-
- Returns:
- Tuple of (parsed_value, ConfigType)
- """
- if not value or value == '1':
- return (True, ConfigType.BOOLEAN)
-
- # Try integer
- try:
- return (int(value, 0), ConfigType.INTEGER) # Support hex/octal
- except ValueError:
- pass
-
- # Try string (remove quotes)
- if value.startswith('"') and value.endswith('"'):
- return (value[1:-1], ConfigType.STRING)
-
- # Default to string
- return (value, ConfigType.STRING)
- class ConfigManager:
- """
- Configuration manager for build system.
-
- This class manages configuration options and provides dependency checking.
- """
-
- def __init__(self):
- self.parser = ConfigParser()
- self.options: Dict[str, ConfigOption] = {}
- self.cache: Dict[str, bool] = {}
-
- def load_from_file(self, filepath: str) -> None:
- """
- Load configuration from file.
-
- Args:
- filepath: Path to rtconfig.h
- """
- self.options = self.parser.parse_file(filepath)
- self.cache.clear() # Clear dependency cache
-
- def get_option(self, name: str) -> Optional[ConfigOption]:
- """
- Get configuration option.
-
- Args:
- name: Option name
-
- Returns:
- ConfigOption or None
- """
- return self.options.get(name)
-
- def get_value(self, name: str, default: Any = None) -> Any:
- """
- Get configuration value.
-
- Args:
- name: Option name
- default: Default value if not found
-
- Returns:
- Configuration value
- """
- option = self.options.get(name)
- if option:
- return option.value
- return default
-
- def get_dependency(self, depend: Union[str, List[str]]) -> bool:
- """
- Check if dependency is satisfied.
-
- Args:
- depend: Single dependency or list of dependencies
-
- Returns:
- True if all dependencies are satisfied
- """
- # Handle empty dependency
- if not depend:
- return True
-
- # Convert to list
- if isinstance(depend, str):
- depend = [depend]
-
- # Check cache
- cache_key = ','.join(sorted(depend))
- if cache_key in self.cache:
- return self.cache[cache_key]
-
- # Check all dependencies (AND logic)
- result = all(self._check_single_dependency(d) for d in depend)
-
- # Cache result
- self.cache[cache_key] = result
- return result
-
- def _check_single_dependency(self, name: str) -> bool:
- """Check a single dependency."""
- option = self.options.get(name)
- if not option:
- return False
-
- # For RT-Thread, any defined macro is considered True
- # except if explicitly set to 0
- if option.type == ConfigType.INTEGER:
- return option.value != 0
- elif option.type == ConfigType.BOOLEAN:
- return option.value
- elif option.type == ConfigType.STRING:
- return bool(option.value)
-
- return True
-
- def get_all_options(self) -> Dict[str, Any]:
- """
- Get all configuration options as a simple dictionary.
-
- Returns:
- Dictionary of option names to values
- """
- return {name: opt.value for name, opt in self.options.items()}
-
- def validate(self) -> List[str]:
- """
- Validate configuration.
-
- Returns:
- List of validation errors
- """
- errors = []
-
- # Check for common issues
- if 'RT_NAME_MAX' in self.options:
- name_max = self.options['RT_NAME_MAX'].as_int()
- if name_max < 4:
- errors.append("RT_NAME_MAX should be at least 4")
-
- if 'RT_THREAD_PRIORITY_MAX' in self.options:
- prio_max = self.options['RT_THREAD_PRIORITY_MAX'].as_int()
- if prio_max not in [8, 32, 256]:
- errors.append("RT_THREAD_PRIORITY_MAX should be 8, 32, or 256")
-
- return errors
|