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