Allow rule writers to create exec_group related exec transitions via config.exec(exec_group) and attach them to starlark dependency attrs.

PiperOrigin-RevId: 308290724
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 92efb5b..4de5d46 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1557,9 +1557,11 @@
         ":config/transitions/patch_transition",
         ":config/transitions/transition_factory",
         ":platform_options",
+        ":toolchain_collection",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/events",
         "//src/main/java/com/google/devtools/build/lib/packages",
+        "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi",
         "//third_party:guava",
         "//third_party:jsr305",
     ],
@@ -2035,6 +2037,7 @@
     name = "skylark/starlark_config",
     srcs = ["skylark/StarlarkConfig.java"],
     deps = [
+        ":config/execution_transition_factory",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/packages:type",
         "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
index 6dbb7f0..b84e2f9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
 import com.google.devtools.build.lib.analysis.config.Fragment;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
 import com.google.devtools.build.lib.analysis.config.TransitionResolver;
@@ -291,7 +292,8 @@
           @Nullable Rule fromRule,
           ConfiguredAttributeMapper attributeMap,
           @Nullable ToolchainCollection<ToolchainContext> toolchainContexts,
-          Iterable<Aspect> aspects) {
+          Iterable<Aspect> aspects)
+          throws EvalException {
     OrderedSetMultimap<DependencyKind, PartiallyResolvedDependency> partiallyResolvedDeps =
         OrderedSetMultimap.create();
 
@@ -335,13 +337,28 @@
           aspects, attribute.getName(), entry.getKey().getOwningAspect(), propagatingAspects);
 
       Label executionPlatformLabel = null;
