Ver Fonte

Start refactoring toolchain config into separate files (#6587)

This moves the simplest parts of the toolchain config into separate
files. These parts are either unparameterized or trivially parameterized
and so easily extracted from the main file.

I tried to minimize the interesting edits here, but wasn't _completely_
successful I'm afraid. I'll try to describe them.

First, all of the interesting content of the new files is copied and
re-indented, no interesting edits were done.

The main file sees some more significant edits in order to realize this
refactoring:

- Extract the feature array building to a helper method.
- Collapse some extraneous features as there was no where to extract
them.
- Restructure how the array itself is built to support building it using
array fragments from the various files.

The only interesting semantic change I'm aware of here is that this
somewhat changes the order of command line flags in compiles and links.
The previous order was "fine", but not especially logical. I've tried to
more logically have features that should "override" or are "more
specific" come later here. However, that results in a slightly different
ordering. None of the current features had any flags that overlap, so
this should have no behavior change other than the changed flag order.

This is only the first step, however. There remain complex features in
the main configuration that I want to move out. However, to make those
moves simple requires some significant changes to how these remaining
features work and so I wanted to break them out. I've tried to leave
TODOs that can help as breadcrumbs on the parts of this refactoring that
aren't yet complete.

The comments also are mostly what we already had. I'm happy to try and
add some, but not sure how much I can cover as there is a _lot_ of code
here that I'm just moving around. Please let me know if there are
particularly places that would benefit from comments.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Chandler Carruth há 3 meses atrás
pai
commit
3603ec7d54

+ 1 - 1
MODULE.bazel.lock

@@ -251,7 +251,7 @@
   "moduleExtensions": {
     "//bazel/cc_toolchains:clang_configuration.bzl%clang_toolchain_extension": {
       "general": {
-        "bzlTransitiveDigest": "0KCe1WImgzaPngWGfJswCYagC+t+fC+G9oQJ7+WYLvA=",
+        "bzlTransitiveDigest": "bboGyUY23/USpijt9AwiA8C2A4TI3wom/e7F3b5LeCI=",
         "usagesDigest": "lTxkeAFhR0iBEa3dg5hWvtd2HFCr5zCJx/fl27A+IKA=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},

+ 47 - 0
bazel/cc_toolchains/cc_toolchain_actions.bzl

@@ -0,0 +1,47 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Useful sets of actions for defining `cc_toolchain_config` features."""
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+
+all_c_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.assemble,
+    ACTION_NAMES.preprocess_assemble,
+]
+
+all_cpp_compile_actions = [
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+    ACTION_NAMES.cpp_module_codegen,
+]
+
+all_compile_actions = all_c_compile_actions + all_cpp_compile_actions
+
+preprocessor_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_header_parsing,
+    ACTION_NAMES.cpp_module_compile,
+]
+
+codegen_compile_actions = [
+    ACTION_NAMES.c_compile,
+    ACTION_NAMES.cpp_compile,
+    ACTION_NAMES.linkstamp_compile,
+    ACTION_NAMES.assemble,
+    ACTION_NAMES.preprocess_assemble,
+    ACTION_NAMES.cpp_module_codegen,
+]
+
+all_link_actions = [
+    ACTION_NAMES.cpp_link_executable,
+    ACTION_NAMES.cpp_link_dynamic_library,
+    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+]

+ 90 - 0
bazel/cc_toolchains/cc_toolchain_base_features.bzl

@@ -0,0 +1,90 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions used for the base features of a `cc_toolchain_config`."""
+
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "flag_group",
+    "flag_set",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_compile_actions",
+    "all_link_actions",
+)
+
+# Declare features that are used by Bazel to model specific build modes.
+dbg_feature = feature(name = "dbg")
+fastbuild_feature = feature(name = "fastbuild")
+host_feature = feature(name = "host")
+opt_feature = feature(name = "opt")
+
+# Declare features that control enabling and disabling Bazel logic.
+no_legacy_features_feature = feature(name = "no_legacy_features")
+supports_pic_feature = feature(name = "supports_pic", enabled = True)
+
+user_flags_feature = feature(
+    name = "user_flags",
+    enabled = True,
+    flag_sets = [
+        flag_set(
+            actions = all_compile_actions,
+            flag_groups = [flag_group(
+                expand_if_available = "user_compile_flags",
+                flags = ["%{user_compile_flags}"],
+                iterate_over = "user_compile_flags",
+            )],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [flag_group(
+                expand_if_available = "user_link_flags",
+                flags = ["%{user_link_flags}"],
+                iterate_over = "user_link_flags",
+            )],
+        ),
+    ],
+)
+
+# TODO: It's not clear this is the right location for these flags, and it is a
+# little awkward.
+output_flags_feature = feature(
+    name = "output_flags",
+    enabled = True,
+    flag_sets = [
+        flag_set(
+            actions = all_compile_actions,
+            flag_groups = [
+                # For compile actions we have a single source and so put it at
+                # the end next to the output.
+                flag_group(
+                    expand_if_available = "source_file",
+                    flags = ["%{source_file}"],
+                ),
+                flag_group(
+                    expand_if_available = "output_file",
+                    flags = ["-o", "%{output_file}"],
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [flag_group(
+                expand_if_available = "output_execpath",
+                flags = ["-o", "%{output_execpath}"],
+            )],
+        ),
+    ],
+)
+
+base_features = [
+    dbg_feature,
+    fastbuild_feature,
+    host_feature,
+    no_legacy_features_feature,
+    opt_feature,
+    supports_pic_feature,
+]

+ 139 - 0
bazel/cc_toolchains/cc_toolchain_debugging.bzl

@@ -0,0 +1,139 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions of debugging related features used in a `cc_toolchain_config`."""
+
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "flag_set",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_link_actions",
+    "codegen_compile_actions",
+)
+
+# Handle different levels and forms of debug info emission with individual
+# features so that they can be ordered and the defaults can override the
+# minimal settings if both are enabled.
+minimal_debug_info_flags = feature(
+    name = "minimal_debug_info_flags",
+    implies = ["debug_info_compression_flags"],
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions,
+        flag_groups = [flag_group(flags = ["-gmlt"])],
+    )],
+)
+debug_info_flags = feature(
+    name = "debug_info_flags",
+    implies = ["debug_info_compression_flags"],
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions,
+        flag_groups = [
+            flag_group(flags = ["-g"]),
+            flag_group(
+                expand_if_available = "per_object_debug_info_file",
+                flags = ["-gsplit-dwarf"],
+            ),
+        ],
+    )],
+)
+debug_info_compression_flags = feature(
+    name = "debug_info_compression_flags",
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions + all_link_actions,
+        flag_groups = [flag_group(flags = ["-gz"])],
+    )],
+)
+
+# Define a set of mutually exclusive debugger flags.
+debugger_flags = feature(name = "debugger_flags")
+lldb_flags = feature(
+    # Use a convenient name for users to select if needed.
+    name = "lldb_flags",
+    # Default enable LLDB-optimized flags whenever debugging.
+    enabled = True,
+    requires = [feature_set(features = ["debug_info_flags"])],
+    provides = ["debugger_flags"],
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions,
+        flag_groups = [flag_group(flags = [
+            "-glldb",
+            "-gpubnames",
+            "-gsimple-template-names",
+        ])],
+    )],
+)
+gdb_flags = feature(
+    # Use a convenient name for users to select if needed.
+    name = "gdb_flags",
+    requires = [feature_set(features = ["debug_info_flags"])],
+    provides = ["debugger_flags"],
+    flag_sets = [
+        flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = [
+                "-ggdb",
+                "-ggnu-pubnames",
+            ])],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [flag_group(flags = ["-Wl,--gdb-index"])],
+        ),
+    ],
+)
+
+# This feature can be enabled in conjunction with any optimizations to
+# ensure accurate call stacks and backtraces for profilers or errors.
+preserve_call_stacks = feature(
+    name = "preserve_call_stacks",
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions,
+        flag_groups = [flag_group(flags = [
+            # Ensure good backtraces by preserving frame pointers and
+            # disabling tail call elimination.
+            "-fno-omit-frame-pointer",
+            "-mno-omit-leaf-frame-pointer",
+            "-fno-optimize-sibling-calls",
+        ])],
+    )],
+)
+
+# Enable split debug info whenever debug info is requested.
+enable_split_debug_info = feature(
+    name = "per_object_debug_info",
+    enabled = True,
+    # This has to be directly conditioned on requesting debug info at
+    # all, otherwise Bazel will look for an extra output file and not
+    # find one.
+    requires = [feature_set(features = ["debug_info_flags"])],
+)
+
+# Enable debug info whenever in the `dbg` build mode. We do this separately from
+# the `debug_info_flags` feature itself as other things may want to enable that
+# feature as well.
+enable_debug_info_in_dbg = feature(
+    name = "enable_debug_info_in_dbg",
+    enabled = True,
+    requires = [feature_set(["dbg"])],
+    implies = ["debug_info_flags"],
+)
+
+# Note that the order of features is significant in this list and determines the
+# relative order of flags from the features listed.
+debugging_features = [
+    minimal_debug_info_flags,
+    debug_info_flags,
+    debug_info_compression_flags,
+    debugger_flags,
+    lldb_flags,
+    gdb_flags,
+    preserve_call_stacks,
+    enable_split_debug_info,
+    enable_debug_info_in_dbg,
+]

+ 287 - 0
bazel/cc_toolchains/cc_toolchain_linking.bzl

@@ -0,0 +1,287 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions of linking related features used in a `cc_toolchain_config`."""
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "flag_group",
+    "flag_set",
+    "variable_with_value",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_link_actions",
+)
+
+default_link_libraries_feature = feature(
+    name = "default_link_libraries",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = all_link_actions,
+        flag_groups = [
+            flag_group(
+                expand_if_available = "linkstamp_paths",
+                flags = ["%{linkstamp_paths}"],
+                iterate_over = "linkstamp_paths",
+            ),
+            flag_group(
+                expand_if_available = "libraries_to_link",
+                flag_groups = [
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["-Wl,--start-lib"],
+                    ),
+                    flag_group(
+                        expand_if_true = "libraries_to_link.is_whole_archive",
+                        flags = ["-Wl,-whole-archive"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["%{libraries_to_link.object_files}"],
+                        iterate_over = "libraries_to_link.object_files",
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file",
+                        ),
+                        flags = ["%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "interface_library",
+                        ),
+                        flags = ["%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "static_library",
+                        ),
+                        flags = ["%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "dynamic_library",
+                        ),
+                        flags = ["-l%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "versioned_dynamic_library",
+                        ),
+                        flags = ["-l:%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_true = "libraries_to_link.is_whole_archive",
+                        flags = ["-Wl,-no-whole-archive"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["-Wl,--end-lib"],
+                    ),
+                ],
+                iterate_over = "libraries_to_link",
+            ),
+            # Note that the params file comes at the end, after the
+            # libraries to link above.
+            flag_group(
+                expand_if_available = "linker_param_file",
+                flags = ["@%{linker_param_file}"],
+            ),
+        ],
+    )],
+)
+
+macos_link_libraries_feature = feature(
+    name = "macos_link_libraries",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = all_link_actions,
+        flag_groups = [
+            flag_group(
+                expand_if_available = "linkstamp_paths",
+                flags = ["%{linkstamp_paths}"],
+                iterate_over = "linkstamp_paths",
+            ),
+            flag_group(
+                expand_if_available = "libraries_to_link",
+                flag_groups = [
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["-Wl,--start-lib"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flag_groups = [
+                            flag_group(
+                                expand_if_false = "libraries_to_link.is_whole_archive",
+                                flags = ["%{libraries_to_link.object_files}"],
+                            ),
+                            flag_group(
+                                expand_if_true = "libraries_to_link.is_whole_archive",
+                                flags = ["-Wl,-force_load,%{libraries_to_link.object_files}"],
+                            ),
+                        ],
+                        iterate_over = "libraries_to_link.object_files",
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file",
+                        ),
+                        flag_groups = [
+                            flag_group(
+                                expand_if_false = "libraries_to_link.is_whole_archive",
+                                flags = ["%{libraries_to_link.name}"],
+                            ),
+                            flag_group(
+                                expand_if_true = "libraries_to_link.is_whole_archive",
+                                flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
+                            ),
+                        ],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "interface_library",
+                        ),
+                        flag_groups = [
+                            flag_group(
+                                expand_if_false = "libraries_to_link.is_whole_archive",
+                                flags = ["%{libraries_to_link.name}"],
+                            ),
+                            flag_group(
+                                expand_if_true = "libraries_to_link.is_whole_archive",
+                                flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
+                            ),
+                        ],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "static_library",
+                        ),
+                        flag_groups = [
+                            flag_group(
+                                expand_if_false = "libraries_to_link.is_whole_archive",
+                                flags = ["%{libraries_to_link.name}"],
+                            ),
+                            flag_group(
+                                expand_if_true = "libraries_to_link.is_whole_archive",
+                                flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
+                            ),
+                        ],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "dynamic_library",
+                        ),
+                        flags = ["-l%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "versioned_dynamic_library",
+                        ),
+                        flags = ["-l:%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_true = "libraries_to_link.is_whole_archive",
+                        flag_groups = [
+                            flag_group(
+                                expand_if_false = "macos_flags",
+                                flags = ["-Wl,-no-whole-archive"],
+                            ),
+                        ],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["-Wl,--end-lib"],
+                    ),
+                ],
+                iterate_over = "libraries_to_link",
+            ),
+            # Note that the params file comes at the end, after the
+            # libraries to link above.
+            flag_group(
+                expand_if_available = "linker_param_file",
+                flags = ["@%{linker_param_file}"],
+            ),
+        ],
+    )],
+)
+
+# Archive actions have an entirely independent set of flags and don't
+# interact with either compiler or link actions.
+default_archiver_flags_feature = feature(
+    name = "default_archiver_flags",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = [ACTION_NAMES.cpp_link_static_library],
+        flag_groups = [
+            flag_group(flags = ["rcsD"]),
+            flag_group(
+                expand_if_available = "output_execpath",
+                flags = ["%{output_execpath}"],
+            ),
+            flag_group(
+                expand_if_available = "libraries_to_link",
+                flag_groups = [
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file",
+                        ),
+                        flags = ["%{libraries_to_link.name}"],
+                    ),
+                    flag_group(
+                        expand_if_equal = variable_with_value(
+                            name = "libraries_to_link.type",
+                            value = "object_file_group",
+                        ),
+                        flags = ["%{libraries_to_link.object_files}"],
+                        iterate_over = "libraries_to_link.object_files",
+                    ),
+                ],
+                iterate_over = "libraries_to_link",
+            ),
+            flag_group(
+                expand_if_available = "linker_param_file",
+                flags = ["@%{linker_param_file}"],
+            ),
+        ],
+    )],
+)
+
+# Note that the order of features is significant in this list and determines the
+# relative order of flags from the features listed.
+linking_features = [
+    default_archiver_flags_feature,
+]

