Implemented msvc.py

This commit is contained in:
Kyler Olsen 2025-12-19 14:58:28 -07:00
parent a6d867c31e
commit 720842c448
1 changed files with 224 additions and 0 deletions

View File

@ -0,0 +1,224 @@
#!/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