Started compilers
This commit is contained in:
parent
d775ab6067
commit
a66bce0041
|
|
@ -0,0 +1,143 @@
|
||||||
|
"""
|
||||||
|
Compiler module for the build system.
|
||||||
|
|
||||||
|
This module provides compiler implementations for various toolchains:
|
||||||
|
- GCC (Unix/Linux)
|
||||||
|
- Clang (macOS/Unix)
|
||||||
|
- MSVC (Windows)
|
||||||
|
- ARM GCC (embedded ARM targets)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from compilers import GCCCompiler, ClangCompiler, MSVCCompiler
|
||||||
|
|
||||||
|
compiler = GCCCompiler()
|
||||||
|
compiler.compile(source, output, includes=[...], defines={...})
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .base import Compiler
|
||||||
|
from .gcc import GCCCompiler
|
||||||
|
from .clang import ClangCompiler
|
||||||
|
from .msvc import MSVCCompiler
|
||||||
|
from .arm_gcc import ARMGCCCompiler, RP2040Compiler
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Compiler',
|
||||||
|
'GCCCompiler',
|
||||||
|
'ClangCompiler',
|
||||||
|
'MSVCCompiler',
|
||||||
|
'ARMGCCCompiler',
|
||||||
|
'RP2040Compiler',
|
||||||
|
]
|
||||||
|
|
||||||
|
def detect_compiler(platform: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Detect and return an appropriate compiler for the given platform.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
platform: Target platform ('linux', 'windows', 'darwin'/'macos', 'rp2040', etc.)
|
||||||
|
If None, detects current platform.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An instance of the appropriate Compiler subclass
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If no suitable compiler is found
|
||||||
|
"""
|
||||||
|
import platform as plat
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# Detect current platform if not specified
|
||||||
|
if platform is None:
|
||||||
|
system = plat.system().lower()
|
||||||
|
if system == "darwin":
|
||||||
|
platform = "macos"
|
||||||
|
else:
|
||||||
|
platform = system
|
||||||
|
|
||||||
|
platform = platform.lower()
|
||||||
|
|
||||||
|
# Try to find appropriate compiler
|
||||||
|
if platform in ("linux", "unix"):
|
||||||
|
# Prefer GCC on Linux
|
||||||
|
if shutil.which("gcc"):
|
||||||
|
return GCCCompiler("gcc")
|
||||||
|
elif shutil.which("clang"):
|
||||||
|
return ClangCompiler("clang")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("No suitable compiler found (tried gcc, clang)")
|
||||||
|
|
||||||
|
elif platform in ("macos", "darwin"):
|
||||||
|
# Prefer Clang on macOS (default compiler)
|
||||||
|
if shutil.which("clang"):
|
||||||
|
return ClangCompiler("clang")
|
||||||
|
elif shutil.which("gcc"):
|
||||||
|
return GCCCompiler("gcc")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("No suitable compiler found (tried clang, gcc)")
|
||||||
|
|
||||||
|
elif platform == "windows":
|
||||||
|
# Try MSVC first, then MinGW GCC
|
||||||
|
if shutil.which("cl"):
|
||||||
|
return MSVCCompiler("cl")
|
||||||
|
elif shutil.which("gcc"):
|
||||||
|
return GCCCompiler("gcc")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("No suitable compiler found (tried cl, gcc)")
|
||||||
|
|
||||||
|
elif platform == "rp2040":
|
||||||
|
# RP2040 needs ARM toolchain
|
||||||
|
if shutil.which("arm-none-eabi-gcc"):
|
||||||
|
return RP2040Compiler()
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
"ARM GCC toolchain not found. Please install arm-none-eabi-gcc:\n"
|
||||||
|
" Ubuntu/Debian: sudo apt install gcc-arm-none-eabi\n"
|
||||||
|
" macOS: brew install arm-none-eabi-gcc"
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown platform: {platform}")
|
||||||
|
|
||||||
|
def get_available_compilers():
|
||||||
|
"""
|
||||||
|
Get a list of all compilers available on the current system.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping compiler names to Compiler instances
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
available = {}
|
||||||
|
|
||||||
|
# Check GCC
|
||||||
|
if shutil.which("gcc"):
|
||||||
|
available['gcc'] = GCCCompiler()
|
||||||
|
|
||||||
|
# Check Clang
|
||||||
|
if shutil.which("clang"):
|
||||||
|
available['clang'] = ClangCompiler()
|
||||||
|
|
||||||
|
# Check MSVC
|
||||||
|
if shutil.which("cl"):
|
||||||
|
available['msvc'] = MSVCCompiler()
|
||||||
|
|
||||||
|
# Check ARM GCC
|
||||||
|
if shutil.which("arm-none-eabi-gcc"):
|
||||||
|
available['arm-gcc'] = ARMGCCCompiler()
|
||||||
|
available['rp2040'] = RP2040Compiler()
|
||||||
|
|
||||||
|
return available
|
||||||
|
|
||||||
|
def print_available_compilers():
|
||||||
|
"""Print all available compilers on the system."""
|
||||||
|
compilers = get_available_compilers()
|
||||||
|
|
||||||
|
if not compilers:
|
||||||
|
print("No compilers found on this system.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Available compilers:")
|
||||||
|
for name, compiler in compilers.items():
|
||||||
|
print(f" - {name}: {compiler.executable}")
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
"""
|
||||||
|
ARM GCC compiler implementation for embedded targets.
|
||||||
|
|
||||||
|
This module implements the Compiler interface for ARM GCC toolchain,
|
||||||
|
used for cross-compiling to ARM Cortex-M processors (e.g., RP2040).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from .gcc import GCCCompiler
|
||||||
|
|
||||||
|
|
||||||
|
class ARMGCCCompiler(GCCCompiler):
|
||||||
|
"""
|
||||||
|
ARM GCC compiler implementation for embedded targets.
|
||||||
|
|
||||||
|
Uses the arm-none-eabi-gcc toolchain for bare-metal ARM development.
|
||||||
|
Extends GCCCompiler with ARM Cortex-M specific flags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
executable: str = "arm-none-eabi-gcc",
|
||||||
|
cpu: str = "cortex-m0plus"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize ARM GCC compiler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
executable: ARM GCC executable (default: "arm-none-eabi-gcc")
|
||||||
|
cpu: Target CPU architecture (default: "cortex-m0plus" for RP2040)
|
||||||
|
"""
|
||||||
|
super().__init__(executable=executable, is_clang=False)
|
||||||
|
self.cpu = cpu
|
||||||
|
self.fpu = None
|
||||||
|
self.float_abi = None
|
||||||
|
self.additional_arch_flags = []
|
||||||
|
|
||||||
|
def set_cpu(self, cpu: str):
|
||||||
|
"""
|
||||||
|
Set target CPU architecture.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cpu: CPU name (e.g., "cortex-m0plus", "cortex-m4", "cortex-m7")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self for method chaining
|
||||||
|
"""
|
||||||
|
self.cpu = cpu
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_fpu(self, fpu: str, float_abi: str = "hard"):
|
||||||
|
"""
|
||||||
|
Set FPU configuration (for Cortex-M4F, M7, etc.).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fpu: FPU type (e.g., "fpv4-sp-d16", "fpv5-d16")
|
||||||
|
float_abi: Float ABI ("soft", "softfp", or "hard")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self for method chaining
|
||||||
|
"""
|
||||||
|
self.fpu = fpu
|
||||||
|
self.float_abi = float_abi
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_arch_flags(self, flags: List[str]):
|
||||||
|
"""
|
||||||
|
Add additional architecture-specific flags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flags: List of flags to add
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self for method chaining
|
||||||
|
"""
|
||||||
|
self.additional_arch_flags.extend(flags)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def compile(
|
||||||
|
self,
|
||||||
|
source: Path,
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
includes: Optional[List[Path]] = None,
|
||||||
|
defines: Optional[dict] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
generate_deps: bool = True,
|
||||||
|
is_test: bool = False
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Compile using ARM GCC with embedded-specific flags.
|
||||||
|
|
||||||
|
Adds ARM Cortex-M architecture flags before calling parent compile.
|
||||||
|
"""
|
||||||
|
flags = flags or []
|
||||||
|
|
||||||
|
# ARM architecture flags (must come early)
|
||||||
|
arch_flags = [
|
||||||
|
f"-mcpu={self.cpu}",
|
||||||
|
"-mthumb", # Use Thumb instruction set
|
||||||
|
]
|
||||||
|
|
||||||
|
# FPU flags if configured
|
||||||
|
if self.fpu:
|
||||||
|
arch_flags.append(f"-mfpu={self.fpu}")
|
||||||
|
arch_flags.append(f"-mfloat-abi={self.float_abi}")
|
||||||
|
|
||||||
|
# Additional architecture flags
|
||||||
|
arch_flags.extend(self.additional_arch_flags)
|
||||||
|
|
||||||
|
# Prepend architecture flags (they need to come before other flags)
|
||||||
|
flags = arch_flags + flags
|
||||||
|
|
||||||
|
return super().compile(
|
||||||
|
source,
|
||||||
|
output,
|
||||||
|
includes=includes,
|
||||||
|
defines=defines,
|
||||||
|
flags=flags,
|
||||||
|
generate_deps=generate_deps,
|
||||||
|
is_test=is_test
|
||||||
|
)
|
||||||
|
|
||||||
|
def link(
|
||||||
|
self,
|
||||||
|
objects: List[Path],
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
libraries: Optional[List[str]] = None,
|
||||||
|
library_paths: Optional[List[Path]] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
linker_script: Optional[Path] = None,
|
||||||
|
specs: Optional[str] = None
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Link using ARM GCC with embedded-specific flags.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objects: Object files to link
|
||||||
|
output: Output executable path
|
||||||
|
libraries: Libraries to link
|
||||||
|
library_paths: Library search paths
|
||||||
|
flags: Additional linker flags
|
||||||
|
linker_script: Path to linker script (.ld file)
|
||||||
|
specs: Specs file (e.g., "nosys.specs", "nano.specs")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to linked executable
|
||||||
|
"""
|
||||||
|
flags = flags or []
|
||||||
|
|
||||||
|
# ARM architecture flags (need to be passed to linker too)
|
||||||
|
arch_flags = [
|
||||||
|
f"-mcpu={self.cpu}",
|
||||||
|
"-mthumb",
|
||||||
|
]
|
||||||
|
|
||||||
|
# FPU flags
|
||||||
|
if self.fpu:
|
||||||
|
arch_flags.append(f"-mfpu={self.fpu}")
|
||||||
|
arch_flags.append(f"-mfloat-abi={self.float_abi}")
|
||||||
|
|
||||||
|
# Additional architecture flags
|
||||||
|
arch_flags.extend(self.additional_arch_flags)
|
||||||
|
|
||||||
|
# Linker script
|
||||||
|
if linker_script:
|
||||||
|
arch_flags.append(f"-T{linker_script}")
|
||||||
|
|
||||||
|
# Specs file (for selecting C library variant)
|
||||||
|
if specs:
|
||||||
|
arch_flags.append(f"--specs={specs}")
|
||||||
|
|
||||||
|
# Prepend architecture flags
|
||||||
|
flags = arch_flags + flags
|
||||||
|
|
||||||
|
return super().link(
|
||||||
|
objects,
|
||||||
|
output,
|
||||||
|
libraries=libraries,
|
||||||
|
library_paths=library_paths,
|
||||||
|
flags=flags
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_executable_extension(self) -> str:
|
||||||
|
"""
|
||||||
|
Get executable extension for ARM embedded.
|
||||||
|
|
||||||
|
Returns .elf for embedded binaries (can be converted to .bin, .hex, .uf2)
|
||||||
|
"""
|
||||||
|
return ".elf"
|
||||||
|
|
||||||
|
|
||||||
|
class RP2040Compiler(ARMGCCCompiler):
|
||||||
|
"""
|
||||||
|
Specialized ARM GCC compiler for RP2040 (Raspberry Pi Pico).
|
||||||
|
|
||||||
|
Pre-configured for Cortex-M0+ with RP2040-specific settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable: str = "arm-none-eabi-gcc"):
|
||||||
|
"""Initialize compiler for RP2040."""
|
||||||
|
super().__init__(executable=executable, cpu="cortex-m0plus")
|
||||||
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
"""
|
||||||
|
Abstract base class for compilers.
|
||||||
|
|
||||||
|
This module defines the interface that all compiler implementations must follow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class Compiler(ABC):
|
||||||
|
"""
|
||||||
|
Abstract base class for all compiler implementations.
|
||||||
|
|
||||||
|
Subclasses must implement compile() and link() methods with
|
||||||
|
compiler-specific logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable: str):
|
||||||
|
"""
|
||||||
|
Initialize the compiler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
executable: Name or path to the compiler executable
|
||||||
|
"""
|
||||||
|
self.executable = executable
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def compile(
|
||||||
|
self,
|
||||||
|
source: Path,
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
includes: Optional[List[Path]] = None,
|
||||||
|
defines: Optional[dict] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
generate_deps: bool = True,
|
||||||
|
is_test: bool = False
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Compile a single source file to an object file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: Path to source file (.c)
|
||||||
|
output: Path to output object file (.o or .obj)
|
||||||
|
includes: List of include directories
|
||||||
|
defines: Dictionary of preprocessor defines {name: value}
|
||||||
|
flags: Additional compiler flags
|
||||||
|
generate_deps: Whether to generate dependency files
|
||||||
|
is_test: Whether this is a test compilation (may affect optimization)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the compiled object file
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def link(
|
||||||
|
self,
|
||||||
|
objects: List[Path],
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
libraries: Optional[List[str]] = None,
|
||||||
|
library_paths: Optional[List[Path]] = None,
|
||||||
|
flags: Optional[List[str]] = None
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Link object files into an executable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objects: List of object files to link
|
||||||
|
output: Path to output executable
|
||||||
|
libraries: List of libraries to link against (e.g., ['m', 'pthread'])
|
||||||
|
library_paths: List of library search paths
|
||||||
|
flags: Additional linker flags
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the linked executable
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_object_extension(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the object file extension for this compiler.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Object file extension (e.g., '.o' or '.obj')
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_executable_extension(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the executable extension for this compiler/platform.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Executable extension (e.g., '' for Unix, '.exe' for Windows)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_dependency_file(self, object_file: Path) -> Path:
|
||||||
|
"""
|
||||||
|
Get the dependency file path for an object file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
object_file: Path to object file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to dependency file (usually .d extension)
|
||||||
|
"""
|
||||||
|
return object_file.with_suffix('.d')
|
||||||
|
|
||||||
|
def is_available(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check if this compiler is available on the system.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if compiler executable exists in PATH
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
return shutil.which(self.executable) is not None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}(executable='{self.executable}')"
|
||||||
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
"""
|
||||||
|
Clang compiler implementation.
|
||||||
|
|
||||||
|
This module implements the Compiler interface specifically for Clang,
|
||||||
|
with any Clang-specific features or flags that differ from GCC.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from .gcc import GCCCompiler
|
||||||
|
|
||||||
|
|
||||||
|
class ClangCompiler(GCCCompiler):
|
||||||
|
"""
|
||||||
|
Clang compiler implementation.
|
||||||
|
|
||||||
|
Clang uses a GCC-compatible command-line interface, so most functionality
|
||||||
|
is inherited from GCCCompiler. This class adds Clang-specific features:
|
||||||
|
1. macOS version targeting
|
||||||
|
2. Sanitizer support
|
||||||
|
3. Future Clang-specific optimizations
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable: str = "clang"):
|
||||||
|
"""
|
||||||
|
Initialize Clang compiler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
executable: Clang executable name (default: "clang")
|
||||||
|
"""
|
||||||
|
# Call parent with is_clang=True
|
||||||
|
super().__init__(executable=executable, is_clang=True)
|
||||||
|
self.macos_min_version = None
|
||||||
|
self.sanitizers = []
|
||||||
|
|
||||||
|
def set_macos_version(self, min_version: str = "10.13"):
|
||||||
|
"""
|
||||||
|
Set minimum macOS version for cross-compilation or macOS builds.
|
||||||
|
|
||||||
|
This is a Clang-specific feature commonly used on macOS.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
min_version: Minimum macOS version (e.g., "10.13")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self for method chaining
|
||||||
|
"""
|
||||||
|
self.macos_min_version = min_version
|
||||||
|
return self
|
||||||
|
|
||||||
|
def enable_sanitizers(self, sanitizers: List[str]):
|
||||||
|
"""
|
||||||
|
Enable Clang sanitizers (address, thread, undefined, memory, etc.).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sanitizers: List of sanitizers to enable (e.g., ['address', 'undefined'])
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Self for method chaining
|
||||||
|
"""
|
||||||
|
self.sanitizers = sanitizers
|
||||||
|
return self
|
||||||
|
|
||||||
|
def compile(
|
||||||
|
self,
|
||||||
|
source: Path,
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
includes: Optional[List[Path]] = None,
|
||||||
|
defines: Optional[dict] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
generate_deps: bool = True,
|
||||||
|
is_test: bool = False
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Compile using Clang with optional sanitizers and macOS flags.
|
||||||
|
|
||||||
|
Extends GCCCompiler.compile() with Clang-specific features.
|
||||||
|
"""
|
||||||
|
flags = flags or []
|
||||||
|
|
||||||
|
# Add macOS version flag if set
|
||||||
|
if self.macos_min_version:
|
||||||
|
flags.append(f"-mmacosx-version-min={self.macos_min_version}")
|
||||||
|
|
||||||
|
# Add sanitizers if enabled
|
||||||
|
for san in self.sanitizers:
|
||||||
|
flags.append(f"-fsanitize={san}")
|
||||||
|
|
||||||
|
return super().compile(
|
||||||
|
source,
|
||||||
|
output,
|
||||||
|
includes=includes,
|
||||||
|
defines=defines,
|
||||||
|
flags=flags,
|
||||||
|
generate_deps=generate_deps,
|
||||||
|
is_test=is_test
|
||||||
|
)
|
||||||
|
|
||||||
|
def link(
|
||||||
|
self,
|
||||||
|
objects: List[Path],
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
libraries: Optional[List[str]] = None,
|
||||||
|
library_paths: Optional[List[Path]] = None,
|
||||||
|
flags: Optional[List[str]] = None
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Link using Clang with optional sanitizer support.
|
||||||
|
|
||||||
|
Sanitizers need to be enabled at link time as well.
|
||||||
|
"""
|
||||||
|
flags = flags or []
|
||||||
|
|
||||||
|
# Add sanitizers at link time
|
||||||
|
for san in self.sanitizers:
|
||||||
|
flags.append(f"-fsanitize={san}")
|
||||||
|
|
||||||
|
return super().link(
|
||||||
|
objects,
|
||||||
|
output,
|
||||||
|
libraries=libraries,
|
||||||
|
library_paths=library_paths,
|
||||||
|
flags=flags
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
"""
|
||||||
|
GCC and Clang compiler implementation.
|
||||||
|
|
||||||
|
This module implements the Compiler interface for GCC and Clang,
|
||||||
|
which share similar command-line interfaces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from .base import Compiler
|
||||||
|
|
||||||
|
|
||||||
|
class GCCCompiler(Compiler):
|
||||||
|
"""
|
||||||
|
GCC/Clang compiler implementation.
|
||||||
|
|
||||||
|
Works with both gcc and clang as they use compatible command-line syntax.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable: str = "gcc", is_clang: bool = False):
|
||||||
|
"""
|
||||||
|
Initialize GCC/Clang compiler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
executable: Compiler executable name (default: "gcc")
|
||||||
|
is_clang: Whether this is clang (affects some flags)
|
||||||
|
"""
|
||||||
|
super().__init__(executable)
|
||||||
|
self.is_clang = is_clang
|
||||||
|
|
||||||
|
def compile(
|
||||||
|
self,
|
||||||
|
source: Path,
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
includes: Optional[List[Path]] = None,
|
||||||
|
defines: Optional[dict] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
generate_deps: bool = True,
|
||||||
|
is_test: bool = False
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Compile a source file using GCC/Clang.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: Path to source file
|
||||||
|
output: Path to output object file
|
||||||
|
includes: Include directories
|
||||||
|
defines: Preprocessor defines
|
||||||
|
flags: Additional compiler flags
|
||||||
|
generate_deps: Generate dependency file
|
||||||
|
is_test: Test build (disables optimization)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to compiled object file
|
||||||
|
"""
|
||||||
|
cmd = [self.executable]
|
||||||
|
|
||||||
|
# Add user flags first (can be overridden by later flags)
|
||||||
|
if flags:
|
||||||
|
cmd.extend(flags)
|
||||||
|
|
||||||
|
# Standard flags
|
||||||
|
cmd.extend([
|
||||||
|
"-std=c99",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-Werror",
|
||||||
|
"-g" # Debug symbols
|
||||||
|
])
|
||||||
|
|
||||||
|
# Test builds: disable optimization and allow unused functions
|
||||||
|
if is_test:
|
||||||
|
cmd.extend(["-O0", "-Wno-unused-function"])
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
if includes:
|
||||||
|
for inc in includes:
|
||||||
|
cmd.append(f"-I{inc}")
|
||||||
|
|
||||||
|
# Defines
|
||||||
|
if defines:
|
||||||
|
for name, value in defines.items():
|
||||||
|
if value is None:
|
||||||
|
cmd.append(f"-D{name}")
|
||||||
|
else:
|
||||||
|
# Properly escape string values
|
||||||
|
if isinstance(value, str):
|
||||||
|
cmd.append(f"-D{name}=\"{value}\"")
|
||||||
|
else:
|
||||||
|
cmd.append(f"-D{name}={value}")
|
||||||
|
|
||||||
|
# Dependency generation
|
||||||
|
if generate_deps:
|
||||||
|
cmd.extend(["-MMD", "-MP"])
|
||||||
|
|
||||||
|
# Compile only
|
||||||
|
cmd.extend(["-c", str(source)])
|
||||||
|
|
||||||
|
# Output file
|
||||||
|
cmd.extend(["-o", str(output)])
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Run compilation
|
||||||
|
self._run(cmd)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def link(
|
||||||
|
self,
|
||||||
|
objects: List[Path],
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
libraries: Optional[List[str]] = None,
|
||||||
|
library_paths: Optional[List[Path]] = None,
|
||||||
|
flags: Optional[List[str]] = None
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Link object files into an executable using GCC/Clang.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objects: Object files to link
|
||||||
|
output: Output executable path
|
||||||
|
libraries: Libraries to link (e.g., ['m', 'pthread'])
|
||||||
|
library_paths: Library search paths
|
||||||
|
flags: Additional linker flags
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to linked executable
|
||||||
|
"""
|
||||||
|
cmd = [self.executable]
|
||||||
|
|
||||||
|
# Add user flags first
|
||||||
|
if flags:
|
||||||
|
cmd.extend(flags)
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
cmd.extend(str(obj) for obj in objects)
|
||||||
|
|
||||||
|
# Library paths
|
||||||
|
if library_paths:
|
||||||
|
for path in library_paths:
|
||||||
|
cmd.append(f"-L{path}")
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
if libraries:
|
||||||
|
for lib in libraries:
|
||||||
|
cmd.append(f"-l{lib}")
|
||||||
|
|
||||||
|
# Output file
|
||||||
|
cmd.extend(["-o", str(output)])
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Run linker
|
||||||
|
self._run(cmd)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_object_extension(self) -> str:
|
||||||
|
"""Get object file extension for GCC/Clang."""
|
||||||
|
return ".o"
|
||||||
|
|
||||||
|
def get_executable_extension(self) -> str:
|
||||||
|
"""Get executable extension (empty for Unix-like systems)."""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _run(self, cmd: List[str]):
|
||||||
|
"""
|
||||||
|
Execute a command and handle errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd: Command to execute
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
subprocess.CalledProcessError: If compilation/linking fails
|
||||||
|
"""
|
||||||
|
print(">>", " ".join(str(c) for c in cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
"""
|
||||||
|
MSVC (Microsoft Visual C++) compiler implementation.
|
||||||
|
|
||||||
|
This module implements the Compiler interface for Microsoft's MSVC compiler,
|
||||||
|
which has a completely different command-line syntax from GCC/Clang.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from .base import Compiler
|
||||||
|
|
||||||
|
|
||||||
|
class MSVCCompiler(Compiler):
|
||||||
|
"""
|
||||||
|
Microsoft Visual C++ compiler implementation.
|
||||||
|
|
||||||
|
MSVC uses different command-line syntax and conventions:
|
||||||
|
- Flags start with / instead of -
|
||||||
|
- Different flag names (/Fo vs -o, /Fe vs -o for linking)
|
||||||
|
- .obj instead of .o for object files
|
||||||
|
- .exe extension for executables
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, executable: str = "cl"):
|
||||||
|
"""
|
||||||
|
Initialize MSVC compiler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
executable: MSVC compiler executable (default: "cl")
|
||||||
|
"""
|
||||||
|
super().__init__(executable)
|
||||||
|
|
||||||
|
def compile(
|
||||||
|
self,
|
||||||
|
source: Path,
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
includes: Optional[List[Path]] = None,
|
||||||
|
defines: Optional[dict] = None,
|
||||||
|
flags: Optional[List[str]] = None,
|
||||||
|
generate_deps: bool = True,
|
||||||
|
is_test: bool = False
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Compile a source file using MSVC.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: Path to source file
|
||||||
|
output: Path to output object file
|
||||||
|
includes: Include directories
|
||||||
|
defines: Preprocessor defines
|
||||||
|
flags: Additional compiler flags
|
||||||
|
generate_deps: Generate dependency file (not used by MSVC)
|
||||||
|
is_test: Test build (disables optimization)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to compiled object file
|
||||||
|
"""
|
||||||
|
cmd = [self.executable]
|
||||||
|
|
||||||
|
# Add user flags first
|
||||||
|
if flags:
|
||||||
|
cmd.extend(flags)
|
||||||
|
|
||||||
|
# Standard flags
|
||||||
|
cmd.extend([
|
||||||
|
"/std:c11", # C11 standard (closest to c99)
|
||||||
|
"/W4", # Warning level 4 (similar to -Wall -Wextra)
|
||||||
|
"/WX", # Treat warnings as errors (like -Werror)
|
||||||
|
"/Zi", # Generate debug info (like -g)
|
||||||
|
])
|
||||||
|
|
||||||
|
# Test builds: disable optimization
|
||||||
|
if is_test:
|
||||||
|
cmd.append("/Od") # Disable optimization
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
if includes:
|
||||||
|
for inc in includes:
|
||||||
|
cmd.append(f"/I{inc}")
|
||||||
|
|
||||||
|
# Defines
|
||||||
|
if defines:
|
||||||
|
for name, value in defines.items():
|
||||||
|
if value is None:
|
||||||
|
cmd.append(f"/D{name}")
|
||||||
|
else:
|
||||||
|
# MSVC handles string escaping differently
|
||||||
|
if isinstance(value, str):
|
||||||
|
# Use backslash to escape quotes in MSVC
|
||||||
|
escaped = value.replace('"', '\\"')
|
||||||
|
cmd.append(f"/D{name}=\"{escaped}\"")
|
||||||
|
else:
|
||||||
|
cmd.append(f"/D{name}={value}")
|
||||||
|
|
||||||
|
# Compile only (don't link)
|
||||||
|
cmd.append("/c")
|
||||||
|
|
||||||
|
# Output file (note: /Fo with no space)
|
||||||
|
cmd.append(f"/Fo{output}")
|
||||||
|
|
||||||
|
# Source file
|
||||||
|
cmd.append(str(source))
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Run compilation
|
||||||
|
self._run(cmd)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def link(
|
||||||
|
self,
|
||||||
|
objects: List[Path],
|
||||||
|
output: Path,
|
||||||
|
*,
|
||||||
|
libraries: Optional[List[str]] = None,
|
||||||
|
library_paths: Optional[List[Path]] = None,
|
||||||
|
flags: Optional[List[str]] = None
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Link object files into an executable using MSVC.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objects: Object files to link
|
||||||
|
output: Output executable path
|
||||||
|
libraries: Libraries to link (e.g., ['kernel32', 'user32'])
|
||||||
|
library_paths: Library search paths
|
||||||
|
flags: Additional linker flags
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to linked executable
|
||||||
|
"""
|
||||||
|
cmd = [self.executable]
|
||||||
|
|
||||||
|
# Add user flags first
|
||||||
|
if flags:
|
||||||
|
cmd.extend(flags)
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
cmd.extend(str(obj) for obj in objects)
|
||||||
|
|
||||||
|
# Library paths
|
||||||
|
if library_paths:
|
||||||
|
for path in library_paths:
|
||||||
|
cmd.append(f"/LIBPATH:{path}")
|
||||||
|
|
||||||
|
# Libraries (MSVC uses .lib extension)
|
||||||
|
if libraries:
|
||||||
|
for lib in libraries:
|
||||||
|
# Add .lib if not present
|
||||||
|
if not lib.endswith('.lib'):
|
||||||
|
lib = f"{lib}.lib"
|
||||||
|
cmd.append(lib)
|
||||||
|
|
||||||
|
# Output file (note: /Fe with no space)
|
||||||
|
cmd.append(f"/Fe{output}")
|
||||||
|
|
||||||
|
# Link flag
|
||||||
|
cmd.append("/link")
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Run linker
|
||||||
|
self._run(cmd)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_object_extension(self) -> str:
|
||||||
|
"""Get object file extension for MSVC."""
|
||||||
|
return ".obj"
|
||||||
|
|
||||||
|
def get_executable_extension(self) -> str:
|
||||||
|
"""Get executable extension for Windows."""
|
||||||
|
return ".exe"
|
||||||
|
|
||||||
|
def _run(self, cmd: List[str]):
|
||||||
|
"""
|
||||||
|
Execute a command and handle errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd: Command to execute
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
subprocess.CalledProcessError: If compilation/linking fails
|
||||||
|
"""
|
||||||
|
print(">>", " ".join(str(c) for c in cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
Loading…
Reference in New Issue