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