From a6d867c31ea9c328cae0afb8175676e0f7637cbb Mon Sep 17 00:00:00 2001 From: Kyler Date: Fri, 19 Dec 2025 14:30:31 -0700 Subject: [PATCH] Refactored to use BaseGCCCompiler --- SLS_C/build_system/compilers/arm_gcc.py | 145 +++++++------------ SLS_C/build_system/compilers/base_gcc.py | 153 ++++++++++++++++++++ SLS_C/build_system/compilers/clang.py | 44 ++++-- SLS_C/build_system/compilers/gcc.py | 171 ++++++----------------- 4 files changed, 279 insertions(+), 234 deletions(-) create mode 100644 SLS_C/build_system/compilers/base_gcc.py diff --git a/SLS_C/build_system/compilers/arm_gcc.py b/SLS_C/build_system/compilers/arm_gcc.py index 600ba3a..267dced 100644 --- a/SLS_C/build_system/compilers/arm_gcc.py +++ b/SLS_C/build_system/compilers/arm_gcc.py @@ -6,11 +6,11 @@ Used primarily for RP2040 and other ARM Cortex-M microcontrollers. from pathlib import Path from typing import List, Optional -from .base import Compiler, CompileResult -from build_system.utils import run +from .base_gcc import BaseGCCCompiler +from .base import CompileResult -class ARMGCCCompiler(Compiler): +class ARMGCCCompiler(BaseGCCCompiler): """ARM GCC compiler for embedded targets.""" def __init__(self, executable: str = "arm-none-eabi-gcc", cpu: str = "cortex-m0plus"): @@ -25,6 +25,18 @@ class ARMGCCCompiler(Compiler): self.cpu = cpu self.thumb = True # Use Thumb instruction set by default + def _get_cpu_flags(self) -> List[str]: + """ + Get CPU-specific flags. + + Returns: + List of CPU-specific compiler flags + """ + flags = [f"-mcpu={self.cpu}"] + if self.thumb: + flags.append("-mthumb") + return flags + def compile(self, source: Path, output: Path, @@ -49,51 +61,18 @@ class ARMGCCCompiler(Compiler): Raises: subprocess.CalledProcessError: If compilation fails """ - # Ensure output directory exists - output.parent.mkdir(parents=True, exist_ok=True) + # Prepend CPU-specific flags to user flags + cpu_flags = self._get_cpu_flags() + combined_flags = cpu_flags + (flags or []) - # Build command - cmd = [self.executable] - - # Add CPU-specific flags - cmd.append(f"-mcpu={self.cpu}") - if self.thumb: - cmd.append("-mthumb") - - # Add flags - if flags: - cmd.extend(flags) - - # Add compile-only flag - cmd.append(self.get_compile_only_flag()) - - # Add defines - if defines: - cmd.extend([self.get_define_flag(d) for d in defines]) - - # Add include directories - if include_dirs: - cmd.extend([self.get_include_flag(d) for d in include_dirs]) - - # Add dependency generation if requested - dep_file = None - if dependency_file: - cmd.extend(["-MMD", "-MP"]) - dep_file = dependency_file - - # Add source file - cmd.append(str(source)) - - # Add output file - cmd.extend(self.get_output_flag(output)) - - # Run compilation - run(cmd) # type: ignore - - return CompileResult( - object_file=output, - dependency_file=dep_file, - success=True + # Call parent compile with combined flags + return super().compile( + source=source, + output=output, + flags=combined_flags, + defines=defines, + include_dirs=include_dirs, + dependency_file=dependency_file ) def link(self, @@ -118,51 +97,18 @@ class ARMGCCCompiler(Compiler): Raises: subprocess.CalledProcessError: If linking fails """ - # Ensure output directory exists - output.parent.mkdir(parents=True, exist_ok=True) + # Prepend CPU-specific flags to user flags + cpu_flags = self._get_cpu_flags() + combined_flags = cpu_flags + (flags or []) - # Build command - cmd = [self.executable] - - # Add CPU-specific flags - cmd.append(f"-mcpu={self.cpu}") - if self.thumb: - cmd.append("-mthumb") - - # Add object files - cmd.extend([str(obj) for obj in objects]) - - # Add linker flags - if flags: - cmd.extend(flags) - - # Add library directories - if library_dirs: - cmd.extend([f"-L{lib_dir}" for lib_dir in library_dirs]) - - # Add libraries - if libraries: - cmd.extend([f"-l{lib}" for lib in libraries]) - - # Add output file - cmd.extend(["-o", str(output)]) - - # Run linker - run(cmd) # type: ignore - - return True - - def get_object_extension(self) -> str: - """Get object file extension for ARM GCC.""" - return ".o" - - def get_dependency_extension(self) -> str: - """Get dependency file extension for ARM GCC.""" - return ".d" - - def supports_dependency_generation(self) -> bool: - """ARM GCC supports automatic dependency generation.""" - return True + # Call parent link with combined flags + return super().link( + objects=objects, + output=output, + flags=combined_flags, + libraries=libraries, + library_dirs=library_dirs + ) def set_cpu(self, cpu: str) -> None: """ @@ -183,6 +129,22 @@ class ARMGCCCompiler(Compiler): self.thumb = enabled +def detect_arm_gcc_compiler() -> Optional[ARMGCCCompiler]: + """ + Detect and return an available ARM GCC compiler. + + Returns: + ARMGCCCompiler instance if found, None otherwise + """ + from build_system.utils import which + + # Try to find ARM GCC + if which("arm-none-eabi-gcc"): + return ARMGCCCompiler() + + return None + + def check_arm_toolchain() -> bool: """ Check if ARM toolchain is available. @@ -200,3 +162,4 @@ def check_arm_toolchain() -> bool: " Ubuntu/Debian: sudo apt install gcc-arm-none-eabi\n" " macOS: brew install arm-none-eabi-gcc" ) + return False diff --git a/SLS_C/build_system/compilers/base_gcc.py b/SLS_C/build_system/compilers/base_gcc.py new file mode 100644 index 0000000..824c9aa --- /dev/null +++ b/SLS_C/build_system/compilers/base_gcc.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +""" +Base GCC-like compiler implementation. +Provides common functionality for GCC, Clang, and other GCC-compatible compilers. +""" + +from pathlib import Path +from typing import List, Optional +from .base import Compiler, CompileResult +from build_system.utils import run + + +class BaseGCCCompiler(Compiler): + """Base class for GCC-compatible compilers (GCC, Clang, etc.).""" + + def __init__(self, name: str, executable: str): + """ + Initialize base GCC-compatible compiler. + + Args: + name: Human-readable compiler name + executable: Compiler executable name + """ + super().__init__(name, executable) + + def compile(self, + source: Path, + output: Path, + flags: Optional[List[str]] = None, + defines: Optional[List[str]] = None, + include_dirs: Optional[List[Path]] = None, + dependency_file: Optional[Path] = None) -> CompileResult: + """ + Compile a source file using GCC-compatible compiler. + + Args: + source: Source file path + output: Output object file path + flags: Additional compiler flags + defines: Preprocessor defines + include_dirs: Additional include directories + dependency_file: Path for dependency file (.d file) + + Returns: + CompileResult with object file and dependency file paths + + Raises: + subprocess.CalledProcessError: If compilation fails + """ + # Ensure output directory exists + output.parent.mkdir(parents=True, exist_ok=True) + + # Build command + cmd = [self.executable] + + # Add flags + if flags: + cmd.extend(flags) + + # Add compile-only flag + cmd.append(self.get_compile_only_flag()) + + # Add defines + if defines: + cmd.extend([self.get_define_flag(d) for d in defines]) + + # Add include directories + if include_dirs: + cmd.extend([self.get_include_flag(d) for d in include_dirs]) + + # Add dependency generation if requested + dep_file = None + if dependency_file: + cmd.extend(["-MMD", "-MP"]) + dep_file = dependency_file + + # Add source file + cmd.append(str(source)) + + # Add output file + cmd.extend(self.get_output_flag(output)) + + # Run compilation + run(cmd) # type: ignore + + return CompileResult( + object_file=output, + dependency_file=dep_file, + success=True + ) + + def link(self, + objects: List[Path], + output: Path, + flags: Optional[List[str]] = None, + libraries: Optional[List[str]] = None, + library_dirs: Optional[List[Path]] = None) -> bool: + """ + Link object files into an executable using GCC-compatible compiler. + + Args: + objects: List of object file paths + output: Output executable path + flags: Additional linker flags + libraries: Libraries to link against + library_dirs: Additional library search directories + + Returns: + True if linking succeeded + + Raises: + subprocess.CalledProcessError: If linking fails + """ + # Ensure output directory exists + output.parent.mkdir(parents=True, exist_ok=True) + + # Build command + cmd = [self.executable] + + # Add object files + cmd.extend([str(obj) for obj in objects]) + + # Add linker flags + if flags: + cmd.extend(flags) + + # Add library directories + if library_dirs: + cmd.extend([f"-L{lib_dir}" for lib_dir in library_dirs]) + + # Add libraries + if libraries: + cmd.extend([f"-l{lib}" for lib in libraries]) + + # Add output file + cmd.extend(["-o", str(output)]) + + # Run linker + run(cmd) # type: ignore + + return True + + def get_object_extension(self) -> str: + """Get object file extension for GCC-compatible compilers.""" + return ".o" + + def get_dependency_extension(self) -> str: + """Get dependency file extension for GCC-compatible compilers.""" + return ".d" + + def supports_dependency_generation(self) -> bool: + """GCC-compatible compilers support automatic dependency generation.""" + return True diff --git a/SLS_C/build_system/compilers/clang.py b/SLS_C/build_system/compilers/clang.py index 3b1202a..e2b23de 100644 --- a/SLS_C/build_system/compilers/clang.py +++ b/SLS_C/build_system/compilers/clang.py @@ -1,17 +1,15 @@ #!/usr/bin/env python3 """ Clang compiler implementation. -While Clang is compatible with GCC, this provides a dedicated implementation -for cases where Clang-specific features or flags are needed. """ -from pathlib import Path -from typing import List, Optional -from .gcc import GCCCompiler +from typing import Optional +from .base_gcc import BaseGCCCompiler +import subprocess -class ClangCompiler(GCCCompiler): - """Clang compiler implementation (inherits from GCC).""" +class ClangCompiler(BaseGCCCompiler): + """Clang compiler implementation.""" def __init__(self, executable: str = "clang"): """ @@ -20,9 +18,7 @@ class ClangCompiler(GCCCompiler): Args: executable: Compiler executable name ("clang") """ - # Call parent constructor but override the name - super().__init__(executable) - self.name = "Clang" + super().__init__("Clang", executable) def get_version(self) -> Optional[str]: """ @@ -31,10 +27,13 @@ class ClangCompiler(GCCCompiler): Returns: Version string if available, None otherwise """ - from build_system.utils import run_quiet - try: - result = run_quiet([self.executable, "--version"], check=False) + result = subprocess.run( + [self.executable, "--version"], + capture_output=True, + text=True, + check=False + ) if result.returncode == 0 and result.stdout: # Parse first line of version output first_line = result.stdout.strip().split('\n')[0] @@ -43,3 +42,22 @@ class ClangCompiler(GCCCompiler): pass return None + + +def detect_clang_compiler() -> Optional[ClangCompiler]: + """ + Detect and return an available Clang compiler. + + Returns: + ClangCompiler instance if found, None otherwise + """ + from build_system.utils import which + + # Try to find Clang + compilers = ["clang", "clang-18", "clang-17", "clang-16", "clang-15"] + + for compiler in compilers: + if which(compiler): + return ClangCompiler(compiler) + + return None diff --git a/SLS_C/build_system/compilers/gcc.py b/SLS_C/build_system/compilers/gcc.py index 0d9e5f3..9fa3933 100644 --- a/SLS_C/build_system/compilers/gcc.py +++ b/SLS_C/build_system/compilers/gcc.py @@ -1,153 +1,64 @@ #!/usr/bin/env python3 """ -GCC/Clang compiler implementation. -Handles both GCC and Clang as they use similar command-line interfaces. +GCC compiler implementation. """ -from pathlib import Path -from typing import List, Optional -from .base import Compiler, CompileResult -from build_system.utils import run +from typing import Optional +from .base_gcc import BaseGCCCompiler -class GCCCompiler(Compiler): - """GCC or Clang compiler implementation.""" +class GCCCompiler(BaseGCCCompiler): + """GCC compiler implementation.""" def __init__(self, executable: str = "gcc"): """ - Initialize GCC/Clang compiler. + Initialize GCC compiler. Args: - executable: Compiler executable name ("gcc" or "clang") + executable: Compiler executable name ("gcc") """ - name = "Clang" if "clang" in executable.lower() else "GCC" - super().__init__(name, executable) + super().__init__("GCC", executable) - def compile(self, - source: Path, - output: Path, - flags: Optional[List[str]] = None, - defines: Optional[List[str]] = None, - include_dirs: Optional[List[Path]] = None, - dependency_file: Optional[Path] = None) -> CompileResult: + def get_version(self) -> Optional[str]: """ - Compile a source file using GCC/Clang. - - Args: - source: Source file path - output: Output object file path - flags: Additional compiler flags - defines: Preprocessor defines - include_dirs: Additional include directories - dependency_file: Path for dependency file (.d file) + Get the GCC version string. Returns: - CompileResult with object file and dependency file paths - - Raises: - subprocess.CalledProcessError: If compilation fails + Version string if available, None otherwise """ - # Ensure output directory exists - output.parent.mkdir(parents=True, exist_ok=True) + import subprocess - # Build command - cmd = [self.executable] + try: + result = subprocess.run( + [self.executable, "--version"], + capture_output=True, + text=True, + check=False + ) + if result.returncode == 0 and result.stdout: + # Parse first line of version output + first_line = result.stdout.strip().split('\n')[0] + return first_line + except Exception: + pass - # Add flags - if flags: - cmd.extend(flags) - - # Add compile-only flag - cmd.append(self.get_compile_only_flag()) - - # Add defines - if defines: - cmd.extend([self.get_define_flag(d) for d in defines]) - - # Add include directories - if include_dirs: - cmd.extend([self.get_include_flag(d) for d in include_dirs]) - - # Add dependency generation if requested - dep_file = None - if dependency_file: - cmd.extend(["-MMD", "-MP"]) - dep_file = dependency_file - - # Add source file - cmd.append(str(source)) - - # Add output file - cmd.extend(self.get_output_flag(output)) - - # Run compilation - run(cmd) # type: ignore - - return CompileResult( - object_file=output, - dependency_file=dep_file, - success=True - ) + return None + + +def detect_gcc_compiler() -> Optional[GCCCompiler]: + """ + Detect and return an available GCC compiler. - def link(self, - objects: List[Path], - output: Path, - flags: Optional[List[str]] = None, - libraries: Optional[List[str]] = None, - library_dirs: Optional[List[Path]] = None) -> bool: - """ - Link object files into an executable using GCC/Clang. - - Args: - objects: List of object file paths - output: Output executable path - flags: Additional linker flags - libraries: Libraries to link against - library_dirs: Additional library search directories - - Returns: - True if linking succeeded - - Raises: - subprocess.CalledProcessError: If linking fails - """ - # Ensure output directory exists - output.parent.mkdir(parents=True, exist_ok=True) - - # Build command - cmd = [self.executable] - - # Add object files - cmd.extend([str(obj) for obj in objects]) - - # Add linker flags - if flags: - cmd.extend(flags) - - # Add library directories - if library_dirs: - cmd.extend([f"-L{lib_dir}" for lib_dir in library_dirs]) - - # Add libraries - if libraries: - cmd.extend([f"-l{lib}" for lib in libraries]) - - # Add output file - cmd.extend(["-o", str(output)]) - - # Run linker - run(cmd) # type: ignore - - return True + Returns: + GCCCompiler instance if found, None otherwise + """ + from build_system.utils import which - def get_object_extension(self) -> str: - """Get object file extension for GCC/Clang.""" - return ".o" + # Try to find GCC + compilers = ["gcc", "gcc-13", "gcc-12", "gcc-11", "cc"] - def get_dependency_extension(self) -> str: - """Get dependency file extension for GCC/Clang.""" - return ".d" + for compiler in compilers: + if which(compiler): + return GCCCompiler(compiler) - def supports_dependency_generation(self) -> bool: - """GCC/Clang supports automatic dependency generation.""" - return True + return None