+ 82 - 0
bazel/cc_toolchains/cc_toolchain_modules.bzl

@@ -0,0 +1,82 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions of C++ and Clang header modules toolchain features."""
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "flag_set",
+)
+
+use_module_maps = feature(
+    name = "use_module_maps",
+    requires = [feature_set(features = ["module_maps"])],
+    flag_sets = [
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+            ],
+            flag_groups = [
+                # These flag groups are separate so they do not expand to
+                # the cross product of the variables.
+                flag_group(flags = ["-fmodule-name=%{module_name}"]),
+                flag_group(
+                    flags = ["-fmodule-map-file=%{module_map_file}"],
+                ),
+            ],
+        ),
+    ],
+)
+
+# Tell bazel we support module maps in general, so they will be generated
+# for all c/c++ rules.
+# Note: not all C++ rules support module maps; thus, do not imply this
+# feature from other features - instead, require it.
+module_maps = feature(
+    name = "module_maps",
+    enabled = True,
+    implies = [
+        # "module_map_home_cwd",
+        # "module_map_without_extern_module",
+        # "generate_submodules",
+    ],
+)
+
+layering_check = feature(
+    name = "layering_check",
+    implies = ["use_module_maps"],
+    flag_sets = [flag_set(
+        actions = [
+            ACTION_NAMES.c_compile,
+            ACTION_NAMES.cpp_compile,
+            ACTION_NAMES.cpp_header_parsing,
+            ACTION_NAMES.cpp_module_compile,
+        ],
+        flag_groups = [
+            flag_group(flags = [
+                "-fmodules-strict-decluse",
+                "-Wprivate-header",
+            ]),
+            flag_group(
+                iterate_over = "dependent_module_map_files",
+                flags = ["-fmodule-map-file=%{dependent_module_map_files}"],
+            ),
+        ],
+    )],
+)
+
+# Note that the order of features is significant in this list and determines the
+# relative order of flags from the features listed.
+modules_features = [
+    layering_check,
+    module_maps,
+    use_module_maps,
+]

+ 69 - 0
bazel/cc_toolchains/cc_toolchain_optimization.bzl

@@ -0,0 +1,69 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions of optimization `cc_toolchain_config` features."""
+
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "flag_set",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_compile_actions",
+    "codegen_compile_actions",
+)
+
+# Handle different levels of optimization with individual features so that
+# they can be ordered and the defaults can override the minimal settings if
+# both are enabled.
+minimal_optimization_flags = feature(
+    name = "minimal_optimization_flags",
+    flag_sets = [flag_set(
+        actions = codegen_compile_actions,
+        flag_groups = [flag_group(flags = ["-O1"])],
+    )],
+)
+default_optimization_flags = feature(
+    name = "default_optimization_flags",
+    enabled = True,
+    requires = [feature_set(["opt"])],
+    flag_sets = [
+        flag_set(
+            actions = all_compile_actions,
+            flag_groups = [flag_group(flags = ["-DNDEBUG"])],
+        ),
+        flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = ["-O3"])],
+        ),
+    ],
+)
+
+aarch64_cpu_flags = feature(
+    name = "aarch64_cpu_flags",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = all_compile_actions,
+        flag_groups = [flag_group(flags = ["-march=armv8.2-a"])],
+    )],
+)
+
+x86_64_cpu_flags = feature(
+    name = "x86_64_cpu_flags",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = all_compile_actions,
+        flag_groups = [flag_group(flags = ["-march=x86-64-v2"])],
+    )],
+)
+
+# Note that the order of features is significant in this list and determines the
+# relative order of flags from the features listed.
+optimization_features = [
+    minimal_optimization_flags,
+    default_optimization_flags,
+]

