Skip to content

Commit 05f01d8

Browse files
authored
bpo-30389 Adds detection of VS 2017 to distutils._msvccompiler (#1632)
1 parent a853a8b commit 05f01d8

16 files changed

+1272
-110
lines changed

Include/pyatomic.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,20 +285,20 @@ typedef struct _Py_atomic_int {
285285
a uintptr_t it will do an unsigned compare and crash
286286
*/
287287
inline intptr_t _Py_atomic_load_64bit(volatile uintptr_t* value, int order) {
288-
uintptr_t old;
288+
__int64 old;
289289
switch (order) {
290290
case _Py_memory_order_acquire:
291291
{
292292
do {
293293
old = *value;
294-
} while(_InterlockedCompareExchange64_HLEAcquire(value, old, old) != old);
294+
} while(_InterlockedCompareExchange64_HLEAcquire((volatile __int64*)value, old, old) != old);
295295
break;
296296
}
297297
case _Py_memory_order_release:
298298
{
299299
do {
300300
old = *value;
301-
} while(_InterlockedCompareExchange64_HLERelease(value, old, old) != old);
301+
} while(_InterlockedCompareExchange64_HLERelease((volatile __int64*)value, old, old) != old);
302302
break;
303303
}
304304
case _Py_memory_order_relaxed:
@@ -308,7 +308,7 @@ inline intptr_t _Py_atomic_load_64bit(volatile uintptr_t* value, int order) {
308308
{
309309
do {
310310
old = *value;
311-
} while(_InterlockedCompareExchange64(value, old, old) != old);
311+
} while(_InterlockedCompareExchange64((volatile __int64*)value, old, old) != old);
312312
break;
313313
}
314314
}
@@ -320,20 +320,20 @@ inline intptr_t _Py_atomic_load_64bit(volatile uintptr_t* value, int order) {
320320
#endif
321321

322322
inline int _Py_atomic_load_32bit(volatile int* value, int order) {
323-
int old;
323+
long old;
324324
switch (order) {
325325
case _Py_memory_order_acquire:
326326
{
327327
do {
328328
old = *value;
329-
} while(_InterlockedCompareExchange_HLEAcquire(value, old, old) != old);
329+
} while(_InterlockedCompareExchange_HLEAcquire((volatile long*)value, old, old) != old);
330330
break;
331331
}
332332
case _Py_memory_order_release:
333333
{
334334
do {
335335
old = *value;
336-
} while(_InterlockedCompareExchange_HLERelease(value, old, old) != old);
336+
} while(_InterlockedCompareExchange_HLERelease((volatile long*)value, old, old) != old);
337337
break;
338338
}
339339
case _Py_memory_order_relaxed:
@@ -343,7 +343,7 @@ inline int _Py_atomic_load_32bit(volatile int* value, int order) {
343343
{
344344
do {
345345
old = *value;
346-
} while(_InterlockedCompareExchange(value, old, old) != old);
346+
} while(_InterlockedCompareExchange((volatile long*)value, old, old) != old);
347347
break;
348348
}
349349
}

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)