| """ |
| This file specifies a clang toolchain that can run on a Windows host. |
| |
| See download_windows_toolchain.bzl for more details on the creation of the toolchain. |
| |
| It uses the usr subfolder of the built toolchain as a sysroot |
| |
| It follows the example of: |
| - linux_amd64_toolchain_config.bzl |
| """ |
| |
| # https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl |
| load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") |
| |
| # https://github.com/bazelbuild/bazel/blob/master/tools/cpp/cc_toolchain_config_lib.bzl |
| load( |
| "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", |
| "action_config", |
| "artifact_name_pattern", |
| "feature", |
| "flag_group", |
| "flag_set", |
| "tool", |
| "variable_with_value", |
| "with_feature_set", |
| ) |
| load("@rules_cc//cc/common:cc_common.bzl", "cc_common") |
| |
| # These variables were copied from the generated @clang_windows_amd64//:vars.bzl file. |
| # They are available to be directly used here, but in order to do so, Bazel needs access |
| # to both the Clang and MSVC archives, even when we aren't going to use this toolchain. |
| # Due to the time required to download and extract these archives, we've opted to hard-code |
| # the versions and paths here. |
| MSVC_VERSION = "14.51.36231" |
| MSVC_INCLUDE = "VC/Tools/MSVC/" + MSVC_VERSION + "/include" |
| MSVC_LIB = "VC/Tools/MSVC/" + MSVC_VERSION + "/lib" |
| |
| WIN_SDK_VERSION = "10.0.26100.0" |
| WIN_SDK_INCLUDE = "win_sdk/Include/" + WIN_SDK_VERSION |
| WIN_SDK_LIB = "win_sdk/Lib/" + WIN_SDK_VERSION |
| |
| def _windows_amd64_toolchain_info(ctx): |
| # https://bazel.build/rules/lib/builtins/Label#repo_name |
| clang_repo_name = ctx.attr.clang_windows_amd64.label.repo_name |
| clang_toolchain_path = "external/" + clang_repo_name |
| action_configs = _make_action_configs(clang_toolchain_path) |
| features = [ |
| # These are defined https://github.com/bazelbuild/rules_cc/blob/77eb752fc74b89e96e729ce9777854836fcbd9a2/cc/private/toolchain/windows_cc_toolchain_config.bzl#L468-L476 |
| feature( |
| name = "archive_param_file", |
| enabled = True, |
| ), |
| feature( |
| name = "compiler_param_file", |
| enabled = True, |
| ), |
| feature( |
| name = "linker_param_file", |
| enabled = True, |
| ), |
| # This feature is defined but disabled by rules_rust |
| # https://github.com/bazelbuild/rules_rust/blob/ed321505851d2cc8a5ace048188ef3ba8f7e8d71/rust/private/utils.bzl#L38-L41 |
| # We enable it when compiling C++ code so we can make the toolchain behave differently when |
| # linking C++ code vs linking rust code. The rust toolchain assumes it is talking to a raw |
| # linker but the C++ rules assume they are talking to a compiler driver. Thus, we set the |
| # tool for cpp_link_executable_action (and others) differently depending on if we are in |
| # the rust toolchain vs the C++ toolchain. |
| feature( |
| name = "rules_rust_unsupported_feature", |
| enabled = True, |
| ), |
| ] |
| features += _make_default_flags(clang_toolchain_path) |
| features += _make_diagnostic_flags() |
| |
| # Tells Bazel that our compiler will add .exe etc to the outputs |
| # https://stackoverflow.com/a/63889676 |
| artifact_name_patterns = [ |
| artifact_name_pattern( |
| category_name = "executable", |
| prefix = "", |
| extension = ".exe", |
| ), |
| artifact_name_pattern( |
| category_name = "dynamic_library", |
| prefix = "", |
| extension = ".dll", |
| ), |
| artifact_name_pattern( |
| category_name = "static_library", |
| prefix = "", |
| extension = ".lib", |
| ), |
| ] |
| |
| # https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info |
| # Note, this rule is defined in Java code, not Starlark |
| # https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java |
| return cc_common.create_cc_toolchain_config_info( |
| ctx = ctx, |
| features = features, |
| action_configs = action_configs, |
| # These are required, but do nothing |
| compiler = "", |
| target_cpu = "", |
| target_libc = "", |
| target_system_name = "", |
| toolchain_identifier = "", |
| artifact_name_patterns = artifact_name_patterns, |
| ) |
| |
| provide_windows_amd64_toolchain_config = rule( |
| attrs = { |
| "clang_windows_amd64": attr.label(default = "@clang_windows_amd64//:compile_files"), |
| }, |
| provides = [CcToolchainConfigInfo], |
| implementation = _windows_amd64_toolchain_info, |
| ) |
| |
| def _make_action_configs(clang_toolchain_path): |
| """ |
| This function sets up the tools needed to perform the various compile/link actions. |
| |
| Tools: |
| https://cs.opensource.google/bazel/bazel/+/master:tools/cpp/cc_toolchain_config_lib.bzl;l=435;drc=3b9e6f201a9a3465720aad8712ab7bcdeaf2e5da |
| Action Configs: |
| https://cs.opensource.google/bazel/bazel/+/master:tools/cpp/cc_toolchain_config_lib.bzl;l=488;drc=3b9e6f201a9a3465720aad8712ab7bcdeaf2e5da |
| """ |
| |
| # Clang can run in GCC compatible mode or MSVC compatible mode depending on the file name. |
| # The Bazel rules assume GCC mode for compiling but we have to use MSVC mode for llvm-lib |
| # (which is `lld-link.exe /lib` under the hood). GCC mode for compiling makes it easier to |
| # align copts.bzl (and other custome C++ flags) at the expense of a more complicated toolchain. |
| compile_gcc_tool = tool(path = "../" + clang_toolchain_path + "/bin/clang.exe") |
| archive_msvc_tool = tool(path = "../" + clang_toolchain_path + "/bin/llvm-lib.bat") |
| |
| link_cpp_tool = tool( |
| path = "../" + clang_toolchain_path + "/bin/clang-cl.exe", |
| # "The tool applied to the action will be the first Tool with a feature set that matches the |
| # feature configuration." Bazel assumes it's talking to a C++ "compiler driver" when linking |
| # C++ code (e.g. cl.exe or clang-cl.exe), so we only enable it when the |
| # rules_rust_unsupported_feature is set (see above). |
| with_features = [with_feature_set(features = ["rules_rust_unsupported_feature"])], |
| ) |
| link_raw_tool = tool(path = "../" + clang_toolchain_path + "/bin/lld-link.exe") |
| |
| assemble_action = action_config( |
| action_name = ACTION_NAMES.assemble, |
| tools = [compile_gcc_tool], |
| ) |
| c_compile_action = action_config( |
| action_name = ACTION_NAMES.c_compile, |
| tools = [compile_gcc_tool], |
| ) |
| cpp_compile_action = action_config( |
| action_name = ACTION_NAMES.cpp_compile, |
| tools = [compile_gcc_tool], |
| ) |
| linkstamp_compile_action = action_config( |
| action_name = ACTION_NAMES.linkstamp_compile, |
| tools = [compile_gcc_tool], |
| ) |
| preprocess_assemble_action = action_config( |
| action_name = ACTION_NAMES.preprocess_assemble, |
| tools = [compile_gcc_tool], |
| ) |
| |
| cpp_link_dynamic_library_action = action_config( |
| action_name = ACTION_NAMES.cpp_link_dynamic_library, |
| tools = [link_cpp_tool, link_raw_tool], # C++ mode with fallback for rust |
| ) |
| cpp_link_executable_action = action_config( |
| action_name = ACTION_NAMES.cpp_link_executable, |
| tools = [link_cpp_tool, link_raw_tool], |
| ) |
| cpp_link_nodeps_dynamic_library_action = action_config( |
| action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library, |
| tools = [link_cpp_tool, link_raw_tool], |
| ) |
| |
| # By default, there are no flags or libraries passed to the linker tool, so |
| # we need to specify them. The variables mentioned by expand_if_available are defined |
| # https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables |
| cpp_link_static_library_action = action_config( |
| action_name = ACTION_NAMES.cpp_link_static_library, |
| # We need to use the param file to avoid Windows command line limits. Enable that here |
| # (Bazel still decides when to use it or not). |
| implies = ["archive_param_file"], |
| # The order of these sets matters. We effectively want |
| # archive_msvc_tool /OUT:thing.lib first.obj second.obj third.obj ... |
| # Thus, we put the set that has /OUT *first* and the logic to handle .obj files second. |
| # The third set is for when Bazel decides things have gotten and it puts all those previous |
| # args in a param file and runs |
| # archive_msvc_tool @thing_lib.params |
| flag_sets = [ |
| flag_set( |
| flag_groups = [ |
| # How to read basic flag_groups |
| # (see also https://bazel.build/docs/cc-toolchain-config-reference#flag-groups) |
| # flag_group( |
| # # If Bazel has this variable... |
| # expand_if_available = "SOME_VARIABLE", |
| # # ... here's how to format it (with substitution) |
| # flags = ["/MV:%{SOME_VARIABLE}"], |
| # ), |
| flag_group( |
| # Despite the name, output_execpath just refers to linker output, |
| # e.g. libFoo.lib |
| expand_if_available = "output_execpath", |
| # lld-link.exe in /lib mode expects the MSVC-style /OUT:<file> option |
| flags = ["/OUT:%{output_execpath}"], |
| ), |
| ], |
| ), |
| flag_set( |
| flag_groups = [ |
| flag_group( |
| iterate_over = "libraries_to_link", |
| flag_groups = [ |
| # Pull out only the object files as a list (e.g. SkColor.obj) |
| flag_group( |
| flags = ["%{libraries_to_link.name}"], |
| expand_if_equal = variable_with_value( |
| name = "libraries_to_link.type", |
| value = "object_file", |
| ), |
| ), |
| # Handle nested lists of object files. |
| flag_group( |
| flags = ["%{libraries_to_link.object_files}"], |
| iterate_over = "libraries_to_link.object_files", |
| expand_if_equal = variable_with_value( |
| name = "libraries_to_link.type", |
| value = "object_file_group", |
| ), |
| ), |
| ], |
| expand_if_available = "libraries_to_link", |
| ), |
| ], |
| ), |
| flag_set( |
| flag_groups = [ |
| flag_group( |
| # This should be available because of the archive_param_file feature above. |
| expand_if_available = "linker_param_file", |
| flags = ["@%{linker_param_file}"], |
| ), |
| ], |
| ), |
| ], |
| tools = [archive_msvc_tool], |
| ) |
| |
| action_configs = [ |
| assemble_action, |
| c_compile_action, |
| cpp_compile_action, |
| cpp_link_dynamic_library_action, |
| cpp_link_executable_action, |
| cpp_link_nodeps_dynamic_library_action, |
| cpp_link_static_library_action, |
| linkstamp_compile_action, |
| preprocess_assemble_action, |
| ] |
| return action_configs |
| |
| def _make_default_flags(clang_toolchain): |
| """Here we define the flags for certain actions that are always applied. |
| |
| For any flag that might be conditionally applied, it should be defined in //bazel/copts.bzl. |
| |
| Flags that are set here will be unconditionally applied to everything we compile with |
| this toolchain, even third_party deps. |
| """ |
| full_msvc_include = clang_toolchain + "/" + MSVC_INCLUDE |
| full_msvc_lib = clang_toolchain + "/" + MSVC_LIB |
| full_win_sdk_include = clang_toolchain + "/" + WIN_SDK_INCLUDE |
| full_win_sdk_lib = clang_toolchain + "/" + WIN_SDK_LIB |
| |
| # Note: These values must be kept in sync with those defined in cmake_exporter.go. |
| cxx_compile_includes = flag_set( |
| actions = [ |
| ACTION_NAMES.c_compile, |
| ACTION_NAMES.cpp_compile, |
| ], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| # THIS ORDER MATTERS GREATLY. If these are in the wrong order, the |
| # #include_next directives will fail to find the files, causing a compilation |
| # error (or, without -no-canonical-prefixes, a mysterious case where files |
| # are included with an absolute path and fail the build). |
| "-isystem", |
| clang_toolchain + "/include/clang", |
| "-isystem", |
| clang_toolchain + "/include/clang-c", |
| "-isystem", |
| clang_toolchain + "/include/clang-tidy", |
| "-isystem", |
| clang_toolchain + "/include/lld", |
| "-isystem", |
| clang_toolchain + "/include/lldb", |
| "-isystem", |
| clang_toolchain + "/include/llvm", |
| "-isystem", |
| clang_toolchain + "/include/llvm-c", |
| "-isystem", |
| clang_toolchain + "/lib/clang/23/include", |
| "-isystem", |
| full_win_sdk_include + "/shared", |
| "-isystem", |
| full_win_sdk_include + "/ucrt", |
| "-isystem", |
| full_win_sdk_include + "/um", |
| "-isystem", |
| full_win_sdk_include + "/winrt", |
| "-isystem", |
| full_win_sdk_include + "/cppwinrt", |
| "-isystem", |
| full_msvc_include, |
| # We do not want clang to search in absolute paths for files. This makes |
| # Bazel think we are using an outside resource and fail the compile. |
| "-no-canonical-prefixes", |
| ], |
| ), |
| ], |
| ) |
| |
| cpp_compile_flags = flag_set( |
| actions = [ |
| ACTION_NAMES.cpp_compile, |
| ], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| "-std=c++20", |
| "-D_CRT_SECURE_NO_WARNINGS", # Disables warnings about sscanf() |
| "-D_HAS_EXCEPTIONS=0", # Disables exceptions in MSVC STL |
| "-DWIN32_LEAN_AND_MEAN", # Avoids pollution of global namespace |
| "-DNOMINMAX", # https://stackoverflow.com/a/22744273 |
| ], |
| ), |
| ], |
| ) |
| |
| link_exe_flags_cpp = flag_set( |
| actions = [ |
| ACTION_NAMES.cpp_link_executable, |
| ACTION_NAMES.cpp_link_dynamic_library, |
| ACTION_NAMES.cpp_link_nodeps_dynamic_library, |
| ], |
| with_features = [with_feature_set(features = ["rules_rust_unsupported_feature"])], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| "/MT", # clang in GCC mode implies the static C runtime (/MT) by default |
| "/link", |
| "/subsystem:console", # Make a console window and look for main() |
| "libcpmt.lib", # Standard C++ library |
| "libcmt.lib", # static core C runtime |
| "libvcruntime.lib", # Visual C++ |
| "libucrt.lib", # Universal C Runtime |
| "kernel32.lib", # Win32 base API |
| "ntdll.lib", # native NT system services. |
| "/LIBPATH:" + full_msvc_lib + "/x64", |
| "/LIBPATH:" + full_win_sdk_lib + "/ucrt/x64", |
| "/LIBPATH:" + full_win_sdk_lib + "/um/x64", |
| ], |
| ), |
| ], |
| ) |
| |
| link_exe_flags_rust = flag_set( |
| actions = [ |
| ACTION_NAMES.cpp_link_executable, |
| ACTION_NAMES.cpp_link_dynamic_library, |
| ACTION_NAMES.cpp_link_nodeps_dynamic_library, |
| ], |
| with_features = [with_feature_set(not_features = ["rules_rust_unsupported_feature"])], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| # Ignore dynamic runtime libraries to force static linking. This is *very* |
| # important when compiling Rust code on our Windows CI machines, which don't |
| # have MSVC installed. Without these flags, the rust toolchain makes some |
| # helper tools (e.g. copy_file.exe) that depend on vcruntime140.dll and friends |
| # and then runs those with a flag that removes any PATH modifications we've made |
| # (e.g. adding win_toolchain from CIPD to the PATH). This means the Rust steps |
| # will fail with exit status 0xc0000135 (STATUS_DLL_NOT_FOUND). Since we can't |
| # pipe the location of the missing .dlls into the rules_rust toolchain, it's |
| # easier to statically link the runtime instead of dynamically linking it. |
| "/NODEFAULTLIB:msvcrt.lib", |
| "/NODEFAULTLIB:msvcrtd.lib", |
| "/NODEFAULTLIB:msvcprt.lib", |
| "/NODEFAULTLIB:msvcprtd.lib", |
| # Since we are ignoring the default ones, we need to list them ourselves to |
| # make sure the static versions get linked in. |
| "libcpmt.lib", # Standard C++ library |
| "libcmt.lib", # core C runtime |
| "libvcruntime.lib", # Visual C++ |
| "libucrt.lib", # Universal C Runtime |
| "kernel32.lib", # Win32 base API |
| "ntdll.lib", # native NT system services. |
| "/LIBPATH:" + full_msvc_lib + "/x64", |
| "/LIBPATH:" + full_win_sdk_lib + "/ucrt/x64", |
| "/LIBPATH:" + full_win_sdk_lib + "/um/x64", |
| ], |
| ), |
| ], |
| ) |
| return [feature( |
| "default_flags", |
| enabled = True, |
| flag_sets = [ |
| cxx_compile_includes, |
| cpp_compile_flags, |
| link_exe_flags_cpp, |
| link_exe_flags_rust, |
| ], |
| )] |
| |
| def _make_diagnostic_flags(): |
| """Here we define the flags that can be turned on via features to yield debug info.""" |
| cxx_diagnostic = flag_set( |
| actions = [ |
| ACTION_NAMES.c_compile, |
| ACTION_NAMES.cpp_compile, |
| ], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| "--trace-includes", |
| "-v", |
| ], |
| ), |
| ], |
| ) |
| |
| link_diagnostic = flag_set( |
| actions = [ACTION_NAMES.cpp_link_executable], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| "-Wl,-verbose", |
| "-v", |
| ], |
| ), |
| ], |
| ) |
| |
| link_search_dirs = flag_set( |
| actions = [ACTION_NAMES.cpp_link_executable], |
| flag_groups = [ |
| flag_group( |
| flags = [ |
| "--print-search-dirs", |
| ], |
| ), |
| ], |
| ) |
| return [ |
| # Running a Bazel command with --features diagnostic will cause the compilation and |
| # link steps to be more verbose. |
| feature( |
| "diagnostic", |
| enabled = False, |
| flag_sets = [ |
| cxx_diagnostic, |
| link_diagnostic, |
| ], |
| ), |
| feature( |
| "diagnostic_link", |
| enabled = False, |
| flag_sets = [ |
| link_diagnostic, |
| ], |
| ), |
| # Running a Bazel command with --features print_search_dirs will cause the link to fail |
| # but directories searched for libraries, etc will be displayed. |
| feature( |
| "print_search_dirs", |
| enabled = False, |
| flag_sets = [ |
| link_search_dirs, |
| ], |
| ), |
| ] |