From 1f0fccc86663e92ce1d053ea5d8349561a922ce3 Mon Sep 17 00:00:00 2001 From: Kyler Date: Fri, 19 Dec 2025 13:49:48 -0700 Subject: [PATCH] Created config.py --- SLS_C/build_system/config.json | 91 ++++++++++++ SLS_C/build_system/config.py | 255 +++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) diff --git a/SLS_C/build_system/config.json b/SLS_C/build_system/config.json index e69de29..2df787c 100644 --- a/SLS_C/build_system/config.json +++ b/SLS_C/build_system/config.json @@ -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" + } +} diff --git a/SLS_C/build_system/config.py b/SLS_C/build_system/config.py index e69de29..afb900f 100644 --- a/SLS_C/build_system/config.py +++ b/SLS_C/build_system/config.py @@ -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