Allow cc_toolchain_info rule to be used as a parameter to pass into native.cc_toolchain(config = ...)

BEGIN_PUBLIC
Allow cc_toolchain_info rule to be used as a parameter to pass into native.cc_toolchain(config = ...)
END_PUBLIC

PiperOrigin-RevId: 613000772
Change-Id: I8348e2cbb4aa7d0a523341dcaf1e2c2bc647f640
diff --git a/cc/toolchains/impl/legacy_converter.bzl b/cc/toolchains/impl/legacy_converter.bzl
new file mode 100644
index 0000000..a216f66
--- /dev/null
+++ b/cc/toolchains/impl/legacy_converter.bzl
@@ -0,0 +1,190 @@
+# 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.
+"""Conversion helper functions to legacy cc_toolchain_config_info."""
+
+load(
+    "//cc:cc_toolchain_config_lib.bzl",
+    legacy_action_config = "action_config",
+    legacy_env_entry = "env_entry",
+    legacy_env_set = "env_set",
+    legacy_feature = "feature",
+    legacy_feature_set = "feature_set",
+    legacy_flag_group = "flag_group",
+    legacy_flag_set = "flag_set",
+    legacy_tool = "tool",
+    legacy_with_feature_set = "with_feature_set",
+)
+load(
+    "//cc/toolchains:cc_toolchain_info.bzl",
+    "ArgsListInfo",
+    "FeatureInfo",
+)
+
+visibility([
+    "//cc/toolchains/...",
+    "//tests/rule_based_toolchain/...",
+])
+
+# Note that throughout this file, we sort anything for which the order is
+# nondeterministic (eg. depset's .to_list(), dictionary iteration).
+# This allows our tests to call equals() on the output,
+# and *may* provide better caching properties.
+
+def _convert_actions(actions):
+    return sorted([action.name for action in actions.to_list()])
+
+def convert_feature_constraint(constraint):
+    return legacy_with_feature_set(
+        features = sorted([ft.name for ft in constraint.all_of.to_list()]),
+        not_features = sorted([ft.name for ft in constraint.none_of.to_list()]),
+    )
+
+def _convert_add_arg(add_arg):
+    return [legacy_flag_group(flags = list(add_arg.args))]
+
+def _convert_args(args):
+    actions = _convert_actions(args.actions)
+    with_features = [
+        convert_feature_constraint(fc)
+        for fc in args.requires_any_of
+    ]
+
+    flag_sets = []
+    if args.args:
+        flag_groups = []
+        for add_args in args.args:
+            flag_groups.extend(_convert_add_arg(add_args))
+        flag_sets.append(legacy_flag_set(
+            actions = actions,
+            with_features = with_features,
+            flag_groups = flag_groups,
+        ))
+
+    env_sets = []
+    if args.env:
+        env_sets.append(legacy_env_set(
+            actions = actions,
+            with_features = with_features,
+            env_entries = [
+                legacy_env_entry(
+                    key = key,
+                    value = value,
+                )
+                for key, value in args.env.items()
+            ],
+        ))
+    return struct(
+        flag_sets = flag_sets,
+        env_sets = env_sets,
+    )
+
+def _convert_args_sequence(args_sequence):
+    flag_sets = []
+    env_sets = []
+    for args in args_sequence:
+        legacy_args = _convert_args(args)
+        flag_sets.extend(legacy_args.flag_sets)
+        env_sets.extend(legacy_args.env_sets)
+
+    return struct(flag_sets = flag_sets, env_sets = env_sets)
+
+def convert_feature(feature):
+    if feature.external:
+        return None
+
+    args = _convert_args_sequence(feature.args.args)
+
+    return legacy_feature(
+        name = feature.name,
+        enabled = feature.enabled,
+        flag_sets = args.flag_sets,
+        env_sets = args.env_sets,
+        implies = sorted([ft.name for ft in feature.implies.to_list()]),
+        requires = [
+            legacy_feature_set(sorted([
+                feature.name
+                for feature in requirement.features.to_list()
+            ]))
+            for requirement in feature.requires_any_of
+        ],
+        provides = [
+            mutex.name
+            for mutex in feature.mutually_exclusive
+        ],
+    )
+
+def convert_tool(tool):
+    return legacy_tool(
+        tool = tool.exe,
+        execution_requirements = list(tool.execution_requirements),
+        with_features = [
+            convert_feature_constraint(fc)
+            for fc in tool.requires_any_of
+        ],
+    )
+
+def _convert_action_type_config(atc):
+    implies = sorted([ft.name for ft in atc.implies.to_list()])
+    if atc.args:
+        implies.append("implied_by_%s" % atc.action_type.name)
+
+    return legacy_action_config(
+        action_name = atc.action_type.name,
+        enabled = True,
+        tools = [convert_tool(tool) for tool in atc.tools],
+        implies = implies,
+    )
+
+def convert_toolchain(toolchain):
+    """Converts a rule-based toolchain into the legacy providers.
+
+    Args:
+        toolchain: CcToolchainConfigInfo: The toolchain config to convert.
+    Returns:
+        A struct containing parameters suitable to pass to
+          cc_common.create_cc_toolchain_config_info.
+    """
+    features = [convert_feature(feature) for feature in toolchain.features]
+    features.append(convert_feature(FeatureInfo(
+        # We reserve names starting with implied_by. This ensures we don't
+        # conflict with the name of a feature the user creates.
+        name = "implied_by_always_enabled",
+        enabled = True,
+        args = ArgsListInfo(args = toolchain.args),
+        implies = depset([]),
+        requires_any_of = [],
+        mutually_exclusive = [],
+        external = False,
+    )))
+    action_configs = []
+    for atc in toolchain.action_type_configs.values():
+        # Action configs don't take in an env like they do a flag set.
+        # In order to support them, we create a feature with the env that the action
+        # config will enable, and imply it in the action config.
+        if atc.args:
+            features.append(convert_feature(FeatureInfo(
+                name = "implied_by_%s" % atc.action_type.name,
+                enabled = False,
+                args = ArgsListInfo(args = atc.args),
+                implies = depset([]),
+                requires_any_of = [],
+                mutually_exclusive = [],
+                external = False,
+            )))
+        action_configs.append(_convert_action_type_config(atc))
+
+    return struct(
+        features = sorted([ft for ft in features if ft != None], key = lambda ft: ft.name),
+        action_configs = sorted(action_configs, key = lambda ac: ac.action_name),
+    )
diff --git a/cc/toolchains/impl/toolchain_config.bzl b/cc/toolchains/impl/toolchain_config.bzl
index 551b0d0..185f242 100644
--- a/cc/toolchains/impl/toolchain_config.bzl
+++ b/cc/toolchains/impl/toolchain_config.bzl
@@ -23,6 +23,7 @@
     "ToolchainConfigInfo",
 )
 load(":collect.bzl", "collect_action_types")
