first commit for chrg
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user