256 lines
9.0 KiB
Python
256 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Configuration management for the build system.
|
|
Loads default configuration from config.json and provides
|
|
access to build settings, paths, and compiler flags.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
from importlib.resources import files
|
|
|
|
|
|
class Config:
|
|
"""Central configuration manager for the build system."""
|
|
|
|
def __init__(self, config_override: Optional[Dict[str, Any]] = None):
|
|
"""
|
|
Initialize configuration.
|
|
|
|
Args:
|
|
config_override: Optional dictionary to override default config values
|
|
"""
|
|
self._config = self._load_default_config()
|
|
if config_override:
|
|
self._deep_update(self._config, config_override)
|
|
|
|
# Cache for computed values
|
|
self._git_hash: Optional[str] = None
|
|
self._python_cmd: Optional[str] = None
|
|
|
|
def _load_default_config(self) -> Dict[str, Any]:
|
|
"""Load configuration from config.json."""
|
|
try:
|
|
# Load config.json from the same package
|
|
config_file = files(__package__).joinpath('config.json') # type: ignore
|
|
with config_file.open('r') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
print(f"Warning: Could not load config.json: {e}")
|
|
print("Using minimal default configuration")
|
|
return self._minimal_default_config()
|
|
|
|
def _minimal_default_config(self) -> Dict[str, Any]:
|
|
"""Fallback configuration if config.json cannot be loaded."""
|
|
return {
|
|
"directories": {
|
|
"src": "src",
|
|
"test": "tests",
|
|
"obj": "obj",
|
|
"bin": "bin",
|
|
"include": "include"
|
|
},
|
|
"targets": {},
|
|
"compiler_flags": {},
|
|
"test_generation": {},
|
|
"python_command": {"windows": "python", "unix": "python3"}
|
|
}
|
|
|
|
def _deep_update(self, base: Dict, update: Dict) -> None:
|
|
"""Recursively update nested dictionaries."""
|
|
for key, value in update.items():
|
|
if isinstance(value, dict) and key in base and isinstance(base[key], dict):
|
|
self._deep_update(base[key], value)
|
|
else:
|
|
base[key] = value
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Directory paths
|
|
# -------------------------------------------------------------------------
|
|
|
|
@property
|
|
def src_dir(self) -> Path:
|
|
"""Source directory path."""
|
|
return Path(self._config["directories"]["src"])
|
|
|
|
@property
|
|
def test_dir(self) -> Path:
|
|
"""Test directory path."""
|
|
return Path(self._config["directories"]["test"])
|
|
|
|
@property
|
|
def obj_dir(self) -> Path:
|
|
"""Object files directory path."""
|
|
return Path(self._config["directories"]["obj"])
|
|
|
|
@property
|
|
def bin_dir(self) -> Path:
|
|
"""Binary output directory path."""
|
|
return Path(self._config["directories"]["bin"])
|
|
|
|
@property
|
|
def include_dir(self) -> Path:
|
|
"""Include directory path."""
|
|
return Path(self._config["directories"]["include"])
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Target configuration
|
|
# -------------------------------------------------------------------------
|
|
|
|
def get_target_config(self, target_name: str) -> Dict[str, Any]:
|
|
"""
|
|
Get configuration for a specific target.
|
|
|
|
Args:
|
|
target_name: Name of the target (e.g., 'linux', 'windows', 'rp2040')
|
|
|
|
Returns:
|
|
Dictionary containing target-specific configuration
|
|
"""
|
|
return self._config.get("targets", {}).get(target_name, {})
|
|
|
|
def get_binary_name(self, target_name: str) -> str:
|
|
"""Get the output binary name for a target."""
|
|
target_config = self.get_target_config(target_name)
|
|
return target_config.get("binary_name", "sls")
|
|
|
|
def get_test_binary_name(self, target_name: str) -> str:
|
|
"""Get the test binary name for a target."""
|
|
target_config = self.get_target_config(target_name)
|
|
return target_config.get("test_binary_name", "sls_tests")
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Compiler flags
|
|
# -------------------------------------------------------------------------
|
|
|
|
def get_compiler_flags(self, compiler: str, flag_type: str = "common") -> List[str]:
|
|
"""
|
|
Get compiler flags for a specific compiler and flag type.
|
|
|
|
Args:
|
|
compiler: Compiler name (e.g., 'gcc', 'msvc', 'arm_gcc')
|
|
flag_type: Type of flags ('common', 'test', 'link')
|
|
|
|
Returns:
|
|
List of compiler flags
|
|
"""
|
|
compiler_config = self._config.get("compiler_flags", {}).get(compiler, {})
|
|
return compiler_config.get(flag_type, []).copy()
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Test generation
|
|
# -------------------------------------------------------------------------
|
|
|
|
@property
|
|
def test_script_path(self) -> Path:
|
|
"""Path to the test generation script."""
|
|
path_str = self._config.get("test_generation", {}).get("script_path", "")
|
|
return Path(path_str) if path_str else Path()
|
|
|
|
@property
|
|
def test_yaml_path(self) -> Path:
|
|
"""Path to the test cases YAML file."""
|
|
path_str = self._config.get("test_generation", {}).get("yaml_path", "")
|
|
return Path(path_str) if path_str else Path()
|
|
|
|
@property
|
|
def test_output_file(self) -> Path:
|
|
"""Path to the generated test output file."""
|
|
path_str = self._config.get("test_generation", {}).get("output_file", "")
|
|
return Path(path_str) if path_str else Path()
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Git information
|
|
# -------------------------------------------------------------------------
|
|
|
|
@property
|
|
def git_hash(self) -> str:
|
|
"""Get the current git commit hash with date."""
|
|
if self._git_hash is None:
|
|
self._git_hash = self._compute_git_hash()
|
|
return self._git_hash
|
|
|
|
def _compute_git_hash(self) -> str:
|
|
"""Compute git commit hash and date."""
|
|
try:
|
|
result_hash = subprocess.check_output(
|
|
["git", "describe", "--always", "--dirty", "--abbrev=7"],
|
|
cwd=".",
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
result_date = subprocess.check_output(
|
|
["git", "show", "-s", "--format=%ci"],
|
|
cwd=".",
|
|
stderr=subprocess.DEVNULL,
|
|
text=True
|
|
).strip()
|
|
return f"{result_hash} {result_date}"
|
|
except Exception:
|
|
return "unknown"
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Platform-specific settings
|
|
# -------------------------------------------------------------------------
|
|
|
|
@property
|
|
def python_command(self) -> str:
|
|
"""Get the appropriate Python command for the current platform."""
|
|
if self._python_cmd is None:
|
|
if os.name == "nt":
|
|
self._python_cmd = self._config.get("python_command", {}).get("windows", "python")
|
|
else:
|
|
self._python_cmd = self._config.get("python_command", {}).get("unix", "python3")
|
|
return self._python_cmd # type: ignore
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Environment variables
|
|
# -------------------------------------------------------------------------
|
|
|
|
def get_env_var(self, target: str, var_name: str, default: Optional[str] = None) -> Optional[str]:
|
|
"""
|
|
Get an environment variable value, with target-specific defaults.
|
|
|
|
Args:
|
|
target: Target name
|
|
var_name: Environment variable name
|
|
default: Default value if not found
|
|
|
|
Returns:
|
|
Environment variable value or default
|
|
"""
|
|
target_config = self.get_target_config(target)
|
|
env_key = target_config.get(f"{var_name}_env")
|
|
|
|
if env_key and env_key in os.environ:
|
|
return os.environ[env_key]
|
|
|
|
config_default = target_config.get(f"{var_name}_default")
|
|
if config_default:
|
|
# Expand ~ in paths
|
|
return str(Path(config_default).expanduser())
|
|
|
|
return default
|
|
|
|
|
|
# Global config instance
|
|
_config_instance: Optional[Config] = None
|
|
|
|
|
|
def get_config() -> Config:
|
|
"""Get the global configuration instance."""
|
|
global _config_instance
|
|
if _config_instance is None:
|
|
_config_instance = Config()
|
|
return _config_instance
|
|
|
|
|
|
def set_config(config: Config) -> None:
|
|
"""Set the global configuration instance."""
|
|
global _config_instance
|
|
_config_instance = config
|