Update native VS search from VS2015 up to VS2022. (#24975)

Pre-VS2022 are all obsolete by now. VS2022 is the only version that
exists.

Enables running tests in the suite that require native compilation.
diff --git a/test/clang_native.py b/test/clang_native.py
index 1193eb9..28c99f5 100644
--- a/test/clang_native.py
+++ b/test/clang_native.py
@@ -66,19 +66,22 @@
       CACHED_CLANG_NATIVE_ENV = env
       return env
 
-    # Guess where VS2015 is installed (VSINSTALLDIR env. var in VS2015 X64 Command Prompt)
+    # VSINSTALLDIR is not in environment, so the user is not running in Visual Studio
+    # Command Prompt. Attempt to autopopulate INCLUDE and LIB directives.
+
+    # Guess where Visual Studio is installed (VSINSTALLDIR env. var in VS X64 Command Prompt)
     if 'VSINSTALLDIR' in env:
       visual_studio_path = env['VSINSTALLDIR']
-    elif 'VS140COMNTOOLS' in env:
-      visual_studio_path = os.path.normpath(os.path.join(env['VS140COMNTOOLS'], '../..'))
+    elif 'VS170COMNTOOLS' in env:
+      visual_studio_path = os.path.normpath(os.path.join(env['VS170COMNTOOLS'], '../..'))
     elif 'ProgramFiles(x86)' in env:
-      visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles(x86)'], 'Microsoft Visual Studio 14.0'))
+      visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles(x86)'], 'Microsoft Visual Studio', '2022', 'Community'))
     elif 'ProgramFiles' in env:
-      visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles'], 'Microsoft Visual Studio 14.0'))
+      visual_studio_path = os.path.normpath(os.path.join(env['ProgramFiles'], 'Microsoft Visual Studio', '2022', 'Community'))
     else:
-      visual_studio_path = 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0'
+      visual_studio_path = 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community'
     if not os.path.isdir(visual_studio_path):
-      raise Exception('Visual Studio 2015 was not found in "' + visual_studio_path + '"! Run in Visual Studio X64 command prompt to avoid the need to autoguess this location (or set VSINSTALLDIR env var).')
+      raise Exception('Visual Studio was not found in "' + visual_studio_path + '"! Run in Visual Studio X64 command prompt to avoid the need to autoguess this location (or set VSINSTALLDIR env var).')
 
     # Guess where Program Files (x86) is located
     if 'ProgramFiles(x86)' in env:
@@ -92,53 +95,60 @@
     else:
       raise Exception('Unable to detect Program files directory for native Visual Studio build!')
 
-    # Guess where Windows 8.1 SDK is located
-    if 'WindowsSdkDir' in env:
-      windows8_sdk_dir = env['WindowsSdkDir']
-    else:
-      windows8_sdk_dir = os.path.join(prog_files_x86, 'Windows Kits', '8.1')
-    if not os.path.isdir(windows8_sdk_dir):
-      raise Exception('Windows 8.1 SDK was not found in "' + windows8_sdk_dir + '"! Run in Visual Studio command prompt to avoid the need to autoguess this location (or set WindowsSdkDir env var).')
+    # Find include directory root
+    def highest_version_subdir(path):
+      candidates = []
+      for entry in os.listdir(path):
+        full = os.path.join(path, entry)
+        if os.path.isdir(full):
+          try:
+            candidates.append((tuple(int(p) for p in entry.split('.')), full))
+          except ValueError:
+            continue
+      if len(candidates) == 0:
+        return None
+      return max(candidates, key=lambda x: x[0])[1]
 
-    # Guess where Windows 10 SDK is located
-    if os.path.isdir(os.path.join(prog_files_x86, 'Windows Kits', '10')):
-      windows10_sdk_dir = os.path.join(prog_files_x86, 'Windows Kits', '10')
-    if not os.path.isdir(windows10_sdk_dir):
-      raise Exception('Windows 10 SDK was not found in "' + windows10_sdk_dir + '"! Run in Visual Studio command prompt to avoid the need to autoguess this location.')
+    vc_root = os.path.join(visual_studio_path, 'VC')
+    vc_code_root = highest_version_subdir(os.path.join(vc_root, 'Tools', 'MSVC'))
+    if not vc_code_root:
+      raise Exception ('Unable to find Visual Studio INCLUDE root directory. Run in Visual Studio command prompt to avoid the need to autoguess this location.')
+
+    windows_sdk_dir = highest_version_subdir(os.path.join(prog_files_x86, 'Windows Kits'))
+    if not windows_sdk_dir:
+      raise Exception ('Unable to find Windows SDK root directory. Run in Visual Studio command prompt to avoid the need to autoguess this location.')
 
     env.setdefault('VSINSTALLDIR', visual_studio_path)
     env.setdefault('VCINSTALLDIR', os.path.join(visual_studio_path, 'VC'))
 
-    windows10sdk_kits_include_dir = os.path.join(windows10_sdk_dir, 'Include')
-    windows10sdk_kit_version_name = [x for x in os.listdir(windows10sdk_kits_include_dir) if os.path.isdir(os.path.join(windows10sdk_kits_include_dir, x))][0] # e.g. "10.0.10150.0" or "10.0.10240.0"
+    windows_sdk_include_dir = highest_version_subdir(os.path.join(windows_sdk_dir, 'include'))
+    windows_sdk_lib_dir = highest_version_subdir(os.path.join(windows_sdk_dir, 'lib'))
 
-    def append_item(key, item):
+    def append_path_item(key, path):
+      if not os.path.isdir(path):
+        logger.warning(f'VS path {path} does not exist')
+        return
+
       if key not in env or len(env[key].strip()) == 0:
-        env[key] = item
+        env[key] = path
       else:
-        env[key] = env[key] + ';' + item
+        env[key] = env[key] + ';' + path
 
-    append_item('INCLUDE', os.path.join(env['VCINSTALLDIR'], 'INCLUDE'))
-    append_item('INCLUDE', os.path.join(env['VCINSTALLDIR'], 'ATLMFC', 'INCLUDE'))
-    append_item('INCLUDE', os.path.join(windows10_sdk_dir, 'include', windows10sdk_kit_version_name, 'ucrt'))
-    #   append_item('INCLUDE', 'C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.6.1\\include\\um') # VS2015 X64 command prompt has this, but not needed for Emscripten
-    append_item('INCLUDE', os.path.join(env['VCINSTALLDIR'], 'ATLMFC', 'INCLUDE'))
-    append_item('INCLUDE', os.path.join(windows8_sdk_dir, 'include', 'shared'))
-    append_item('INCLUDE', os.path.join(windows8_sdk_dir, 'include', 'um'))
-    append_item('INCLUDE', os.path.join(windows8_sdk_dir, 'include', 'winrt'))
-    logger.debug('VS2015 native build INCLUDE: ' + env['INCLUDE'])
+    append_path_item('INCLUDE', os.path.join(vc_code_root, 'include'))
+    append_path_item('INCLUDE', os.path.join(vc_code_root, 'ATLMFC', 'include'))
+    append_path_item('INCLUDE', os.path.join(vc_root, 'Auxiliary', 'VS', 'include'))
+    for d in ['ucrt', 'um', 'shared', 'winrt', 'cppwinrt']:
+      append_path_item('INCLUDE', os.path.join(windows_sdk_include_dir, d))
+    logger.debug('Visual Studio native build INCLUDE: ' + env['INCLUDE'])
 
-    append_item('LIB', os.path.join(env['VCINSTALLDIR'], 'LIB', 'amd64'))
-    append_item('LIB', os.path.join(env['VCINSTALLDIR'], 'ATLMFC', 'LIB', 'amd64'))
-    append_item('LIB', os.path.join(windows10_sdk_dir, 'lib', windows10sdk_kit_version_name, 'ucrt', 'x64'))
-    #   append_item('LIB', 'C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.6.1\\lib\\um\\x64') # VS2015 X64 command prompt has this, but not needed for Emscripten
-    append_item('LIB', os.path.join(windows8_sdk_dir, 'lib', 'winv6.3', 'um', 'x64'))
-    logger.debug('VS2015 native build LIB: ' + env['LIB'])
+    append_path_item('LIB', os.path.join(vc_code_root, 'ATLMFC', 'lib', 'x64'))
+    append_path_item('LIB', os.path.join(vc_code_root, 'lib', 'x64'))
+    append_path_item('LIB', os.path.join(windows_sdk_lib_dir, 'ucrt', 'x64'))
+    append_path_item('LIB', os.path.join(windows_sdk_lib_dir, 'um', 'x64'))
+    logger.debug('Visual Studio native build LIB: ' + env['LIB'])
 
     env['PATH'] = env['PATH'] + ';' + os.path.join(env['VCINSTALLDIR'], 'BIN')
-    logger.debug('VS2015 native build PATH: ' + env['PATH'])
-
-  # Current configuration above is all Visual Studio -specific, so on non-Windowses, no action needed.
+    logger.debug('Visual Studio native build PATH: ' + env['PATH'])
 
   CACHED_CLANG_NATIVE_ENV = env
   return env