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