""" 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)