Skip to content

Commit 76006f2

Browse files
authored
[3.6] bpo-30389 Adds detection of VS 2017 to distutils._msvccompiler GH-1632 (#3425)
1 parent b036232 commit 76006f2

14 files changed

Lines changed: 1189 additions & 32 deletions

Lib/distutils/_msvccompiler.py

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
import shutil
1818
import stat
1919
import subprocess
20+
import winreg
2021

2122
from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
2223
CompileError, LibError, LinkError
2324
from distutils.ccompiler import CCompiler, gen_lib_options
2425
from distutils import log
2526
from distutils.util import get_platform
2627

27-
import winreg
2828
from itertools import count
2929

30-
def _find_vcvarsall(plat_spec):
30+
def _find_vc2015():
3131
try:
3232
key = winreg.OpenKeyEx(
3333
winreg.HKEY_LOCAL_MACHINE,
@@ -38,9 +38,9 @@ def _find_vcvarsall(plat_spec):
3838
log.debug("Visual C++ is not registered")
3939
return None, None
4040

41+
best_version = 0
42+
best_dir = None
4143
with key:
42-
best_version = 0
43-
best_dir = None
4444
for i in count():
4545
try:
4646
v, vc_dir, vt = winreg.EnumValue(key, i)
@@ -53,25 +53,74 @@ def _find_vcvarsall(plat_spec):
5353
continue
5454
if version >= 14 and version > best_version:
5555
best_version, best_dir = version, vc_dir
56-
if not best_version:
57-
log.debug("No suitable Visual C++ version found")
58-
return None, None
56+
return best_version, best_dir
57+
58+
def _find_vc2017():
59+
import _findvs
60+
import threading
61+
62+
best_version = 0, # tuple for full version comparisons
63+
best_dir = None
64+
65+
# We need to call findall() on its own thread because it will
66+
# initialize COM.
67+
all_packages = []
68+
def _getall():
69+
all_packages.extend(_findvs.findall())
70+
t = threading.Thread(target=_getall)
71+
t.start()
72+
t.join()
73+
74+
for name, version_str, path, packages in all_packages:
75+
if 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' in packages:
76+
vc_dir = os.path.join(path, 'VC', 'Auxiliary', 'Build')
77+
if not os.path.isdir(vc_dir):
78+
continue
79+
try:
80+
version = tuple(int(i) for i in version_str.split('.'))
81+
except (ValueError, TypeError):
82+
continue
83+
if version > best_version:
84+
best_version, best_dir = version, vc_dir
85+
try:
86+
best_version = best_version[0]
87+
except IndexError:
88+
best_version = None
89+
return best_version, best_dir
5990

60-
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
61-
if not os.path.isfile(vcvarsall):
62-
log.debug("%s cannot be found", vcvarsall)
63-
return None, None
91+
def _find_vcvarsall(plat_spec):
92+
best_version, best_dir = _find_vc2017()
93+
vcruntime = None
94+
vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
95+
if best_version:
96+
vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
97+
"Microsoft.VC141.CRT", "vcruntime140.dll")
98+
try:
99+
import glob
100+
vcruntime = glob.glob(vcredist, recursive=True)[-1]
101+
except (ImportError, OSError, LookupError):
102+
vcruntime = None
103+
104+
if not best_version:
105+
best_version, best_dir = _find_vc2015()
106+
if best_version:
107+
vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat,
108+
"Microsoft.VC140.CRT", "vcruntime140.dll")
109+
110+
if not best_version:
111+
log.debug("No suitable Visual C++ version found")
112+
return None, None
64113

114+
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
115+
if not os.path.isfile(vcvarsall):
116+
log.debug("%s cannot be found", vcvarsall)
117+
return None, None
118+
119+
if not vcruntime or not os.path.isfile(vcruntime):
120+
log.debug("%s cannot be found", vcruntime)
65121
vcruntime = None
66-
vcruntime_spec = _VCVARS_PLAT_TO_VCRUNTIME_REDIST.get(plat_spec)
67-
if vcruntime_spec:
68-
vcruntime = os.path.join(best_dir,
69-
vcruntime_spec.format(best_version))
70-
if not os.path.isfile(vcruntime):
71-
log.debug("%s cannot be found", vcruntime)
72-
vcruntime = None
73122

74-
return vcvarsall, vcruntime
123+
return vcvarsall, vcruntime
75124

76125
def _get_vc_env(plat_spec):
77126
if os.getenv("DISTUTILS_USE_SDK"):
@@ -130,14 +179,6 @@ def _find_exe(exe, paths=None):
130179
'win-amd64' : 'x86_amd64',
131180
}
132181

133-
# A map keyed by get_platform() return values to the file under
134-
# the VC install directory containing the vcruntime redistributable.
135-
_VCVARS_PLAT_TO_VCRUNTIME_REDIST = {
136-
'x86' : 'redist\\x86\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
137-
'amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
138-
'x86_amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
139-
}
140-
141182
# A set containing the DLLs that are guaranteed to be available for
142183
# all micro versions of this Python version. Known extension
143184
# dependencies that are not in this set will be copied to the output

Lib/distutils/tests/test_msvccompiler.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ def test_vcruntime_skip_copy(self):
7676
compiler = _msvccompiler.MSVCCompiler()
7777
compiler.initialize()
7878
dll = compiler._vcruntime_redist
79-
self.assertTrue(os.path.isfile(dll))
79+
self.assertTrue(os.path.isfile(dll), dll or "<None>")
8080

8181
compiler._copy_vcruntime(tempdir)
8282

8383
self.assertFalse(os.path.isfile(os.path.join(
84-
tempdir, os.path.basename(dll))))
84+
tempdir, os.path.basename(dll))), dll or "<None>")
8585

8686
def test_get_vc_env_unicode(self):
8787
import distutils._msvccompiler as _msvccompiler
@@ -101,6 +101,30 @@ def test_get_vc_env_unicode(self):
101101
if old_distutils_use_sdk:
102102
os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk
103103

104+
def test_get_vc2017(self):
105+
import distutils._msvccompiler as _msvccompiler
106+
107+
# This function cannot be mocked, so pass it if we find VS 2017
108+
# and mark it skipped if we do not.
109+
version, path = _msvccompiler._find_vc2017()
110+
if version:
111+
self.assertGreaterEqual(version, 15)
112+
self.assertTrue(os.path.isdir(path))
113+
else:
114+
raise unittest.SkipTest("VS 2017 is not installed")
115+
116+
def test_get_vc2015(self):
117+
import distutils._msvccompiler as _msvccompiler
118+
119+
# This function cannot be mocked, so pass it if we find VS 2015
120+
# and mark it skipped if we do not.
121+
version, path = _msvccompiler._find_vc2015()
122+
if version:
123+
self.assertGreaterEqual(version, 14)
124+
self.assertTrue(os.path.isdir(path))
125+
else:
126+
raise unittest.SkipTest("VS 2015 is not installed")
127+
104128
def test_suite():
105129
return unittest.makeSuite(msvccompilerTestCase)
106130

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adds detection of Visual Studio 2017 to distutils on Windows.

0 commit comments

Comments
 (0)