xref: /aosp_15_r20/external/bazelbuild-rules_cc/cc/private/toolchain/windows_cc_configure.bzl (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
1# pylint: disable=g-bad-file-header
2# Copyright 2016 The Bazel Authors. All rights reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Configuring the C++ toolchain on Windows."""
16
17load(
18    ":lib_cc_configure.bzl",
19    "auto_configure_fail",
20    "auto_configure_warning",
21    "auto_configure_warning_maybe",
22    "escape_string",
23    "execute",
24    "resolve_labels",
25    "write_builtin_include_directory_paths",
26)
27
28def _get_path_env_var(repository_ctx, name):
29    """Returns a path from an environment variable.
30
31    Removes quotes, replaces '/' with '\', and strips trailing '\'s."""
32    if name in repository_ctx.os.environ:
33        value = repository_ctx.os.environ[name]
34        if value[0] == "\"":
35            if len(value) == 1 or value[-1] != "\"":
36                auto_configure_fail("'%s' environment variable has no trailing quote" % name)
37            value = value[1:-1]
38        if "/" in value:
39            value = value.replace("/", "\\")
40        if value[-1] == "\\":
41            value = value.rstrip("\\")
42        return value
43    else:
44        return None
45
46def _get_temp_env(repository_ctx):
47    """Returns the value of TMP, or TEMP, or if both undefined then C:\\Windows."""
48    tmp = _get_path_env_var(repository_ctx, "TMP")
49    if not tmp:
50        tmp = _get_path_env_var(repository_ctx, "TEMP")
51    if not tmp:
52        tmp = "C:\\Windows\\Temp"
53        auto_configure_warning(
54            "neither 'TMP' nor 'TEMP' environment variables are set, using '%s' as default" % tmp,
55        )
56    return tmp
57
58def _get_escaped_windows_msys_starlark_content(repository_ctx, use_mingw = False):
59    """Return the content of msys cc toolchain rule."""
60    msys_root = ""
61    bazel_sh = _get_path_env_var(repository_ctx, "BAZEL_SH")
62    if bazel_sh:
63        bazel_sh = bazel_sh.replace("\\", "/").lower()
64        tokens = bazel_sh.rsplit("/", 1)
65        if tokens[0].endswith("/usr/bin"):
66            msys_root = tokens[0][:len(tokens[0]) - len("usr/bin")]
67        elif tokens[0].endswith("/bin"):
68            msys_root = tokens[0][:len(tokens[0]) - len("bin")]
69
70    prefix = "mingw64" if use_mingw else "usr"
71    tool_path_prefix = escape_string(msys_root) + prefix
72    tool_bin_path = tool_path_prefix + "/bin"
73    tool_path = {}
74
75    for tool in ["ar", "compat-ld", "cpp", "dwp", "gcc", "gcov", "ld", "nm", "objcopy", "objdump", "strip"]:
76        if msys_root:
77            tool_path[tool] = tool_bin_path + "/" + tool
78        else:
79            tool_path[tool] = "msys_gcc_installation_error.bat"
80    tool_paths = ",\n        ".join(['"%s": "%s"' % (k, v) for k, v in tool_path.items()])
81    include_directories = ('        "%s/",\n        ' % tool_path_prefix) if msys_root else ""
82    return tool_paths, tool_bin_path, include_directories
83
84def _get_system_root(repository_ctx):
85    """Get System root path on Windows, default is C:\\Windows. Doesn't %-escape the result."""
86    systemroot = _get_path_env_var(repository_ctx, "SYSTEMROOT")
87    if not systemroot:
88        systemroot = "C:\\Windows"
89        auto_configure_warning_maybe(
90            repository_ctx,
91            "SYSTEMROOT is not set, using default SYSTEMROOT=C:\\Windows",
92        )
93    return escape_string(systemroot)
94
95def _add_system_root(repository_ctx, env):
96    """Running VCVARSALL.BAT and VCVARSQUERYREGISTRY.BAT need %SYSTEMROOT%\\\\system32 in PATH."""
97    if "PATH" not in env:
98        env["PATH"] = ""
99    env["PATH"] = env["PATH"] + ";" + _get_system_root(repository_ctx) + "\\system32"
100    return env
101
102def _find_vc_path(repository_ctx):
103    """Find Visual C++ build tools install path. Doesn't %-escape the result."""
104
105    # 1. Check if BAZEL_VC or BAZEL_VS is already set by user.
106    bazel_vc = _get_path_env_var(repository_ctx, "BAZEL_VC")
107    if bazel_vc:
108        if repository_ctx.path(bazel_vc).exists:
109            return bazel_vc
110        else:
111            auto_configure_warning_maybe(
112                repository_ctx,
113                "%BAZEL_VC% is set to non-existent path, ignoring.",
114            )
115
116    bazel_vs = _get_path_env_var(repository_ctx, "BAZEL_VS")
117    if bazel_vs:
118        if repository_ctx.path(bazel_vs).exists:
119            bazel_vc = bazel_vs + "\\VC"
120            if repository_ctx.path(bazel_vc).exists:
121                return bazel_vc
122            else:
123                auto_configure_warning_maybe(
124                    repository_ctx,
125                    "No 'VC' directory found under %BAZEL_VS%, ignoring.",
126                )
127        else:
128            auto_configure_warning_maybe(
129                repository_ctx,
130                "%BAZEL_VS% is set to non-existent path, ignoring.",
131            )
132
133    auto_configure_warning_maybe(
134        repository_ctx,
135        "Neither %BAZEL_VC% nor %BAZEL_VS% are set, start looking for the latest Visual C++" +
136        " installed.",
137    )
138
139    # 2. Check if VS%VS_VERSION%COMNTOOLS is set, if true then try to find and use
140    # vcvarsqueryregistry.bat / VsDevCmd.bat to detect VC++.
141    auto_configure_warning_maybe(repository_ctx, "Looking for VS%VERSION%COMNTOOLS environment variables, " +
142                                                 "eg. VS140COMNTOOLS")
143    for vscommontools_env, script in [
144        ("VS160COMNTOOLS", "VsDevCmd.bat"),
145        ("VS150COMNTOOLS", "VsDevCmd.bat"),
146        ("VS140COMNTOOLS", "vcvarsqueryregistry.bat"),
147        ("VS120COMNTOOLS", "vcvarsqueryregistry.bat"),
148        ("VS110COMNTOOLS", "vcvarsqueryregistry.bat"),
149        ("VS100COMNTOOLS", "vcvarsqueryregistry.bat"),
150        ("VS90COMNTOOLS", "vcvarsqueryregistry.bat"),
151    ]:
152        if vscommontools_env not in repository_ctx.os.environ:
153            continue
154        script = _get_path_env_var(repository_ctx, vscommontools_env) + "\\" + script
155        if not repository_ctx.path(script).exists:
156            continue
157        repository_ctx.file(
158            "get_vc_dir.bat",
159            "@echo off\n" +
160            "call \"" + script + "\"\n" +
161            "echo %VCINSTALLDIR%",
162            True,
163        )
164        env = _add_system_root(repository_ctx, repository_ctx.os.environ)
165        vc_dir = execute(repository_ctx, ["./get_vc_dir.bat"], environment = env)
166
167        auto_configure_warning_maybe(repository_ctx, "Visual C++ build tools found at %s" % vc_dir)
168        return vc_dir
169
170    # 3. User might have purged all environment variables. If so, look for Visual C++ in registry.
171    # Works for Visual Studio 2017 and older. (Does not work for Visual Studio 2019 Preview.)
172    # TODO(laszlocsomor): check if "16.0" also has this registry key, after VS 2019 is released.
173    auto_configure_warning_maybe(repository_ctx, "Looking for Visual C++ through registry")
174    reg_binary = _get_system_root(repository_ctx) + "\\system32\\reg.exe"
175    vc_dir = None
176    for key, suffix in (("VC7", ""), ("VS7", "\\VC")):
177        for version in ["15.0", "14.0", "12.0", "11.0", "10.0", "9.0", "8.0"]:
178            if vc_dir:
179                break
180            result = repository_ctx.execute([reg_binary, "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\" + key, "/v", version])
181            auto_configure_warning_maybe(repository_ctx, "registry query result for VC %s:\n\nSTDOUT(start)\n%s\nSTDOUT(end)\nSTDERR(start):\n%s\nSTDERR(end)\n" %
182                                                         (version, result.stdout, result.stderr))
183            if not result.stderr:
184                for line in result.stdout.split("\n"):
185                    line = line.strip()
186                    if line.startswith(version) and line.find("REG_SZ") != -1:
187                        vc_dir = line[line.find("REG_SZ") + len("REG_SZ"):].strip() + suffix
188    if vc_dir:
189        auto_configure_warning_maybe(repository_ctx, "Visual C++ build tools found at %s" % vc_dir)
190        return vc_dir
191
192    # 4. Check default directories for VC installation
193    auto_configure_warning_maybe(repository_ctx, "Looking for default Visual C++ installation directory")
194    program_files_dir = _get_path_env_var(repository_ctx, "PROGRAMFILES(X86)")
195    if not program_files_dir:
196        program_files_dir = "C:\\Program Files (x86)"
197        auto_configure_warning_maybe(
198            repository_ctx,
199            "'PROGRAMFILES(X86)' environment variable is not set, using '%s' as default" % program_files_dir,
200        )
201    for path in [
202        "Microsoft Visual Studio\\2019\\Preview\\VC",
203        "Microsoft Visual Studio\\2019\\BuildTools\\VC",
204        "Microsoft Visual Studio\\2019\\Community\\VC",
205        "Microsoft Visual Studio\\2019\\Professional\\VC",
206        "Microsoft Visual Studio\\2019\\Enterprise\\VC",
207        "Microsoft Visual Studio\\2017\\BuildTools\\VC",
208        "Microsoft Visual Studio\\2017\\Community\\VC",
209        "Microsoft Visual Studio\\2017\\Professional\\VC",
210        "Microsoft Visual Studio\\2017\\Enterprise\\VC",
211        "Microsoft Visual Studio 14.0\\VC",
212    ]:
213        path = program_files_dir + "\\" + path
214        if repository_ctx.path(path).exists:
215            vc_dir = path
216            break
217
218    if not vc_dir:
219        auto_configure_warning_maybe(repository_ctx, "Visual C++ build tools not found.")
220        return None
221    auto_configure_warning_maybe(repository_ctx, "Visual C++ build tools found at %s" % vc_dir)
222    return vc_dir
223
224def _is_vs_2017_or_2019(vc_path):
225    """Check if the installed VS version is Visual Studio 2017."""
226
227    # In VS 2017 and 2019, the location of VC is like:
228    # C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\
229    # In VS 2015 or older version, it is like:
230    # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\
231    return vc_path.find("2017") != -1 or vc_path.find("2019") != -1
232
233def _find_vcvars_bat_script(repository_ctx, vc_path):
234    """Find batch script to set up environment variables for VC. Doesn't %-escape the result."""
235    if _is_vs_2017_or_2019(vc_path):
236        vcvars_script = vc_path + "\\Auxiliary\\Build\\VCVARSALL.BAT"
237    else:
238        vcvars_script = vc_path + "\\VCVARSALL.BAT"
239
240    if not repository_ctx.path(vcvars_script).exists:
241        return None
242
243    return vcvars_script
244
245def _is_support_vcvars_ver(vc_full_version):
246    """-vcvars_ver option is supported from version 14.11.25503 (VS 2017 version 15.3)."""
247    version = [int(i) for i in vc_full_version.split(".")]
248    min_version = [14, 11, 25503]
249    return version >= min_version
250
251def _is_support_winsdk_selection(repository_ctx, vc_path):
252    """Windows SDK selection is supported with VC 2017 / 2019 or with full VS 2015 installation."""
253    if _is_vs_2017_or_2019(vc_path):
254        return True
255
256    # By checking the source code of VCVARSALL.BAT in VC 2015, we know that
257    # when devenv.exe or wdexpress.exe exists, VCVARSALL.BAT supports Windows SDK selection.
258    vc_common_ide = repository_ctx.path(vc_path).dirname.get_child("Common7").get_child("IDE")
259    for tool in ["devenv.exe", "wdexpress.exe"]:
260        if vc_common_ide.get_child(tool).exists:
261            return True
262    return False
263
264def setup_vc_env_vars(repository_ctx, vc_path, envvars = [], allow_empty = False, escape = True):
265    """Get environment variables set by VCVARSALL.BAT script. Doesn't %-escape the result!
266
267    Args:
268        repository_ctx: the repository_ctx object
269        vc_path: Visual C++ root directory
270        envvars: list of envvars to retrieve; default is ["PATH", "INCLUDE", "LIB", "WINDOWSSDKDIR"]
271        allow_empty: allow unset envvars; if False then report errors for those
272        escape: if True, escape "\" as "\\" and "%" as "%%" in the envvar values
273
274    Returns:
275        dictionary of the envvars
276    """
277    if not envvars:
278        envvars = ["PATH", "INCLUDE", "LIB", "WINDOWSSDKDIR"]
279
280    vcvars_script = _find_vcvars_bat_script(repository_ctx, vc_path)
281    if not vcvars_script:
282        auto_configure_fail("Cannot find VCVARSALL.BAT script under %s" % vc_path)
283
284    # Getting Windows SDK version set by user.
285    # Only supports VC 2017 & 2019 and VC 2015 with full VS installation.
286    winsdk_version = _get_winsdk_full_version(repository_ctx)
287    if winsdk_version and not _is_support_winsdk_selection(repository_ctx, vc_path):
288        auto_configure_warning(("BAZEL_WINSDK_FULL_VERSION=%s is ignored, " +
289                                "because standalone Visual C++ Build Tools 2015 doesn't support specifying Windows " +
290                                "SDK version, please install the full VS 2015 or use VC 2017/2019.") % winsdk_version)
291        winsdk_version = ""
292
293    # Get VC version set by user. Only supports VC 2017 & 2019.
294    vcvars_ver = ""
295    if _is_vs_2017_or_2019(vc_path):
296        full_version = _get_vc_full_version(repository_ctx, vc_path)
297
298        # Because VCVARSALL.BAT is from the latest VC installed, so we check if the latest
299        # version supports -vcvars_ver or not.
300        if _is_support_vcvars_ver(_get_latest_subversion(repository_ctx, vc_path)):
301            vcvars_ver = "-vcvars_ver=" + full_version
302
303    cmd = "\"%s\" amd64 %s %s" % (vcvars_script, winsdk_version, vcvars_ver)
304    print_envvars = ",".join(["{k}=%{k}%".format(k = k) for k in envvars])
305    repository_ctx.file(
306        "get_env.bat",
307        "@echo off\n" +
308        ("call %s > NUL \n" % cmd) + ("echo %s \n" % print_envvars),
309        True,
310    )
311    env = _add_system_root(repository_ctx, {k: "" for k in envvars})
312    envs = execute(repository_ctx, ["./get_env.bat"], environment = env).split(",")
313    env_map = {}
314    for env in envs:
315        key, value = env.split("=", 1)
316        env_map[key] = escape_string(value.replace("\\", "\\\\")) if escape else value
317    if not allow_empty:
318        _check_env_vars(env_map, cmd, expected = envvars)
319    return env_map
320
321def _check_env_vars(env_map, cmd, expected):
322    for env in expected:
323        if not env_map.get(env):
324            auto_configure_fail(
325                "Setting up VC environment variables failed, %s is not set by the following command:\n    %s" % (env, cmd),
326            )
327
328def _get_latest_subversion(repository_ctx, vc_path):
329    """Get the latest subversion of a VS 2017/2019 installation.
330
331    For VS 2017 & 2019, there could be multiple versions of VC build tools.
332    The directories are like:
333      <vc_path>\\Tools\\MSVC\\14.10.24930\\bin\\HostX64\\x64
334      <vc_path>\\Tools\\MSVC\\14.16.27023\\bin\\HostX64\\x64
335    This function should return 14.16.27023 in this case."""
336    versions = [path.basename for path in repository_ctx.path(vc_path + "\\Tools\\MSVC").readdir()]
337    if len(versions) < 1:
338        auto_configure_warning_maybe(repository_ctx, "Cannot find any VC installation under BAZEL_VC(%s)" % vc_path)
339        return None
340
341    # Parse the version string into integers, then sort the integers to prevent textual sorting.
342    version_list = []
343    for version in versions:
344        parts = [int(i) for i in version.split(".")]
345        version_list.append((parts, version))
346
347    version_list = sorted(version_list)
348    latest_version = version_list[-1][1]
349
350    auto_configure_warning_maybe(repository_ctx, "Found the following VC verisons:\n%s\n\nChoosing the latest version = %s" % ("\n".join(versions), latest_version))
351    return latest_version
352
353def _get_vc_full_version(repository_ctx, vc_path):
354    """Return the value of BAZEL_VC_FULL_VERSION if defined, otherwise the latest version."""
355    if "BAZEL_VC_FULL_VERSION" in repository_ctx.os.environ:
356        return repository_ctx.os.environ["BAZEL_VC_FULL_VERSION"]
357    return _get_latest_subversion(repository_ctx, vc_path)
358
359def _get_winsdk_full_version(repository_ctx):
360    """Return the value of BAZEL_WINSDK_FULL_VERSION if defined, otherwise an empty string."""
361    return repository_ctx.os.environ.get("BAZEL_WINSDK_FULL_VERSION", default = "")
362
363def _find_msvc_tool(repository_ctx, vc_path, tool):
364    """Find the exact path of a specific build tool in MSVC. Doesn't %-escape the result."""
365    tool_path = None
366    if _is_vs_2017_or_2019(vc_path):
367        full_version = _get_vc_full_version(repository_ctx, vc_path)
368        if full_version:
369            tool_path = "%s\\Tools\\MSVC\\%s\\bin\\HostX64\\x64\\%s" % (vc_path, full_version, tool)
370    else:
371        # For VS 2015 and older version, the tools are under:
372        # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64
373        tool_path = vc_path + "\\bin\\amd64\\" + tool
374
375    if not tool_path or not repository_ctx.path(tool_path).exists:
376        return None
377
378    return tool_path.replace("\\", "/")
379
380def _find_missing_vc_tools(repository_ctx, vc_path):
381    """Check if any required tool is missing under given VC path."""
382    missing_tools = []
383    if not _find_vcvars_bat_script(repository_ctx, vc_path):
384        missing_tools.append("VCVARSALL.BAT")
385
386    for tool in ["cl.exe", "link.exe", "lib.exe", "ml64.exe"]:
387        if not _find_msvc_tool(repository_ctx, vc_path, tool):
388            missing_tools.append(tool)
389
390    return missing_tools
391
392def _is_support_debug_fastlink(repository_ctx, linker):
393    """Run linker alone to see if it supports /DEBUG:FASTLINK."""
394    if _use_clang_cl(repository_ctx):
395        # LLVM's lld-link.exe doesn't support /DEBUG:FASTLINK.
396        return False
397    result = execute(repository_ctx, [linker], expect_failure = True)
398    return result.find("/DEBUG[:{FASTLINK|FULL|NONE}]") != -1
399
400def _find_llvm_path(repository_ctx):
401    """Find LLVM install path."""
402
403    # 1. Check if BAZEL_LLVM is already set by user.
404    bazel_llvm = _get_path_env_var(repository_ctx, "BAZEL_LLVM")
405    if bazel_llvm:
406        return bazel_llvm
407
408    auto_configure_warning_maybe(repository_ctx, "'BAZEL_LLVM' is not set, " +
409                                                 "start looking for LLVM installation on machine.")
410
411    # 2. Look for LLVM installation through registry.
412    auto_configure_warning_maybe(repository_ctx, "Looking for LLVM installation through registry")
413    reg_binary = _get_system_root(repository_ctx) + "\\system32\\reg.exe"
414    llvm_dir = None
415    result = repository_ctx.execute([reg_binary, "query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\LLVM\\LLVM"])
416    auto_configure_warning_maybe(repository_ctx, "registry query result for LLVM:\n\nSTDOUT(start)\n%s\nSTDOUT(end)\nSTDERR(start):\n%s\nSTDERR(end)\n" %
417                                                 (result.stdout, result.stderr))
418    if not result.stderr:
419        for line in result.stdout.split("\n"):
420            line = line.strip()
421            if line.startswith("(Default)") and line.find("REG_SZ") != -1:
422                llvm_dir = line[line.find("REG_SZ") + len("REG_SZ"):].strip()
423    if llvm_dir:
424        auto_configure_warning_maybe(repository_ctx, "LLVM installation found at %s" % llvm_dir)
425        return llvm_dir
426
427    # 3. Check default directories for LLVM installation
428    auto_configure_warning_maybe(repository_ctx, "Looking for default LLVM installation directory")
429    program_files_dir = _get_path_env_var(repository_ctx, "PROGRAMFILES")
430    if not program_files_dir:
431        program_files_dir = "C:\\Program Files"
432        auto_configure_warning_maybe(
433            repository_ctx,
434            "'PROGRAMFILES' environment variable is not set, using '%s' as default" % program_files_dir,
435        )
436    path = program_files_dir + "\\LLVM"
437    if repository_ctx.path(path).exists:
438        llvm_dir = path
439
440    if not llvm_dir:
441        auto_configure_warning_maybe(repository_ctx, "LLVM installation not found.")
442        return None
443    auto_configure_warning_maybe(repository_ctx, "LLVM installation found at %s" % llvm_dir)
444    return llvm_dir
445
446def _find_llvm_tool(repository_ctx, llvm_path, tool):
447    """Find the exact path of a specific build tool in LLVM. Doesn't %-escape the result."""
448    tool_path = llvm_path + "\\bin\\" + tool
449
450    if not repository_ctx.path(tool_path).exists:
451        return None
452
453    return tool_path.replace("\\", "/")
454
455def _use_clang_cl(repository_ctx):
456    """Returns True if USE_CLANG_CL is set to 1."""
457    return repository_ctx.os.environ.get("USE_CLANG_CL", default = "0") == "1"
458
459def _find_missing_llvm_tools(repository_ctx, llvm_path):
460    """Check if any required tool is missing under given LLVM path."""
461    missing_tools = []
462    for tool in ["clang-cl.exe", "lld-link.exe", "llvm-lib.exe"]:
463        if not _find_llvm_tool(repository_ctx, llvm_path, tool):
464            missing_tools.append(tool)
465
466    return missing_tools
467
468def _get_clang_version(repository_ctx, clang_cl):
469    result = repository_ctx.execute([clang_cl, "-v"])
470    if result.return_code != 0:
471        auto_configure_fail("Failed to get clang version by running \"%s -v\"" % clang_cl)
472
473    # Stderr should look like "clang version X.X.X ..."
474    return result.stderr.splitlines()[0].split(" ")[2]
475
476def _get_msys_mingw_vars(repository_ctx):
477    """Get the variables we need to populate the msys/mingw toolchains."""
478    tool_paths, tool_bin_path, inc_dir_msys = _get_escaped_windows_msys_starlark_content(repository_ctx)
479    tool_paths_mingw, tool_bin_path_mingw, inc_dir_mingw = _get_escaped_windows_msys_starlark_content(repository_ctx, use_mingw = True)
480    write_builtin_include_directory_paths(repository_ctx, "mingw", [inc_dir_mingw], file_suffix = "_mingw")
481    msys_mingw_vars = {
482        "%{cxx_builtin_include_directories}": inc_dir_msys,
483        "%{mingw_cxx_builtin_include_directories}": inc_dir_mingw,
484        "%{mingw_tool_bin_path}": tool_bin_path_mingw,
485        "%{mingw_tool_paths}": tool_paths_mingw,
486        "%{tool_bin_path}": tool_bin_path,
487        "%{tool_paths}": tool_paths,
488    }
489    return msys_mingw_vars
490
491def _get_msvc_vars(repository_ctx, paths):
492    """Get the variables we need to populate the MSVC toolchains."""
493    msvc_vars = dict()
494    vc_path = _find_vc_path(repository_ctx)
495    missing_tools = None
496    if not vc_path:
497        repository_ctx.template(
498            "vc_installation_error.bat",
499            paths["@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"],
500            {"%{vc_error_message}": ""},
501        )
502    else:
503        missing_tools = _find_missing_vc_tools(repository_ctx, vc_path)
504        if missing_tools:
505            message = "\r\n".join([
506                "echo. 1>&2",
507                "echo Visual C++ build tools seems to be installed at %s 1>&2" % vc_path,
508                "echo But Bazel can't find the following tools: 1>&2",
509                "echo     %s 1>&2" % ", ".join(missing_tools),
510                "echo. 1>&2",
511            ])
512            repository_ctx.template(
513                "vc_installation_error.bat",
514                paths["@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"],
515                {"%{vc_error_message}": message},
516            )
517
518    if not vc_path or missing_tools:
519        write_builtin_include_directory_paths(repository_ctx, "msvc", [], file_suffix = "_msvc")
520        msvc_vars = {
521            "%{dbg_mode_debug_flag}": "/DEBUG",
522            "%{fastbuild_mode_debug_flag}": "/DEBUG",
523            "%{msvc_cl_path}": "vc_installation_error.bat",
524            "%{msvc_cxx_builtin_include_directories}": "",
525            "%{msvc_env_include}": "msvc_not_found",
526            "%{msvc_env_lib}": "msvc_not_found",
527            "%{msvc_env_path}": "msvc_not_found",
528            "%{msvc_env_tmp}": "msvc_not_found",
529            "%{msvc_lib_path}": "vc_installation_error.bat",
530            "%{msvc_link_path}": "vc_installation_error.bat",
531            "%{msvc_ml_path}": "vc_installation_error.bat",
532        }
533        return msvc_vars
534
535    env = setup_vc_env_vars(repository_ctx, vc_path)
536    escaped_paths = escape_string(env["PATH"])
537    escaped_include_paths = escape_string(env["INCLUDE"])
538    escaped_lib_paths = escape_string(env["LIB"])
539    escaped_tmp_dir = escape_string(_get_temp_env(repository_ctx).replace("\\", "\\\\"))
540
541    llvm_path = ""
542    if _use_clang_cl(repository_ctx):
543        llvm_path = _find_llvm_path(repository_ctx)
544        if not llvm_path:
545            auto_configure_fail("\nUSE_CLANG_CL is set to 1, but Bazel cannot find Clang installation on your system.\n" +
546                                "Please install Clang via http://releases.llvm.org/download.html\n")
547        cl_path = _find_llvm_tool(repository_ctx, llvm_path, "clang-cl.exe")
548        link_path = _find_llvm_tool(repository_ctx, llvm_path, "lld-link.exe")
549        if not link_path:
550            link_path = _find_msvc_tool(repository_ctx, vc_path, "link.exe")
551        lib_path = _find_llvm_tool(repository_ctx, llvm_path, "llvm-lib.exe")
552        if not lib_path:
553            lib_path = _find_msvc_tool(repository_ctx, vc_path, "lib.exe")
554    else:
555        cl_path = _find_msvc_tool(repository_ctx, vc_path, "cl.exe")
556        link_path = _find_msvc_tool(repository_ctx, vc_path, "link.exe")
557        lib_path = _find_msvc_tool(repository_ctx, vc_path, "lib.exe")
558
559    msvc_ml_path = _find_msvc_tool(repository_ctx, vc_path, "ml64.exe")
560    escaped_cxx_include_directories = []
561
562    for path in escaped_include_paths.split(";"):
563        if path:
564            escaped_cxx_include_directories.append("\"%s\"" % path)
565    if llvm_path:
566        clang_version = _get_clang_version(repository_ctx, cl_path)
567        clang_dir = llvm_path + "\\lib\\clang\\" + clang_version
568        clang_include_path = (clang_dir + "\\include").replace("\\", "\\\\")
569        escaped_cxx_include_directories.append("\"%s\"" % clang_include_path)
570        clang_lib_path = (clang_dir + "\\lib\\windows").replace("\\", "\\\\")
571        escaped_lib_paths = escaped_lib_paths + ";" + clang_lib_path
572
573    support_debug_fastlink = _is_support_debug_fastlink(repository_ctx, link_path)
574
575    write_builtin_include_directory_paths(repository_ctx, "msvc", escaped_cxx_include_directories, file_suffix = "_msvc")
576    msvc_vars = {
577        "%{dbg_mode_debug_flag}": "/DEBUG:FULL" if support_debug_fastlink else "/DEBUG",
578        "%{fastbuild_mode_debug_flag}": "/DEBUG:FASTLINK" if support_debug_fastlink else "/DEBUG",
579        "%{msvc_cl_path}": cl_path,
580        "%{msvc_cxx_builtin_include_directories}": "        " + ",\n        ".join(escaped_cxx_include_directories),
581        "%{msvc_env_include}": escaped_include_paths,
582        "%{msvc_env_lib}": escaped_lib_paths,
583        "%{msvc_env_path}": escaped_paths,
584        "%{msvc_env_tmp}": escaped_tmp_dir,
585        "%{msvc_lib_path}": lib_path,
586        "%{msvc_link_path}": link_path,
587        "%{msvc_ml_path}": msvc_ml_path,
588    }
589    return msvc_vars
590
591def _get_clang_cl_vars(repository_ctx, paths, msvc_vars):
592    """Get the variables we need to populate the clang-cl toolchains."""
593    llvm_path = _find_llvm_path(repository_ctx)
594    error_script = None
595    if msvc_vars["%{msvc_cl_path}"] == "vc_installation_error.bat":
596        error_script = "vc_installation_error.bat"
597    elif not llvm_path:
598        repository_ctx.template(
599            "clang_installation_error.bat",
600            paths["@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"],
601            {"%{clang_error_message}": ""},
602        )
603        error_script = "clang_installation_error.bat"
604    else:
605        missing_tools = _find_missing_llvm_tools(repository_ctx, llvm_path)
606        if missing_tools:
607            message = "\r\n".join([
608                "echo. 1>&2",
609                "echo LLVM/Clang seems to be installed at %s 1>&2" % llvm_path,
610                "echo But Bazel can't find the following tools: 1>&2",
611                "echo     %s 1>&2" % ", ".join(missing_tools),
612                "echo. 1>&2",
613            ])
614            repository_ctx.template(
615                "clang_installation_error.bat",
616                paths["@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"],
617                {"%{clang_error_message}": message},
618            )
619            error_script = "clang_installation_error.bat"
620
621    if error_script:
622        write_builtin_include_directory_paths(repository_ctx, "clang-cl", [], file_suffix = "_clangcl")
623        clang_cl_vars = {
624            "%{clang_cl_cl_path}": error_script,
625            "%{clang_cl_cxx_builtin_include_directories}": "",
626            "%{clang_cl_dbg_mode_debug_flag}": "/DEBUG",
627            "%{clang_cl_env_include}": "clang_cl_not_found",
628            "%{clang_cl_env_lib}": "clang_cl_not_found",
629            "%{clang_cl_env_path}": "clang_cl_not_found",
630            "%{clang_cl_env_tmp}": "clang_cl_not_found",
631            "%{clang_cl_fastbuild_mode_debug_flag}": "/DEBUG",
632            "%{clang_cl_lib_path}": error_script,
633            "%{clang_cl_link_path}": error_script,
634            "%{clang_cl_ml_path}": error_script,
635        }
636        return clang_cl_vars
637
638    clang_cl_path = _find_llvm_tool(repository_ctx, llvm_path, "clang-cl.exe")
639    lld_link_path = _find_llvm_tool(repository_ctx, llvm_path, "lld-link.exe")
640    llvm_lib_path = _find_llvm_tool(repository_ctx, llvm_path, "llvm-lib.exe")
641
642    clang_version = _get_clang_version(repository_ctx, clang_cl_path)
643    clang_dir = llvm_path + "\\lib\\clang\\" + clang_version
644    clang_include_path = (clang_dir + "\\include").replace("\\", "\\\\")
645    clang_lib_path = (clang_dir + "\\lib\\windows").replace("\\", "\\\\")
646
647    clang_cl_include_directories = msvc_vars["%{msvc_cxx_builtin_include_directories}"] + (",\n        \"%s\"" % clang_include_path)
648    write_builtin_include_directory_paths(repository_ctx, "clang-cl", [clang_cl_include_directories], file_suffix = "_clangcl")
649    clang_cl_vars = {
650        "%{clang_cl_cl_path}": clang_cl_path,
651        "%{clang_cl_cxx_builtin_include_directories}": clang_cl_include_directories,
652        # LLVM's lld-link.exe doesn't support /DEBUG:FASTLINK.
653        "%{clang_cl_dbg_mode_debug_flag}": "/DEBUG",
654        "%{clang_cl_env_include}": msvc_vars["%{msvc_env_include}"] + ";" + clang_include_path,
655        "%{clang_cl_env_lib}": msvc_vars["%{msvc_env_lib}"] + ";" + clang_lib_path,
656        "%{clang_cl_env_path}": msvc_vars["%{msvc_env_path}"],
657        "%{clang_cl_env_tmp}": msvc_vars["%{msvc_env_tmp}"],
658        "%{clang_cl_fastbuild_mode_debug_flag}": "/DEBUG",
659        "%{clang_cl_lib_path}": llvm_lib_path,
660        "%{clang_cl_link_path}": lld_link_path,
661        "%{clang_cl_ml_path}": msvc_vars["%{msvc_ml_path}"],
662    }
663    return clang_cl_vars
664
665def configure_windows_toolchain(repository_ctx):
666    """Configure C++ toolchain on Windows.
667
668    Args:
669      repository_ctx: The repository context.
670    """
671    paths = resolve_labels(repository_ctx, [
672        "@rules_cc//cc/private/toolchain:BUILD.windows.tpl",
673        "@rules_cc//cc/private/toolchain:windows_cc_toolchain_config.bzl",
674        "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl",
675        "@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl",
676        "@rules_cc//cc/private/toolchain:msys_gcc_installation_error.bat",
677        "@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl",
678    ])
679
680    repository_ctx.symlink(
681        paths["@rules_cc//cc/private/toolchain:windows_cc_toolchain_config.bzl"],
682        "windows_cc_toolchain_config.bzl",
683    )
684    repository_ctx.symlink(
685        paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"],
686        "armeabi_cc_toolchain_config.bzl",
687    )
688    repository_ctx.symlink(
689        paths["@rules_cc//cc/private/toolchain:msys_gcc_installation_error.bat"],
690        "msys_gcc_installation_error.bat",
691    )
692
693    template_vars = dict()
694    msvc_vars = _get_msvc_vars(repository_ctx, paths)
695    template_vars.update(msvc_vars)
696    template_vars.update(_get_clang_cl_vars(repository_ctx, paths, msvc_vars))
697    template_vars.update(_get_msys_mingw_vars(repository_ctx))
698
699    repository_ctx.template(
700        "BUILD",
701        paths["@rules_cc//cc/private/toolchain:BUILD.windows.tpl"],
702        template_vars,
703    )
704