@@ -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+
172232def 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
304358VERSION = 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