Windows: Windows SDK version can be specified through BAZEL_WINSDK_FULL_VERSION

Fixes https://github.com/bazelbuild/bazel/issues/8721

Closes #8747.

PiperOrigin-RevId: 255958288
diff --git a/site/docs/windows.md b/site/docs/windows.md
index 262cc56..3043b7b 100644
--- a/site/docs/windows.md
+++ b/site/docs/windows.md
@@ -184,7 +184,19 @@
     SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk).
 
     The Windows SDK contains header files and libraries you need when building
-    Windows applications, including Bazel itself.
+    Windows applications, including Bazel itself. By default, the latest Windows SDK installed will
+    be used. You also can specify Windows SDK version by setting `BAZEL_WINSDK_FULL_VERSION`. You
+    can use a full Windows 10 SDK number such as 10.0.10240.0, or specify 8.1 to use the Windows 8.1
+    SDK (only one version of Windows 8.1 SDK is available). Please make sure you have the specified
+    Windows SDK installed.
+
+    **Requirement**: This is supported with VC 2017 and 2019. The standalone VC 2015 Build Tools doesn't
+    support selecting Windows SDK, you'll need the full Visual Studio 2015 installation, otherwise
+    `BAZEL_WINSDK_FULL_VERSION` will be ignored.
+
+    ```
+    set BAZEL_WINSDK_FULL_VERSION=10.0.10240.0
+    ```
 
 If everything is set up, you can build a C++ target now!
 
diff --git a/tools/cpp/cc_configure.bzl b/tools/cpp/cc_configure.bzl
index 66dce02..b2e2080 100644
--- a/tools/cpp/cc_configure.bzl
+++ b/tools/cpp/cc_configure.bzl
@@ -139,6 +139,7 @@
         "BAZEL_VC",
         "BAZEL_VC_FULL_VERSION",
         "BAZEL_VS",
+        "BAZEL_WINSDK_FULL_VERSION",
         "BAZEL_LLVM",
         "USE_CLANG_CL",
         "CC",
diff --git a/tools/cpp/windows_cc_configure.bzl b/tools/cpp/windows_cc_configure.bzl
index dcdf3d6..5a52486 100644
--- a/tools/cpp/windows_cc_configure.bzl
+++ b/tools/cpp/windows_cc_configure.bzl
@@ -229,32 +229,58 @@
     # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\
     return vc_path.find("2017") != -1 or vc_path.find("2019") != -1
 
-def _find_vcvarsall_bat_script(repository_ctx, vc_path):
-    """Find vcvarsall.bat script. Doesn't %-escape the result."""
+def _find_vcvars_bat_script(repository_ctx, vc_path):
+    """Find batch script to set up environment variables for VC. Doesn't %-escape the result."""
     if _is_vs_2017_or_2019(vc_path):
-        vcvarsall = vc_path + "\\Auxiliary\\Build\\VCVARSALL.BAT"
+        vcvars_script = vc_path + "\\Auxiliary\\Build\\VCVARSALL.BAT"
     else:
-        vcvarsall = vc_path + "\\VCVARSALL.BAT"
+        vcvars_script = vc_path + "\\VCVARSALL.BAT"
 
-    if not repository_ctx.path(vcvarsall).exists:
+    if not repository_ctx.path(vcvars_script).exists:
         return None
 
-    return vcvarsall
+    return vcvars_script
+
+def _is_support_winsdk_selection(repository_ctx, vc_path):
+    """Windows SDK selection is supported with VC 2017 / 2019 or with full VS 2015 installation."""
+    if _is_vs_2017_or_2019(vc_path):
+        return True
+
+    # By checking the source code of VCVARSALL.BAT in VC 2015, we know that
+    # when devenv.exe or wdexpress.exe exists, VCVARSALL.BAT supports Windows SDK selection.
+    vc_common_ide = repository_ctx.path(vc_path).dirname.get_child("Common7").get_child("IDE")
+    for tool in ["devenv.exe", "wdexpress.exe"]:
+        if vc_common_ide.get_child(tool).exists:
+            return True
+    return False
 
 def setup_vc_env_vars(repository_ctx, vc_path):
