Implement cc_toolchain_config rule and cc_legacy_file_group rule.

BEGIN_PUBLIC
Implement cc_toolchain_config rule and cc_legacy_file_group rule.

Note that this rule is in the impl/ directory because we require users to use the cc_toolchain_config rule via the cc_toolchain macro that we will define later, to ensure that parameters such as `compile_files` are passed correctly.
END_PUBLIC

PiperOrigin-RevId: 612998387
Change-Id: I986d11775e368c4386a930ab2ce8663956a57f9d
diff --git a/cc/toolchains/BUILD b/cc/toolchains/BUILD
index c9b29fc..cde0cb5 100644
--- a/cc/toolchains/BUILD
+++ b/cc/toolchains/BUILD
@@ -11,3 +11,11 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+    name = "experimental_enable_rule_based_toolchains",
+    build_setting_default = False,
+    visibility = ["//visibility:public"],
+)
diff --git a/cc/toolchains/impl/toolchain_config.bzl b/cc/toolchains/impl/toolchain_config.bzl
new file mode 100644
index 0000000..551b0d0
--- /dev/null
+++ b/cc/toolchains/impl/toolchain_config.bzl
@@ -0,0 +1,81 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Implementation of the cc_toolchain rule."""
+
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load(
+    "//cc/toolchains:cc_toolchain_info.bzl",
+    "ActionTypeConfigSetInfo",
+    "ActionTypeSetInfo",
+    "ArgsListInfo",
+    "FeatureSetInfo",
+    "ToolchainConfigInfo",
+)
+load(":collect.bzl", "collect_action_types")
+load(":toolchain_config_info.bzl", "toolchain_config_info")
+
+visibility([
+    "//cc/toolchains/...",
+    "//tests/rule_based_toolchain/...",
+])
+
+def _cc_legacy_file_group_impl(ctx):
+    files = ctx.attr.config[ToolchainConfigInfo].files
+
+    return [DefaultInfo(files = depset(transitive = [
+        files[action]
+        for action in collect_action_types(ctx.attr.actions).to_list()
+        if action in files
+    ]))]
+
+cc_legacy_file_group = rule(
+    implementation = _cc_legacy_file_group_impl,
+    attrs = {
+        "actions": attr.label_list(providers = [ActionTypeSetInfo], mandatory = True),
+        "config": attr.label(providers = [ToolchainConfigInfo], mandatory = True),
+    },
+)
+
+def _cc_toolchain_config_impl(ctx):
+    if ctx.attr.features:
+        fail("Features is a reserved attribute in bazel. Did you mean 'toolchain_features'")
+
+    if not ctx.attr._enabled[BuildSettingInfo].value and not ctx.attr.skip_experimental_flag_validation_for_test:
+        fail("Rule based toolchains are experimental. To use it, please add --//cc/toolchains:experimental_enable_rule_based_toolchains to your bazelrc")
+
+    toolchain_config = toolchain_config_info(
+        label = ctx.label,
+        features = ctx.attr.toolchain_features + [ctx.attr._builtin_features],
+        action_type_configs = ctx.attr.action_type_configs,
+        args = ctx.attr.args,
+    )
+
+    return [
+        # TODO: Transform toolchain_config into legacy cc_toolchain_config_info
+        toolchain_config,
+    ]
+
+cc_toolchain_config = rule(
+    implementation = _cc_toolchain_config_impl,
+    # @unsorted-dict-items
+    attrs = {
+        "action_type_configs": attr.label_list(providers = [ActionTypeConfigSetInfo]),
+        "args": attr.label_list(providers = [ArgsListInfo]),
+        "toolchain_features": attr.label_list(providers = [FeatureSetInfo]),
+        "skip_experimental_flag_validation_for_test": attr.bool(default = False),
+        "_builtin_features": attr.label(default = "//cc/toolchains/features:all_builtin_features"),
+        "_enabled": attr.label(default = "//cc/toolchains:experimental_enable_rule_based_toolchains"),
+    },
+    provides = [ToolchainConfigInfo],
+)
diff --git a/tests/rule_based_toolchain/generics.bzl b/tests/rule_based_toolchain/generics.bzl
index 90dfa21..17bd3a6 100644
--- a/tests/rule_based_toolchain/generics.bzl
+++ b/tests/rule_based_toolchain/generics.bzl
@@ -134,6 +134,6 @@
 dict_key_subject = lambda factory: lambda value, *, meta: struct(
     get = lambda key: factory(
         value[key],
-        meta = meta.derive(".get({})".format(key)),
+        meta = meta.derive("get({})".format(key)),
     ),
 )
diff --git a/tests/rule_based_toolchain/toolchain_config/BUILD b/tests/rule_based_toolchain/toolchain_config/BUILD
index 4517b6a..56a0866 100644
--- a/tests/rule_based_toolchain/toolchain_config/BUILD
+++ b/tests/rule_based_toolchain/toolchain_config/BUILD
@@ -5,6 +5,7 @@
 load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
 load("//cc/toolchains:tool.bzl", "cc_tool")
 load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
+load("//cc/toolchains/impl:toolchain_config.bzl", "cc_legacy_file_group", "cc_toolchain_config")
 load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
 load(":toolchain_config_test.bzl", "TARGETS", "TESTS")
 
@@ -33,6 +34,29 @@
 )
 
 util.helper_target(
+    cc_toolchain_config,
+    name = "collects_files_toolchain_config",
+    action_type_configs = [":compile_config"],
+    args = [":c_compile_args"],
+    skip_experimental_flag_validation_for_test = True,
+    toolchain_features = [":compile_feature"],
+)
+
+util.helper_target(
+    cc_legacy_file_group,
+    name = "collects_files_c_compile",
+    actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+    config = ":collects_files_toolchain_config",
+)
+
+util.helper_target(
+    cc_legacy_file_group,
+    name = "collects_files_cpp_compile",
+    actions = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+    config = ":collects_files_toolchain_config",
+)
+
+util.helper_target(
     cc_args,
     name = "compile_args",
     actions = ["//tests/rule_based_toolchain/actions:all_compile"],
diff --git a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
index feee70a..83e453f 100644
--- a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
+++ b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
@@ -13,7 +13,7 @@
 # limitations under the License.
 """Tests for the cc_args rule."""
 
-load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo")
+load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "ToolchainConfigInfo")
 load("//cc/toolchains/impl:toolchain_config_info.bzl", _toolchain_config_info = "toolchain_config_info")
 load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
 
@@ -21,6 +21,18 @@
 
 toolchain_config_info = result_fn_wrapper(_toolchain_config_info)
 
+_COLLECTED_CPP_COMPILE_FILES = [
+    # From :compile_config's tool
+    "tests/rule_based_toolchain/testdata/bin",
+    # From :compile_feature's args
+    "tests/rule_based_toolchain/testdata/file2",
+]
+
+_COLLECTED_C_COMPILE_FILES = _COLLECTED_CPP_COMPILE_FILES + [
+    # From :c_compile_args
+    "tests/rule_based_toolchain/testdata/file1",
+]
+
 def _expect_that_toolchain(env, expr = None, **kwargs):
     return env.expect.that_value(
         value = toolchain_config_info(label = Label("//:toolchain"), **kwargs),
@@ -164,32 +176,27 @@
     )
 
 def _toolchain_collects_files_test(env, targets):
-    tc = _expect_that_toolchain(
-        env,
-        args = [targets.c_compile_args],
-        action_type_configs = [targets.compile_config],
-        features = [targets.compile_feature],
-    ).ok()
-    tc.files().get(targets.c_compile[ActionTypeInfo]).contains_exactly([
-        # From :compile_config's tool
-        "tests/rule_based_toolchain/testdata/bin",
-        # From :c_compile_args
-        "tests/rule_based_toolchain/testdata/file1",
-        # From :compile_feature's args
-        "tests/rule_based_toolchain/testdata/file2",
-    ])
-    tc.files().get(targets.cpp_compile[ActionTypeInfo]).contains_exactly([
-        # From :compile_config's tool
-        "tests/rule_based_toolchain/testdata/bin",
-        # From :compile_feature's args
-        "tests/rule_based_toolchain/testdata/file2",
-    ])
+    tc = env.expect.that_target(
+        targets.collects_files_toolchain_config,
+    ).provider(ToolchainConfigInfo)
+    tc.files().get(targets.c_compile[ActionTypeInfo]).contains_exactly(_COLLECTED_C_COMPILE_FILES)
+    tc.files().get(targets.cpp_compile[ActionTypeInfo]).contains_exactly(_COLLECTED_CPP_COMPILE_FILES)
+
+    env.expect.that_target(
+        targets.collects_files_c_compile,
+    ).default_outputs().contains_exactly(_COLLECTED_C_COMPILE_FILES)
+    env.expect.that_target(
+        targets.collects_files_cpp_compile,
+    ).default_outputs().contains_exactly(_COLLECTED_CPP_COMPILE_FILES)
 
 TARGETS = [
     "//tests/rule_based_toolchain/actions:c_compile",
     "//tests/rule_based_toolchain/actions:cpp_compile",
     ":builtin_feature",
     ":compile_config",
+    ":collects_files_c_compile",
+    ":collects_files_cpp_compile",
+    ":collects_files_toolchain_config",
     ":compile_feature",
     ":c_compile_args",
     ":c_compile_config",