+ 113 - 0
bazel/cc_toolchains/cc_toolchain_sanitizer_features.bzl

@@ -0,0 +1,113 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Definitions of sanitizer-related `cc_toolchain_config` features."""
+
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "flag_set",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_compile_actions",
+    "all_link_actions",
+)
+
+sanitizer_common_flags = feature(
+    name = "sanitizer_common_flags",
+    implies = ["minimal_debug_info_flags", "preserve_call_stacks"],
+)
+
+# Separated from the feature above so it can only be included on platforms
+# where it is supported. There is no negative flag in Clang so we can't just
+# override it later.
+sanitizer_static_lib_flags = feature(
+    name = "sanitizer_static_lib_flags",
+    enabled = True,
+    requires = [feature_set(["sanitizer_common_flags"])],
+    flag_sets = [flag_set(
+        actions = all_link_actions,
+        flag_groups = [flag_group(flags = ["-static-libsan"])],
+    )],
+)
+
+asan = feature(
+    name = "asan",
+    implies = ["sanitizer_common_flags"],
+    flag_sets = [flag_set(
+        actions = all_compile_actions + all_link_actions,
+        flag_groups = [flag_group(flags = [
+            "-fsanitize=address,undefined,nullability",
+            "-fsanitize-address-use-after-scope",
+            # Outlining is almost always the right tradeoff for our
+            # sanitizer usage where we're more pressured on generated code
+            # size than runtime performance.
+            "-fsanitize-address-outline-instrumentation",
+            # We don't need the recovery behavior of UBSan as we expect
+            # builds to be clean. Not recovering is a bit cheaper.
+            "-fno-sanitize-recover=undefined,nullability",
+            # Don't embed the full path name for files. This limits the size
+            # and combined with line numbers is unlikely to result in many
+            # ambiguities.
+            "-fsanitize-undefined-strip-path-components=-1",
+            # Needed due to clang AST issues, such as in
+            # clang/AST/Redeclarable.h line 199.
+            "-fno-sanitize=vptr",
+        ])],
+    )],
+)
+
+# A feature that further reduces the generated code size of our the ASan
+# feature, but at the cost of lower quality diagnostics. This is enabled
+# along with ASan in our fastbuild configuration, but can be disabled
+# explicitly to get better error messages.
+asan_min_size = feature(
+    name = "asan_min_size",
+    requires = [feature_set(["asan"])],
+    flag_sets = [flag_set(
+        actions = all_compile_actions + all_link_actions,
+        flag_groups = [flag_group(flags = [
+            # Force two UBSan checks that have especially large code size
+            # cost to use the minimal branch to a trapping instruction model
+            # instead of the full diagnostic.
+            "-fsanitize-trap=alignment,null",
+        ])],
+    )],
+)
+
+# Likely due to being unable to use the static-linked and up-to-date
+# sanitizer runtimes, we have to disable a number of sanitizers on macOS.
+macos_asan_workarounds = feature(
+    name = "macos_sanitizer_workarounds",
+    enabled = True,
+    requires = [feature_set(["asan"])],
+    flag_sets = [flag_set(
+        actions = all_compile_actions + all_link_actions,
+        flag_groups = [flag_group(flags = [
+            "-fno-sanitize=function",
+        ])],
+    )],
+)
+
+fuzzer = feature(
+    name = "fuzzer",
+    flag_sets = [flag_set(
+        actions = all_compile_actions + all_link_actions,
+        flag_groups = [flag_group(flags = [
+            "-fsanitize=fuzzer-no-link",
+        ])],
+    )],
+)
+
+# Note that the order of features is significant in this list and determines the
+# relative order of flags from the features listed.
+sanitizer_features = [
+    sanitizer_common_flags,
+    asan,
+    asan_min_size,
+    fuzzer,
+]

+ 81 - 0
bazel/cc_toolchains/cc_toolchain_tools.bzl

@@ -0,0 +1,81 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Macros to produce tool-related parts of a `cc_toolchain_config`.
+
+These macros cover both the `actions_config` array and the `tool_paths` array.
+
+They presume an LLVM and Clang toolchain's tools, but support both a single
+installation and installations that split the LLVM tools and Clang tools apart.
+"""
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "action_config",
+    "tool",
+    "tool_path",
+)
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_c_compile_actions",
+    "all_cpp_compile_actions",
+    "all_link_actions",
+)
+
+def llvm_tool_paths(llvm_bindir, clang_bindir = None):
+    if not clang_bindir:
+        clang_bindir = llvm_bindir
+    return [
+        tool_path(name = "ar", path = llvm_bindir + "/llvm-ar"),
+        tool_path(name = "ld", path = clang_bindir + "/ld.lld"),
+        tool_path(name = "cpp", path = clang_bindir + "/clang-cpp"),
+        tool_path(name = "gcc", path = clang_bindir + "/clang++"),
+        tool_path(name = "dwp", path = llvm_bindir + "/llvm-dwp"),
+        tool_path(name = "gcov", path = llvm_bindir + "/llvm-cov"),
+        tool_path(name = "nm", path = llvm_bindir + "/llvm-nm"),
+        tool_path(name = "objcopy", path = llvm_bindir + "/llvm-objcopy"),
+        tool_path(name = "objdump", path = llvm_bindir + "/llvm-objdump"),
+        tool_path(name = "strip", path = llvm_bindir + "/llvm-strip"),
+    ]
+
+def llvm_action_configs(llvm_bindir, clang_bindir = None):
+    if not clang_bindir:
+        clang_bindir = llvm_bindir
+    return [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = clang_bindir + "/clang")],
+        )
+        for name in all_c_compile_actions
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = clang_bindir + "/clang++")],
+        )
+        for name in all_cpp_compile_actions
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = clang_bindir + "/clang++")],
+        )
+        for name in all_link_actions
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = llvm_bindir + "/llvm-ar")],
+        )
+        for name in [ACTION_NAMES.cpp_link_static_library]
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = llvm_bindir + "/llvm-strip")],
+        )
+        for name in [ACTION_NAMES.strip]
+    ]

