1 """distutils._msvccompiler
2
3 Contains MSVCCompiler, an implementation of the abstract CCompiler class
4 for Microsoft Visual Studio 2015.
5
6 The module is compatible with VS 2015 and later. You can find legacy support
7 for 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
16 import os
17 import subprocess
18 import winreg
19
20 from distutils.errors import DistutilsPlatformError
21 from distutils.ccompiler import CCompiler
22 from distutils import log
23
24 from itertools import count
25
26 def _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
54 def _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
86 PLAT_SPEC_TO_RUNTIME = {
87 'x86' : 'x86',
88 'x86_amd64' : 'x64',
89 'x86_arm' : 'arm',
90 'x86_arm64' : 'arm64'
91 }
92
93 def _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
111 def _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
141 def _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.
161 PLAT_TO_VCVARS = {
162 'win32' : 'x86',
163 'win-amd64' : 'x86_amd64',
164 'win-arm32' : 'x86_arm',
165 'win-arm64' : 'x86_arm64'
166 }
167
168 class ESC[4;38;5;81mMSVCCompiler(ESC[4;38;5;149mCCompiler) :
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