+load(":legacy_converter.bzl", "convert_toolchain")
 load(":toolchain_config_info.bzl", "toolchain_config_info")
 
 visibility([
@@ -61,21 +62,54 @@
         args = ctx.attr.args,
     )
 
+    legacy = convert_toolchain(toolchain_config)
+
     return [
-        # TODO: Transform toolchain_config into legacy cc_toolchain_config_info
         toolchain_config,
+        cc_common.create_cc_toolchain_config_info(
+            ctx = ctx,
+            action_configs = legacy.action_configs,
+            features = legacy.features,
+            cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories,
+            toolchain_identifier = ctx.attr.toolchain_identifier,
+            target_system_name = ctx.attr.target_system_name,
+            target_cpu = ctx.attr.target_cpu,
+            target_libc = ctx.attr.target_libc,
+            compiler = ctx.attr.compiler,
+            abi_version = ctx.attr.abi_version,
+            abi_libc_version = ctx.attr.abi_libc_version,
+            builtin_sysroot = ctx.attr.sysroot or None,
+        ),
     ]
 
 cc_toolchain_config = rule(
     implementation = _cc_toolchain_config_impl,
     # @unsorted-dict-items
     attrs = {
+        # Attributes new to this rule.
         "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"),
+
+        # Attributes from create_cc_toolchain_config_info.
+        # artifact_name_patterns is currently unused. Consider adding it later.
+        # TODO: Consider making this into a label_list that takes a
+        #  cc_directory_marker rule as input.
+        "cxx_builtin_include_directories": attr.string_list(),
+        "toolchain_identifier": attr.string(mandatory = True),
+        "target_system_name": attr.string(mandatory = True),
+        "target_cpu": attr.string(mandatory = True),
+        "target_libc": attr.string(mandatory = True),
+        "compiler": attr.string(mandatory = True),
+        "abi_version": attr.string(),
+        "abi_libc_version": attr.string(),
+        # tool_paths currently unused.
+        # TODO: Consider making this into a label that takes a
+        #  cc_directory_marker rule as an input.
+        "sysroot": attr.string(),
     },
     provides = [ToolchainConfigInfo],
 )
diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl
index 01ea481..797ef52 100644
--- a/cc/toolchains/tool.bzl
+++ b/cc/toolchains/tool.bzl
@@ -70,7 +70,7 @@
             doc = "Additional files that are required for this tool to run.",
         ),
         "execution_requirements": attr.string_list(
-            doc = "A list of strings that provide hints for execution environment compatibility (e.g. `requires-mac`).",
+            doc = "A list of strings that provide hints for execution environment compatibility (e.g. `requires-network`).",
         ),
         "requires_any_of": attr.label_list(
             providers = [FeatureConstraintInfo],
diff --git a/tests/rule_based_toolchain/features/BUILD b/tests/rule_based_toolchain/features/BUILD
index 1d83660..c7ea334 100644
--- a/tests/rule_based_toolchain/features/BUILD
+++ b/tests/rule_based_toolchain/features/BUILD
@@ -73,6 +73,7 @@
     name = "direct_constraint",
     all_of = [":simple"],
     none_of = [":simple2"],
+    visibility = ["//tests/rule_based_toolchain:__subpackages__"],
 )
 
 util.helper_target(
diff --git a/tests/rule_based_toolchain/features/features_test.bzl b/tests/rule_based_toolchain/features/features_test.bzl
index 3406bdd..f0b15fd 100644
--- a/tests/rule_based_toolchain/features/features_test.bzl
+++ b/tests/rule_based_toolchain/features/features_test.bzl
@@ -11,9 +11,15 @@
 # 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.
-"""Tests for actions for the rule based toolchain."""
+"""Tests for features for the rule based toolchain."""
 
 load(
+    "//cc:cc_toolchain_config_lib.bzl",
+    legacy_feature_set = "feature_set",
+    legacy_flag_group = "flag_group",
+    legacy_flag_set = "flag_set",
+)
+load(
     "//cc/toolchains:cc_toolchain_info.bzl",
     "ArgsInfo",
     "FeatureConstraintInfo",
@@ -21,6 +27,11 @@
     "FeatureSetInfo",
     "MutuallyExclusiveCategoryInfo",
 )
+load(
+    "//cc/toolchains/impl:legacy_converter.bzl",
+    "convert_feature",
+    "convert_feature_constraint",
+)
 
 visibility("private")
 
@@ -41,13 +52,28 @@
     c_compile_action.files().contains_exactly([_C_COMPILE_FILE])
     c_compile_action.args().contains_exactly([targets.c_compile[ArgsInfo]])
 
+    legacy = convert_feature(simple.actual)
+    env.expect.that_str(legacy.name).equals("feature_name")
+    env.expect.that_bool(legacy.enabled).equals(False)
+    env.expect.that_collection(legacy.flag_sets).contains_exactly([
+        legacy_flag_set(
+            actions = ["c_compile"],
+            with_features = [],
+            flag_groups = [legacy_flag_group(flags = ["c"])],
+        ),
+    ])
+
 def _feature_collects_requirements_test(env, targets):
-    env.expect.that_target(targets.requires).provider(
-        FeatureInfo,
-    ).requires_any_of().contains_exactly([
+    ft = env.expect.that_target(targets.requires).provider(FeatureInfo)
+    ft.requires_any_of().contains_exactly([
         targets.feature_set.label,
     ])
 
+    legacy = convert_feature(ft.actual)
+    env.expect.that_collection(legacy.requires).contains_exactly([
+        legacy_feature_set(features = ["feature_name", "simple2"]),
+    ])
+
 def _feature_collects_implies_test(env, targets):
     env.expect.that_target(targets.implies).provider(
         FeatureInfo,
@@ -93,10 +119,24 @@
         targets.implies.label,
     ])
 
+    legacy = convert_feature_constraint(constraint.actual)
+    env.expect.that_collection(legacy.features).contains_exactly([
+        "feature_name",
+        "requires",
+    ])
+    env.expect.that_collection(legacy.not_features).contains_exactly([
+        "simple2",
+        "implies",
+    ])
+
 def _external_feature_is_a_feature_test(env, targets):
-    env.expect.that_target(targets.builtin_feature).provider(
+    external_feature = env.expect.that_target(targets.builtin_feature).provider(
         FeatureInfo,
-    ).name().equals("builtin_feature")
+    )
+    external_feature.name().equals("builtin_feature")
+
+    # It's not a string, but we don't have a factory for the type.
+    env.expect.that_str(convert_feature(external_feature.actual)).equals(None)
 
 def _feature_can_be_overridden_test(env, targets):
     overrides = env.expect.that_target(targets.overrides).provider(FeatureInfo)
diff --git a/tests/rule_based_toolchain/tool/BUILD b/tests/rule_based_toolchain/tool/BUILD
index de026f8..31c2813 100644
--- a/tests/rule_based_toolchain/tool/BUILD
+++ b/tests/rule_based_toolchain/tool/BUILD
@@ -8,7 +8,8 @@
     name = "tool",
     data = ["//tests/rule_based_toolchain/testdata:bin"],
     executable = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
-    execution_requirements = ["requires-mac"],
+    execution_requirements = ["requires-network"],
+    requires_any_of = ["//tests/rule_based_toolchain/features:direct_constraint"],
 )
 
 util.helper_target(
diff --git a/tests/rule_based_toolchain/tool/tool_test.bzl b/tests/rule_based_toolchain/tool/tool_test.bzl
index 840ae84..8e9b68a 100644
--- a/tests/rule_based_toolchain/tool/tool_test.bzl
+++ b/tests/rule_based_toolchain/tool/tool_test.bzl
@@ -13,8 +13,13 @@
 # limitations under the License.
 """Tests for the cc_args rule."""
 
+load(
+    "//cc:cc_toolchain_config_lib.bzl",
+    legacy_with_feature_set = "with_feature_set",
+)
 load("//cc/toolchains:cc_toolchain_info.bzl", "ToolInfo")
 load("//cc/toolchains/impl:collect.bzl", _collect_tools = "collect_tools")
+load("//cc/toolchains/impl:legacy_converter.bzl", "convert_tool")
 load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
 
 visibility("private")
@@ -35,10 +40,22 @@
 def _tool_test(env, targets):
     tool = env.expect.that_target(targets.tool).provider(ToolInfo)
     tool.exe().short_path_equals(_BIN_WRAPPER)
+    tool.execution_requirements().contains_exactly(["requires-network"])
     tool.runfiles().contains_exactly([
         _BIN_WRAPPER,
         _BIN,
     ])
+    tool.requires_any_of().contains_exactly([targets.direct_constraint.label])
+
+    legacy = convert_tool(tool.actual)
+    env.expect.that_file(legacy.tool).equals(tool.actual.exe)
+    env.expect.that_collection(legacy.execution_requirements).contains_exactly(["requires-network"])
+    env.expect.that_collection(legacy.with_features).contains_exactly([
+        legacy_with_feature_set(
+            features = ["feature_name"],
+            not_features = ["simple2"],
+        ),
+    ])
 
 def _wrapped_tool_includes_runfiles_test(env, targets):
     tool = env.expect.that_target(targets.wrapped_tool).provider(ToolInfo)
@@ -86,6 +103,7 @@
     ).err()
 
 TARGETS = [
+    "//tests/rule_based_toolchain/features:direct_constraint",
     "//tests/rule_based_toolchain/tool:tool",
     "//tests/rule_based_toolchain/tool:wrapped_tool",
     "//tests/rule_based_toolchain/testdata:bin_wrapper",
diff --git a/tests/rule_based_toolchain/toolchain_config/BUILD b/tests/rule_based_toolchain/toolchain_config/BUILD
index 56a0866..f7f194d 100644
--- a/tests/rule_based_toolchain/toolchain_config/BUILD
+++ b/tests/rule_based_toolchain/toolchain_config/BUILD
@@ -34,12 +34,25 @@
 )
 
 util.helper_target(
+    cc_args,
+    name = "cpp_compile_args",
+    actions = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+    args = ["cpp_compile_args"],
+    env = {"CPP_COMPILE": "1"},
+)
+
+util.helper_target(
     cc_toolchain_config,
     name = "collects_files_toolchain_config",
     action_type_configs = [":compile_config"],
     args = [":c_compile_args"],
+    compiler = "gcc-4.1.1",
     skip_experimental_flag_validation_for_test = True,
+    target_cpu = "k8",
+    target_libc = "glibc-2.2.2",
+    target_system_name = "local",
     toolchain_features = [":compile_feature"],
+    toolchain_identifier = "collects_files_toolchain",
 )
 
 util.helper_target(
@@ -68,8 +81,9 @@
     cc_action_type_config,
     name = "compile_config",
     action_types = ["//tests/rule_based_toolchain/actions:all_compile"],
+    args = [":cpp_compile_args"],
     tools = [
-        "//tests/rule_based_toolchain/testdata:bin",
+        "//tests/rule_based_toolchain/tool:wrapped_tool",
     ],
 )
 
@@ -87,7 +101,7 @@
     action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
     implies = [":simple_feature"],
     tools = [
-        "//tests/rule_based_toolchain/testdata:bin",
+        "//tests/rule_based_toolchain/tool:wrapped_tool",
     ],
 )
 
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 83e453f..e188772 100644
--- a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
+++ b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
@@ -11,9 +11,20 @@
 # 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.
