194 lines
5.5 KiB
Python
194 lines
5.5 KiB
Python
"""
|
|
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)
|
|
|