+ 102 - 763
bazel/cc_toolchains/clang_cc_toolchain_config.bzl

@@ -7,18 +7,53 @@
 load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
 load(
     "@rules_cc//cc:cc_toolchain_config_lib.bzl",
-    "action_config",
     "feature",
     "feature_set",
     "flag_group",
     "flag_set",
-    "tool",
-    "tool_path",
-    "variable_with_value",
     "with_feature_set",
 )
 load("@rules_cc//cc:defs.bzl", "cc_toolchain")
 load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_compile_actions",
+    "all_cpp_compile_actions",
+    "all_link_actions",
+    "codegen_compile_actions",
+    "preprocessor_compile_actions",
+)
+load(
+    ":cc_toolchain_base_features.bzl",
+    "base_features",
+    "output_flags_feature",
+    "user_flags_feature",
+)
+load(":cc_toolchain_debugging.bzl", "debugging_features")
+load(
+    ":cc_toolchain_linking.bzl",
+    "default_link_libraries_feature",
+    "linking_features",
+    "macos_link_libraries_feature",
+)
+load(":cc_toolchain_modules.bzl", "modules_features")
+load(
+    ":cc_toolchain_optimization.bzl",
+    "aarch64_cpu_flags",
+    "optimization_features",
+    "x86_64_cpu_flags",
+)
+load(
+    ":cc_toolchain_sanitizer_features.bzl",
+    "macos_asan_workarounds",
+    "sanitizer_features",
+    "sanitizer_static_lib_flags",
+)
+load(
+    ":cc_toolchain_tools.bzl",
+    "llvm_action_configs",
+    "llvm_tool_paths",
+)
 load(
     ":clang_detected_variables.bzl",
     "clang_bindir",
@@ -30,97 +65,7 @@ load(
     "sysroot_dir",
 )
 
-all_c_compile_actions = [
-    ACTION_NAMES.c_compile,
-    ACTION_NAMES.assemble,
-    ACTION_NAMES.preprocess_assemble,
-]
-
-all_cpp_compile_actions = [
-    ACTION_NAMES.cpp_compile,
-    ACTION_NAMES.linkstamp_compile,
-    ACTION_NAMES.cpp_header_parsing,
-    ACTION_NAMES.cpp_module_compile,
-    ACTION_NAMES.cpp_module_codegen,
-]
-
-all_compile_actions = all_c_compile_actions + all_cpp_compile_actions
-
-preprocessor_compile_actions = [
-    ACTION_NAMES.c_compile,
-    ACTION_NAMES.cpp_compile,
-    ACTION_NAMES.linkstamp_compile,
-    ACTION_NAMES.preprocess_assemble,
-    ACTION_NAMES.cpp_header_parsing,
-    ACTION_NAMES.cpp_module_compile,
-]
-
-codegen_compile_actions = [
-    ACTION_NAMES.c_compile,
-    ACTION_NAMES.cpp_compile,
-    ACTION_NAMES.linkstamp_compile,
-    ACTION_NAMES.assemble,
-    ACTION_NAMES.preprocess_assemble,
-    ACTION_NAMES.cpp_module_codegen,
-]
-
-all_link_actions = [
-    ACTION_NAMES.cpp_link_executable,
-    ACTION_NAMES.cpp_link_dynamic_library,
-    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
-]
-
-def _impl(ctx):
-    tool_paths = [
-        tool_path(name = "ar", path = llvm_bindir + "/llvm-ar"),
-        tool_path(name = "ld", path = clang_bindir + "/ld.lld"),
-        tool_path(name = "cpp", path = clang_bindir + "/clang-cpp"),
-        tool_path(name = "gcc", path = clang_bindir + "/clang++"),
-        tool_path(name = "dwp", path = llvm_bindir + "/llvm-dwp"),
-        tool_path(name = "gcov", path = llvm_bindir + "/llvm-cov"),
-        tool_path(name = "nm", path = llvm_bindir + "/llvm-nm"),
-        tool_path(name = "objcopy", path = llvm_bindir + "/llvm-objcopy"),
-        tool_path(name = "objdump", path = llvm_bindir + "/llvm-objdump"),
-        tool_path(name = "strip", path = llvm_bindir + "/llvm-strip"),
-    ]
-
-    action_configs = [
-        action_config(
-            action_name = name,
-            enabled = True,
-            tools = [tool(path = clang_bindir + "/clang")],
-        )
-        for name in all_c_compile_actions
-    ] + [
-        action_config(
-            action_name = name,
-            enabled = True,
-            tools = [tool(path = clang_bindir + "/clang++")],
-        )
-        for name in all_cpp_compile_actions
-    ] + [
-        action_config(
-            action_name = name,
-            enabled = True,
-            tools = [tool(path = clang_bindir + "/clang++")],
-        )
-        for name in all_link_actions
-    ] + [
-        action_config(
-            action_name = name,
-            enabled = True,
-            tools = [tool(path = llvm_bindir + "/llvm-ar")],
-        )
-        for name in [ACTION_NAMES.cpp_link_static_library]
-    ] + [
-        action_config(
-            action_name = name,
-            enabled = True,
-            tools = [tool(path = llvm_bindir + "/llvm-strip")],
-        )
-        for name in [ACTION_NAMES.strip]
-    ]
-
+def _build_features(ctx):
     std_compile_flags = ["-std=c++20"]
 
     # libc++ is only used on non-Windows platforms.
@@ -137,16 +82,23 @@ def _impl(ctx):
     else:
         missing_field_init_flags = []
 
+    # TODO: Refactor this into a reusable form in its own file.
     default_flags_feature = feature(
         name = "default_flags",
         enabled = True,
         flag_sets = [
             flag_set(
                 actions = all_compile_actions + all_link_actions,
-                flag_groups = [flag_group(flags = [
-                    "-no-canonical-prefixes",
-                    "-fcolor-diagnostics",
-                ])],
+                flag_groups = [
+                    flag_group(flags = [
+                        "-no-canonical-prefixes",
+                        "-fcolor-diagnostics",
+                    ]),
+                    flag_group(
+                        expand_if_available = "sysroot",
+                        flags = ["--sysroot=%{sysroot}"],
+                    ),
+                ],
             ),
             flag_set(
                 actions = all_compile_actions,
@@ -293,295 +245,6 @@ def _impl(ctx):
         ],
     )
 
-    # Handle different levels of optimization with individual features so that
-    # they can be ordered and the defaults can override the minimal settings if
-    # both are enabled.
-    minimal_optimization_flags = feature(
-        name = "minimal_optimization_flags",
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions,
-            flag_groups = [flag_group(flags = ["-O1"])],
-        )],
-    )
-    default_optimization_flags = feature(
-        name = "default_optimization_flags",
-        enabled = True,
-        requires = [feature_set(["opt"])],
-        flag_sets = [
-            flag_set(
-                actions = all_compile_actions,
-                flag_groups = [flag_group(flags = ["-DNDEBUG"])],
-            ),
-            flag_set(
-                actions = codegen_compile_actions,
-                flag_groups = [flag_group(flags = ["-O3"])],
-            ),
-        ],
-    )
-
-    x86_64_cpu_flags = feature(
-        name = "x86_64_cpu_flags",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = all_compile_actions,
-            flag_groups = [flag_group(flags = ["-march=x86-64-v2"])],
-        )],
-    )
-
-    aarch64_cpu_flags = feature(
-        name = "aarch64_cpu_flags",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = all_compile_actions,
-            flag_groups = [flag_group(flags = ["-march=armv8.2-a"])],
-        )],
-    )
-
-    # Handle different levels and forms of debug info emission with individual
-    # features so that they can be ordered and the defaults can override the
-    # minimal settings if both are enabled.
-    minimal_debug_info_flags = feature(
-        name = "minimal_debug_info_flags",
-        implies = ["debug_info_compression_flags"],
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions,
-            flag_groups = [flag_group(flags = ["-gmlt"])],
-        )],
-    )
-    debug_info_flags = feature(
-        name = "debug_info_flags",
-        implies = ["debug_info_compression_flags"],
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions,
-            flag_groups = [
-                flag_group(flags = ["-g"]),
-                flag_group(
-                    expand_if_available = "per_object_debug_info_file",
-                    flags = ["-gsplit-dwarf"],
-                ),
-            ],
-        )],
-    )
-    debug_info_compression_flags = feature(
-        name = "debug_info_compression_flags",
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions + all_link_actions,
-            flag_groups = [flag_group(flags = ["-gz"])],
-        )],
-    )
-
-    # Define a set of mutually exclusive debugger flags.
-    debugger_flags = feature(name = "debugger_flags")
-    lldb_flags = feature(
-        # Use a convenient name for users to select if needed.
-        name = "lldb_flags",
-        # Default enable LLDB-optimized flags whenever debugging.
-        enabled = True,
-        requires = [feature_set(features = ["debug_info_flags"])],
-        provides = ["debugger_flags"],
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions,
-            flag_groups = [flag_group(flags = [
-                "-glldb",
-                "-gpubnames",
-                "-gsimple-template-names",
-            ])],
-        )],
-    )
-    gdb_flags = feature(
-        # Use a convenient name for users to select if needed.
-        name = "gdb_flags",
-        requires = [feature_set(features = ["debug_info_flags"])],
-        provides = ["debugger_flags"],
-        flag_sets = [
-            flag_set(
-                actions = codegen_compile_actions,
-                flag_groups = [flag_group(flags = [
-                    "-ggdb",
-                    "-ggnu-pubnames",
-                ])],
-            ),
-            flag_set(
-                actions = all_link_actions,
-                flag_groups = [flag_group(flags = ["-Wl,--gdb-index"])],
-            ),
-        ],
-    )
-
-    # An enabled feature that requires the `dbg` compilation mode. This is used
-    # to toggle on more general features to use by default, while allowing those
-    # general features to be explicitly enabled where needed.
-    enable_in_dbg = feature(
-        name = "enable_in_dbg",
-        enabled = True,
-        requires = [feature_set(["dbg"])],
-        implies = ["debug_info_flags"],
-    )
-
-    # This feature can be enabled in conjunction with any optimizations to
-    # ensure accurate call stacks and backtraces for profilers or errors.
-    preserve_call_stacks = feature(
-        name = "preserve_call_stacks",
-        flag_sets = [flag_set(
-            actions = codegen_compile_actions,
-            flag_groups = [flag_group(flags = [
-                # Ensure good backtraces by preserving frame pointers and
-                # disabling tail call elimination.
-                "-fno-omit-frame-pointer",
-                "-mno-omit-leaf-frame-pointer",
-                "-fno-optimize-sibling-calls",
-            ])],
-        )],
-    )
-
-    sysroot_feature = feature(
-        name = "sysroot",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = all_compile_actions + all_link_actions,
-            flag_groups = [flag_group(
-                expand_if_available = "sysroot",
-                flags = ["--sysroot=%{sysroot}"],
-            )],
-        )],
-    )
-
-    use_module_maps = feature(
-        name = "use_module_maps",
-        requires = [feature_set(features = ["module_maps"])],
-        flag_sets = [
-            flag_set(
-                actions = [
-                    ACTION_NAMES.c_compile,
-                    ACTION_NAMES.cpp_compile,
-                    ACTION_NAMES.cpp_header_parsing,
-                    ACTION_NAMES.cpp_module_compile,
-                ],
-                flag_groups = [
-                    # These flag groups are separate so they do not expand to
-                    # the cross product of the variables.
-                    flag_group(flags = ["-fmodule-name=%{module_name}"]),
-                    flag_group(
-                        flags = ["-fmodule-map-file=%{module_map_file}"],
-                    ),
-                ],
-            ),
-        ],
-    )
-
-    # Tell bazel we support module maps in general, so they will be generated
-    # for all c/c++ rules.
-    # Note: not all C++ rules support module maps; thus, do not imply this
-    # feature from other features - instead, require it.
-    module_maps = feature(
-        name = "module_maps",
-        enabled = True,
-        implies = [
-            # "module_map_home_cwd",
-            # "module_map_without_extern_module",
-            # "generate_submodules",
-        ],
-    )
-
-    layering_check = feature(
-        name = "layering_check",
-        implies = ["use_module_maps"],
-        flag_sets = [flag_set(
-            actions = [
-                ACTION_NAMES.c_compile,
-                ACTION_NAMES.cpp_compile,
-                ACTION_NAMES.cpp_header_parsing,
-                ACTION_NAMES.cpp_module_compile,
-            ],
-            flag_groups = [
-                flag_group(flags = [
-                    "-fmodules-strict-decluse",
-                    "-Wprivate-header",
-                ]),
-                flag_group(
-                    iterate_over = "dependent_module_map_files",
-                    flags = ["-fmodule-map-file=%{dependent_module_map_files}"],
-                ),
-            ],
-        )],
-    )
-
-    sanitizer_common_flags = feature(
-        name = "sanitizer_common_flags",
-        implies = ["minimal_debug_info_flags", "preserve_call_stacks"],
-    )
-
-    # Separated from the feature above so it can only be included on platforms
-    # where it is supported. There is no negative flag in Clang so we can't just
-    # override it later.
-    sanitizer_static_lib_flags = feature(
-        name = "sanitizer_static_lib_flags",
-        enabled = True,
-        requires = [feature_set(["sanitizer_common_flags"])],
-        flag_sets = [flag_set(
-            actions = all_link_actions,
-            flag_groups = [flag_group(flags = ["-static-libsan"])],
-        )],
-    )
-
-    asan = feature(
-        name = "asan",
-        implies = ["sanitizer_common_flags"],
-        flag_sets = [flag_set(
-            actions = all_compile_actions + all_link_actions,
-            flag_groups = [flag_group(flags = [
-                "-fsanitize=address,undefined,nullability",
-                "-fsanitize-address-use-after-scope",
-                # Outlining is almost always the right tradeoff for our
-                # sanitizer usage where we're more pressured on generated code
-                # size than runtime performance.
-                "-fsanitize-address-outline-instrumentation",
-                # We don't need the recovery behavior of UBSan as we expect
-                # builds to be clean. Not recovering is a bit cheaper.
-                "-fno-sanitize-recover=undefined,nullability",
-                # Don't embed the full path name for files. This limits the size
-                # and combined with line numbers is unlikely to result in many
-                # ambiguities.
-                "-fsanitize-undefined-strip-path-components=-1",
-                # Needed due to clang AST issues, such as in
-                # clang/AST/Redeclarable.h line 199.
-                "-fno-sanitize=vptr",
-            ])],
-        )],
-    )
-
-    # A feature that further reduces the generated code size of our the ASan
-    # feature, but at the cost of lower quality diagnostics. This is enabled
-    # along with ASan in our fastbuild configuration, but can be disabled
-    # explicitly to get better error messages.
-    asan_min_size = feature(
-        name = "asan_min_size",
-        requires = [feature_set(["asan"])],
-        flag_sets = [flag_set(
-            actions = all_compile_actions + all_link_actions,
-            flag_groups = [flag_group(flags = [
-                # Force two UBSan checks that have especially large code size
-                # cost to use the minimal branch to a trapping instruction model
-                # instead of the full diagnostic.
-                "-fsanitize-trap=alignment,null",
-            ])],
-        )],
-    )
-
-    # Likely due to being unable to use the static-linked and up-to-date
-    # sanitizer runtimes, we have to disable a number of sanitizers on macOS.
-    macos_asan_workarounds = feature(
-        name = "macos_sanitizer_workarounds",
-        enabled = True,
-        requires = [feature_set(["asan"])],
-        flag_sets = [flag_set(
-            actions = all_compile_actions + all_link_actions,
-            flag_groups = [flag_group(flags = [
-                "-fno-sanitize=function",
-            ])],
-        )],
-    )
-
     # An enabled feature that requires the `fastbuild` compilation. This is used
     # to toggle general features on by default, while allowing them to be
     # directly enabled and disabled more generally as desired.
