Some changes

This commit is contained in:
Kyler Olsen 2025-12-19 13:44:52 -07:00
parent d7c23225f3
commit 627fadfea1
4 changed files with 162 additions and 84 deletions

View File

@ -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"
]
}
}

View File

@ -2,118 +2,116 @@
Build system configuration. Build system configuration.
This module centralizes all build configuration including paths, 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 os
import json
from pathlib import Path from pathlib import Path
from typing import List, Dict, Optional from typing import List, Dict, Optional
from importlib import resources
# TODO: This should be utilized more throughout the build system.
class Config: class Config:
""" """
Centralized build configuration. Centralized build configuration.
All paths, flags, and settings are defined here for easy modification. 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. Initialize build configuration.
Args: Args:
project_root: Project root directory (default: current directory) 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 # Project structure
self.project_root = project_root or Path.cwd() self.project_root = project_root or Path.cwd()
self.src_dir = self.project_root / "src" self._setup_paths()
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"
# Build settings # Build settings
self.parallel_jobs = os.cpu_count() or 1 self.parallel_jobs = self._data.get("parallel_jobs", os.cpu_count() or 1)
self.verbose = False self.verbose = self._data.get("verbose", False)
self.debug = True self.debug = self._data.get("debug", True)
# Git integration # Git integration
self.git_hash = self._get_git_hash() self.git_hash = self._get_git_hash()
# Compiler flags by category # Compiler flags
self._init_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 # 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: def _get_git_hash(self) -> str:
"""Get git commit hash for version info.""" """Get git commit hash for version info."""
from .utils import git_commit_hash from .utils import git_commit_hash
return 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: def get_defines(self, extra: Optional[Dict] = None) -> Dict:
""" """
Get preprocessor defines. Get preprocessor defines.
@ -210,9 +208,8 @@ class Config:
Returns: Returns:
List of source files (excludes main.c, repl.c, file.c, test files) List of source files (excludes main.c, repl.c, file.c, test files)
""" """
sources = self.get_source_files( excludes = self._data.get("source_excludes", {}).get("rp2040", [])
exclude=["main.c", "repl.c", "file.c", "test"] sources = self.get_source_files(exclude=excludes)
)
# Add pico_main.c if it exists # Add pico_main.c if it exists
pico_main = self.src_dir / "pico_main.c" pico_main = self.src_dir / "pico_main.c"
@ -254,6 +251,16 @@ class Config:
self.parallel_jobs = jobs self.parallel_jobs = jobs
return self 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: def __repr__(self) -> str:
"""String representation of config.""" """String representation of config."""
return ( return (
@ -271,19 +278,20 @@ class Config:
_config = None _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. Get the global configuration instance.
Args: Args:
project_root: Project root directory (only used on first call) project_root: Project root directory (only used on first call)
config_file: Custom config file (only used on first call)
Returns: Returns:
Config instance Config instance
""" """
global _config global _config
if _config is None: if _config is None:
_config = Config(project_root) _config = Config(project_root, config_file)
return _config return _config

View File

@ -76,7 +76,7 @@ class Target(ABC):
output = self.get_output_path() output = self.get_output_path()
self.link_executable(objects, output) self.link_executable(objects, output)
print(f"\nBuild successful: {output}\n") print(f"\nBuild successful: {output}\n")
def build_tests(self): def build_tests(self):
""" """
@ -111,7 +111,7 @@ class Target(ABC):
output = self.get_test_output_path() output = self.get_test_output_path()
self.link_executable(test_objects + shared_objects, output) self.link_executable(test_objects + shared_objects, output)
print(f"\nTest build successful: {output}\n") print(f"\nTest build successful: {output}\n")
def run(self): def run(self):
""" """
@ -199,7 +199,7 @@ class Target(ABC):
rm_tree(self.config.bin_dir) rm_tree(self.config.bin_dir)
print(f"Removed {self.config.bin_dir}") print(f"Removed {self.config.bin_dir}")
print("\nClean complete\n") print("\nClean complete\n")
def compile_source(self, source: Path, is_test: bool = False) -> Path: def compile_source(self, source: Path, is_test: bool = False) -> Path:
""" """

View File

@ -15,6 +15,7 @@ from pathlib import Path
from typing import Optional from typing import Optional
# TODO: Is this needed?
def mkdir(path: Path): def mkdir(path: Path):
""" """
Create a directory if it doesn't exist. Create a directory if it doesn't exist.
@ -25,6 +26,7 @@ def mkdir(path: Path):
path.mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
# TODO: Is this needed?
def rm_tree(path: Path): def rm_tree(path: Path):
""" """
Remove a directory tree if it exists. Remove a directory tree if it exists.
@ -36,6 +38,7 @@ def rm_tree(path: Path):
shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, ignore_errors=True)
# TODO: Is this needed?
def run_command(cmd: list, cwd: Optional[Path] = None, capture_output: bool = False): def run_command(cmd: list, cwd: Optional[Path] = None, capture_output: bool = False):
""" """
Run a command and optionally capture its output. Run a command and optionally capture its output.