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 subprocess
18 import winreg
19
20 from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
21 CompileError, LibError, LinkError
22 from distutils.ccompiler import CCompiler, gen_lib_options
23 from distutils import log
24 from distutils.util import get_platform
25
26 from itertools import count
27
28 def _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
56 def _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
88 PLAT_SPEC_TO_RUNTIME = {
89 'x86' : 'x86',
90 'x86_amd64' : 'x64',
91 'x86_arm' : 'arm',
92 'x86_arm64' : 'arm64'
93 }
94
95 def _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
113 def _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
143 def _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.
163 PLAT_TO_VCVARS = {
164 'win32' : 'x86',
165 'win-amd64' : 'x86_amd64',
166 'win-arm32' : 'x86_arm',
167 'win-arm64' : 'x86_arm64'
168 }
169
170 class ESC[4;38;5;81mMSVCCompiler(ESC[4;38;5;149mCCompiler) :
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