Skip to content

Commit 0522e7e

Browse files
i-shenlMatthewZMD
authored andcommitted
Merge in the py 3 detection for VS 2015 and higher
1 parent fdaa2c6 commit 0522e7e

File tree

1 file changed

+132
-78
lines changed

1 file changed

+132
-78
lines changed

Lib/distutils/msvc9compiler.py

Lines changed: 132 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,66 @@ def sub(self, s):
169169
s = s.replace(k, v)
170170
return s
171171

172+
def _find_vc2015():
173+
try:
174+
key = _winreg.OpenKeyEx(
175+
_winreg.HKEY_LOCAL_MACHINE,
176+
r"Software\Microsoft\VisualStudio\SxS\VC7",
177+
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
178+
)
179+
except OSError:
180+
log.debug("Visual C++ is not registered")
181+
return None, None
182+
183+
best_version = 0
184+
best_dir = None
185+
with key:
186+
for i in count():
187+
try:
188+
v, vc_dir, vt = _winreg.EnumValue(key, i)
189+
except OSError:
190+
break
191+
if v and vt == _winreg.REG_SZ and os.path.isdir(vc_dir):
192+
try:
193+
version = int(float(v))
194+
except (ValueError, TypeError):
195+
continue
196+
if version >= 14 and version > best_version:
197+
best_version, best_dir = version, vc_dir
198+
return best_version, best_dir
199+
200+
def _find_vc2017():
201+
"""Returns "15, path" based on the result of invoking vswhere.exe
202+
If no install is found, returns "None, None"
203+
204+
The version is returned to avoid unnecessarily changing the function
205+
result. It may be ignored when the path is not None.
206+
207+
If vswhere.exe is not available, by definition, VS 2017 is not
208+
installed.
209+
"""
210+
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
211+
if not root:
212+
return None, None
213+
214+
try:
215+
path = subprocess.check_output([
216+
os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
217+
"-latest",
218+
"-prerelease",
219+
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
220+
"-property", "installationPath",
221+
"-products", "*",
222+
]).strip()
223+
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
224+
return None, None
225+
226+
path = os.path.join(path, "VC", "Auxiliary", "Build")
227+
if os.path.isdir(path):
228+
return 15, path
229+
230+
return None, None
231+
172232
def get_build_version():
173233
"""Return the version of MSVC that was used to build Python.
174234
@@ -216,89 +276,83 @@ def removeDuplicates(variable):
216276
newVariable = os.pathsep.join(newList)
217277
return newVariable
218278

219-
def find_vcvarsall(version):
220-
"""Find the vcvarsall.bat file
221279

