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