Fix toolchain and execution platform registration to use patterns.

This allows more flexibility in registering toolchains and execution
platforms, both in the WORKSPACE and from the command-line.

Change-Id: I6fe75507d1a74de74085b7c927fdf093c152b894
PiperOrigin-RevId: 188813688
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
index fefc871..7b94b93 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
@@ -34,17 +34,17 @@
 )
 public class PlatformConfiguration extends BuildConfiguration.Fragment {
   private final Label hostPlatform;
-  private final ImmutableList<Label> extraExecutionPlatforms;
+  private final ImmutableList<String> extraExecutionPlatforms;
   private final ImmutableList<Label> targetPlatforms;
-  private final ImmutableList<Label> extraToolchains;
+  private final ImmutableList<String> extraToolchains;
   private final ImmutableList<Label> enabledToolchainTypes;
 
   @AutoCodec.Instantiator
   PlatformConfiguration(
       Label hostPlatform,
-      ImmutableList<Label> extraExecutionPlatforms,
+      ImmutableList<String> extraExecutionPlatforms,
       ImmutableList<Label> targetPlatforms,
-      ImmutableList<Label> extraToolchains,
+      ImmutableList<String> extraToolchains,
       ImmutableList<Label> enabledToolchainTypes) {
     this.hostPlatform = hostPlatform;
     this.extraExecutionPlatforms = extraExecutionPlatforms;
@@ -58,8 +58,11 @@
     return hostPlatform;
   }
 
-  /** Additional platforms that are available for action execution. */
-  public ImmutableList<Label> getExtraExecutionPlatforms() {
+  /**
+   * Target patterns that select additional platforms that will be made available for action
+   * execution.
+   */
+  public ImmutableList<String> getExtraExecutionPlatforms() {
     return extraExecutionPlatforms;
   }
 
@@ -68,8 +71,11 @@
     return targetPlatforms;
   }
 
-  /** Additional toolchains that should be considered during toolchain resolution. */
-  public ImmutableList<Label> getExtraToolchains() {
+  /**
+   * Target patterns that select additional toolchains that will be considered during toolchain
+   * resolution.
+   */
+  public ImmutableList<String> getExtraToolchains() {
     return extraToolchains;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
index 688e418..dd8fff4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
@@ -58,16 +59,17 @@
 
   @Option(
     name = "extra_execution_platforms",
-    converter = LabelListConverter.class,
+    converter = CommaSeparatedOptionListConverter.class,
     defaultValue = "",
     documentationCategory = OptionDocumentationCategory.TOOLCHAIN,
     effectTags = {OptionEffectTag.EXECUTION},
     help =
-        "The labels of platforms that are available as execution platforms to run actions. "
+        "The platforms that are available as execution platforms to run actions. "
+            + "Platforms can be specified by exact target, or as a target pattern. "
             + "These platforms will be considered before those declared in the WORKSPACE file by "
             + "register_execution_platforms()."
   )
-  public List<Label> extraExecutionPlatforms;
+  public List<String> extraExecutionPlatforms;
 
   @Option(
     name = "platforms",
@@ -87,8 +89,8 @@
 
   @Option(
     name = "extra_toolchains",
-    converter = LabelListConverter.class,
     defaultValue = "",
+    converter = CommaSeparatedOptionListConverter.class,
     documentationCategory = OptionDocumentationCategory.TOOLCHAIN,
     effectTags = {
       OptionEffectTag.AFFECTS_OUTPUTS,
@@ -96,11 +98,12 @@
       OptionEffectTag.LOADING_AND_ANALYSIS
     },
     help =
-        "The labels of toolchain rules to be considered during toolchain resolution. "
+        "The toolchain rules to be considered during toolchain resolution. "
+            + "Toolchains can be specified by exact target, or as a target pattern. "
             + "These toolchains will be considered before those declared in the WORKSPACE file by "
             + "register_toolchains()."
   )
-  public List<Label> extraToolchains;
+  public List<String> extraToolchains;
 
   @Option(
     name = "toolchain_resolution_override",
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index 03f2de6..25c375e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -203,8 +203,8 @@
   private ImmutableList<Event> events;
   private ImmutableList<Postable> posts;
 
-  private ImmutableList<Label> registeredExecutionPlatformLabels;
-  private ImmutableList<Label> registeredToolchainLabels;
+  private ImmutableList<String> registeredExecutionPlatforms;
+  private ImmutableList<String> registeredToolchains;
 
   /**
    * Package initialization, part 1 of 3: instantiates a new package with the
@@ -340,9 +340,8 @@
     this.features = ImmutableSortedSet.copyOf(builder.features);
     this.events = ImmutableList.copyOf(builder.events);
     this.posts = ImmutableList.copyOf(builder.posts);
-    this.registeredExecutionPlatformLabels =
-        ImmutableList.copyOf(builder.registeredExecutionPlatformLabels);
-    this.registeredToolchainLabels = ImmutableList.copyOf(builder.registeredToolchainLabels);
+    this.registeredExecutionPlatforms = ImmutableList.copyOf(builder.registeredExecutionPlatforms);
+    this.registeredToolchains = ImmutableList.copyOf(builder.registeredToolchains);
   }
 
   /**
@@ -659,12 +658,12 @@
     return defaultRestrictedTo;
   }
 
-  public ImmutableList<Label> getRegisteredExecutionPlatformLabels() {
-    return registeredExecutionPlatformLabels;
+  public ImmutableList<String> getRegisteredExecutionPlatforms() {
+    return registeredExecutionPlatforms;
   }
 
-  public ImmutableList<Label> getRegisteredToolchainLabels() {
-    return registeredToolchainLabels;
+  public ImmutableList<String> getRegisteredToolchains() {
+    return registeredToolchains;
   }
 
   @Override
@@ -789,8 +788,8 @@
     protected Map<Label, Path> subincludes = null;
     protected ImmutableList<Label> skylarkFileDependencies = ImmutableList.of();
 
-    protected List<Label> registeredExecutionPlatformLabels = new ArrayList<>();
-    protected List<Label> registeredToolchainLabels = new ArrayList<>();
+    protected List<String> registeredExecutionPlatforms = new ArrayList<>();
+    protected List<String> registeredToolchains = new ArrayList<>();
 
     /**
      * True iff the "package" function has already been called in this package.
@@ -1313,12 +1312,12 @@
       addRuleUnchecked(rule);
     }
 
-    public void addRegisteredExecutionPlatformLabels(List<Label> platforms) {
-      this.registeredExecutionPlatformLabels.addAll(platforms);
+    public void addRegisteredExecutionPlatforms(List<String> platforms) {
+      this.registeredExecutionPlatforms.addAll(platforms);
     }
 
-    void addRegisteredToolchainLabels(List<Label> toolchains) {
-      this.registeredToolchainLabels.addAll(toolchains);
+    void addRegisteredToolchains(List<String> toolchains) {
+      this.registeredToolchains.addAll(toolchains);
     }
 
     private Builder beforeBuild(boolean discoverAssumedInputFiles)
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 702f6af..4e7af5f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -53,9 +53,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
 import com.google.devtools.build.lib.vfs.Path;
 import java.io.File;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -275,8 +273,8 @@
     if (aPackage.containsErrors()) {
       builder.setContainsErrors();
     }
-    builder.addRegisteredExecutionPlatformLabels(aPackage.getRegisteredExecutionPlatformLabels());
-    builder.addRegisteredToolchainLabels(aPackage.getRegisteredToolchainLabels());
+    builder.addRegisteredExecutionPlatforms(aPackage.getRegisteredExecutionPlatforms());
+    builder.addRegisteredToolchains(aPackage.getRegisteredToolchains());
     for (Rule rule : aPackage.getTargets(Rule.class)) {
       try {
         // The old rule references another Package instance and we wan't to keep the invariant that
@@ -416,25 +414,10 @@
                 SkylarkList<String> platformLabels, Location location, Environment env)
                 throws EvalException, InterruptedException {
 
-              // Collect the platform labels.
-              List<Label> platforms = new ArrayList<>();
-              for (String rawLabel : platformLabels.getContents(String.class, "platform_labels")) {
-                try {
-                  platforms.add(Label.parseAbsolute(rawLabel));
-                } catch (LabelSyntaxException e) {
-                  throw new EvalException(
-                      location,
-                      String.format(
-                          "In register_execution_platforms: unable to parse platform label %s: %s",
-                          rawLabel, e.getMessage()),
-                      e);
-                }
-              }
-
               // Add to the package definition for later.
-              Package.Builder builder =
-                  PackageFactory.getContext(env, location).pkgBuilder;
-              builder.addRegisteredExecutionPlatformLabels(platforms);
+              Package.Builder builder = PackageFactory.getContext(env, location).pkgBuilder;
+              builder.addRegisteredExecutionPlatforms(
+                  platformLabels.getContents(String.class, "platform_labels"));
 
               return NONE;
             }
@@ -468,26 +451,10 @@
                 SkylarkList<String> toolchainLabels, Location location, Environment env)
                 throws EvalException, InterruptedException {
 
-              // Collect the toolchain labels.
-              List<Label> toolchains = new ArrayList<>();
-              for (String rawLabel :
-                  toolchainLabels.getContents(String.class, "toolchain_labels")) {
-                try {
-                  toolchains.add(Label.parseAbsolute(rawLabel));
-                } catch (LabelSyntaxException e) {
-                  throw new EvalException(
-                      location,
-                      String.format(
-                          "In register_toolchains: unable to parse toolchain label %s: %s",
-                          rawLabel, e.getMessage()),
-                      e);
-                }
-              }
-
               // Add to the package definition for later.
-              Package.Builder builder =
-                  PackageFactory.getContext(env, location).pkgBuilder;
-              builder.addRegisteredToolchainLabels(toolchains);
+              Package.Builder builder = PackageFactory.getContext(env, location).pkgBuilder;
+              builder.addRegisteredToolchains(
+                  toolchainLabels.getContents(String.class, "toolchain_labels"));
 
               return NONE;
             }
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
index e914396..9936f33 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
@@ -13,10 +13,12 @@
 // limitations under the License.
 package com.google.devtools.build.lib.pkgcache;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import java.util.Objects;
 
 /**
@@ -34,6 +36,10 @@
     return new AndFilteringPolicy(x, y);
   }
 
+  public static FilteringPolicy ruleType(String ruleName, boolean keepExplicit) {
+    return RuleTypeFilter.create(ruleName, keepExplicit);
+  }
+
   private FilteringPolicies() {
   }
 
@@ -87,6 +93,33 @@
     }
   }
 
+  /** FilteringPolicy that only matches a specific rule name. */
+  @AutoValue
+  @AutoCodec
+  abstract static class RuleTypeFilter extends FilteringPolicy {
+    abstract String ruleName();
+
+    abstract boolean keepExplicit();
+
+    @Override
+    public boolean shouldRetain(Target target, boolean explicit) {
+      if (explicit && keepExplicit()) {
+        return true;
+      }
+
+      if (target.getAssociatedRule().getRuleClass().equals(ruleName())) {
+        return true;
+      }
+
+      return false;
+    }
+
+    @AutoCodec.Instantiator
+    static RuleTypeFilter create(String ruleName, boolean keepExplicit) {
+      return new AutoValue_FilteringPolicies_RuleTypeFilter(ruleName, keepExplicit);
+    }
+  }
+
   /** FilteringPolicy for combining FilteringPolicies. */
   public static class AndFilteringPolicy extends FilteringPolicy {
     private final FilteringPolicy firstPolicy;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunction.java
index 9837589..ad775ca 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunction.java
@@ -22,7 +22,9 @@
 import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
 import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
 import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
 import com.google.devtools.build.lib.skyframe.ToolchainUtil.InvalidPlatformException;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -51,26 +53,39 @@
     }
     BuildConfiguration configuration = buildConfigurationValue.getConfiguration();
 
-    ImmutableList.Builder<Label> registeredExecutionPlatformLabels = new ImmutableList.Builder<>();
+    ImmutableList.Builder<String> targetPatterns = new ImmutableList.Builder<>();
 
     // Get the execution platforms from the configuration.
     PlatformConfiguration platformConfiguration =
         configuration.getFragment(PlatformConfiguration.class);
     if (platformConfiguration != null) {
-      registeredExecutionPlatformLabels.addAll(platformConfiguration.getExtraExecutionPlatforms());
+      targetPatterns.addAll(platformConfiguration.getExtraExecutionPlatforms());
     }
 
     // Get the registered execution platforms from the WORKSPACE.
-    List<Label> workspaceExecutionPlatforms = getWorkspaceExecutionPlatforms(env);
+    List<String> workspaceExecutionPlatforms = getWorkspaceExecutionPlatforms(env);
     if (workspaceExecutionPlatforms == null) {
       return null;
     }
-    registeredExecutionPlatformLabels.addAll(workspaceExecutionPlatforms);
+    targetPatterns.addAll(workspaceExecutionPlatforms);
+
+    // Expand target patterns.
+    ImmutableList<Label> platformLabels;
+    try {
+      platformLabels =
+          ToolchainUtil.expandTargetPatterns(
+              env, targetPatterns.build(), FilteringPolicies.ruleType("platform", true));
+      if (env.valuesMissing()) {
+        return null;
+      }
+    } catch (ToolchainUtil.InvalidTargetPatternException e) {
+      throw new RegisteredExecutionPlatformsFunctionException(
+          new InvalidExecutionPlatformLabelException(e), Transience.PERSISTENT);
+    }
 
     // Load the configured target for each, and get the declared execution platforms providers.
     ImmutableList<ConfiguredTargetKey> registeredExecutionPlatformKeys =
-        configureRegisteredExecutionPlatforms(
-            env, configuration, registeredExecutionPlatformLabels.build());
+        configureRegisteredExecutionPlatforms(env, configuration, platformLabels);
     if (env.valuesMissing()) {
       return null;
     }
@@ -85,7 +100,7 @@
    */
   @Nullable
   @VisibleForTesting
-  public static List<Label> getWorkspaceExecutionPlatforms(Environment env)
+  public static List<String> getWorkspaceExecutionPlatforms(Environment env)
       throws InterruptedException {
     PackageValue externalPackageValue =
         (PackageValue) env.getValue(PackageValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER));
@@ -94,7 +109,7 @@
     }
 
     Package externalPackage = externalPackageValue.getPackage();
-    return externalPackage.getRegisteredExecutionPlatformLabels();
+    return externalPackage.getRegisteredExecutionPlatforms();
   }
 
   private ImmutableList<ConfiguredTargetKey> configureRegisteredExecutionPlatforms(
@@ -149,12 +164,35 @@
   }
 
   /**
+   * Used to indicate that the given {@link Label} represents a {@link ConfiguredTarget} which is
+   * not a valid {@link PlatformInfo} provider.
+   */
+  static final class InvalidExecutionPlatformLabelException extends Exception {
+
+    public InvalidExecutionPlatformLabelException(ToolchainUtil.InvalidTargetPatternException e) {
+      this(e.getInvalidPattern(), e.getTpe());
+    }
+
+    public InvalidExecutionPlatformLabelException(String invalidPattern, TargetParsingException e) {
+      super(
+          String.format(
+              "invalid registered execution platform '%s': %s", invalidPattern, e.getMessage()),
+          e);
+    }
+  }
+
+  /**
    * Used to declare all the exception types that can be wrapped in the exception thrown by {@link
    * #compute}.
    */
   private static class RegisteredExecutionPlatformsFunctionException extends SkyFunctionException {
 
     private RegisteredExecutionPlatformsFunctionException(
+        InvalidExecutionPlatformLabelException cause, Transience transience) {
+      super(cause, transience);
+    }
+
+    private RegisteredExecutionPlatformsFunctionException(
         InvalidPlatformException cause, Transience transience) {
       super(cause, transience);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunction.java
index d6a8bfe..5d3b580 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunction.java
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.skyframe;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
@@ -21,7 +23,9 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.platform.DeclaredToolchainInfo;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
 import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
@@ -51,22 +55,36 @@
     }
     BuildConfiguration configuration = buildConfigurationValue.getConfiguration();
 
-    ImmutableList.Builder<Label> registeredToolchainLabels = new ImmutableList.Builder<>();
+    ImmutableList.Builder<String> targetPatterns = new ImmutableList.Builder<>();
 
     // Get the toolchains from the configuration.
     PlatformConfiguration platformConfiguration =
         configuration.getFragment(PlatformConfiguration.class);
-    registeredToolchainLabels.addAll(platformConfiguration.getExtraToolchains());
+    targetPatterns.addAll(platformConfiguration.getExtraToolchains());
 
     // Get the registered toolchains from the WORKSPACE.
-    registeredToolchainLabels.addAll(getWorkspaceToolchains(env));
+    targetPatterns.addAll(getWorkspaceToolchains(env));
     if (env.valuesMissing()) {
       return null;
     }
 
+    // Expand target patterns.
+    ImmutableList<Label> toolchainLabels;
+    try {
+      toolchainLabels =
+          ToolchainUtil.expandTargetPatterns(
+              env, targetPatterns.build(), FilteringPolicies.ruleType("toolchain", true));
+      if (env.valuesMissing()) {
+        return null;
+      }
+    } catch (ToolchainUtil.InvalidTargetPatternException e) {
+      throw new RegisteredToolchainsFunctionException(
+          new InvalidToolchainLabelException(e), Transience.PERSISTENT);
+    }
+
     // Load the configured target for each, and get the declared toolchain providers.
     ImmutableList<DeclaredToolchainInfo> registeredToolchains =
-        configureRegisteredToolchains(env, configuration, registeredToolchainLabels.build());
+        configureRegisteredToolchains(env, configuration, toolchainLabels);
     if (env.valuesMissing()) {
       return null;
     }
@@ -74,23 +92,23 @@
     return RegisteredToolchainsValue.create(registeredToolchains);
   }
 
-  private Iterable<? extends Label> getWorkspaceToolchains(Environment env)
+  private Iterable<? extends String> getWorkspaceToolchains(Environment env)
       throws InterruptedException {
-    List<Label> labels = getRegisteredToolchainLabels(env);
-    if (labels == null) {
+    List<String> patterns = getRegisteredToolchains(env);
+    if (patterns == null) {
       return ImmutableList.of();
     }
-    return labels;
+    return patterns;
   }
 
   /**
-   * Loads the external package and then returns the registered toolchain labels.
+   * Loads the external package and then returns the registered toolchains.
    *
    * @param env the environment to use for lookups
    */
-  @Nullable @VisibleForTesting
-  public static List<Label> getRegisteredToolchainLabels(Environment env)
-      throws InterruptedException {
+  @Nullable
+  @VisibleForTesting
+  public static List<String> getRegisteredToolchains(Environment env) throws InterruptedException {
     PackageValue externalPackageValue =
         (PackageValue) env.getValue(PackageValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER));
     if (externalPackageValue == null) {
@@ -98,7 +116,7 @@
     }
 
     Package externalPackage = externalPackageValue.getPackage();
-    return externalPackage.getRegisteredToolchainLabels();
+    return externalPackage.getRegisteredToolchains();
   }
 
   private ImmutableList<DeclaredToolchainInfo> configureRegisteredToolchains(
@@ -108,7 +126,7 @@
         labels
             .stream()
             .map(label -> ConfiguredTargetKey.of(label, configuration))
-            .collect(ImmutableList.toImmutableList());
+            .collect(toImmutableList());
 
     Map<SkyKey, ValueOrException<ConfiguredValueCreationException>> values =
         env.getValuesOrThrow(keys, ConfiguredValueCreationException.class);
@@ -156,25 +174,27 @@
    */
   public static final class InvalidToolchainLabelException extends Exception {
 
-    private final Label invalidLabel;
-
     public InvalidToolchainLabelException(Label invalidLabel) {
       super(
           String.format(
               "invalid registered toolchain '%s': "
                   + "target does not provide the DeclaredToolchainInfo provider",
               invalidLabel));
-      this.invalidLabel = invalidLabel;
+    }
+
+    public InvalidToolchainLabelException(ToolchainUtil.InvalidTargetPatternException e) {
+      this(e.getInvalidPattern(), e.getTpe());
+    }
+
+    public InvalidToolchainLabelException(String invalidPattern, TargetParsingException e) {
+      super(
+          String.format("invalid registered toolchain '%s': %s", invalidPattern, e.getMessage()),
+          e);
     }
 
     public InvalidToolchainLabelException(Label invalidLabel, ConfiguredValueCreationException e) {
       super(
           String.format("invalid registered toolchain '%s': %s", invalidLabel, e.getMessage()), e);
-      this.invalidLabel = invalidLabel;
-    }
-
-    public Label getInvalidLabel() {
-      return invalidLabel;
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
index 3b84c81..e231789 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToolchainUtil.java
@@ -31,7 +31,9 @@
 import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
 import com.google.devtools.build.lib.analysis.platform.PlatformProviderUtils;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.TargetParsingException;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
 import com.google.devtools.build.lib.skyframe.RegisteredToolchainsFunction.InvalidToolchainLabelException;
 import com.google.devtools.build.lib.skyframe.ToolchainResolutionFunction.NoToolchainFoundException;
@@ -377,6 +379,67 @@
         toolchains);
   }
 
+  @Nullable
+  static ImmutableList<Label> expandTargetPatterns(
+      Environment env, List<String> targetPatterns, FilteringPolicy filteringPolicy)
+      throws InvalidTargetPatternException, InterruptedException {
+
+    // First parse the patterns, and throw any errors immediately.
+    List<TargetPatternValue.TargetPatternKey> patternKeys = new ArrayList<>();
+    for (TargetPatternValue.TargetPatternSkyKeyOrException keyOrException :
+        TargetPatternValue.keys(targetPatterns, filteringPolicy, "")) {
+
+      try {
+        patternKeys.add(keyOrException.getSkyKey());
+      } catch (TargetParsingException e) {
+        throw new InvalidTargetPatternException(keyOrException.getOriginalPattern(), e);
+      }
+    }
+
+    // Then, resolve the patterns.
+    ImmutableList.Builder<Label> labels = new ImmutableList.Builder<>();
+    Map<SkyKey, ValueOrException<TargetParsingException>> resolvedPatterns =
+        env.getValuesOrThrow(patternKeys, TargetParsingException.class);
+    if (env.valuesMissing()) {
+      return null;
+    }
+
+    for (TargetPatternValue.TargetPatternKey pattern : patternKeys) {
+      TargetPatternValue value;
+      try {
+        value = (TargetPatternValue) resolvedPatterns.get(pattern).get();
+        labels.addAll(value.getTargets().getTargets());
+      } catch (TargetParsingException e) {
+        throw new InvalidTargetPatternException(pattern.getPattern(), e);
+      }
+    }
+
+    return labels.build();
+  }
+
+  /**
+   * Exception used when an error occurs in {@link #expandTargetPatterns(Environment, List,
+   * FilteringPolicy)}.
+   */
+  static final class InvalidTargetPatternException extends Exception {
+    private String invalidPattern;
+    private TargetParsingException tpe;
+
+    public InvalidTargetPatternException(String invalidPattern, TargetParsingException tpe) {
+      super(tpe);
+      this.invalidPattern = invalidPattern;
+      this.tpe = tpe;
+    }
+
+    public String getInvalidPattern() {
+      return invalidPattern;
+    }
+
+    public TargetParsingException getTpe() {
+      return tpe;
+    }
+  }
+
   /** Exception used when a platform label is not a valid platform. */
   static final class InvalidPlatformException extends Exception {
     InvalidPlatformException(Label label) {