@@ -597,16 +260,6 @@ def _impl(ctx):
         ],
     )
 
-    fuzzer = feature(
-        name = "fuzzer",
-        flag_sets = [flag_set(
-            actions = all_compile_actions + all_link_actions,
-            flag_groups = [flag_group(flags = [
-                "-fsanitize=fuzzer-no-link",
-            ])],
-        )],
-    )
-
     # Clang HARDENING_MODE has 4 possible values:
     # https://libcxx.llvm.org/Hardening.html#notes-for-users
     #
@@ -621,6 +274,7 @@ def _impl(ctx):
         "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST",
     ]
 
+    # TODO: Refactor this into a reusable form in its own file.
     linux_flags_feature = feature(
         name = "linux_flags",
         enabled = True,
@@ -677,6 +331,7 @@ def _impl(ctx):
         ],
     )
 
+    # TODO: Refactor this into a reusable form in its own file.
     macos_flags_feature = feature(
         name = "macos_flags",
         enabled = True,
@@ -691,6 +346,7 @@ def _impl(ctx):
         ],
     )
 
+    # TODO: Refactor this into a reusable form in its own file.
     freebsd_flags_feature = feature(
         name = "freebsd_flags",
         enabled = True,
@@ -714,367 +370,26 @@ def _impl(ctx):
         ],
     )
 
