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