mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2025-10-18 09:47:27 +08:00
297 lines
8.6 KiB
Python
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 |