Created config.py
This commit is contained in:
parent
9f8d66ccae
commit
1f0fccc866
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"directories": {
|
||||
"src": "src",
|
||||
"test": "tests",
|
||||
"obj": "obj",
|
||||
"bin": "bin",
|
||||
"include": "include"
|
||||
},
|
||||
"targets": {
|
||||
"linux": {
|
||||
"binary_name": "sls",
|
||||
"test_binary_name": "sls_tests"
|
||||
},
|
||||
"windows": {
|
||||
"binary_name": "sls.exe",
|
||||
"test_binary_name": "sls_tests.exe"
|
||||
},
|
||||
"rp2040": {
|
||||
"binary_name": "sls.elf",
|
||||
"build_dir": "build_pico",
|
||||
"toolchain_file": "pico_arm_gcc_toolchain.cmake",
|
||||
"sdk_path_env": "PICO_SDK_PATH",
|
||||
"sdk_path_default": "~/pico/pico-sdk",
|
||||
"excluded_sources": [
|
||||
"main.c",
|
||||
"repl.c",
|
||||
"file.c"
|
||||
],
|
||||
"required_sources": [
|
||||
"pico_main.c"
|
||||
]
|
||||
}
|
||||
},
|
||||
"compiler_flags": {
|
||||
"gcc": {
|
||||
"common": [
|
||||
"-std=c99",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Iinclude",
|
||||
"-g"
|
||||
],
|
||||
"test": [
|
||||
"-std=c99",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Wno-unused-function",
|
||||
"-Werror",
|
||||
"-Iinclude",
|
||||
"-g",
|
||||
"-O0"
|
||||
],
|
||||
"link": [
|
||||
"-lm"
|
||||
]
|
||||
},
|
||||
"msvc": {
|
||||
"common": [
|
||||
"/std:c11",
|
||||
"/Zi",
|
||||
"/Iinclude"
|
||||
],
|
||||
"test": [
|
||||
"/std:c11",
|
||||
"/Zi",
|
||||
"/Iinclude"
|
||||
],
|
||||
"link": []
|
||||
},
|
||||
"arm_gcc": {
|
||||
"common": [
|
||||
"-std=c99",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-mcpu=cortex-m0plus",
|
||||
"-mthumb"
|
||||
],
|
||||
"link": []
|
||||
}
|
||||
},
|
||||
"test_generation": {
|
||||
"script_path": "../SLS_Tests/yaml_to_c_tests.py",
|
||||
"yaml_path": "../SLS_Tests/cases.yaml",
|
||||
"output_file": "tests/lexer_tests.c"
|
||||
},
|
||||
"python_command": {
|
||||
"windows": "python",
|
||||
"unix": "python3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
#!/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
|
||||
Loading…
Reference in New Issue