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