1"""distutils._msvccompiler 2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class 4for Microsoft Visual Studio 2015. 5 6The module is compatible with VS 2015 and later. You can find legacy support 7for older versions in distutils.msvc9compiler and distutils.msvccompiler. 8""" 9 10# Written by Perry Stoll 11# hacked by Robin Becker and Thomas Heller to do a better job of 12# finding DevStudio (through the registry) 13# ported to VS 2005 and VS 2008 by Christian Heimes 14# ported to VS 2015 by Steve Dower 15 16import os 17import subprocess 18import winreg 19 20from distutils.errors import DistutilsPlatformError 21from distutils.ccompiler import CCompiler 22from distutils import log 23 24from itertools import count 25 26def _find_vc2015(): 27 try: 28 key = winreg.OpenKeyEx( 29 winreg.HKEY_LOCAL_MACHINE, 30 r"Software\Microsoft\VisualStudio\SxS\VC7", 31 access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY 32 ) 33 except OSError: 34 log.debug("Visual C++ is not registered") 35 return None, None 36 37 best_version = 0 38 best_dir = None 39 with key: 40 for i in count(): 41 try: 42 v, vc_dir, vt = winreg.EnumValue(key, i) 43 except OSError: 44 break 45 if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): 46 try: 47 version = int(float(v)) 48 except (ValueError, TypeError): 49 continue 50 if version >= 14 and version > best_version: 51 best_version, best_dir = version, vc_dir 52 return best_version, best_dir 53 54def _find_vc2017(): 55 """Returns "15, path" based on the result of invoking vswhere.exe 56 If no install is found, returns "None, None" 57 58 The version is returned to avoid unnecessarily changing the function 59 result. It may be ignored when the path is not None. 60 61 If vswhere.exe is not available, by definition, VS 2017 is not 62 installed. 63 """ 64 root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") 65 if not root: 66 return None, None 67 68 try: 69 path = subprocess.check_output([ 70 os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), 71 "-latest", 72 "-prerelease", 73 "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", 74 "-property", "installationPath", 75 "-products", "*", 76 ], encoding="mbcs", errors="strict").strip() 77 except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): 78 return None, None 79 80 path = os.path.join(path, "VC", "Auxiliary", "Build") 81 if os.path.isdir(path): 82 return 15, path 83 84 return None, None 85 86PLAT_SPEC_TO_RUNTIME = { 87 'x86' : 'x86', 88 'x86_amd64' : 'x64', 89 'x86_arm' : 'arm', 90 'x86_arm64' : 'arm64' 91} 92 93def _find_vcvarsall(plat_spec): 94 # bpo-38597: Removed vcruntime return value 95 _, best_dir = _find_vc2017() 96 97 if not best_dir: 98 best_version, best_dir = _find_vc2015() 99 100 if not best_dir: 101 log.debug("No suitable Visual C++ version found") 102 return None, None 103 104 vcvarsall = os.path.join(best_dir, "vcvarsall.bat") 105 if not os.path.isfile(vcvarsall): 106 log.debug("%s cannot be found", vcvarsall) 107 return None, None 108 109 return vcvarsall, None 110 111def _get_vc_env(plat_spec): 112 if os.getenv("DISTUTILS_USE_SDK"): 113 return { 114 key.lower(): value 115 for key, value in os.environ.items() 116 } 117 118 vcvarsall, _ = _find_vcvarsall(plat_spec) 119 if not vcvarsall: 120 raise DistutilsPlatformError("Unable to find vcvarsall.bat") 121 122 try: 123 out = subprocess.check_output( 124 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), 125 stderr=subprocess.STDOUT, 126 ).decode('utf-16le', errors='replace') 127 except subprocess.CalledProcessError as exc: 128 log.error(exc.output) 129 raise DistutilsPlatformError("Error executing {}" 130 .format(exc.cmd)) 131 132 env = { 133 key.lower(): value 134 for key, _, value in 135 (line.partition('=') for line in out.splitlines()) 136 if key and value 137 } 138 139 return env 140 141def _find_exe(exe, paths=None): 142 """Return path to an MSVC executable program. 143 144 Tries to find the program in several places: first, one of the 145 MSVC program search paths from the registry; next, the directories 146 in the PATH environment variable. If any of those work, return an 147 absolute path that is known to exist. If none of them work, just 148 return the original program name, 'exe'. 149 """ 150 if not paths: 151 paths = os.getenv('path').split(os.pathsep) 152 for p in paths: 153 fn = os.path.join(os.path.abspath(p), exe) 154 if os.path.isfile(fn): 155 return fn 156 return exe 157 158# A map keyed by get_platform() return values to values accepted by 159# 'vcvarsall.bat'. Always cross-compile from x86 to work with the 160# lighter-weight MSVC installs that do not include native 64-bit tools. 161PLAT_TO_VCVARS = { 162 'win32' : 'x86', 163 'win-amd64' : 'x86_amd64', 164 'win-arm32' : 'x86_arm', 165 'win-arm64' : 'x86_arm64' 166} 167 168class MSVCCompiler(CCompiler) : 169 """Concrete class that implements an interface to Microsoft Visual C++, 170 as defined by the CCompiler abstract class.""" 171 172 compiler_type = 'msvc' 173 174 # Just set this so CCompiler's constructor doesn't barf. We currently 175 # don't use the 'set_executables()' bureaucracy provided by CCompiler, 176 # as it really isn't necessary for this sort of single-compiler class. 177 # Would be nice to have a consistent interface with UnixCCompiler, 178 # though, so it's worth thinking about. 179 executables = {} 180 181 # Private class data (need to distinguish C from C++ source for compiler) 182 _c_extensions = ['.c'] 183 _cpp_extensions = ['.cc', '.cpp', '.cxx'] 184 _rc_extensions = ['.rc'] 185 _mc_extensions = ['.mc'] 186 187 # Needed for the filename generation methods provided by the 188 # base class, CCompiler. 189 src_extensions = (_c_extensions + _cpp_extensions + 190 _rc_extensions + _mc_extensions) 191 res_extension = '.res' 192 obj_extension = '.obj' 193 static_lib_extension = '.lib' 194 shared_lib_extension = '.dll' 195 static_lib_format = shared_lib_format = '%s%s' 196 exe_extension = '.exe' 197 198 199 def __init__(self, verbose=0, dry_run=0, force=0): 200 CCompiler.__init__ (self, verbose, dry_run, force) 201 # target platform (.plat_name is consistent with 'bdist') 202 self.plat_name = None 203 self.initialized = False 204