-"""Tests for the cc_args rule."""
+"""Tests for the cc_toolchain_config rule."""
 
+load(
+    "//cc:cc_toolchain_config_lib.bzl",
+    legacy_action_config = "action_config",
+    legacy_env_entry = "env_entry",
+    legacy_env_set = "env_set",
+    legacy_feature = "feature",
+    legacy_flag_group = "flag_group",
+    legacy_flag_set = "flag_set",
+    legacy_tool = "tool",
+)
 load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "ToolchainConfigInfo")
+load("//cc/toolchains/impl:legacy_converter.bzl", "convert_toolchain")
 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")
 
@@ -24,6 +35,7 @@
 _COLLECTED_CPP_COMPILE_FILES = [
     # From :compile_config's tool
     "tests/rule_based_toolchain/testdata/bin",
+    "tests/rule_based_toolchain/testdata/bin_wrapper",
     # From :compile_feature's args
     "tests/rule_based_toolchain/testdata/file2",
 ]
@@ -189,6 +201,61 @@
         targets.collects_files_cpp_compile,
     ).default_outputs().contains_exactly(_COLLECTED_CPP_COMPILE_FILES)
 
+    legacy = convert_toolchain(tc.actual)
+    env.expect.that_collection(legacy.features).contains_exactly([
+        legacy_feature(
+            name = "compile_feature",
+            enabled = True,
+            flag_sets = [legacy_flag_set(
+                actions = ["c_compile", "cpp_compile"],
+                flag_groups = [
+                    legacy_flag_group(flags = ["compile_args"]),
+                ],
+            )],
+        ),
+        legacy_feature(
+            name = "implied_by_always_enabled",
+            enabled = True,
+            flag_sets = [legacy_flag_set(
+                actions = ["c_compile"],
+                flag_groups = [
+                    legacy_flag_group(flags = ["c_compile_args"]),
+                ],
+            )],
+        ),
+        legacy_feature(
+            name = "implied_by_cpp_compile",
+            enabled = False,
+            flag_sets = [legacy_flag_set(
+                actions = ["cpp_compile"],
+                flag_groups = [
+                    legacy_flag_group(flags = ["cpp_compile_args"]),
+                ],
+            )],
+            env_sets = [legacy_env_set(
+                actions = ["cpp_compile"],
+                env_entries = [legacy_env_entry(key = "CPP_COMPILE", value = "1")],
+            )],
+        ),
+    ]).in_order()
+
+    exe = tc.action_type_configs().get(
+        targets.c_compile[ActionTypeInfo],
+    ).actual.tools[0].exe
+    env.expect.that_collection(legacy.action_configs).contains_exactly([
+        legacy_action_config(
+            action_name = "c_compile",
+            enabled = True,
+            tools = [legacy_tool(tool = exe)],
+        ),
+        legacy_action_config(
+            action_name = "cpp_compile",
+            enabled = True,
+            tools = [legacy_tool(tool = exe)],
+            implies = ["implied_by_cpp_compile"],
+        ),
+    ]).in_order()
+
 TARGETS = [
     "//tests/rule_based_toolchain/actions:c_compile",
     "//tests/rule_based_toolchain/actions:cpp_compile",