-    default_link_libraries_feature = feature(
-        name = "default_link_libraries",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = all_link_actions,
-            flag_groups = [
-                flag_group(
-                    expand_if_available = "linkstamp_paths",
-                    flags = ["%{linkstamp_paths}"],
-                    iterate_over = "linkstamp_paths",
-                ),
-                flag_group(
-                    expand_if_available = "libraries_to_link",
-                    flag_groups = [
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["-Wl,--start-lib"],
-                        ),
-                        flag_group(
-                            expand_if_true = "libraries_to_link.is_whole_archive",
-                            flags = ["-Wl,-whole-archive"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["%{libraries_to_link.object_files}"],
-                            iterate_over = "libraries_to_link.object_files",
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file",
-                            ),
-                            flags = ["%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "interface_library",
-                            ),
-                            flags = ["%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "static_library",
-                            ),
-                            flags = ["%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "dynamic_library",
-                            ),
-                            flags = ["-l%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "versioned_dynamic_library",
-                            ),
-                            flags = ["-l:%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_true = "libraries_to_link.is_whole_archive",
-                            flags = ["-Wl,-no-whole-archive"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["-Wl,--end-lib"],
-                        ),
-                    ],
-                    iterate_over = "libraries_to_link",
-                ),
-                # Note that the params file comes at the end, after the
-                # libraries to link above.
-                flag_group(
-                    expand_if_available = "linker_param_file",
-                    flags = ["@%{linker_param_file}"],
-                ),
-            ],
-        )],
-    )
-
-    macos_link_libraries_feature = feature(
-        name = "macos_link_libraries",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = all_link_actions,
-            flag_groups = [
-                flag_group(
-                    expand_if_available = "linkstamp_paths",
-                    flags = ["%{linkstamp_paths}"],
-                    iterate_over = "linkstamp_paths",
-                ),
-                flag_group(
-                    expand_if_available = "libraries_to_link",
-                    flag_groups = [
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["-Wl,--start-lib"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flag_groups = [
-                                flag_group(
-                                    expand_if_false = "libraries_to_link.is_whole_archive",
-                                    flags = ["%{libraries_to_link.object_files}"],
-                                ),
-                                flag_group(
-                                    expand_if_true = "libraries_to_link.is_whole_archive",
-                                    flags = ["-Wl,-force_load,%{libraries_to_link.object_files}"],
-                                ),
-                            ],
-                            iterate_over = "libraries_to_link.object_files",
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file",
-                            ),
-                            flag_groups = [
-                                flag_group(
-                                    expand_if_false = "libraries_to_link.is_whole_archive",
-                                    flags = ["%{libraries_to_link.name}"],
-                                ),
-                                flag_group(
-                                    expand_if_true = "libraries_to_link.is_whole_archive",
-                                    flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
-                                ),
-                            ],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "interface_library",
-                            ),
-                            flag_groups = [
-                                flag_group(
-                                    expand_if_false = "libraries_to_link.is_whole_archive",
-                                    flags = ["%{libraries_to_link.name}"],
-                                ),
-                                flag_group(
-                                    expand_if_true = "libraries_to_link.is_whole_archive",
-                                    flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
-                                ),
-                            ],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "static_library",
-                            ),
-                            flag_groups = [
-                                flag_group(
-                                    expand_if_false = "libraries_to_link.is_whole_archive",
-                                    flags = ["%{libraries_to_link.name}"],
-                                ),
-                                flag_group(
-                                    expand_if_true = "libraries_to_link.is_whole_archive",
-                                    flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
-                                ),
-                            ],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "dynamic_library",
-                            ),
-                            flags = ["-l%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "versioned_dynamic_library",
-                            ),
-                            flags = ["-l:%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_true = "libraries_to_link.is_whole_archive",
-                            flag_groups = [
-                                flag_group(
-                                    expand_if_false = "macos_flags",
-                                    flags = ["-Wl,-no-whole-archive"],
-                                ),
-                            ],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["-Wl,--end-lib"],
-                        ),
-                    ],
-                    iterate_over = "libraries_to_link",
-                ),
-                # Note that the params file comes at the end, after the
-                # libraries to link above.
-                flag_group(
-                    expand_if_available = "linker_param_file",
-                    flags = ["@%{linker_param_file}"],
-                ),
-            ],
-        )],
-    )
-
-    # Place user provided compile flags after all the features so that these
-    # flags can override or customize behavior. The only thing user flags
-    # cannot override is the output file as Bazel depends on that.
-    #
-    # Finally, place the source file (if present) and output file last to make
-    # reading the compile command lines easier for humans.
-    final_flags_feature = feature(
-        name = "final_flags",
-        enabled = True,
-        flag_sets = [
-            flag_set(
-                actions = all_compile_actions,
-                flag_groups = [
-                    flag_group(
-                        expand_if_available = "user_compile_flags",
-                        flags = ["%{user_compile_flags}"],
-                        iterate_over = "user_compile_flags",
-                    ),
-                    flag_group(
-                        expand_if_available = "source_file",
-                        flags = ["%{source_file}"],
-                    ),
-                    flag_group(
-                        expand_if_available = "output_file",
-                        flags = ["-o", "%{output_file}"],
-                    ),
-                ],
-            ),
-            flag_set(
-                actions = all_link_actions,
-                flag_groups = [
-                    flag_group(
-                        expand_if_available = "user_link_flags",
-                        flags = ["%{user_link_flags}"],
-                        iterate_over = "user_link_flags",
-                    ),
-                    flag_group(
-                        expand_if_available = "output_execpath",
-                        flags = ["-o", "%{output_execpath}"],
-                    ),
-                ],
-            ),
-        ],
-    )
-
-    # Archive actions have an entirely independent set of flags and don't
-    # interact with either compiler or link actions.
-    default_archiver_flags_feature = feature(
-        name = "default_archiver_flags",
-        enabled = True,
-        flag_sets = [flag_set(
-            actions = [ACTION_NAMES.cpp_link_static_library],
-            flag_groups = [
-                flag_group(flags = ["rcsD"]),
-                flag_group(
-                    expand_if_available = "output_execpath",
-                    flags = ["%{output_execpath}"],
-                ),
-                flag_group(
-                    expand_if_available = "libraries_to_link",
-                    flag_groups = [
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file",
-                            ),
-                            flags = ["%{libraries_to_link.name}"],
-                        ),
-                        flag_group(
-                            expand_if_equal = variable_with_value(
-                                name = "libraries_to_link.type",
-                                value = "object_file_group",
-                            ),
-                            flags = ["%{libraries_to_link.object_files}"],
-                            iterate_over = "libraries_to_link.object_files",
-                        ),
-                    ],
-                    iterate_over = "libraries_to_link",
-                ),
-                flag_group(
-                    expand_if_available = "linker_param_file",
-                    flags = ["@%{linker_param_file}"],
-                ),
-            ],
-        )],
-    )
+    # The order of the features determines the relative order of flags used.
+    features = []
+    features += base_features
+    features.append(default_flags_feature)
+    features += sanitizer_features
 
