#!/usr/bin/env python3 """ Base target interface for the build system. Defines the abstract Target class that all specific targets must implement. """ from abc import ABC, abstractmethod from pathlib import Path from typing import List, Optional from enum import Enum class BuildType(Enum): """Type of build to perform.""" RELEASE = "release" DEBUG = "debug" TEST = "test" def __str__(self) -> str: return self.value class Target(ABC): """Abstract base class for all build targets.""" def __init__(self, name: str): """ Initialize the target. Args: name: Target name (e.g., "linux", "windows", "rp2040") """ self.name = name self._build_type = BuildType.RELEASE @property def build_type(self) -> BuildType: """Get the current build type.""" return self._build_type @build_type.setter def build_type(self, value: BuildType) -> None: """Set the build type.""" self._build_type = value @abstractmethod def build(self) -> bool: """ Build the target. Returns: True if build succeeded, False otherwise """ pass @abstractmethod def clean(self) -> bool: """ Clean build artifacts for this target. Returns: True if clean succeeded, False otherwise """ pass def run(self) -> bool: """ Run the built executable. Returns: True if execution succeeded, False otherwise Raises: NotImplementedError: If the target doesn't support running """ raise NotImplementedError(f"Target '{self.name}' does not support running") def test(self) -> bool: """ Build and run tests for this target. Returns: True if tests passed, False otherwise Raises: NotImplementedError: If the target doesn't support testing """ raise NotImplementedError(f"Target '{self.name}' does not support testing") def debug(self) -> bool: """ Launch debugger with the built executable. Returns: True if debugger launched successfully, False otherwise Raises: NotImplementedError: If the target doesn't support debugging """ raise NotImplementedError(f"Target '{self.name}' does not support debugging") def get_output_path(self) -> Optional[Path]: """ Get the path to the main output binary. Returns: Path to output binary, or None if not applicable """ return None def get_test_output_path(self) -> Optional[Path]: """ Get the path to the test binary. Returns: Path to test binary, or None if not applicable """ return None def get_build_dir(self) -> Optional[Path]: """ Get the build directory for this target. Returns: Path to build directory, or None if using shared directories """ return None def supports_command(self, command: str) -> bool: """ Check if this target supports a specific command. Args: command: Command name ("build", "run", "test", "debug", "clean") Returns: True if command is supported, False otherwise """ if command == "build": return True elif command == "clean": return True elif command == "run": try: # Check if run() is overridden return type(self).run != Target.run except AttributeError: return False elif command == "test": try: return type(self).test != Target.test except AttributeError: return False elif command == "debug": try: return type(self).debug != Target.debug except AttributeError: return False return False def get_sources(self, exclude: Optional[List[str]] = None) -> List[Path]: """ Get list of source files to compile. Args: exclude: List of source file names to exclude Returns: List of source file paths """ from build_system.config import get_config config = get_config() sources = list(config.src_dir.glob("*.c")) if exclude: sources = [s for s in sources if s.name not in exclude] return sources def get_test_sources(self) -> List[Path]: """ Get list of test source files. Returns: List of test source file paths """ from build_system.config import get_config config = get_config() if not config.test_dir.exists(): return [] return list(config.test_dir.glob("*.c")) def generate_tests(self) -> bool: """ Generate test files from YAML if needed. Returns: True if generation succeeded or was not needed, False otherwise """ from build_system.config import get_config from build_system.utils import run config = get_config() script = config.test_script_path yaml = config.test_yaml_path output = config.test_output_file if not script.exists() or not yaml.exists(): print("Test generation skipped (missing files).") return False if output.exists() and output.stat().st_mtime > yaml.stat().st_mtime: return True try: run([config.python_command, str(script), str(yaml), str(output)]) return True except Exception as e: print(f"ERROR: Test generation failed: {e}") return False def __str__(self) -> str: """String representation of the target.""" return f"{self.name}" def __repr__(self) -> str: """Detailed string representation.""" return f"Target(name='{self.name}', build_type={self.build_type})"