1"""distutils.msvc9compiler 2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class 4for the Microsoft Visual Studio 2008. 5 6The module is compatible with VS 2005 and VS 2008. You can find legacy support 7for older versions of VS in 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 VS2005 and VS 2008 by Christian Heimes 14 15import os 16import subprocess 17import sys 18import re 19 20from distutils.errors import DistutilsPlatformError 21from distutils.ccompiler import CCompiler 22from distutils import log 23 24import winreg 25 26RegOpenKeyEx = winreg.OpenKeyEx 27RegEnumKey = winreg.EnumKey 28RegEnumValue = winreg.EnumValue 29RegError = winreg.error 30 31HKEYS = (winreg.HKEY_USERS, 32 winreg.HKEY_CURRENT_USER, 33 winreg.HKEY_LOCAL_MACHINE, 34 winreg.HKEY_CLASSES_ROOT) 35 36NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32) 37if NATIVE_WIN64: 38 # Visual C++ is a 32-bit application, so we need to look in 39 # the corresponding registry branch, if we're running a 40 # 64-bit Python on Win64 41 VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f" 42 WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows" 43 NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework" 44else: 45 VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" 46 WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" 47 NET_BASE = r"Software\Microsoft\.NETFramework" 48 49# A map keyed by get_platform() return values to values accepted by 50# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is 51# the param to cross-compile on x86 targeting amd64.) 52PLAT_TO_VCVARS = { 53 'win32' : 'x86', 54 'win-amd64' : 'amd64', 55} 56 57class Reg: 58 """Helper class to read values from the registry 59 """ 60 61 def get_value(cls, path, key): 62 for base in HKEYS: 63 d = cls.read_values(base, path) 64 if d and key in d: 65 return d[key] 66 raise KeyError(key) 67 get_value = classmethod(get_value) 68 69 def read_keys(cls, base, key): 70 """Return list of registry keys.""" 71 try: 72 handle = RegOpenKeyEx(base, key) 73 except RegError: 74 return None 75 L = [] 76 i = 0 77 while True: 78 try: 79 k = RegEnumKey(handle, i) 80 except RegError: 81 break 82 L.append(k) 83 i += 1 84 return L 85 read_keys = classmethod(read_keys) 86 87 def read_values(cls, base, key): 88 """Return dict of registry keys and values. 89 90 All names are converted to lowercase. 91 """ 92 try: 93 handle = RegOpenKeyEx(base, key) 94 except RegError: 95 return None 96 d = {} 97 i = 0 98 while True: 99 try: 100 name, value, type = RegEnumValue(handle, i) 101 except RegError: 102 break 103 name = name.lower() 104 d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) 105 i += 1 106 return d 107 read_values = classmethod(read_values) 108 109 def convert_mbcs(s): 110 dec = getattr(s, "decode", None) 111 if dec is not None: 112 try: 113 s = dec("mbcs") 114 except UnicodeError: 115 pass 116 return s 117 convert_mbcs = staticmethod(convert_mbcs) 118 119class MacroExpander: 120 121 def __init__(self, version): 122 self.macros = {} 123 self.vsbase = VS_BASE % version 124 self.load_macros(version) 125 126 def set_macro(self, macro, path, key): 127 self.macros["$(%s)" % macro] = Reg.get_value(path, key) 128 129 def load_macros(self, version): 130 self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") 131 self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") 132 self.set_macro("FrameworkDir", NET_BASE, "installroot") 133 try: 134 if version >= 8.0: 135 self.set_macro("FrameworkSDKDir", NET_BASE, 136 "sdkinstallrootv2.0") 137 else: 138 raise KeyError("sdkinstallrootv2.0") 139 except KeyError: 140 raise DistutilsPlatformError( 141 """Python was built with Visual Studio 2008; 142extensions must be built with a compiler than can generate compatible binaries. 143Visual Studio 2008 was not found on this system. If you have Cygwin installed, 144you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") 145 146 if version >= 9.0: 147 self.set_macro("FrameworkVersion", self.vsbase, "clr version") 148 self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") 149 else: 150 p = r"Software\Microsoft\NET Framework Setup\Product" 151 for base in HKEYS: 152 try: 153 h = RegOpenKeyEx(base, p) 154 except RegError: 155 continue 156 key = RegEnumKey(h, 0) 157 d = Reg.get_value(base, r"%s\%s" % (p, key)) 158 self.macros["$(FrameworkVersion)"] = d["version"] 159 160 def sub(self, s): 161 for k, v in self.macros.items(): 162 s = s.replace(k, v) 163 return s 164 165def get_build_version(): 166 """Return the version of MSVC that was used to build Python. 167 168 For Python 2.3 and up, the version number is included in 169 sys.version. For earlier versions, assume the compiler is MSVC 6. 170 """ 171 prefix = "MSC v." 172 i = sys.version.find(prefix) 173 if i == -1: 174 return 6 175 i = i + len(prefix) 176 s, rest = sys.version[i:].split(" ", 1) 177 majorVersion = int(s[:-2]) - 6 178 if majorVersion >= 13: 179 # v13 was skipped and should be v14 180 majorVersion += 1 181 minorVersion = int(s[2:3]) / 10.0 182 # I don't think paths are affected by minor version in version 6 183 if majorVersion == 6: 184 minorVersion = 0 185 if majorVersion >= 6: 186 return majorVersion + minorVersion 187 # else we don't know what version of the compiler this is 188 return None 189 190def normalize_and_reduce_paths(paths): 191 """Return a list of normalized paths with duplicates removed. 192 193 The current order of paths is maintained. 194 """ 195 # Paths are normalized so things like: /a and /a/ aren't both preserved. 196 reduced_paths = [] 197 for p in paths: 198 np = os.path.normpath(p) 199 # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. 200 if np not in reduced_paths: 201 reduced_paths.append(np) 202 return reduced_paths 203 204def removeDuplicates(variable): 205 """Remove duplicate values of an environment variable. 206 """ 207 oldList = variable.split(os.pathsep) 208 newList = [] 209 for i in oldList: 210 if i not in newList: 211 newList.append(i) 212 newVariable = os.pathsep.join(newList) 213 return newVariable 214 215def find_vcvarsall(version): 216 """Find the vcvarsall.bat file 217 218 At first it tries to find the productdir of VS 2008 in the registry. If 219 that fails it falls back to the VS90COMNTOOLS env var. 220 """ 221 vsbase = VS_BASE % version 222 try: 223 productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, 224 "productdir") 225 except KeyError: 226 log.debug("Unable to find productdir in registry") 227 productdir = None 228 229 if not productdir or not os.path.isdir(productdir): 230 toolskey = "VS%0.f0COMNTOOLS" % version 231 toolsdir = os.environ.get(toolskey, None) 232 233 if toolsdir and os.path.isdir(toolsdir): 234 productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") 235 productdir = os.path.abspath(productdir) 236 if not os.path.isdir(productdir): 237 log.debug("%s is not a valid directory" % productdir) 238 return None 239 else: 240 log.debug("Env var %s is not set or invalid" % toolskey) 241 if not productdir: 242 log.debug("No productdir found") 243 return None 244 vcvarsall = os.path.join(productdir, "vcvarsall.bat") 245 if os.path.isfile(vcvarsall): 246 return vcvarsall 247 log.debug("Unable to find vcvarsall.bat") 248 return None 249 250def query_vcvarsall(version, arch="x86"): 251 """Launch vcvarsall.bat and read the settings from its environment 252 """ 253 vcvarsall = find_vcvarsall(version) 254 interesting = {"include", "lib", "libpath", "path"} 255 result = {} 256 257 if vcvarsall is None: 258 raise DistutilsPlatformError("Unable to find vcvarsall.bat") 259 log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version) 260 popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch), 261 stdout=subprocess.PIPE, 262 stderr=subprocess.PIPE) 263 try: 264 stdout, stderr = popen.communicate() 265 if popen.wait() != 0: 266 raise DistutilsPlatformError(stderr.decode("mbcs")) 267 268 stdout = stdout.decode("mbcs") 269 for line in stdout.split("\n"): 270 line = Reg.convert_mbcs(line) 271 if '=' not in line: 272 continue 273 line = line.strip() 274 key, value = line.split('=', 1) 275 key = key.lower() 276 if key in interesting: 277 if value.endswith(os.pathsep): 278 value = value[:-1] 279 result[key] = removeDuplicates(value) 280 281 finally: 282 popen.stdout.close() 283 popen.stderr.close() 284 285 if len(result) != len(interesting): 286 raise ValueError(str(list(result.keys()))) 287 288 return result 289 290# More globals 291VERSION = get_build_version() 292if VERSION < 8.0: 293 raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION) 294# MACROS = MacroExpander(VERSION) 295 296class MSVCCompiler(CCompiler) : 297 """Concrete class that implements an interface to Microsoft Visual C++, 298 as defined by the CCompiler abstract class.""" 299 300 compiler_type = 'msvc' 301 302 # Just set this so CCompiler's constructor doesn't barf. We currently 303 # don't use the 'set_executables()' bureaucracy provided by CCompiler, 304 # as it really isn't necessary for this sort of single-compiler class. 305 # Would be nice to have a consistent interface with UnixCCompiler, 306 # though, so it's worth thinking about. 307 executables = {} 308 309 # Private class data (need to distinguish C from C++ source for compiler) 310 _c_extensions = ['.c'] 311 _cpp_extensions = ['.cc', '.cpp', '.cxx'] 312 _rc_extensions = ['.rc'] 313 _mc_extensions = ['.mc'] 314 315 # Needed for the filename generation methods provided by the 316 # base class, CCompiler. 317 src_extensions = (_c_extensions + _cpp_extensions + 318 _rc_extensions + _mc_extensions) 319 res_extension = '.res' 320 obj_extension = '.obj' 321 static_lib_extension = '.lib' 322 shared_lib_extension = '.dll' 323 static_lib_format = shared_lib_format = '%s%s' 324 exe_extension = '.exe' 325 326 def __init__(self, verbose=0, dry_run=0, force=0): 327 CCompiler.__init__ (self, verbose, dry_run, force) 328 self.__version = VERSION 329 self.__root = r"Software\Microsoft\VisualStudio" 330 # self.__macros = MACROS 331 self.__paths = [] 332 # target platform (.plat_name is consistent with 'bdist') 333 self.plat_name = None 334 self.__arch = None # deprecated name 335 self.initialized = False 336 337 # -- Worker methods ------------------------------------------------ 338 339 def manifest_setup_ldargs(self, output_filename, build_temp, ld_args): 340 # If we need a manifest at all, an embedded manifest is recommended. 341 # See MSDN article titled 342 # "How to: Embed a Manifest Inside a C/C++ Application" 343 # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) 344 # Ask the linker to generate the manifest in the temp dir, so 345 # we can check it, and possibly embed it, later. 346 temp_manifest = os.path.join( 347 build_temp, 348 os.path.basename(output_filename) + ".manifest") 349 ld_args.append('/MANIFESTFILE:' + temp_manifest) 350 351 def manifest_get_embed_info(self, target_desc, ld_args): 352 # If a manifest should be embedded, return a tuple of 353 # (manifest_filename, resource_id). Returns None if no manifest 354 # should be embedded. See http://bugs.python.org/issue7833 for why 355 # we want to avoid any manifest for extension modules if we can. 356 for arg in ld_args: 357 if arg.startswith("/MANIFESTFILE:"): 358 temp_manifest = arg.split(":", 1)[1] 359 break 360 else: 361 # no /MANIFESTFILE so nothing to do. 362 return None 363 if target_desc == CCompiler.EXECUTABLE: 364 # by default, executables always get the manifest with the 365 # CRT referenced. 366 mfid = 1 367 else: 368 # Extension modules try and avoid any manifest if possible. 369 mfid = 2 370 temp_manifest = self._remove_visual_c_ref(temp_manifest) 371 if temp_manifest is None: 372 return None 373 return temp_manifest, mfid 374 375 def _remove_visual_c_ref(self, manifest_file): 376 try: 377 # Remove references to the Visual C runtime, so they will 378 # fall through to the Visual C dependency of Python.exe. 379 # This way, when installed for a restricted user (e.g. 380 # runtimes are not in WinSxS folder, but in Python's own 381 # folder), the runtimes do not need to be in every folder 382 # with .pyd's. 383 # Returns either the filename of the modified manifest or 384 # None if no manifest should be embedded. 385 manifest_f = open(manifest_file) 386 try: 387 manifest_buf = manifest_f.read() 388 finally: 389 manifest_f.close() 390 pattern = re.compile( 391 r"""<assemblyIdentity.*?name=("|')Microsoft\."""\ 392 r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""", 393 re.DOTALL) 394 manifest_buf = re.sub(pattern, "", manifest_buf) 395 pattern = r"<dependentAssembly>\s*</dependentAssembly>" 396 manifest_buf = re.sub(pattern, "", manifest_buf) 397 # Now see if any other assemblies are referenced - if not, we 398 # don't want a manifest embedded. 399 pattern = re.compile( 400 r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')""" 401 r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL) 402 if re.search(pattern, manifest_buf) is None: 403 return None 404 405 manifest_f = open(manifest_file, 'w') 406 try: 407 manifest_f.write(manifest_buf) 408 return manifest_file 409 finally: 410 manifest_f.close() 411 except OSError: 412 pass 413 414 # -- Miscellaneous methods ----------------------------------------- 415 416 # Helper methods for using the MSVC registry settings 417 418 def find_exe(self, exe): 419 """Return path to an MSVC executable program. 420 421 Tries to find the program in several places: first, one of the 422 MSVC program search paths from the registry; next, the directories 423 in the PATH environment variable. If any of those work, return an 424 absolute path that is known to exist. If none of them work, just 425 return the original program name, 'exe'. 426 """ 427 for p in self.__paths: 428 fn = os.path.join(os.path.abspath(p), exe) 429 if os.path.isfile(fn): 430 return fn 431 432 # didn't find it; try existing path 433 for p in os.environ['Path'].split(';'): 434 fn = os.path.join(os.path.abspath(p),exe) 435 if os.path.isfile(fn): 436 return fn 437 438 return exe 439