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