-    # Now that we have built up the constituent feature definitions, compose
-    # them, including configuration based on the target platform.
+    features += optimization_features
 
-    # First, define features that are simply used to configure others.
-    features = [
-        feature(name = "dbg"),
-        feature(name = "fastbuild"),
-        feature(name = "host"),
-        feature(name = "no_legacy_features"),
-        feature(name = "opt"),
-        feature(name = "supports_dynamic_linker", enabled = ctx.attr.target_os == "linux"),
-        feature(name = "supports_pic", enabled = True),
-        feature(name = "supports_start_end_lib", enabled = ctx.attr.target_os == "linux"),
+    # TODO: Refactor target-specific feature management to be part of
+    # `optimization_features`.
+    if ctx.attr.target_cpu in ["aarch64", "arm64"]:
+        features.append(aarch64_cpu_flags)
+    else:
+        features.append(x86_64_cpu_flags)
 
-        # Enable split debug info whenever debug info is requested.
-        feature(
-            name = "per_object_debug_info",
-            enabled = True,
-            # This has to be directly conditioned on requesting debug info at
-            # all, otherwise Bazel will look for an extra output file and not
-            # find one.
-            requires = [feature_set(features = ["debug_info_flags"])],
-        ),
-    ]
-
-    # The order of the features determines the relative order of flags used.
-    # Start off adding the baseline features.
-    features += [
-        default_flags_feature,
-        minimal_optimization_flags,
-        default_optimization_flags,
-        minimal_debug_info_flags,
-        debug_info_flags,
-        debug_info_compression_flags,
-        debugger_flags,
-        lldb_flags,
-        gdb_flags,
-        enable_in_dbg,
-        preserve_call_stacks,
-        sysroot_feature,
-        sanitizer_common_flags,
-        asan,
-        asan_min_size,
-        enable_in_fastbuild,
-        fuzzer,
-        layering_check,
-        module_maps,
-        use_module_maps,
-        default_archiver_flags_feature,
-    ]
+    features += modules_features
+    features += debugging_features
 
     # Next, add the features based on the target platform. Here too the
