Add toolchain support for clang-cl on Windows

Previously we [added clang-cl.exe support](https://github.com/bazelbuild/bazel/pull/6553) through `USE_CLANG_CL` environment variable. That is a "hack" on the existing MSVC toolchain to make it use Clang tools. This change add proper toolchain support for `clang-cl.exe` on Windows.
Because `clang-cl.exe` is MSVC compatible, we can just reuse the MSVC toolchain definition.

After this change, to select the clang-cl toolchain on Windows, you can use:

-  Before Bazel 1.0, specify build flag `--compiler=clang-cl`, this is deprecated and will be disabled in Bazel 1.0.
-  From Bazel 1.0 (or with `--incompatible_enable_cc_toolchain_resolution` flag). You have to add a platform target to your build file (eg. the top level BUILD file):
```
platform(
  name = "windows-clang-cl",
  constraint_values = [
    "@platforms//cpu:x86_64",
    "@platforms//os:windows",
    "@bazel_tools//tools/cpp:clang-cl",
  ]
)
```
Then you can tell Bazel to use the clang-cl toolchain by specifying build flags
```
--extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl --extra_execution_platforms=//:windows-clang-cl
```
or registering the platform and toolchain in your WORKSPACE file:
```
register_execution_platforms(
  ":windows-clang-cl"
)

register_toolchains(
    "@local_config_cc//:cc-toolchain-x64_windows-clang-cl",
)
```
Related issue: [incompatible_enable_cc_toolchain_resolution: Turn on toolchain resolution for cc rules](https://github.com/bazelbuild/bazel/issues/7260)

Closes #8917.

PiperOrigin-RevId: 258961022
diff --git a/site/docs/windows.md b/site/docs/windows.md
index 3043b7b..1f3d445 100644
--- a/site/docs/windows.md
+++ b/site/docs/windows.md
@@ -117,9 +117,9 @@
   See the [Build C++ section](#build_cpp) below.
 
 <a name="build_cpp"></a>
-### Build C++
+### Build C++ with MSVC
 
-To build C++ targets, you need:
+To build C++ targets with MSVC, you need:
 
 *   The Visual C++ compiler.
 
@@ -212,6 +212,58 @@
 To build and use Dynamically Linked Libraries (DLL files), see [this
 example](https://github.com/bazelbuild/bazel/tree/master/examples/windows/dll).
 
+### Build C++ with Clang
+
+From 0.29.0, Bazel supports building with LLVM's MSVC-compatible compiler driver (`clang-cl.exe`).
+
+**Requirement**: To build with Clang, you have to install **both**
+[LLVM](http://releases.llvm.org/download.html) and Visual C++ Build tools, because although we use
+`clang-cl.exe` as compiler, we still need to link to Visual C++ libraries.
+
+Bazel can automatically detect LLVM installation on your system, or you can explicitly tell
+Bazel where LLVM is installed by `BAZEL_LLVM`.
+
+*   `BAZEL_LLVM` the LLVM installation directory
+
+    ```
+    set BAZEL_LLVM=C:\Program Files\LLVM
+    ```
+
+To enable the Clang toolchain for building C++, there are several situations.
+
+* In bazel 0.28 and older: Clang is not supported.
+
+* In Bazel 0.29.0: You can enable the Clang toolchain by a build flag `--compiler=clang-cl`.
+  This is deprecated and will be removed in Bazel 1.0.
+
+* From Bazel 1.0: You have to add a platform target to your build file (eg. the top level BUILD file):
+    ```
+    platform(
+        name = "windows-clang-cl",
+        constraint_values = [
+            "@platforms//cpu:x86_64",
+            "@platforms//os:windows",
+            "@bazel_tools//tools/cpp:clang-cl",
+        ],
+    )
+    ```
+    Then you can enable the Clang toolchain by either of the following two ways:
+    * Specify the following build flags:
+    ```
+    --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl --extra_execution_platforms=//:windows-clang-cl
+    ```
+    * Register the platform and toolchain in your WORKSPACE file:
+    ```
+    register_execution_platforms(
+        ":windows-clang-cl"
+    )
+
+    register_toolchains(
+        "@local_config_cc//:cc-toolchain-x64_windows-clang-cl",
+    )
+    ```
+    The reason we have those two ways is because [--incompatible_enable_cc_toolchain_resolution](https://github.com/bazelbuild/bazel/issues/7260) flag.
+
 ### Build Java
 
 There's no setup necessary.
diff --git a/src/test/py/bazel/bazel_windows_cpp_test.py b/src/test/py/bazel/bazel_windows_cpp_test.py
index 274779f..f28c615 100644
--- a/src/test/py/bazel/bazel_windows_cpp_test.py
+++ b/src/test/py/bazel/bazel_windows_cpp_test.py
@@ -592,6 +592,63 @@
     self.AssertExitCode(exit_code, 1, stderr)
     self.assertIn('this_is_an_error', ''.join(stdout))
 
+  def testBuildWithClangClByCompilerFlag(self):
+    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        ')',
+    ])
+    self.ScratchFile('main.cc', [
+        'int main() {',
+        '  return 0;',
+        '}',
+    ])
+    exit_code, _, stderr = self.RunBazel([
+        'build', '-s', '--compiler=clang-cl',
+        '--incompatible_enable_cc_toolchain_resolution=false', '//:main'
+    ])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('clang-cl.exe', ''.join(stderr))
+
+  def testBuildWithClangClByToolchainResolution(self):
+    self.CreateWorkspaceWithDefaultRepos('WORKSPACE', [
+        'register_execution_platforms(',
+        '  ":windows_clang"',
+        ')',
+        '',
+        'register_toolchains(',
+        '  "@local_config_cc//:cc-toolchain-x64_windows-clang-cl",',
+        ')',
+    ])
+    self.ScratchFile('BUILD', [
+        'platform(',
+        '  name = "windows_clang",',
+        '  constraint_values = [',
+        '    "@platforms//cpu:x86_64",',
+        '    "@platforms//os:windows",',
+        '    "@bazel_tools//tools/cpp:clang-cl",',
+        '  ]',
+        ')',
+        '',
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        ')',
+    ])
+    self.ScratchFile('main.cc', [
+        'int main() {',
+        '  return 0;',
+        '}',
+    ])
+    exit_code, _, stderr = self.RunBazel([
+        'build', '-s', '--incompatible_enable_cc_toolchain_resolution=true',
+        '//:main'
+    ])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('clang-cl.exe', ''.join(stderr))
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py
index ca6f792..bbb2184 100644
--- a/src/test/py/bazel/test_base.py
+++ b/src/test/py/bazel/test_base.py
@@ -115,12 +115,12 @@
         actual_exit_code, lambda x: x != not_expected_exit_code,
         '(against expectations)', stderr_lines, stdout_lines)
 
-  def CreateWorkspaceWithDefaultRepos(self, path):
+  def CreateWorkspaceWithDefaultRepos(self, path, lines=None):
     rule_definition = [
         'load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")'
     ]
     rule_definition.extend(self.GetDefaultRepoRules())
-    self.ScratchFile(path, rule_definition)
+    self.ScratchFile(path, rule_definition + (lines if lines else []))
 
   def GetDefaultRepoRules(self):
     return self.GetCcRulesRepoRule()
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index 8222d23..d7cc616 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -41,6 +41,11 @@
 )
 
 constraint_value(
+    name = "clang-cl",
+    constraint_setting = ":cc_compiler",
+)
+
+constraint_value(
     name = "mingw",
     constraint_setting = ":cc_compiler",
 )
diff --git a/tools/cpp/BUILD.windows.tpl b/tools/cpp/BUILD.windows.tpl
index 18df650..be903f9 100644
--- a/tools/cpp/BUILD.windows.tpl
+++ b/tools/cpp/BUILD.windows.tpl
@@ -35,6 +35,7 @@
         "x64_windows|msvc-cl": ":cc-compiler-x64_windows",
         "x64_windows|msys-gcc": ":cc-compiler-x64_windows_msys",
         "x64_windows|mingw-gcc": ":cc-compiler-x64_windows_mingw",
+        "x64_windows|clang-cl": ":cc-compiler-x64_windows-clang-cl",
         "x64_windows_msys": ":cc-compiler-x64_windows_msys",
         "x64_windows": ":cc-compiler-x64_windows",
         "armeabi-v7a": ":cc-compiler-armeabi-v7a",
@@ -179,6 +180,7 @@
         "objdump": "wrapper/bin/msvc_nop.bat",
         "strip": "wrapper/bin/msvc_nop.bat",
     },
+    default_link_flags = ["/MACHINE:X64"],
     dbg_mode_debug_flag = "%{dbg_mode_debug_flag}",
     fastbuild_mode_debug_flag = "%{fastbuild_mode_debug_flag}",
 )