-    """Get environment variables set by VCVARSALL.BAT. Doesn't %-escape the result!"""
-    vcvarsall = _find_vcvarsall_bat_script(repository_ctx, vc_path)
-    if not vcvarsall:
-        return None
+    """Get environment variables set by VCVARSALL.BAT script. Doesn't %-escape the result!"""
+    vcvars_script = _find_vcvars_bat_script(repository_ctx, vc_path)
+    if not vcvars_script:
+        auto_configure_fail("Cannot find VCVARSALL.BAT script under %s" % vc_path)
+
+    # Getting Windows SDK version set by user.
+    # Only supports VC 2017 & 2019 and VC 2015 with full VS installation.
+    winsdk_version = _get_winsdk_full_version(repository_ctx)
+    if winsdk_version and not _is_support_winsdk_selection(repository_ctx, vc_path):
+        auto_configure_warning(("BAZEL_WINSDK_FULL_VERSION=%s is ignored, " +
+                                "because standalone Visual C++ Build Tools 2015 doesn't support specifying Windows " +
+                                "SDK version, please install the full VS 2015 or use VC 2017/2019.") % winsdk_version)
+        winsdk_version = ""
+
+    # Get VC version set by user. Only supports VC 2017 & 2019.
     vcvars_ver = ""
     if _is_vs_2017_or_2019(vc_path):
         full_version = _get_vc_full_version(repository_ctx, vc_path)
         if full_version:
             vcvars_ver = "-vcvars_ver=" + full_version
+
+    cmd = "\"%s\" amd64 %s %s" % (vcvars_script, winsdk_version, vcvars_ver)
     repository_ctx.file(
         "get_env.bat",
         "@echo off\n" +
-        ("call \"%s\" amd64 %s > NUL \n" % (vcvarsall, vcvars_ver)) +
+        ("call %s > NUL \n" % cmd) +
         "echo PATH=%PATH%,INCLUDE=%INCLUDE%,LIB=%LIB%,WINDOWSSDKDIR=%WINDOWSSDKDIR% \n",
         True,
     )
@@ -267,8 +293,17 @@
     for env in envs:
         key, value = env.split("=", 1)
         env_map[key] = escape_string(value.replace("\\", "\\\\"))
+    _check_env_vars(env_map, cmd)
     return env_map
 
+def _check_env_vars(env_map, cmd):
+    envs = ["PATH", "INCLUDE", "LIB", "WINDOWSSDKDIR"]
+    for env in envs:
+        if not env_map.get(env):
+            auto_configure_fail(
+                "Setting up VC environment variables failed, %s is not set by the following command:\n    %s" % (env, cmd),
+            )
+
 def _get_latest_subversion(repository_ctx, vc_path):
     """Get the latest subversion of a VS 2017/2019 installation.
 
@@ -300,6 +335,10 @@
         return repository_ctx.os.environ["BAZEL_VC_FULL_VERSION"]
     return _get_latest_subversion(repository_ctx, vc_path)
 
+def _get_winsdk_full_version(repository_ctx):
+    """Return the value of BAZEL_WINSDK_FULL_VERSION if defined, otherwise an empty string."""
+    return repository_ctx.os.environ.get("BAZEL_WINSDK_FULL_VERSION", default = "")
+
 def find_msvc_tool(repository_ctx, vc_path, tool):
     """Find the exact path of a specific build tool in MSVC. Doesn't %-escape the result."""
     tool_path = None
@@ -320,7 +359,7 @@
 def _find_missing_vc_tools(repository_ctx, vc_path):
     """Check if any required tool is missing under given VC path."""
     missing_tools = []
-    if not _find_vcvarsall_bat_script(repository_ctx, vc_path):
+    if not _find_vcvars_bat_script(repository_ctx, vc_path):
         missing_tools.append("VCVARSALL.BAT")
 
     for tool in ["cl.exe", "link.exe", "lib.exe", "ml64.exe"]: