Add 'toolchain_config' attribute to cc_toolchain to enable replacing CROSSTOOL with a skylark rule

Work towards issue #5380.

RELNOTES: None.
PiperOrigin-RevId: 211793570
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index be06a5d..7318467 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -1918,4 +1918,9 @@
   public ImmutableSet<String> getReservedActionMnemonics() {
     return reservedActionMnemonics;
   }
+
+  public boolean disableLateBoundOptionDefaults() {
+    return options.incompatibleDisableLateBoundOptionDefaults
+        || !options.useLateBoundOptionDefaults;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index 8fbb804..15bd164 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -847,6 +847,13 @@
     if (!config.enableCcToolchainConfigInfoFromSkylark()) {
       throw new InvalidConfigurationException("Creating a CcToolchainConfigInfo is not enabled.");
     }
+    if (!skylarkRuleContext.getConfiguration().disableLateBoundOptionDefaults()
+        || !config.disableMakeVariables()) {
+      throw new InvalidConfigurationException(
+          "--incompatible_disable_late_bound_option_defaults and "
+              + "--incompatible_disable_cc_configuration_make_variables must be set to true in "
+              + "order to configure the C++ toolchain from Starlark.");
+    }
 
     ImmutableList.Builder<Feature> featureBuilder = ImmutableList.builder();
     for (Object feature : features) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
index b6eae3c..e03c69d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -52,6 +52,7 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.License;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
 import com.google.devtools.build.lib.rules.cpp.FdoProvider.FdoMode;
@@ -630,6 +631,36 @@
   private CppToolchainInfo getCppToolchainInfo(
       RuleContext ruleContext, CppConfiguration cppConfiguration) throws RuleErrorException {
 
+    if (cppConfiguration.enableCcToolchainConfigInfoFromSkylark()) {
+      // Attempt to obtain CppToolchainInfo from the 'toolchain_config' attribute of cc_toolchain.
+      CcToolchainConfigInfo configInfo =
+          ruleContext.getPrerequisite(
+              CcToolchainRule.TOOLCHAIN_CONFIG_ATTR, Mode.TARGET, CcToolchainConfigInfo.PROVIDER);
+      if (configInfo != null) {
+        try {
+          return CppToolchainInfo.create(
+              ruleContext.getRepository().getPathUnderExecRoot(),
+              ruleContext.getLabel(),
+              configInfo,
+              cppConfiguration.disableLegacyCrosstoolFields(),
+              cppConfiguration.disableCompilationModeFlags(),
+              cppConfiguration.disableLinkingModeFlags());
+        } catch (InvalidConfigurationException e) {
+          throw ruleContext.throwWithRuleError(e.getMessage());
+        }
+      } else {
+        ruleContext.attributeError(
+            CcToolchainRule.TOOLCHAIN_CONFIG_ATTR,
+            String.format(
+                "The target '%s' from '%s' attribute does not provide "
+                    + "a CcToolchainConfigInfo provider",
+                CcToolchainRule.TOOLCHAIN_CONFIG_ATTR,
+                ruleContext
+                    .attributes()
+                    .get(CcToolchainRule.TOOLCHAIN_CONFIG_ATTR, BuildType.LABEL)));
+      }
+    }
+
     // Attempt to find a toolchain based on the target attributes, not the configuration.
     CToolchain toolchain = getToolchainFromAttributes(ruleContext, cppConfiguration);
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainConfigInfo.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainConfigInfo.java
index 1213ee9..e08940e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainConfigInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainConfigInfo.java
@@ -535,55 +535,64 @@
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getOptCompilationModeCompilerFlags() {
-    return compilationModeCompilerFlags.get(CompilationMode.OPT);
+    ImmutableList<String> flags = compilationModeCompilerFlags.get(CompilationMode.OPT);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getOptCompilationModeCxxFlags() {
-    return compilationModeCxxFlags.get(CompilationMode.OPT);
+    ImmutableList<String> flags = compilationModeCxxFlags.get(CompilationMode.OPT);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getOptCompilationModeLinkerFlags() {
-    return compilationModeLinkerFlags.get(CompilationMode.OPT);
+    ImmutableList<String> flags = compilationModeLinkerFlags.get(CompilationMode.OPT);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getDbgCompilationModeCompilerFlags() {
-    return compilationModeCompilerFlags.get(CompilationMode.DBG);
+    ImmutableList<String> flags = compilationModeCompilerFlags.get(CompilationMode.DBG);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getDbgCompilationModeCxxFlags() {
-    return compilationModeCxxFlags.get(CompilationMode.DBG);
+    ImmutableList<String> flags = compilationModeCxxFlags.get(CompilationMode.DBG);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getDbgCompilationModeLinkerFlags() {
-    return compilationModeLinkerFlags.get(CompilationMode.DBG);
+    ImmutableList<String> flags = compilationModeLinkerFlags.get(CompilationMode.DBG);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getFastbuildCompilationModeCompilerFlags() {
-    return compilationModeCompilerFlags.get(CompilationMode.FASTBUILD);
+    ImmutableList<String> flags = compilationModeCompilerFlags.get(CompilationMode.FASTBUILD);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getFastbuildCompilationModeCxxFlags() {
-    return compilationModeCxxFlags.get(CompilationMode.FASTBUILD);
+    ImmutableList<String> flags = compilationModeCxxFlags.get(CompilationMode.FASTBUILD);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
   @Deprecated
   public ImmutableList<String> getFastbuildCompilationModeLinkerFlags() {
-    return compilationModeLinkerFlags.get(CompilationMode.FASTBUILD);
+    ImmutableList<String> flags = compilationModeLinkerFlags.get(CompilationMode.FASTBUILD);
+    return flags == null ? ImmutableList.of() : flags;
   }
 
   // TODO(b/65151735): Remove once this field is migrated to features.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java
index aa13189..1f2d8ae 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainRule.java
@@ -44,6 +44,7 @@
   public static final String LIBC_TOP_ATTR = ":libc_top";
   public static final String FDO_OPTIMIZE_ATTR = ":fdo_optimize";
   public static final String FDO_PROFILE_ATTR = ":fdo_profile";
+  public static final String TOOLCHAIN_CONFIG_ATTR = "toolchain_config";
 
   /**
    * Determines if the given target is a cc_toolchain or one of its subclasses. New subclasses
@@ -191,6 +192,10 @@
             attr("toolchain_identifier", Type.STRING)
                 .nonconfigurable("Used in configuration creation")
                 .value(""))
+        .add(
+            attr(TOOLCHAIN_CONFIG_ATTR, LABEL)
+                .allowedFileTypes()
+                .mandatoryProviders(CcToolchainConfigInfo.PROVIDER.id()))
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index a019a33..e3bb2ff 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -1166,6 +1166,10 @@
     return cppOptions.disableLinkingModeFlags;
   }
 
+  public boolean disableMakeVariables() {
+    return cppOptions.disableMakeVariables || !cppOptions.enableMakeVariables;
+  }
+
   public boolean enableLinkoptsInUserLinkFlags() {
     return cppOptions.enableLinkoptsInUserLinkFlags;
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainTest.java
index 8f0e8ec..ffa4fc8 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainTest.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.packages.util.MockCcSupport;
+import com.google.devtools.build.lib.packages.util.ResourceLoader;
 import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
 import com.google.devtools.build.lib.testutil.TestConstants;
@@ -778,6 +779,192 @@
     assertThat(toolchainProvider.getAbi()).isEqualTo("banana");
   }
 
+  private void loadCcToolchainConfigLib() throws IOException {
+    scratch.appendFile("tools/cpp/BUILD", "");
+    scratch.file(
+        "tools/cpp/cc_toolchain_config_lib.bzl",
+        ResourceLoader.readFromResources(
+            TestConstants.BAZEL_REPO_PATH + "tools/cpp/cc_toolchain_config_lib.bzl"));
+  }
+
+  @Test
+  public void testToolchainFromSkylarkRule() throws Exception {
+    loadCcToolchainConfigLib();
+    scratch.file(
+        "a/BUILD",
+        "load(':crosstool_rule.bzl', 'cc_toolchain_config_rule')",
+        "cc_toolchain_config_rule(name = 'toolchain_config')",
+        "filegroup(",
+        "   name='empty')",
+        "cc_toolchain(",
+        "    name = 'b',",
+        "    cpu = 'banana',",
+        "    all_files = ':empty',",
+        "    ar_files = ':empty',",
+        "    as_files = ':empty',",
+        "    compiler_files = ':empty',",
+        "    dwp_files = ':empty',",
+        "    linker_files = ':empty',",
+        "    strip_files = ':empty',",
+        "    objcopy_files = ':empty',",
+        "    dynamic_runtime_libs = [':empty'],",
+        "    static_runtime_libs = [':empty'],",
+        "    toolchain_config = ':toolchain_config')");
+
+    scratch.file(
+        "a/crosstool_rule.bzl",
+        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
+        "        'feature',",
+        "        'action_config',",
+        "        'artifact_name_pattern',",
+        "        'env_entry',",
+        "        'variable_with_value',",
+        "        'make_variable',",
+        "        'feature_set',",
+        "        'with_feature_set',",
+        "        'env_set',",
+        "        'flag_group',",
+        "        'flag_set',",
+        "        'tool_path',",
+        "        'tool')",
+        "",
+        "def _impl(ctx):",
+        "    return cc_common.create_cc_toolchain_config_info(",
+        "                ctx = ctx,",
+        "                features = [feature(name = 'simple_feature')],",
+        "                action_configs = [",
+        "                   action_config(action_name = 'simple_action', enabled=True)",
+        "                ],",
+        "                artifact_name_patterns = [artifact_name_pattern(",
+        "                   category_name = 'static_library',",
+        "                   prefix = 'prefix',",
+        "                   extension = '.a')],",
+        "                cxx_builtin_include_directories = ['dir1', 'dir2', 'dir3'],",
+        "                toolchain_identifier = 'toolchain',",
+        "                host_system_name = 'host',",
+        "                target_system_name = 'target',",
+        "                target_cpu = 'cpu',",
+        "                target_libc = 'libc',",
+        "                default_libc_top = 'libc_top',",
+        "                compiler = 'compiler',",
+        "                abi_libc_version = 'abi_libc',",
+        "                abi_version = 'banana',",
+        "                supports_gold_linker = True,",
+        "                supports_start_end_lib = True,",
+        "                tool_paths = [",
+        "                     tool_path(name = 'ar', path = '/some/path'),",
+        "                     tool_path(name = 'cpp', path = '/some/path'),",
+        "                     tool_path(name = 'gcc', path = '/some/path'),",
+        "                     tool_path(name = 'gcov', path = '/some/path'),",
+        "                     tool_path(name = 'gcovtool', path = '/some/path'),",
+        "                     tool_path(name = 'ld', path = '/some/path'),",
+        "                     tool_path(name = 'nm', path = '/some/path'),",
+        "                     tool_path(name = 'objcopy', path = '/some/path'),",
+        "                     tool_path(name = 'objdump', path = '/some/path'),",
+        "                     tool_path(name = 'strip', path = '/some/path'),",
+        "                     tool_path(name = 'dwp', path = '/some/path'),",
+        "                     tool_path(name = 'llvm_profdata', path = '/some/path'),",
+        "                ],",
+        "                cc_target_os = 'os',",
+        "                compiler_flags = ['flag1', 'flag2', 'flag3'],",
+        "                linker_flags = ['flag1'],",
+        "                compilation_mode_compiler_flags = {",
+        "                    'OPT' : ['flagopt'], 'FASTBUILD' : ['flagfast'] ",
+        "                },",
+        "                objcopy_embed_flags = ['flag1'],",
+        "                needs_pic = True,",
+        "                builtin_sysroot = 'sysroot')",
+        "cc_toolchain_config_rule = rule(",
+        "    implementation = _impl,",
+        "    attrs = {},",
+        "    provides = [CcToolchainConfigInfo],",
+        "    fragments = ['cpp']",
+        ")");
+
+    getAnalysisMock()
+        .ccSupport()
+        .setupCrosstool(
+            mockToolsConfig,
+            CrosstoolConfig.CToolchain.newBuilder().setAbiVersion("orange").buildPartial());
+
+    useConfiguration(
+        "--experimental_enable_cc_toolchain_config_info",
+        "--incompatible_disable_late_bound_option_defaults",
+        "--incompatible_disable_cc_configuration_make_variables");
+
+    ConfiguredTarget target = getConfiguredTarget("//a:b");
+    CcToolchainProvider toolchainProvider =
+        (CcToolchainProvider) target.get(ToolchainInfo.PROVIDER);
+
+    assertThat(toolchainProvider.getAbi()).isEqualTo("banana");
+    assertThat(toolchainProvider.getCcToolchainLabel().toString()).isEqualTo("//a:b");
+    assertThat(toolchainProvider.getFeatures().getActivatableNames())
+        .containsExactly("simple_action", "simple_feature");
+  }
+
+  @Test
+  public void testToolchainFromSkylarkRuleWithoutIncompatibleFlagsFlipped() throws Exception {
+    scratch.file(
+        "a/BUILD",
+        "load(':crosstool_rule.bzl', 'cc_toolchain_config_rule')",
+        "cc_toolchain_config_rule(name = 'toolchain_config')",
+        "filegroup(",
+        "   name='empty')",
+        "cc_toolchain(",
+        "    name = 'b',",
+        "    cpu = 'banana',",
+        "    all_files = ':empty',",
+        "    ar_files = ':empty',",
+        "    as_files = ':empty',",
+        "    compiler_files = ':empty',",
+        "    dwp_files = ':empty',",
+        "    linker_files = ':empty',",
+        "    strip_files = ':empty',",
+        "    objcopy_files = ':empty',",
+        "    dynamic_runtime_libs = [':empty'],",
+        "    static_runtime_libs = [':empty'],",
+        "    toolchain_config = ':toolchain_config')");
+
+    scratch.file(
+        "a/crosstool_rule.bzl",
+        "def _impl(ctx):",
+        "    return cc_common.create_cc_toolchain_config_info(",
+        "       ctx = ctx,",
+        "       toolchain_identifier = 'toolchain',",
+        "       host_system_name = 'host',",
+        "       target_system_name = 'target',",
+        "       target_cpu = 'cpu',",
+        "       target_libc = 'libc',",
+        "       compiler = 'compiler',",
+        "       abi_libc_version = 'abi_libc',",
+        "       abi_version = 'banana',",
+        "    )",
+        "cc_toolchain_config_rule = rule(",
+        "    implementation = _impl,",
+        "    attrs = {},",
+        "    provides = [CcToolchainConfigInfo],",
+        "    fragments = ['cpp']",
+        ")");
+
+    getAnalysisMock()
+        .ccSupport()
+        .setupCrosstool(
+            mockToolsConfig,
+            CrosstoolConfig.CToolchain.newBuilder().setAbiVersion("orange").buildPartial());
+
+    useConfiguration("--experimental_enable_cc_toolchain_config_info");
+    try {
+      getConfiguredTarget("//a:b");
+    } catch (AssertionError e) {
+      assertThat(e)
+          .hasMessageThat()
+          .contains(
+              "--incompatible_disable_late_bound_option_defaults and "
+                  + "--incompatible_disable_cc_configuration_make_variables must be set to true in "
+                  + "order to configure the C++ toolchain from Starlark.");
+    }
+  }
+
   @Test
   public void testSupportsDynamicLinkerCheckFeatures() throws Exception {
     writeDummyCcToolchain();
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
index 7e30cc0..e5f4ccd 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
@@ -3971,7 +3971,10 @@
         "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
         "cc_toolchain_alias(name='alias')",
         "cc_toolchain_config_rule(name='r')");
-    useConfiguration("--experimental_enable_cc_toolchain_config_info");
+    useConfiguration(
+        "--experimental_enable_cc_toolchain_config_info",
+        "--incompatible_disable_late_bound_option_defaults",
+        "--incompatible_disable_cc_configuration_make_variables");
     ConfiguredTarget target = getConfiguredTarget("//foo:r");
     assertThat(target).isNotNull();
     CcToolchainConfigInfo ccToolchainConfigInfo =
@@ -4044,7 +4047,10 @@
   @Test
   public void testCcToolchainInfoFromSkylarkAllRequiredStringsPresent() throws Exception {
     setupSkylarkRuleForStringFieldsTesting("");
-    useConfiguration("--experimental_enable_cc_toolchain_config_info");
+    useConfiguration(
+        "--experimental_enable_cc_toolchain_config_info",
+        "--incompatible_disable_late_bound_option_defaults",
+        "--incompatible_disable_cc_configuration_make_variables");
     ConfiguredTarget target = getConfiguredTarget("//foo:r");
     assertThat(target).isNotNull();
     CcToolchainConfigInfo ccToolchainConfigInfo =