-    # features are order sensitive. We also setup the sysroot here.
+    # features are order sensitive.
     if ctx.attr.target_os == "linux":
         features.append(sanitizer_static_lib_flags)
         features.append(linux_flags_feature)
@@ -1095,23 +410,47 @@ def _impl(ctx):
     else:
         fail("Unsupported target OS!")
 
-    if ctx.attr.target_cpu in ["aarch64", "arm64"]:
-        features.append(aarch64_cpu_flags)
-    else:
-        features.append(x86_64_cpu_flags)
+    # Next, append the libraries to link.
+    features += linking_features
 
-    # Finally append the libraries to link and any final flags.
+    # TODO: Refactor the target-specific feature management here to be part of
+    # building `linking_features`.
+    features += [
+        feature(name = "supports_dynamic_linker", enabled = ctx.attr.target_os == "linux"),
+        feature(name = "supports_start_end_lib", enabled = ctx.attr.target_os == "linux"),
+    ]
     if ctx.attr.target_os == "macos":
         features.append(macos_link_libraries_feature)
     else:
         features.append(default_link_libraries_feature)
-    features.append(final_flags_feature)
+
+    # Lastly, we add a feature that enables others in the default `fastbuild`
+    # mode. This is also a good place to add any project-specific features.
+    features.append(enable_in_fastbuild)
+
+    # Add user flags and the output flags at the end.
+    features.append(user_flags_feature)
+    features.append(output_flags_feature)
+    return features
+
+def _impl(ctx):
+    # TODO: See if this can be refactored into platform features.
+    if ctx.attr.target_os == "linux":
+        sysroot = None
+    elif ctx.attr.target_os == "windows":
+        sysroot = None
+    elif ctx.attr.target_os == "macos":
+        sysroot = sysroot_dir
+    elif ctx.attr.target_os == "freebsd":
+        sysroot = sysroot_dir
+    else:
+        fail("Unsupported target OS!")
 
     identifier = "local-{0}-{1}".format(ctx.attr.target_cpu, ctx.attr.target_os)
     return cc_common.create_cc_toolchain_config_info(
         ctx = ctx,
-        features = features,
-        action_configs = action_configs,
+        features = _build_features(ctx),
+        action_configs = llvm_action_configs(llvm_bindir, clang_bindir),
         cxx_builtin_include_directories = clang_include_dirs_list + [
             # Add Clang's resource directory to the end of the builtin include
             # directories to cover the use of sanitizer resource files by the
@@ -1138,7 +477,7 @@ def _impl(ctx):
         abi_libc_version = "local",
 
         # We do have to pass in our tool paths.
-        tool_paths = tool_paths,
+        tool_paths = llvm_tool_paths(llvm_bindir, clang_bindir),
     )
 
 cc_toolchain_config = rule(

+ 14 - 0
bazel/cc_toolchains/clang_configuration.bzl

@@ -174,6 +174,8 @@ def _configure_clang_toolchain_impl(repository_ctx):
         repository_ctx.attr._clang_cc_toolchain_config,
         "cc_toolchain_config.bzl",
     )
+    for file_label in repository_ctx.attr._clang_toolchain_files:
+        repository_ctx.symlink(file_label, file_label.name)
 
     # Find a Clang C++ compiler, and where it lives. We need to walk symlinks
     # here as the other LLVM tools may not be symlinked into the PATH even if
@@ -258,6 +260,18 @@ configure_clang_toolchain = repository_rule(
             default = Label("//bazel/cc_toolchains:clang_toolchain.BUILD"),
             allow_single_file = True,
         ),
+        "_clang_toolchain_files": attr.label_list(
+            default = [
+                Label("//bazel/cc_toolchains:cc_toolchain_actions.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_base_features.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_debugging.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_linking.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_modules.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_optimization.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_sanitizer_features.bzl"),
+                Label("//bazel/cc_toolchains:cc_toolchain_tools.bzl"),
+            ],
+        ),
     },
     environ = ["CC"],
 )