From 627fadfea1bed75118537436319af65ba3be984e Mon Sep 17 00:00:00 2001 From: Kyler Date: Fri, 19 Dec 2025 13:44:52 -0700 Subject: [PATCH] Some changes --- SLS_C/build_system/config.json | 67 ++++++++++++ SLS_C/build_system/config.py | 170 +++++++++++++++-------------- SLS_C/build_system/targets/base.py | 6 +- SLS_C/build_system/utils.py | 3 + 4 files changed, 162 insertions(+), 84 deletions(-) create mode 100644 SLS_C/build_system/config.json diff --git a/SLS_C/build_system/config.json b/SLS_C/build_system/config.json new file mode 100644 index 0000000..aa82cc1 --- /dev/null +++ b/SLS_C/build_system/config.json @@ -0,0 +1,67 @@ +{ + "paths": { + "src_dir": "src", + "test_dir": "tests", + "include_dir": "include", + "obj_dir": "obj", + "bin_dir": "bin", + "build_dir": "build" + }, + "targets": { + "main": "sls", + "test": "sls_tests" + }, + "parallel_jobs": 0, + "verbose": false, + "debug": true, + "gcc_common_flags": [ + "-std=c99", + "-Wall", + "-Wextra", + "-Werror", + "-g" + ], + "gcc_test_flags": [ + "-std=c99", + "-Wall", + "-Wextra", + "-Wno-unused-function", + "-Werror", + "-g", + "-O0" + ], + "msvc_common_flags": [ + "/std:c11", + "/W4", + "/WX", + "/Zi" + ], + "msvc_test_flags": [ + "/std:c11", + "/W4", + "/WX", + "/Zi", + "/Od" + ], + "arm_gcc_flags": [ + "-mcpu=cortex-m0plus", + "-mthumb" + ], + "pico": { + "sdk_path_env": "PICO_SDK_PATH", + "sdk_path_default": "~/pico/pico-sdk", + "build_dir": "build_pico", + "toolchain_file": "pico_arm_gcc_toolchain.cmake" + }, + "macos": { + "min_version": "10.13" + }, + "source_excludes": { + "rp2040": [ + "main.c", + "repl.c", + "file.c", + "test" + ] + } +} diff --git a/SLS_C/build_system/config.py b/SLS_C/build_system/config.py index d7eb122..9225f32 100644 --- a/SLS_C/build_system/config.py +++ b/SLS_C/build_system/config.py @@ -2,118 +2,116 @@ Build system configuration. This module centralizes all build configuration including paths, -compiler flags, and build settings. +compiler flags, and build settings. Configuration can be loaded from +a default JSON file or overridden programmatically. """ import os +import json from pathlib import Path from typing import List, Dict, Optional +from importlib import resources + +# TODO: This should be utilized more throughout the build system. class Config: """ Centralized build configuration. All paths, flags, and settings are defined here for easy modification. + Configuration is loaded from a default JSON file and can be overridden. """ - def __init__(self, project_root: Optional[Path] = None): + def __init__(self, project_root: Optional[Path] = None, config_file: Optional[Path] = None): """ Initialize build configuration. Args: project_root: Project root directory (default: current directory) + config_file: Custom config file path (default: uses built-in config.json) """ + # Load default configuration + self._load_default_config(config_file) + # Project structure self.project_root = project_root or Path.cwd() - self.src_dir = self.project_root / "src" - self.test_dir = self.project_root / "tests" - self.include_dir = self.project_root / "include" - self.obj_dir = self.project_root / "obj" - self.bin_dir = self.project_root / "bin" - self.build_dir = self.project_root / "build" - - # Target executables - self.main_target = self.bin_dir / "sls" - self.test_target = self.bin_dir / "sls_tests" + self._setup_paths() # Build settings - self.parallel_jobs = os.cpu_count() or 1 - self.verbose = False - self.debug = True + self.parallel_jobs = self._data.get("parallel_jobs", os.cpu_count() or 1) + self.verbose = self._data.get("verbose", False) + self.debug = self._data.get("debug", True) # Git integration self.git_hash = self._get_git_hash() - # Compiler flags by category - self._init_compiler_flags() + # Compiler flags + self.gcc_common_flags = self._data.get("gcc_common_flags", []) + self.gcc_test_flags = self._data.get("gcc_test_flags", []) + self.msvc_common_flags = self._data.get("msvc_common_flags", []) + self.msvc_test_flags = self._data.get("msvc_test_flags", []) + self.arm_gcc_flags = self._data.get("arm_gcc_flags", []) # Platform-specific settings - self._init_platform_settings() + self._setup_platform_settings() + + def _load_default_config(self, config_file: Optional[Path] = None): + """ + Load configuration from JSON file. + + Args: + config_file: Custom config file, or None to use default + """ + if config_file and config_file.exists(): + # Load custom config file + with open(config_file, 'r') as f: + self._data = json.load(f) + else: + # Load default config from package resources + config_text = resources.files("build_system").joinpath("config.json").read_text() + self._data = json.loads(config_text) + + def _setup_paths(self): + """Setup directory paths from configuration.""" + paths = self._data.get("paths", {}) + + self.src_dir = self.project_root / paths.get("src_dir", "src") + self.test_dir = self.project_root / paths.get("test_dir", "tests") + self.include_dir = self.project_root / paths.get("include_dir", "include") + self.obj_dir = self.project_root / paths.get("obj_dir", "obj") + self.bin_dir = self.project_root / paths.get("bin_dir", "bin") + self.build_dir = self.project_root / paths.get("build_dir", "build") + + # Target executables + targets = self._data.get("targets", {}) + self.main_target = self.bin_dir / targets.get("main", "sls") + self.test_target = self.bin_dir / targets.get("test", "sls_tests") + + def _setup_platform_settings(self): + """Setup platform-specific settings from configuration.""" + pico_config = self._data.get("pico", {}) + + # Pico SDK settings + sdk_env = pico_config.get("sdk_path_env", "PICO_SDK_PATH") + sdk_default = pico_config.get("sdk_path_default", "~/pico/pico-sdk") + sdk_default = Path(sdk_default).expanduser() + + self.pico_sdk_path = Path(os.environ.get(sdk_env, sdk_default)) + self.pico_build_dir = self.project_root / pico_config.get("build_dir", "build_pico") + self.pico_toolchain_file = self.project_root / pico_config.get( + "toolchain_file", "pico_arm_gcc_toolchain.cmake" + ) + + # macOS settings + macos_config = self._data.get("macos", {}) + self.macos_min_version = macos_config.get("min_version", "10.13") def _get_git_hash(self) -> str: """Get git commit hash for version info.""" from .utils import git_commit_hash return git_commit_hash() - def _init_compiler_flags(self): - """Initialize compiler flag sets.""" - - # Common GCC/Clang flags - self.gcc_common_flags = [ - "-std=c99", - "-Wall", - "-Wextra", - "-Werror", - "-g", # Debug symbols - ] - - self.gcc_test_flags = [ - "-std=c99", - "-Wall", - "-Wextra", - "-Wno-unused-function", - "-Werror", - "-g", - "-O0", # No optimization for tests - ] - - # MSVC flags - self.msvc_common_flags = [ - "/std:c11", - "/W4", - "/WX", - "/Zi", - ] - - self.msvc_test_flags = [ - "/std:c11", - "/W4", - "/WX", - "/Zi", - "/Od", # No optimization - ] - - # ARM GCC flags for RP2040 - self.arm_gcc_flags = [ - "-mcpu=cortex-m0plus", - "-mthumb", - ] - - def _init_platform_settings(self): - """Initialize platform-specific settings.""" - - # Pico SDK settings - self.pico_sdk_path = Path(os.environ.get( - "PICO_SDK_PATH", - Path.home() / "pico" / "pico-sdk" - )) - self.pico_build_dir = self.project_root / "build_pico" - self.pico_toolchain_file = self.project_root / "pico_arm_gcc_toolchain.cmake" - - # macOS settings - self.macos_min_version = "10.13" - def get_defines(self, extra: Optional[Dict] = None) -> Dict: """ Get preprocessor defines. @@ -210,9 +208,8 @@ class Config: Returns: List of source files (excludes main.c, repl.c, file.c, test files) """ - sources = self.get_source_files( - exclude=["main.c", "repl.c", "file.c", "test"] - ) + excludes = self._data.get("source_excludes", {}).get("rp2040", []) + sources = self.get_source_files(exclude=excludes) # Add pico_main.c if it exists pico_main = self.src_dir / "pico_main.c" @@ -254,6 +251,16 @@ class Config: self.parallel_jobs = jobs return self + def save_config(self, output_file: Path): + """ + Save current configuration to a JSON file. + + Args: + output_file: Path to output JSON file + """ + with open(output_file, 'w') as f: + json.dump(self._data, f, indent=2) + def __repr__(self) -> str: """String representation of config.""" return ( @@ -271,19 +278,20 @@ class Config: _config = None -def get_config(project_root: Optional[Path] = None) -> Config: +def get_config(project_root: Optional[Path] = None, config_file: Optional[Path] = None) -> Config: """ Get the global configuration instance. Args: project_root: Project root directory (only used on first call) + config_file: Custom config file (only used on first call) Returns: Config instance """ global _config if _config is None: - _config = Config(project_root) + _config = Config(project_root, config_file) return _config diff --git a/SLS_C/build_system/targets/base.py b/SLS_C/build_system/targets/base.py index a8132a7..301ede8 100644 --- a/SLS_C/build_system/targets/base.py +++ b/SLS_C/build_system/targets/base.py @@ -76,7 +76,7 @@ class Target(ABC): output = self.get_output_path() self.link_executable(objects, output) - print(f"\n✓ Build successful: {output}\n") + print(f"\nBuild successful: {output}\n") def build_tests(self): """ @@ -111,7 +111,7 @@ class Target(ABC): output = self.get_test_output_path() self.link_executable(test_objects + shared_objects, output) - print(f"\n✓ Test build successful: {output}\n") + print(f"\nTest build successful: {output}\n") def run(self): """ @@ -199,7 +199,7 @@ class Target(ABC): rm_tree(self.config.bin_dir) print(f"Removed {self.config.bin_dir}") - print("\n✓ Clean complete\n") + print("\nClean complete\n") def compile_source(self, source: Path, is_test: bool = False) -> Path: """ diff --git a/SLS_C/build_system/utils.py b/SLS_C/build_system/utils.py index fdebae3..84d6551 100644 --- a/SLS_C/build_system/utils.py +++ b/SLS_C/build_system/utils.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import Optional +# TODO: Is this needed? def mkdir(path: Path): """ Create a directory if it doesn't exist. @@ -25,6 +26,7 @@ def mkdir(path: Path): path.mkdir(parents=True, exist_ok=True) +# TODO: Is this needed? def rm_tree(path: Path): """ Remove a directory tree if it exists. @@ -36,6 +38,7 @@ def rm_tree(path: Path): shutil.rmtree(path, ignore_errors=True) +# TODO: Is this needed? def run_command(cmd: list, cwd: Optional[Path] = None, capture_output: bool = False): """ Run a command and optionally capture its output.