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