-      // TODO(b/151742236): support transitions to other ({@link ExecGroup defined}) execution
-      // platforms
-      if (toolchainContexts != null
-          && toolchainContexts.getDefaultToolchainContext().executionPlatform() != null) {
-        executionPlatformLabel =
-            toolchainContexts.getDefaultToolchainContext().executionPlatform().label();
+      if (toolchainContexts != null) {
+        if (attribute.getTransitionFactory() instanceof ExecutionTransitionFactory) {
+          String execGroup =
+              ((ExecutionTransitionFactory) attribute.getTransitionFactory()).getExecGroup();
+          if (!toolchainContexts.hasToolchainContext(execGroup)) {
+            String error =
+                String.format(
+                    "Attr '%s' declares a transition for non-existent exec group '%s'",
+                    attribute.getName(), execGroup);
+            if (fromRule != null) {
+              throw new EvalException(fromRule.getLocation(), error);
+            } else {
+              throw new EvalException(error);
+            }
+          }
+          if (toolchainContexts.getToolchainContext(execGroup).executionPlatform() != null) {
+            executionPlatformLabel =
+                toolchainContexts.getToolchainContext(execGroup).executionPlatform().label();
+          }
+        }
       }
+
       AttributeTransitionData attributeTransitionData =
           AttributeTransitionData.builder()
               .attributes(attributeMap)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainCollection.java
index 2cbe240..206a8d6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainCollection.java
@@ -24,7 +24,7 @@
 import java.util.Map;
 
 /**
- * A wrapper class for a map of exec_group names to their relevent ToolchainContext.
+ * A wrapper class for a map of exec_group names to their relevant ToolchainContext.
  *
  * @param <T> any class that extends ToolchainContext. This generic allows ToolchainCollection to be
  *     used, e.g., both before and after toolchain resolution.
@@ -73,6 +73,10 @@
     return toolchainContexts.get(DEFAULT_EXEC_GROUP_NAME);
   }
 
+  boolean hasToolchainContext(String execGroup) {
+    return toolchainContexts.containsKey(execGroup);
+  }
+
   public T getToolchainContext(String execGroup) {
     return toolchainContexts.get(execGroup);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionTransitionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionTransitionFactory.java
index 643f6fb..28a5bf5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionTransitionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionTransitionFactory.java
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.analysis.config;
 
+import static com.google.devtools.build.lib.analysis.ToolchainCollection.DEFAULT_EXEC_GROUP_NAME;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.PlatformOptions;
@@ -22,6 +24,7 @@
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.AttributeTransitionData;
+import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi.ExecTransitionFactoryApi;
 import javax.annotation.Nullable;
 
 /**
@@ -29,26 +32,29 @@
  * transition to a configuration suitable for building dependencies for the execution platform of
  * the depending target.
  */
-public class ExecutionTransitionFactory implements TransitionFactory<AttributeTransitionData> {
+public class ExecutionTransitionFactory
+    implements TransitionFactory<AttributeTransitionData>, ExecTransitionFactoryApi {
 
-  private ExecutionTransitionFactory() {}
+  private final String execGroup;
 
-  /** Returns a new {@link ExecutionTransitionFactory}. */
-  public static ExecutionTransitionFactory create() {
-    return new ExecutionTransitionFactory();
+  private ExecutionTransitionFactory(String execGroup) {
+    this.execGroup = execGroup;
   }
 
   /**
-   * Returns either a new {@link ExecutionTransitionFactory} or a factory for the {@link
-   * HostTransition}, depending on the value of {@code enableExecutionTransition}.
+   * Returns a new {@link ExecutionTransitionFactory} for the default {@link
+   * com.google.devtools.build.lib.packages.ExecGroup}.
    */
-  public static TransitionFactory<AttributeTransitionData> create(
-      boolean enableExecutionTransition) {
-    if (enableExecutionTransition) {
-      return create();
-    } else {
-      return HostTransition.createFactory();
-    }
+  public static ExecutionTransitionFactory create() {
+    return new ExecutionTransitionFactory(DEFAULT_EXEC_GROUP_NAME);
+  }
+
+  /**
+   * Returns a new {@link ExecutionTransitionFactory} for the given {@link
+   * com.google.devtools.build.lib.packages.ExecGroup}.
+   */
+  public static ExecutionTransitionFactory create(String execGroup) {
+    return new ExecutionTransitionFactory(execGroup);
   }
 
   @Override
@@ -56,6 +62,10 @@
     return new ExecutionTransition(data.executionPlatform());
   }
 
+  public String getExecGroup() {
+    return execGroup;
+  }
+
   @Override
   public boolean isHost() {
     return false;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
index 5fa7eb8..bfb7f3f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
@@ -257,6 +257,8 @@
         builder.cfg(HostTransition.createFactory());
       } else if (trans.equals("exec")) {
         builder.cfg(ExecutionTransitionFactory.create());
+      } else if (trans instanceof ExecutionTransitionFactory) {
+        builder.cfg((ExecutionTransitionFactory) trans);
       } else if (trans instanceof SplitTransition) {
         builder.cfg(TransitionFactories.of((SplitTransition) trans));
       } else if (trans instanceof TransitionFactory) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkConfig.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkConfig.java
index d345023..5b197c6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkConfig.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkConfig.java
@@ -19,9 +19,11 @@
 import static com.google.devtools.build.lib.packages.Type.STRING;
 import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
 
+import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
 import com.google.devtools.build.lib.packages.BuildSetting;
 import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi;
 import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.lib.syntax.Starlark;
 
 /** Starlark namespace for creating build settings. */
 // TODO(juliexxia): Consider adding more types of build settings, specifically other label types.
@@ -48,6 +50,13 @@
   }
 
   @Override
+  public ExecutionTransitionFactory exec(Object execGroupUnchecked) {
+    return execGroupUnchecked == Starlark.NONE
+        ? ExecutionTransitionFactory.create()
+        : ExecutionTransitionFactory.create((String) execGroupUnchecked);
+  }
+
+  @Override
   public void repr(Printer printer) {
     printer.append("<config>");
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkAttrApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkAttrApi.java
index f0618e5..badbe68 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkAttrApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkAttrApi.java
@@ -89,7 +89,7 @@
           + "attribute.";
 
   String CONFIGURATION_ARG = "cfg";
-  // TODO(bazel-team): Update when new Starlark-based configuration framework is implemented.
+  // TODO(b/151742236): Update when new Starlark-based configuration framework is implemented.
   String CONFIGURATION_DOC =
       "<a href=\"../rules.$DOC_EXT#configurations\">Configuration</a> of the attribute. It can be "
           + "either <code>\"host\"</code> or <code>\"target\"</code>.";
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StarlarkConfigApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StarlarkConfigApi.java
index e4c32e1..53a91b5 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StarlarkConfigApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/StarlarkConfigApi.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
+import com.google.devtools.build.lib.syntax.StarlarkSemantics.FlagIdentifier;
 import com.google.devtools.build.lib.syntax.StarlarkValue;
 
 /**
@@ -116,4 +117,29 @@
               + "key-value map of settings like {'cpu': 'ppc', 'copt': '-DFoo'}, this describes a "
               + "single entry in that map.")
   interface BuildSettingApi extends StarlarkValue {}
+
+  @SkylarkCallable(
+      name = "exec",
+      doc = "<i>experimental</i> Creates an execution transition.",
+      enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_EXEC_GROUPS,
+      parameters = {
+        @Param(
+            name = "exec_group",
+            type = String.class,
+            named = true,
+            noneable = true,
+            defaultValue = "None",
+            doc =
+                "The name of the exec group whose execution platform this transition will use. If"
+                    + " not provided, this exec transition will use the target's default execution"
+                    + " platform.")
+      })
+  ExecTransitionFactoryApi exec(Object execGroupUnchecked);
+
+  /** The api for exec transitions. */
+  @SkylarkModule(
+      name = "ExecTransitionFactory",
+      category = SkylarkModuleCategory.BUILTIN,
+      doc = "<i>experimental</i> an execution transition.")
+  interface ExecTransitionFactoryApi extends StarlarkValue {}
 }
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/ConfigApiFakes.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/ConfigApiFakes.java
new file mode 100644
index 0000000..94547d4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/ConfigApiFakes.java
@@ -0,0 +1,41 @@
+// Copyright 2018 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.
+
+package com.google.devtools.build.skydoc.fakebuildapi;
+
+import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi.BuildSettingApi;
+import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi.ExecTransitionFactoryApi;
+import com.google.devtools.build.lib.syntax.Printer;
+
+/**
+ * Fakes for callables under the {@link
+ * com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi} module.
+ */
+public class ConfigApiFakes {
+
+  private ConfigApiFakes() {}
+
+  /** Fake implementation of {@link BuildSettingApi}. */
+  public static class FakeBuildSettingDescriptor implements BuildSettingApi {
+
+    @Override
+    public void repr(Printer printer) {}
+  }
+
+  /** Fake implementation of ExecTransitionFactoryApi. */
+  public static class FakeExecTransitionFactory implements ExecTransitionFactoryApi {
+    @Override
+    public void repr(Printer printer) {}
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeBuildSettingDescriptor.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeBuildSettingDescriptor.java
deleted file mode 100644
index 3566ed2..0000000
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeBuildSettingDescriptor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2018 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.
-
-package com.google.devtools.build.skydoc.fakebuildapi;
-
-import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi.BuildSettingApi;
-import com.google.devtools.build.lib.syntax.Printer;
-
-/**
- * Fake implementation of {@link BuildSettingApi}.
- */
-public class FakeBuildSettingDescriptor implements BuildSettingApi {
-  @Override
-  public void repr(Printer printer) {}
-}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeConfigApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeConfigApi.java
index 627129d..1b52ab9 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeConfigApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeConfigApi.java
@@ -16,6 +16,8 @@
 
 import com.google.devtools.build.lib.skylarkbuildapi.StarlarkConfigApi;
 import com.google.devtools.build.lib.syntax.Printer;
+import com.google.devtools.build.skydoc.fakebuildapi.ConfigApiFakes.FakeBuildSettingDescriptor;
+import com.google.devtools.build.skydoc.fakebuildapi.ConfigApiFakes.FakeExecTransitionFactory;
 
 /** Fake implementation of {@link StarlarkConfigApi}. */
 public class FakeConfigApi implements StarlarkConfigApi {
@@ -41,5 +43,10 @@
   }
 
   @Override
+  public ExecTransitionFactoryApi exec(Object execGroup) {
+    return new FakeExecTransitionFactory();
+  }
+
+  @Override
   public void repr(Printer printer) {}
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkExecGroupTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkExecGroupTest.java
new file mode 100644
index 0000000..6eeeb23
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkExecGroupTest.java
@@ -0,0 +1,185 @@
+// Copyright 2015 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.
+
+package com.google.devtools.build.lib.analysis;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.packages.StarlarkProvider;
+import com.google.devtools.build.lib.packages.StructImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for exec groups. Functionality related to rule context tested in {@link
+ * com.google.devtools.build.lib.skylark.SkylarkRuleContextTest}.
+ */
+@RunWith(JUnit4.class)
+public class StarlarkExecGroupTest extends BuildViewTestCase {
+
+  @Before
+  public final void setUp() throws Exception {
+    setStarlarkSemanticsOptions("--experimental_exec_groups");
+  }
+
+  /**
+   * Sets up two toolchains types, each with a single toolchain implementation and a single
+   * exec_compatible_with platform.
+   *
+   * <p>toolchain_type_1 -> foo_toolchain -> exec_compatible_with platform_1 toolchain_type_2 ->
+   * bar_toolchain -> exec_compatible_with platform_2
+   */
+  private void createToolchainsAndPlatforms() throws Exception {
+    scratch.file(
+        "rule/test_toolchain.bzl",
+        "def _impl(ctx):",
+        "    return [platform_common.ToolchainInfo()]",
+        "test_toolchain = rule(",
+        "    implementation = _impl,",
+        ")");
+    scratch.file(
+        "rule/BUILD",
+        "exports_files(['test_toolchain/bzl'])",
+        "toolchain_type(name = 'toolchain_type_1')",
+        "toolchain_type(name = 'toolchain_type_2')");
+    scratch.file(
+        "toolchain/BUILD",
+        "load('//rule:test_toolchain.bzl', 'test_toolchain')",
+        "test_toolchain(",
+        "    name = 'foo',",
+        ")",
+        "toolchain(",
+        "    name = 'foo_toolchain',",
+        "    toolchain_type = '//rule:toolchain_type_1',",
+        "    target_compatible_with = ['//platform:constraint_1'],",
+        "    exec_compatible_with = ['//platform:constraint_1'],",
+        "    toolchain = ':foo',",
+        ")",
+        "test_toolchain(",
+        "    name = 'bar',",
+        ")",
+        "toolchain(",
+        "    name = 'bar_toolchain',",
+        "    toolchain_type = '//rule:toolchain_type_2',",
+        "    target_compatible_with = ['//platform:constraint_1'],",
+        "    exec_compatible_with = ['//platform:constraint_2'],",
+        "    toolchain = ':bar',",
+        ")");
+
+    scratch.file(
+        "platform/BUILD",
+        "constraint_setting(name = 'setting')",
+        "constraint_value(",
+        "    name = 'constraint_1',",
+        "    constraint_setting = ':setting',",
+        ")",
+        "constraint_value(",
+        "    name = 'constraint_2',",
+        "    constraint_setting = ':setting',",
+        ")",
+        "platform(",
+        "    name = 'platform_1',",
+        "    constraint_values = [':constraint_1'],",
+        ")",
+        "platform(",
+        "    name = 'platform_2',",
+        "    constraint_values = [':constraint_2'],",
+        ")");
+  }
+
+  @Test
+  public void testExecGroupTransition() throws Exception {
+    createToolchainsAndPlatforms();
+    useConfiguration(
+        "--extra_toolchains=//toolchain:foo_toolchain,//toolchain:bar_toolchain",
+        "--platforms=//platform:platform_1",
+        "--extra_execution_platforms=//platform:platform_1,//platform:platform_2");
+
+    scratch.file(
+        "test/defs.bzl",
+        "MyInfo = provider()",
+        "def _impl(ctx):",
+        "  return [MyInfo(dep = ctx.attr.dep, exec_group_dep = ctx.attr.exec_group_dep)]",
+        "with_transition = rule(",
+        "  implementation = _impl,",
+        "  attrs = {",
+        "    'exec_group_dep': attr.label(cfg = config.exec('watermelon')),",
+        "    'dep': attr.label(cfg = 'exec'),",
+        "  },",
+        "  exec_groups = {",
+        "    'watermelon': exec_group(toolchains = ['//rule:toolchain_type_2']),",
+        "  },",
+        "  toolchains = ['//rule:toolchain_type_1'],",
+        ")",
+        "def _impl2(ctx):",
+        "  return []",
+        "simple_rule = rule(implementation = _impl2)");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'with_transition', 'simple_rule')",
+        "with_transition(name = 'parent', dep = ':child', exec_group_dep = ':other-child')",
+        "simple_rule(name = 'child')",
+        "simple_rule(name = 'other-child')");
+
+    ConfiguredTarget target = getConfiguredTarget("//test:parent");
+    Provider.Key key =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "MyInfo");
+    BuildConfiguration dep =
+        getConfiguration((ConfiguredTarget) ((StructImpl) target.get(key)).getValue("dep"));
+    BuildConfiguration execGroupDep =
+        getConfiguration(
+            (ConfiguredTarget) ((StructImpl) target.get(key)).getValue("exec_group_dep"));
+
+    assertThat(dep.getOptions().get(PlatformOptions.class).platforms)
+        .containsExactly(Label.parseAbsoluteUnchecked("//platform:platform_1"));
+    assertThat(execGroupDep.getOptions().get(PlatformOptions.class).platforms)
+        .containsExactly(Label.parseAbsoluteUnchecked("//platform:platform_2"));
+  }
+
+  @Test
+  public void testInvalidExecGroupTransition() throws Exception {
+    scratch.file(
+        "test/defs.bzl",
+        "MyInfo = provider()",
+        "def _impl(ctx):",
+        "  return []",
+        "with_transition = rule(",
+        "  implementation = _impl,",
+        "  attrs = {",
+        "    'exec_group_dep': attr.label(cfg = config.exec('blueberry')),",
+        "  },",
+        ")",
+        "def _impl2(ctx):",
+        "  return []",
+        "simple_rule = rule(implementation = _impl2)");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'with_transition', 'simple_rule')",
+        "with_transition(name = 'parent', exec_group_dep = ':child')",
+        "simple_rule(name = 'child')");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//test:parent");
+    assertContainsEvent(
+        "Attr 'exec_group_dep' declares a transition for non-existent exec group 'blueberry'");
+  }
+}