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