26
26
import sys
27
27
import platform
28
28
import itertools
29
+ import subprocess
29
30
import distutils .errors
30
31
from setuptools .extern .packaging .version import LegacyVersion
31
32
@@ -142,6 +143,154 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
142
143
raise
143
144
144
145
146
+ def _msvc14_find_vc2015 ():
147
+ """Python 3.8 "distutils/_msvccompiler.py" backport"""
148
+ try :
149
+ key = winreg .OpenKey (
150
+ winreg .HKEY_LOCAL_MACHINE ,
151
+ r"Software\Microsoft\VisualStudio\SxS\VC7" ,
152
+ 0 ,
153
+ winreg .KEY_READ | winreg .KEY_WOW64_32KEY
154
+ )
155
+ except OSError :
156
+ return None , None
157
+
158
+ best_version = 0
159
+ best_dir = None
160
+ with key :
161
+ for i in itertools .count ():
162
+ try :
163
+ v , vc_dir , vt = winreg .EnumValue (key , i )
164
+ except OSError :
165
+ break
166
+ if v and vt == winreg .REG_SZ and isdir (vc_dir ):
167
+ try :
168
+ version = int (float (v ))
169
+ except (ValueError , TypeError ):
170
+ continue
171
+ if version >= 14 and version > best_version :
172
+ best_version , best_dir = version , vc_dir
173
+ return best_version , best_dir
174
+
175
+
176
+ def _msvc14_find_vc2017 ():
177
+ """Python 3.8 "distutils/_msvccompiler.py" backport
178
+
179
+ Returns "15, path" based on the result of invoking vswhere.exe
180
+ If no install is found, returns "None, None"
181
+
182
+ The version is returned to avoid unnecessarily changing the function
183
+ result. It may be ignored when the path is not None.
184
+
185
+ If vswhere.exe is not available, by definition, VS 2017 is not
186
+ installed.
187
+ """
188
+ root = environ .get ("ProgramFiles(x86)" ) or environ .get ("ProgramFiles" )
189
+ if not root :
190
+ return None , None
191
+
192
+ try :
193
+ path = subprocess .check_output ([
194
+ join (root , "Microsoft Visual Studio" , "Installer" , "vswhere.exe" ),
195
+ "-latest" ,
196
+ "-prerelease" ,
197
+ "-requires" , "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" ,
198
+ "-property" , "installationPath" ,
199
+ "-products" , "*" ,
200
+ ]).decode (encoding = "mbcs" , errors = "strict" ).strip ()
201
+ except (subprocess .CalledProcessError , OSError , UnicodeDecodeError ):
202
+ return None , None
203
+
204
+ path = join (path , "VC" , "Auxiliary" , "Build" )
205
+ if isdir (path ):
206
+ return 15 , path
207
+
208
+ return None , None
209
+
210
+
211
+ PLAT_SPEC_TO_RUNTIME = {
212
+ 'x86' : 'x86' ,
213
+ 'x86_amd64' : 'x64' ,
214
+ 'x86_arm' : 'arm' ,
215
+ 'x86_arm64' : 'arm64'
216
+ }
217
+
218
+
219
+ def _msvc14_find_vcvarsall (plat_spec ):
220
+ """Python 3.8 "distutils/_msvccompiler.py" backport"""
221
+ _ , best_dir = _msvc14_find_vc2017 ()
222
+ vcruntime = None
223
+
224
+ if plat_spec in PLAT_SPEC_TO_RUNTIME :
225
+ vcruntime_plat = PLAT_SPEC_TO_RUNTIME [plat_spec ]
226
+ else :
227
+ vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
228
+
229
+ if best_dir :
230
+ vcredist = join (best_dir , ".." , ".." , "redist" , "MSVC" , "**" ,
231
+ vcruntime_plat , "Microsoft.VC14*.CRT" ,
232
+ "vcruntime140.dll" )
233
+ try :
234
+ import glob
235
+ vcruntime = glob .glob (vcredist , recursive = True )[- 1 ]
236
+ except (ImportError , OSError , LookupError ):
237
+ vcruntime = None
238
+
239
+ if not best_dir :
240
+ best_version , best_dir = _msvc14_find_vc2015 ()
241
+ if best_version :
242
+ vcruntime = join (best_dir , 'redist' , vcruntime_plat ,
243
+ "Microsoft.VC140.CRT" , "vcruntime140.dll" )
244
+
245
+ if not best_dir :
246
+ return None , None
247
+
248
+ vcvarsall = join (best_dir , "vcvarsall.bat" )
249
+ if not isfile (vcvarsall ):
250
+ return None , None
251
+
252
+ if not vcruntime or not isfile (vcruntime ):
253
+ vcruntime = None
254
+
255
+ return vcvarsall , vcruntime
256
+
257
+
258
+ def _msvc14_get_vc_env (plat_spec ):
259
+ """Python 3.8 "distutils/_msvccompiler.py" backport"""
260
+ if "DISTUTILS_USE_SDK" in environ :
261
+ return {
262
+ key .lower (): value
263
+ for key , value in environ .items ()
264
+ }
265
+
266
+ vcvarsall , vcruntime = _msvc14_find_vcvarsall (plat_spec )
267
+ if not vcvarsall :
268
+ raise distutils .errors .DistutilsPlatformError (
269
+ "Unable to find vcvarsall.bat"
270
+ )
271
+
272
+ try :
273
+ out = subprocess .check_output (
274
+ 'cmd /u /c "{}" {} && set' .format (vcvarsall , plat_spec ),
275
+ stderr = subprocess .STDOUT ,
276
+ ).decode ('utf-16le' , errors = 'replace' )
277
+ except subprocess .CalledProcessError as exc :
278
+ raise distutils .errors .DistutilsPlatformError (
279
+ "Error executing {}" .format (exc .cmd )
280
+ )
281
+
282
+ env = {
283
+ key .lower (): value
284
+ for key , _ , value in
285
+ (line .partition ('=' ) for line in out .splitlines ())
286
+ if key and value
287
+ }
288
+
289
+ if vcruntime :
290
+ env ['py_vcruntime_redist' ] = vcruntime
291
+ return env
292
+
293
+
145
294
def msvc14_get_vc_env (plat_spec ):
146
295
"""
147
296
Patched "distutils._msvccompiler._get_vc_env" for support extra
@@ -159,16 +308,10 @@ def msvc14_get_vc_env(plat_spec):
159
308
dict
160
309
environment
161
310
"""
162
- # Try to get environment from vcvarsall.bat (Classical way)
163
- try :
164
- return get_unpatched (msvc14_get_vc_env )(plat_spec )
165
- except distutils .errors .DistutilsPlatformError :
166
- # Pass error Vcvarsall.bat is missing
167
- pass
168
311
169
- # If error, try to set environment directly
312
+ # Always use backport from CPython 3.8
170
313
try :
171
- return EnvironmentInfo (plat_spec , vc_min_ver = 14.0 ). return_env ( )
314
+ return _msvc14_get_vc_env (plat_spec )
172
315
except distutils .errors .DistutilsPlatformError as exc :
173
316
_augment_exception (exc , 14.0 )
174
317
raise
0 commit comments