bpo-35067: Remove _distutils_findvs and use vswhere.exe instead. (GH-10095)

(cherry picked from commit 53125a53f483db0af76249b6af6efcdc200eb421)

Co-authored-by: Steve Dower <steve.dower@microsoft.com>
diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py
index 30b3b47..84b4ef5 100644
--- a/Lib/distutils/_msvccompiler.py
+++ b/Lib/distutils/_msvccompiler.py
@@ -56,43 +56,43 @@
     return best_version, best_dir
 
 def _find_vc2017():
-    import _distutils_findvs
-    import threading
+    """Returns "15, path" based on the result of invoking vswhere.exe
+    If no install is found, returns "None, None"
 
-    best_version = 0,   # tuple for full version comparisons
-    best_dir = None
+    The version is returned to avoid unnecessarily changing the function
+    result. It may be ignored when the path is not None.
 
-    # We need to call findall() on its own thread because it will
-    # initialize COM.
-    all_packages = []
-    def _getall():
-        all_packages.extend(_distutils_findvs.findall())
-    t = threading.Thread(target=_getall)
-    t.start()
-    t.join()
+    If vswhere.exe is not available, by definition, VS 2017 is not
+    installed.
+    """
+    import json
 
-    for name, version_str, path, packages in all_packages:
-        if 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' in packages:
-            vc_dir = os.path.join(path, 'VC', 'Auxiliary', 'Build')
-            if not os.path.isdir(vc_dir):
-                continue
-            try:
-                version = tuple(int(i) for i in version_str.split('.'))
-            except (ValueError, TypeError):
-                continue
-            if version > best_version:
-                best_version, best_dir = version, vc_dir
+    root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
+    if not root:
+        return None, None
+
     try:
-        best_version = best_version[0]
-    except IndexError:
-        best_version = None
-    return best_version, best_dir
+        path = subprocess.check_output([
+            os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
+            "-latest",
+            "-prerelease",
+            "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+            "-property", "installationPath",
+        ], encoding="mbcs", errors="strict").strip()
+    except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
+        return None, None
+
+    path = os.path.join(path, "VC", "Auxiliary", "Build")
+    if os.path.isdir(path):
+        return 15, path
+
+    return None, None
 
 def _find_vcvarsall(plat_spec):
-    best_version, best_dir = _find_vc2017()
+    _, best_dir = _find_vc2017()
     vcruntime = None
     vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
-    if best_version:
+    if best_dir:
         vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
             "Microsoft.VC141.CRT", "vcruntime140.dll")
         try:
@@ -101,13 +101,13 @@
         except (ImportError, OSError, LookupError):
             vcruntime = None
 
-    if not best_version:
+    if not best_dir:
         best_version, best_dir = _find_vc2015()
         if best_version:
             vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat,
                 "Microsoft.VC140.CRT", "vcruntime140.dll")
 
-    if not best_version:
+    if not best_dir:
         log.debug("No suitable Visual C++ version found")
         return None, None