2626import sys
2727import platform
2828import itertools
29+ import subprocess
2930import distutils .errors
3031from setuptools .extern .packaging .version import LegacyVersion
3132
@@ -142,6 +143,154 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
142143 raise
143144
144145
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+
145294def msvc14_get_vc_env (plat_spec ):
146295 """
147296 Patched "distutils._msvccompiler._get_vc_env" for support extra
@@ -159,16 +308,10 @@ def msvc14_get_vc_env(plat_spec):
159308 dict
160309 environment
161310 """
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
168311
169- # If error, try to set environment directly
312+ # Always use backport from CPython 3.8
170313 try :
171- return EnvironmentInfo (plat_spec , vc_min_ver = 14.0 ). return_env ( )
314+ return _msvc14_get_vc_env (plat_spec )
172315 except distutils .errors .DistutilsPlatformError as exc :
173316 _augment_exception (exc , 14.0 )
174317 raise
0 commit comments