222-
At first it tries to find the productdir of VS 2008 in the registry. If
223-
that fails it falls back to the VS90COMNTOOLS env var.
224-
"""
225-
vsbase = VS_BASE % version
226-
try:
227-
productdir = Reg.get_value(r"%s\Setup\VC" % vsbase,
228-
"productdir")
229-
except KeyError:
230-
productdir = None
231-
232-
# trying Express edition
233-
if productdir is None:
234-
vsbase = VSEXPRESS_BASE % version
235-
try:
236-
productdir = Reg.get_value(r"%s\Setup\VC" % vsbase,
237-
"productdir")
238-
except KeyError:
239-
productdir = None
240-
log.debug("Unable to find productdir in registry")
241-
242-
if not productdir or not os.path.isdir(productdir):
243-
toolskey = "VS%0.f0COMNTOOLS" % version
244-
toolsdir = os.environ.get(toolskey, None)
245-
246-
if toolsdir and os.path.isdir(toolsdir):
247-
productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC")
248-
productdir = os.path.abspath(productdir)
249-
if not os.path.isdir(productdir):
250-
log.debug("%s is not a valid directory" % productdir)
251-
return None
252-
else:
253-
log.debug("Env var %s is not set or invalid" % toolskey)
254-
if not productdir:
255-
log.debug("No productdir found")
256-
return None
257-
vcvarsall = os.path.join(productdir, "vcvarsall.bat")
258-
if os.path.isfile(vcvarsall):
259-
return vcvarsall
260-
log.debug("Unable to find vcvarsall.bat")
261-
return None
280+
def _find_vcvarsall(plat_spec):
281+
# bpo-38597: Removed vcruntime return value
282+
_, best_dir = _find_vc2017()
262283

263-
def query_vcvarsall(version, arch="x86"):
264-
"""Launch vcvarsall.bat and read the settings from its environment
265-
"""
266-
vcvarsall = find_vcvarsall(version)
267-
interesting = set(("include", "lib", "libpath", "path"))
268-
result = {}
284+
if not best_dir:
285+
best_version, best_dir = _find_vc2015()
286+
287+
if not best_dir:
288+
log.debug("No suitable Visual C++ version found")
289+
return None, None
290+
291+
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
292+
if not os.path.isfile(vcvarsall):
293+
log.debug("%s cannot be found", vcvarsall)
294+
return None, None
269295

270-
if vcvarsall is None:
296+
return vcvarsall, None
297+
298+
299+
def _get_vc_env(plat_spec):
300+
if os.getenv("DISTUTILS_USE_SDK"):
301+
return {
302+
key.lower(): value
303+
for key, value in os.environ.items()
304+
}
305+
306+
vcvarsall, _ = _find_vcvarsall(plat_spec)
307+
if not vcvarsall:
271308
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
272-
log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version)
273-
popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch),
274-
stdout=subprocess.PIPE,
275-
stderr=subprocess.PIPE)
276-
try:
277-
stdout, stderr = popen.communicate()
278-
if popen.wait() != 0:
279-
raise DistutilsPlatformError(stderr.decode("mbcs"))
280-
281-
stdout = stdout.decode("mbcs")
282-
for line in stdout.split("\n"):
283-
line = Reg.convert_mbcs(line)
284-
if '=' not in line:
285-
continue
286-
line = line.strip()
287-
key, value = line.split('=', 1)
288-
key = key.lower()
289-
if key in interesting:
290-
if value.endswith(os.pathsep):
291-
value = value[:-1]
292-
result[key] = removeDuplicates(value)
293309

294-
finally:
295-
popen.stdout.close()
296-
popen.stderr.close()
310+
try:
311+
out = subprocess.check_output(
312+
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
313+
stderr=subprocess.STDOUT,
314+
).decode('utf-16le', errors='replace')
315+
except subprocess.CalledProcessError as exc:
316+
log.error(exc.output)
317+
raise DistutilsPlatformError("Error executing {}"
318+
.format(exc.cmd))
319+
320+
env = {
321+
key.lower(): value
322+
for key, _, value in
323+
(line.partition('=') for line in out.splitlines())
324+
if key and value
325+
}
326+
327+
return env
328+
329+
def _find_exe(exe, paths=None):
330+
"""Return path to an MSVC executable program.
331+
332+
Tries to find the program in several places: first, one of the
333+
MSVC program search paths from the registry; next, the directories
334+
in the PATH environment variable. If any of those work, return an
335+
absolute path that is known to exist. If none of them work, just
336+
return the original program name, 'exe'.
337+
"""
338+
if not paths:
339+
paths = os.getenv('path').split(os.pathsep)
340+
for p in paths:
341+
fn = os.path.join(os.path.abspath(p), exe)
342+
if os.path.isfile(fn):
343+
return fn
344+
return exe
297345

298-
if len(result) != len(interesting):
299-
raise ValueError(str(list(result.keys())))
346+
# A map keyed by get_platform() return values to values accepted by
347+
# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
348+
# lighter-weight MSVC installs that do not include native 64-bit tools.
349+
PLAT_TO_VCVARS = {
350+
'win32' : 'x86',
351+
'win-amd64' : 'x86_amd64',
352+
'win-arm32' : 'x86_arm',
353+
'win-arm64' : 'x86_arm64'
354+
}
300355

301-
return result
302356

303357
# More globals
304358
VERSION = get_build_version()
@@ -352,7 +406,7 @@ def initialize(self, plat_name=None):
352406
assert not self.initialized, "don't init multiple times"
353407
if plat_name is None:
354408
plat_name = get_platform()
355-
# sanity check for platforms to prevent obscure errors later.
409+
# sanity check for platforms to prevent obscure errors later.query_vcvarsall
356410
ok_plats = 'win32', 'win-amd64', 'win-ia64'
357411
if plat_name not in ok_plats:
358412
raise DistutilsPlatformError("--plat-name must be one of %s" %
@@ -380,7 +434,7 @@ def initialize(self, plat_name=None):
380434
plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \
381435
PLAT_TO_VCVARS[plat_name]
382436

383-
vc_env = query_vcvarsall(VERSION, plat_spec)
437+
vc_env = _get_vc_env(plat_spec)
384438

385439
# take care to only use strings in the environment.
386440
self.__paths = vc_env['path'].encode('mbcs').split(os.pathsep)

0 commit comments

Comments
 (0)