• 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 subprocess
18import winreg
19
20from distutils.errors import DistutilsPlatformError
21from distutils.ccompiler import CCompiler
22from distutils import log
23
24from itertools import count
25
26def _find_vc2015():
27    try:
28        key = winreg.OpenKeyEx(
29            winreg.HKEY_LOCAL_MACHINE,
30            r"Software\Microsoft\VisualStudio\SxS\VC7",
31            access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
32        )
33    except OSError:
34        log.debug("Visual C++ is not registered")
35        return None, None
36
37    best_version = 0
38    best_dir = None
39    with key:
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    return best_version, best_dir
53
54def _find_vc2017():
55    """Returns "15, path" based on the result of invoking vswhere.exe
56    If no install is found, returns "None, None"
57
58    The version is returned to avoid unnecessarily changing the function
59    result. It may be ignored when the path is not None.
60
61    If vswhere.exe is not available, by definition, VS 2017 is not
62    installed.
63    """
64    root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
65    if not root:
66        return None, None
67
68    try:
69        path = subprocess.check_output([
70            os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
71            "-latest",
72            "-prerelease",
73            "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
74            "-property", "installationPath",
75            "-products", "*",
76        ], encoding="mbcs", errors="strict").strip()
77    except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
78        return None, None
79
80    path = os.path.join(path, "VC", "Auxiliary", "Build")
81    if os.path.isdir(path):
82        return 15, path
83
84    return None, None
85
86PLAT_SPEC_TO_RUNTIME = {
87    'x86' : 'x86',
88    'x86_amd64' : 'x64',
89    'x86_arm' : 'arm',
90    'x86_arm64' : 'arm64'
91}
92
93def _find_vcvarsall(plat_spec):
94    # bpo-38597: Removed vcruntime return value
95    _, best_dir = _find_vc2017()
96
97    if not best_dir:
98        best_version, best_dir = _find_vc2015()
99
100    if not best_dir:
101        log.debug("No suitable Visual C++ version found")
102        return None, None
103
104    vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
105    if not os.path.isfile(vcvarsall):
106        log.debug("%s cannot be found", vcvarsall)
107        return None, None
108
109    return vcvarsall, None
110
111def _get_vc_env(plat_spec):
112    if os.getenv("DISTUTILS_USE_SDK"):
113        return {
114            key.lower(): value
115            for key, value in os.environ.items()
116        }
117
118    vcvarsall, _ = _find_vcvarsall(plat_spec)
119    if not vcvarsall:
120        raise DistutilsPlatformError("Unable to find vcvarsall.bat")
121
122    try:
123        out = subprocess.check_output(
124            'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
125            stderr=subprocess.STDOUT,
126        ).decode('utf-16le', errors='replace')
127    except subprocess.CalledProcessError as exc:
128        log.error(exc.output)
129        raise DistutilsPlatformError("Error executing {}"
130                .format(exc.cmd))
131
132    env = {
133        key.lower(): value
134        for key, _, value in
135        (line.partition('=') for line in out.splitlines())
136        if key and value
137    }
138
139    return env
140
141def _find_exe(exe, paths=None):
142    """Return path to an MSVC executable program.
143
144    Tries to find the program in several places: first, one of the
145    MSVC program search paths from the registry; next, the directories
146    in the PATH environment variable.  If any of those work, return an
147    absolute path that is known to exist.  If none of them work, just
148    return the original program name, 'exe'.
149    """
150    if not paths:
151        paths = os.getenv('path').split(os.pathsep)
152    for p in paths:
153        fn = os.path.join(os.path.abspath(p), exe)
154        if os.path.isfile(fn):
155            return fn
156    return exe
157
158# A map keyed by get_platform() return values to values accepted by
159# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
160# lighter-weight MSVC installs that do not include native 64-bit tools.
161PLAT_TO_VCVARS = {
162    'win32' : 'x86',
163    'win-amd64' : 'x86_amd64',
164    'win-arm32' : 'x86_arm',
165    'win-arm64' : 'x86_arm64'
166}
167
168class MSVCCompiler(CCompiler) :
169    """Concrete class that implements an interface to Microsoft Visual C++,
170       as defined by the CCompiler abstract class."""
171
172    compiler_type = 'msvc'
173
174    # Just set this so CCompiler's constructor doesn't barf.  We currently
175    # don't use the 'set_executables()' bureaucracy provided by CCompiler,
176    # as it really isn't necessary for this sort of single-compiler class.
177    # Would be nice to have a consistent interface with UnixCCompiler,
178    # though, so it's worth thinking about.
179    executables = {}
180
181    # Private class data (need to distinguish C from C++ source for compiler)
182    _c_extensions = ['.c']
183    _cpp_extensions = ['.cc', '.cpp', '.cxx']
184    _rc_extensions = ['.rc']
185    _mc_extensions = ['.mc']
186
187    # Needed for the filename generation methods provided by the
188    # base class, CCompiler.
189    src_extensions = (_c_extensions + _cpp_extensions +
190                      _rc_extensions + _mc_extensions)
191    res_extension = '.res'
192    obj_extension = '.obj'
193    static_lib_extension = '.lib'
194    shared_lib_extension = '.dll'
195    static_lib_format = shared_lib_format = '%s%s'
196    exe_extension = '.exe'
197
198
199    def __init__(self, verbose=0, dry_run=0, force=0):
200        CCompiler.__init__ (self, verbose, dry_run, force)
201        # target platform (.plat_name is consistent with 'bdist')
202        self.plat_name = None
203        self.initialized = False
204