#!/usr/bin/env python3 """ MSVC (Microsoft Visual C++) compiler implementation. Used for Windows native builds. """ from pathlib import Path from typing import List, Optional from .base import Compiler, CompileResult from build_system.utils import run import subprocess class MSVCCompiler(Compiler): """Microsoft Visual C++ compiler implementation.""" def __init__(self, executable: str = "cl"): """ Initialize MSVC compiler. Args: executable: Compiler executable name ("cl") """ super().__init__("MSVC", 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 MSVC. 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 (not used by MSVC) Returns: CompileResult with object file path 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 output file (MSVC uses /Fo) cmd.extend(self.get_output_flag(output)) # Add source file cmd.append(str(source)) # Run compilation run(cmd) # type: ignore return CompileResult( object_file=output, dependency_file=None, # MSVC doesn't generate .d files 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 MSVC. 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 output file (MSVC uses /Fe) cmd.append(f"/Fe{output}") # Add linker flags if flags: cmd.extend(flags) # Add library directories if library_dirs: cmd.extend([f"/LIBPATH:{lib_dir}" for lib_dir in library_dirs]) # Add libraries (MSVC uses .lib extension) if libraries: for lib in libraries: # Add .lib if not present lib_name = lib if lib.endswith('.lib') else f"{lib}.lib" cmd.append(lib_name) # Run linker run(cmd) # type: ignore return True def get_object_extension(self) -> str: """Get object file extension for MSVC.""" return ".obj" def get_dependency_extension(self) -> str: """Get dependency file extension for MSVC.""" return ".d" # Not actually used by MSVC def supports_dependency_generation(self) -> bool: """MSVC does not support automatic dependency generation like GCC.""" return False def get_define_flag(self, define: str) -> str: """Get the MSVC flag for a preprocessor define.""" return f"/D{define}" def get_include_flag(self, include_dir: Path) -> str: """Get the MSVC flag for an include directory.""" return f"/I{include_dir}" def get_output_flag(self, output: Path) -> List[str]: """Get the MSVC flag for specifying output file.""" return [f"/Fo{output}"] def get_compile_only_flag(self) -> str: """Get the MSVC flag to compile without linking.""" return "/c" def get_version(self) -> Optional[str]: """ Get the MSVC version string. Returns: Version string if available, None otherwise """ try: result = subprocess.run( [self.executable], capture_output=True, text=True, check=False ) # MSVC prints version info to stderr when called with no args if result.stderr: lines = result.stderr.strip().split('\n') for line in lines: if "Microsoft" in line or "Version" in line: return line.strip() except Exception: pass return None def detect_msvc_compiler() -> Optional[MSVCCompiler]: """ Detect and return an available MSVC compiler. Returns: MSVCCompiler instance if found, None otherwise """ from build_system.utils import which # Try to find MSVC if which("cl"): return MSVCCompiler() return None def check_msvc_environment() -> bool: """ Check if MSVC environment is properly set up. Returns: True if MSVC is available and environment is configured, False otherwise """ from build_system.utils import check_tool if check_tool("cl"): return True else: print("Make sure to run from a Visual Studio Developer Command Prompt") print("or run vcvarsall.bat to set up the environment.") return False