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 shutil 18import stat 19import subprocess 20import winreg 21 22from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ 23 CompileError, LibError, LinkError 24from distutils.ccompiler import CCompiler, gen_lib_options 25from distutils import log 26from distutils.util import get_platform 27 28from itertools import count 29 30def _find_vc2015(): 31 try: 32 key = winreg.OpenKeyEx( 33 winreg.HKEY_LOCAL_MACHINE, 34 r"Software\Microsoft\VisualStudio\SxS\VC7", 35 access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY 36 ) 37 except OSError: 38 log.debug("Visual C++ is not registered") 39 return None, None 40 41 best_version = 0 42 best_dir = None 43 with key: 44 for i in count(): 45 try: 46 v, vc_dir, vt = winreg.EnumValue(key, i) 47 except OSError: 48 break 49 if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): 50 try: 51 version = int(float(v)) 52 except (ValueError, TypeError): 53 continue 54 if version >= 14 and version > best_version: 55 best_version, best_dir = version, vc_dir 56 return best_version, best_dir 57 58def _find_vc2017(): 59 """Returns "15, path" based on the result of invoking vswhere.exe 60 If no install is found, returns "None, None" 61 62 The version is returned to avoid unnecessarily changing the function 63 result. It may be ignored when the path is not None. 64 65 If vswhere.exe is not available, by definition, VS 2017 is not 66 installed. 67 """ 68 import json 69 70 root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") 71 if not root: 72 return None, None 73 74 try: 75 path = subprocess.check_output([ 76 os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), 77 "-latest", 78 "-prerelease", 79 "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", 80 "-property", "installationPath", 81 "-products", "*", 82 ], encoding="mbcs", errors="strict").strip() 83 except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): 84 return None, None 85 86 path = os.path.join(path, "VC", "Auxiliary", "Build") 87 if os.path.isdir(path): 88 return 15, path 89 90 return None, None 91 92def _find_vcvarsall(plat_spec): 93 _, best_dir = _find_vc2017() 94 vcruntime = None 95 vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' 96 if best_dir: 97 vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**", 98 "Microsoft.VC141.CRT", "vcruntime140.dll") 99 try: 100 import glob 101 vcruntime = glob.glob(vcredist, recursive=True)[-1] 102 except (ImportError, OSError, LookupError): 103 vcruntime = None 104 105 if not best_dir: 106 best_version, best_dir = _find_vc2015() 107 if best_version: 108 vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat, 109 "Microsoft.VC140.CRT", "vcruntime140.dll") 110 111 if not best_dir: 112 log.debug("No suitable Visual C++ version found") 113 return None, None 114 115 vcvarsall = os.path.join(best_dir, "vcvarsall.bat") 116 if not os.path.isfile(vcvarsall): 117 log.debug("%s cannot be found", vcvarsall) 118 return None, None 119 120 if not vcruntime or not os.path.isfile(vcruntime): 121 log.debug("%s cannot be found", vcruntime) 122 vcruntime = None 123 124 return vcvarsall, vcruntime 125 126def _get_vc_env(plat_spec): 127 if os.getenv("DISTUTILS_USE_SDK"): 128 return { 129 key.lower(): value 130 for key, value in os.environ.items() 131 } 132 133 vcvarsall, vcruntime = _find_vcvarsall(plat_spec) 134 if not vcvarsall: 135 raise DistutilsPlatformError("Unable to find vcvarsall.bat") 136 137 try: 138 out = subprocess.check_output( 139 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), 140 stderr=subprocess.STDOUT, 141 ).decode('utf-16le', errors='replace') 142 except subprocess.CalledProcessError as exc: 143 log.error(exc.output) 144 raise DistutilsPlatformError("Error executing {}" 145 .format(exc.cmd)) 146 147 env = { 148 key.lower(): value 149 for key, _, value in 150 (line.partition('=') for line in out.splitlines()) 151 if key and value 152 } 153 154 if vcruntime: 155 env['py_vcruntime_redist'] = vcruntime 156 return env 157 158def _find_exe(exe, paths=None): 159 """Return path to an MSVC executable program. 160 161 Tries to find the program in several places: first, one of the 162 MSVC program search paths from the registry; next, the directories 163 in the PATH environment variable. If any of those work, return an 164 absolute path that is known to exist. If none of them work, just 165 return the original program name, 'exe'. 166 """ 167 if not paths: 168 paths = os.getenv('path').split(os.pathsep) 169 for p in paths: 170 fn = os.path.join(os.path.abspath(p), exe) 171 if os.path.isfile(fn): 172 return fn 173 return exe 174 175# A map keyed by get_platform() return values to values accepted by 176# 'vcvarsall.bat'. Always cross-compile from x86 to work with the 177# lighter-weight MSVC installs that do not include native 64-bit tools. 178PLAT_TO_VCVARS = { 179 'win32' : 'x86', 180 'win-amd64' : 'x86_amd64', 181} 182 183# A set containing the DLLs that are guaranteed to be available for 184# all micro versions of this Python version. Known extension 185# dependencies that are not in this set will be copied to the output 186# path. 187_BUNDLED_DLLS = frozenset(['vcruntime140.dll']) 188 189class MSVCCompiler(CCompiler) : 190 """Concrete class that implements an interface to Microsoft Visual C++, 191 as defined by the CCompiler abstract class.""" 192 193 compiler_type = 'msvc' 194 195 # Just set this so CCompiler's constructor doesn't barf. We currently 196 # don't use the 'set_executables()' bureaucracy provided by CCompiler, 197 # as it really isn't necessary for this sort of single-compiler class. 198 # Would be nice to have a consistent interface with UnixCCompiler, 199 # though, so it's worth thinking about. 200 executables = {} 201 202 # Private class data (need to distinguish C from C++ source for compiler) 203 _c_extensions = ['.c'] 204 _cpp_extensions = ['.cc', '.cpp', '.cxx'] 205 _rc_extensions = ['.rc'] 206 _mc_extensions = ['.mc'] 207 208 # Needed for the filename generation methods provided by the 209 # base class, CCompiler. 210 src_extensions = (_c_extensions + _cpp_extensions + 211 _rc_extensions + _mc_extensions) 212 res_extension = '.res' 213 obj_extension = '.obj' 214 static_lib_extension = '.lib' 215 shared_lib_extension = '.dll' 216 static_lib_format = shared_lib_format = '%s%s' 217 exe_extension = '.exe' 218 219 220 def __init__(self, verbose=0, dry_run=0, force=0): 221 CCompiler.__init__ (self, verbose, dry_run, force) 222 # target platform (.plat_name is consistent with 'bdist') 223 self.plat_name = None 224 self.initialized = False 225 226 def initialize(self, plat_name=None): 227 # multi-init means we would need to check platform same each time... 228 assert not self.initialized, "don't init multiple times" 229 if plat_name is None: 230 plat_name = get_platform() 231 # sanity check for platforms to prevent obscure errors later. 232 if plat_name not in PLAT_TO_VCVARS: 233 raise DistutilsPlatformError("--plat-name must be one of {}" 234 .format(tuple(PLAT_TO_VCVARS))) 235 236 # Get the vcvarsall.bat spec for the requested platform. 237 plat_spec = PLAT_TO_VCVARS[plat_name] 238 239 vc_env = _get_vc_env(plat_spec) 240 if not vc_env: 241 raise DistutilsPlatformError("Unable to find a compatible " 242 "Visual Studio installation.") 243 244 self._paths = vc_env.get('path', '') 245 paths = self._paths.split(os.pathsep) 246 self.cc = _find_exe("cl.exe", paths) 247 self.linker = _find_exe("link.exe", paths) 248 self.lib = _find_exe("lib.exe", paths) 249 self.rc = _find_exe("rc.exe", paths) # resource compiler 250 self.mc = _find_exe("mc.exe", paths) # message compiler 251 self.mt = _find_exe("mt.exe", paths) # message compiler 252 self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '') 253 254 for dir in vc_env.get('include', '').split(os.pathsep): 255 if dir: 256 self.add_include_dir(dir.rstrip(os.sep)) 257 258 for dir in vc_env.get('lib', '').split(os.pathsep): 259 if dir: 260 self.add_library_dir(dir.rstrip(os.sep)) 261 262 self.preprocess_options = None 263 # If vcruntime_redist is available, link against it dynamically. Otherwise, 264 # use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib 265 # later to dynamically link to ucrtbase but not vcruntime. 266 self.compile_options = [ 267 '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG' 268 ] 269 self.compile_options.append('/MD' if self._vcruntime_redist else '/MT') 270 271 self.compile_options_debug = [ 272 '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG' 273 ] 274 275 ldflags = [ 276 '/nologo', '/INCREMENTAL:NO', '/LTCG' 277 ] 278 if not self._vcruntime_redist: 279 ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib')) 280 281 ldflags_debug = [ 282 '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL' 283 ] 284 285 self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1'] 286 self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1'] 287 self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] 288 self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] 289 self.ldflags_static = [*ldflags] 290 self.ldflags_static_debug = [*ldflags_debug] 291 292 self._ldflags = { 293 (CCompiler.EXECUTABLE, None): self.ldflags_exe, 294 (CCompiler.EXECUTABLE, False): self.ldflags_exe, 295 (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug, 296 (CCompiler.SHARED_OBJECT, None): self.ldflags_shared, 297 (CCompiler.SHARED_OBJECT, False): self.ldflags_shared, 298 (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug, 299 (CCompiler.SHARED_LIBRARY, None): self.ldflags_static, 300 (CCompiler.SHARED_LIBRARY, False): self.ldflags_static, 301 (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug, 302 } 303 304 self.initialized = True 305 306 # -- Worker methods ------------------------------------------------ 307 308 def object_filenames(self, 309 source_filenames, 310 strip_dir=0, 311 output_dir=''): 312 ext_map = { 313 **{ext: self.obj_extension for ext in self.src_extensions}, 314 **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions}, 315 } 316 317 output_dir = output_dir or '' 318 319 def make_out_path(p): 320 base, ext = os.path.splitext(p) 321 if strip_dir: 322 base = os.path.basename(base) 323 else: 324 _, base = os.path.splitdrive(base) 325 if base.startswith((os.path.sep, os.path.altsep)): 326 base = base[1:] 327 try: 328 # XXX: This may produce absurdly long paths. We should check 329 # the length of the result and trim base until we fit within 330 # 260 characters. 331 return os.path.join(output_dir, base + ext_map[ext]) 332 except LookupError: 333 # Better to raise an exception instead of silently continuing 334 # and later complain about sources and targets having 335 # different lengths 336 raise CompileError("Don't know how to compile {}".format(p)) 337 338 return list(map(make_out_path, source_filenames)) 339 340 341 def compile(self, sources, 342 output_dir=None, macros=None, include_dirs=None, debug=0, 343 extra_preargs=None, extra_postargs=None, depends=None): 344 345 if not self.initialized: 346 self.initialize() 347 compile_info = self._setup_compile(output_dir, macros, include_dirs, 348 sources, depends, extra_postargs) 349 macros, objects, extra_postargs, pp_opts, build = compile_info 350 351 compile_opts = extra_preargs or [] 352 compile_opts.append('/c') 353 if debug: 354 compile_opts.extend(self.compile_options_debug) 355 else: 356 compile_opts.extend(self.compile_options) 357 358 359 add_cpp_opts = False 360 361 for obj in objects: 362 try: 363 src, ext = build[obj] 364 except KeyError: 365 continue 366 if debug: 367 # pass the full pathname to MSVC in debug mode, 368 # this allows the debugger to find the source file 369 # without asking the user to browse for it 370 src = os.path.abspath(src) 371 372 if ext in self._c_extensions: 373 input_opt = "/Tc" + src 374 elif ext in self._cpp_extensions: 375 input_opt = "/Tp" + src 376 add_cpp_opts = True 377 elif ext in self._rc_extensions: 378 # compile .RC to .RES file 379 input_opt = src 380 output_opt = "/fo" + obj 381 try: 382 self.spawn([self.rc] + pp_opts + [output_opt, input_opt]) 383 except DistutilsExecError as msg: 384 raise CompileError(msg) 385 continue 386 elif ext in self._mc_extensions: 387 # Compile .MC to .RC file to .RES file. 388 # * '-h dir' specifies the directory for the 389 # generated include file 390 # * '-r dir' specifies the target directory of the 391 # generated RC file and the binary message resource 392 # it includes 393 # 394 # For now (since there are no options to change this), 395 # we use the source-directory for the include file and 396 # the build directory for the RC file and message 397 # resources. This works at least for win32all. 398 h_dir = os.path.dirname(src) 399 rc_dir = os.path.dirname(obj) 400 try: 401 # first compile .MC to .RC and .H file 402 self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src]) 403 base, _ = os.path.splitext(os.path.basename (src)) 404 rc_file = os.path.join(rc_dir, base + '.rc') 405 # then compile .RC to .RES file 406 self.spawn([self.rc, "/fo" + obj, rc_file]) 407 408 except DistutilsExecError as msg: 409 raise CompileError(msg) 410 continue 411 else: 412 # how to handle this file? 413 raise CompileError("Don't know how to compile {} to {}" 414 .format(src, obj)) 415 416 args = [self.cc] + compile_opts + pp_opts 417 if add_cpp_opts: 418 args.append('/EHsc') 419 args.append(input_opt) 420 args.append("/Fo" + obj) 421 args.extend(extra_postargs) 422 423 try: 424 self.spawn(args) 425 except DistutilsExecError as msg: 426 raise CompileError(msg) 427 428 return objects 429 430 431 def create_static_lib(self, 432 objects, 433 output_libname, 434 output_dir=None, 435 debug=0, 436 target_lang=None): 437 438 if not self.initialized: 439 self.initialize() 440 objects, output_dir = self._fix_object_args(objects, output_dir) 441 output_filename = self.library_filename(output_libname, 442 output_dir=output_dir) 443 444 if self._need_link(objects, output_filename): 445 lib_args = objects + ['/OUT:' + output_filename] 446 if debug: 447 pass # XXX what goes here? 448 try: 449 log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args)) 450 self.spawn([self.lib] + lib_args) 451 except DistutilsExecError as msg: 452 raise LibError(msg) 453 else: 454 log.debug("skipping %s (up-to-date)", output_filename) 455 456 457 def link(self, 458 target_desc, 459 objects, 460 output_filename, 461 output_dir=None, 462 libraries=None, 463 library_dirs=None, 464 runtime_library_dirs=None, 465 export_symbols=None, 466 debug=0, 467 extra_preargs=None, 468 extra_postargs=None, 469 build_temp=None, 470 target_lang=None): 471 472 if not self.initialized: 473 self.initialize() 474 objects, output_dir = self._fix_object_args(objects, output_dir) 475 fixed_args = self._fix_lib_args(libraries, library_dirs, 476 runtime_library_dirs) 477 libraries, library_dirs, runtime_library_dirs = fixed_args 478 479 if runtime_library_dirs: 480 self.warn("I don't know what to do with 'runtime_library_dirs': " 481 + str(runtime_library_dirs)) 482 483 lib_opts = gen_lib_options(self, 484 library_dirs, runtime_library_dirs, 485 libraries) 486 if output_dir is not None: 487 output_filename = os.path.join(output_dir, output_filename) 488 489 if self._need_link(objects, output_filename): 490 ldflags = self._ldflags[target_desc, debug] 491 492 export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])] 493 494 ld_args = (ldflags + lib_opts + export_opts + 495 objects + ['/OUT:' + output_filename]) 496 497 # The MSVC linker generates .lib and .exp files, which cannot be 498 # suppressed by any linker switches. The .lib files may even be 499 # needed! Make sure they are generated in the temporary build 500 # directory. Since they have different names for debug and release 501 # builds, they can go into the same directory. 502 build_temp = os.path.dirname(objects[0]) 503 if export_symbols is not None: 504 (dll_name, dll_ext) = os.path.splitext( 505 os.path.basename(output_filename)) 506 implib_file = os.path.join( 507 build_temp, 508 self.library_filename(dll_name)) 509 ld_args.append ('/IMPLIB:' + implib_file) 510 511 if extra_preargs: 512 ld_args[:0] = extra_preargs 513 if extra_postargs: 514 ld_args.extend(extra_postargs) 515 516 output_dir = os.path.dirname(os.path.abspath(output_filename)) 517 self.mkpath(output_dir) 518 try: 519 log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args)) 520 self.spawn([self.linker] + ld_args) 521 self._copy_vcruntime(output_dir) 522 except DistutilsExecError as msg: 523 raise LinkError(msg) 524 else: 525 log.debug("skipping %s (up-to-date)", output_filename) 526 527 def _copy_vcruntime(self, output_dir): 528 vcruntime = self._vcruntime_redist 529 if not vcruntime or not os.path.isfile(vcruntime): 530 return 531 532 if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS: 533 return 534 535 log.debug('Copying "%s"', vcruntime) 536 vcruntime = shutil.copy(vcruntime, output_dir) 537 os.chmod(vcruntime, stat.S_IWRITE) 538 539 def spawn(self, cmd): 540 old_path = os.getenv('path') 541 try: 542 os.environ['path'] = self._paths 543 return super().spawn(cmd) 544 finally: 545 os.environ['path'] = old_path 546 547 # -- Miscellaneous methods ----------------------------------------- 548 # These are all used by the 'gen_lib_options() function, in 549 # ccompiler.py. 550 551 def library_dir_option(self, dir): 552 return "/LIBPATH:" + dir 553 554 def runtime_library_dir_option(self, dir): 555 raise DistutilsPlatformError( 556 "don't know how to set runtime library search path for MSVC") 557 558 def library_option(self, lib): 559 return self.library_filename(lib) 560 561 def find_library_file(self, dirs, lib, debug=0): 562 # Prefer a debugging library if found (and requested), but deal 563 # with it if we don't have one. 564 if debug: 565 try_names = [lib + "_d", lib] 566 else: 567 try_names = [lib] 568 for dir in dirs: 569 for name in try_names: 570 libfile = os.path.join(dir, self.library_filename(name)) 571 if os.path.isfile(libfile): 572 return libfile 573 else: 574 # Oops, didn't find it in *any* of 'dirs' 575 return None 576