@@ -198,6 +200,72 @@
 )
 
 cc_toolchain(
+    name = "cc-compiler-x64_windows-clang-cl",
+    toolchain_identifier = "clang_cl_x64",
+    toolchain_config = ":clang_cl_x64",
+    all_files = ":empty",
+    ar_files = ":empty",
+    as_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 1,
+)
+
+cc_toolchain_config(
+    name = "clang_cl_x64",
+    cpu = "x64_windows",
+    compiler = "clang-cl",
+    host_system_name = "local",
+    target_system_name = "local",
+    target_libc = "msvcrt",
+    abi_version = "local",
+    abi_libc_version = "local",
+    toolchain_identifier = "clang_cl_x64",
+    msvc_env_tmp = "%{clang_cl_env_tmp}",
+    msvc_env_path = "%{clang_cl_env_path}",
+    msvc_env_include = "%{clang_cl_env_include}",
+    msvc_env_lib = "%{clang_cl_env_lib}",
+    msvc_cl_path = "%{clang_cl_cl_path}",
+    msvc_ml_path = "%{clang_cl_ml_path}",
+    msvc_link_path = "%{clang_cl_link_path}",
+    msvc_lib_path = "%{clang_cl_lib_path}",
+    cxx_builtin_include_directories = [%{clang_cl_cxx_builtin_include_directories}],
+    tool_paths = {
+        "ar": "%{clang_cl_lib_path}",
+        "ml": "%{clang_cl_ml_path}",
+        "cpp": "%{clang_cl_cl_path}",
+        "gcc": "%{clang_cl_cl_path}",
+        "gcov": "wrapper/bin/msvc_nop.bat",
+        "ld": "%{clang_cl_link_path}",
+        "nm": "wrapper/bin/msvc_nop.bat",
+        "objcopy": "wrapper/bin/msvc_nop.bat",
+        "objdump": "wrapper/bin/msvc_nop.bat",
+        "strip": "wrapper/bin/msvc_nop.bat",
+    },
+    default_link_flags = ["/MACHINE:X64", "/DEFAULTLIB:clang_rt.builtins-x86_64.lib"],
+    dbg_mode_debug_flag = "%{clang_cl_dbg_mode_debug_flag}",
+    fastbuild_mode_debug_flag = "%{clang_cl_fastbuild_mode_debug_flag}",
+)
+
+toolchain(
+    name = "cc-toolchain-x64_windows-clang-cl",
+    exec_compatible_with = [
+        "@platforms//cpu:x86_64",
+        "@platforms//os:windows",
+        "@bazel_tools//tools/cpp:clang-cl",
+    ],
+    target_compatible_with = [
+        "@platforms//cpu:x86_64",
+        "@platforms//os:windows",
+    ],
+    toolchain = ":cc-compiler-x64_windows-clang-cl",
+    toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+cc_toolchain(
     name = "cc-compiler-armeabi-v7a",
     toolchain_identifier = "stub_armeabi-v7a",
     toolchain_config = ":stub_armeabi-v7a",
diff --git a/tools/cpp/clang_installation_error.bat.tpl b/tools/cpp/clang_installation_error.bat.tpl
new file mode 100644
index 0000000..bec44eb
--- /dev/null
+++ b/tools/cpp/clang_installation_error.bat.tpl
@@ -0,0 +1,24 @@
+:: Copyright 2019 The Bazel Authors. All rights reserved.
+::
+:: Licensed under the Apache License, Version 2.0 (the "License");
+:: you may not use this file except in compliance with the License.
+:: You may obtain a copy of the License at
+::
+::    http://www.apache.org/licenses/LICENSE-2.0
+::
+:: Unless required by applicable law or agreed to in writing, software
+:: distributed under the License is distributed on an "AS IS" BASIS,
+:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+:: See the License for the specific language governing permissions and
+:: limitations under the License.
+
+@echo OFF
+
+echo. 1>&2
+echo The target you are compiling requires the Clang compiler. 1>&2
+echo Bazel couldn't find a valid Clang installation on your machine. 1>&2
+%{clang_error_message}
+echo Please check your installation following https://docs.bazel.build/versions/master/windows.html#using 1>&2
+echo. 1>&2
+
+exit /b 1
diff --git a/tools/cpp/windows_cc_configure.bzl b/tools/cpp/windows_cc_configure.bzl
index 8cd9771..2b6e690 100644
--- a/tools/cpp/windows_cc_configure.bzl
+++ b/tools/cpp/windows_cc_configure.bzl
@@ -455,6 +455,15 @@
     """Returns True if USE_CLANG_CL is set to 1."""
     return repository_ctx.os.environ.get("USE_CLANG_CL", default = "0") == "1"
 
+def _find_missing_llvm_tools(repository_ctx, llvm_path):
+    """Check if any required tool is missing under given LLVM path."""
+    missing_tools = []
+    for tool in ["clang-cl.exe", "lld-link.exe", "llvm-lib.exe"]:
+        if not find_llvm_tool(repository_ctx, llvm_path, tool):
+            missing_tools.append(tool)
+
+    return missing_tools
+
 def _get_clang_version(repository_ctx, clang_cl):
     result = repository_ctx.execute([clang_cl, "-v"])
     if result.return_code != 0:
@@ -575,6 +584,77 @@
     }
     return msvc_vars
 
+def _get_clang_cl_vars(repository_ctx, paths, msvc_vars):
+    """Get the variables we need to populate the clang-cl toolchains."""
+    llvm_path = find_llvm_path(repository_ctx)
+    error_script = None
+    if msvc_vars["%{msvc_cl_path}"] == "vc_installation_error.bat":
+        error_script = "vc_installation_error.bat"
+    elif not llvm_path:
+        repository_ctx.template(
+            "clang_installation_error.bat",
+            paths["@bazel_tools//tools/cpp:clang_installation_error.bat.tpl"],
+            {"%{clang_error_message}": ""},
+        )
+        error_script = "clang_installation_error.bat"
+    else:
+        missing_tools = _find_missing_llvm_tools(repository_ctx, llvm_path)
+        if missing_tools:
+            message = "\r\n".join([
+                "echo. 1>&2",
+                "echo LLVM/Clang seems to be installed at %s 1>&2" % llvm_path,
+                "echo But Bazel can't find the following tools: 1>&2",
+                "echo     %s 1>&2" % ", ".join(missing_tools),
+                "echo. 1>&2",
+            ])
+            repository_ctx.template(
+                "clang_installation_error.bat",
+                paths["@bazel_tools//tools/cpp:clang_installation_error.bat.tpl"],
+                {"%{clang_error_message}": message},
+            )
+            error_script = "clang_installation_error.bat"
+
+    if error_script:
+        clang_cl_vars = {
+            "%{clang_cl_env_tmp}": "clang_cl_not_found",
+            "%{clang_cl_env_path}": "clang_cl_not_found",
+            "%{clang_cl_env_include}": "clang_cl_not_found",
+            "%{clang_cl_env_lib}": "clang_cl_not_found",
+            "%{clang_cl_cl_path}": error_script,
+            "%{clang_cl_link_path}": error_script,
+            "%{clang_cl_lib_path}": error_script,
+            "%{clang_cl_ml_path}": error_script,
+            "%{clang_cl_dbg_mode_debug_flag}": "/DEBUG",
+            "%{clang_cl_fastbuild_mode_debug_flag}": "/DEBUG",
+            "%{clang_cl_cxx_builtin_include_directories}": "",
+        }
+        return clang_cl_vars
+
+    clang_cl_path = find_llvm_tool(repository_ctx, llvm_path, "clang-cl.exe")
+    lld_link_path = find_llvm_tool(repository_ctx, llvm_path, "lld-link.exe")
+    llvm_lib_path = find_llvm_tool(repository_ctx, llvm_path, "llvm-lib.exe")
+
+    clang_version = _get_clang_version(repository_ctx, clang_cl_path)
+    clang_dir = llvm_path + "\\lib\\clang\\" + clang_version
+    clang_include_path = (clang_dir + "\\include").replace("\\", "\\\\")
+    clang_lib_path = (clang_dir + "\\lib\\windows").replace("\\", "\\\\")
+
+    clang_cl_vars = {
+        "%{clang_cl_env_tmp}": msvc_vars["%{msvc_env_tmp}"],
+        "%{clang_cl_env_path}": msvc_vars["%{msvc_env_path}"],
+        "%{clang_cl_env_include}": msvc_vars["%{msvc_env_include}"] + ";" + clang_include_path,
+        "%{clang_cl_env_lib}": msvc_vars["%{msvc_env_lib}"] + ";" + clang_lib_path,
+        "%{clang_cl_cxx_builtin_include_directories}": msvc_vars["%{msvc_cxx_builtin_include_directories}"] + (",\n        \"%s\"" % clang_include_path),
+        "%{clang_cl_cl_path}": clang_cl_path,
+        "%{clang_cl_link_path}": lld_link_path,
+        "%{clang_cl_lib_path}": llvm_lib_path,
+        "%{clang_cl_ml_path}": msvc_vars["%{msvc_ml_path}"],
+        # LLVM's lld-link.exe doesn't support /DEBUG:FASTLINK.
+        "%{clang_cl_dbg_mode_debug_flag}": "/DEBUG",
+        "%{clang_cl_fastbuild_mode_debug_flag}": "/DEBUG",
+    }
+    return clang_cl_vars
+
 def configure_windows_toolchain(repository_ctx):
     """Configure C++ toolchain on Windows."""
     paths = resolve_labels(repository_ctx, [
@@ -583,6 +663,7 @@
         "@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl",
         "@bazel_tools//tools/cpp:vc_installation_error.bat.tpl",
         "@bazel_tools//tools/cpp:msys_gcc_installation_error.bat",
+        "@bazel_tools//tools/cpp:clang_installation_error.bat.tpl",
     ])
 
     repository_ctx.symlink(
@@ -599,7 +680,9 @@
     )
 
     template_vars = dict()
-    template_vars.update(_get_msvc_vars(repository_ctx, paths))
+    msvc_vars = _get_msvc_vars(repository_ctx, paths)
+    template_vars.update(msvc_vars)
+    template_vars.update(_get_clang_cl_vars(repository_ctx, paths, msvc_vars))
     template_vars.update(_get_msys_mingw_vars(repository_ctx))
 
     repository_ctx.template(
diff --git a/tools/cpp/windows_cc_toolchain_config.bzl b/tools/cpp/windows_cc_toolchain_config.bzl
index e2aa853..30571b6 100644
--- a/tools/cpp/windows_cc_toolchain_config.bzl
+++ b/tools/cpp/windows_cc_toolchain_config.bzl
@@ -79,8 +79,11 @@
     ACTION_NAMES.cpp_link_nodeps_dynamic_library,
 ]
 
+def _use_msvc_toolchain(ctx):
+    return ctx.attr.cpu == "x64_windows" and (ctx.attr.compiler == "msvc-cl" or ctx.attr.compiler == "clang-cl")
+
 def _impl(ctx):
-    if ctx.attr.cpu == "x64_windows" and ctx.attr.compiler == "msvc-cl":
+    if _use_msvc_toolchain(ctx):
         artifact_name_patterns = [
             artifact_name_pattern(
                 category_name = "object_file",
@@ -122,7 +125,7 @@
             ),
         ]
 
-    if ctx.attr.cpu == "x64_windows" and ctx.attr.compiler == "msvc-cl":
+    if _use_msvc_toolchain(ctx):
         cpp_link_nodeps_dynamic_library_action = action_config(
             action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
             implies = [
@@ -261,7 +264,7 @@
     else:
         action_configs = []
 
-    if ctx.attr.cpu == "x64_windows" and ctx.attr.compiler == "msvc-cl":
+    if _use_msvc_toolchain(ctx):
         msvc_link_env_feature = feature(
             name = "msvc_link_env",
             env_sets = [
@@ -299,7 +302,7 @@
                                 "-D__DATE__=\"redacted\"",
                                 "-D__TIMESTAMP__=\"redacted\"",
                                 "-D__TIME__=\"redacted\"",
-                            ],
+                            ] + (["-Wno-builtin-macro-redefined"] if ctx.attr.compiler == "clang-cl" else []),
                         ),
                     ],
                 ),
@@ -508,7 +511,7 @@
             flag_sets = [
                 flag_set(
                     actions = all_link_actions,
-                    flag_groups = [flag_group(flags = ["/MACHINE:X64"])],
+                    flag_groups = [flag_group(flags = ctx.attr.default_link_flags)],
                 ),
             ],
         )
@@ -1322,6 +1325,7 @@
         "abi_libc_version": attr.string(),
         "tool_paths": attr.string_dict(),
         "cxx_builtin_include_directories": attr.string_list(),
+        "default_link_flags": attr.string_list(default = []),
         "msvc_env_tmp": attr.string(default = "msvc_not_found"),
         "msvc_env_path": attr.string(default = "msvc_not_found"),
         "msvc_env_include": attr.string(default = "msvc_not_found"),