Files
rt-thread/tools/ng/config.py

297 lines
8.6 KiB
Python

# -*- 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