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