Add toolchains data to RuleClass and RuleContext.

Also expose both sides to Skylark.
Part of #2219.

Change-Id: I4d749dd9981fe33f75310acb0ec3927cff6f28fe
PiperOrigin-RevId: 156340638
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 3e753e6..37e20c9 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -591,6 +591,7 @@
         ":util",
         ":vfs",
         "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform",
         "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
         "//src/main/java/com/google/devtools/build/lib/causes",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index a6c43d4..f1e1db2 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -167,6 +167,7 @@
   private final ErrorReporter reporter;
   private final ImmutableBiMap<String, Class<? extends TransitiveInfoProvider>>
       skylarkProviderRegistry;
+  private final ToolchainContext toolchainContext;
 
   private ActionOwner actionOwner;
 
@@ -197,6 +198,8 @@
     this.skylarkProviderRegistry = builder.skylarkProviderRegistry;
     this.hostConfiguration = builder.hostConfiguration;
     reporter = builder.reporter;
+    // TODO(katre): Populate the actual selected toolchains.
+    this.toolchainContext = new ToolchainContext(null);
   }
 
   private ImmutableSet<String> getEnabledFeatures() {
@@ -1121,6 +1124,10 @@
     }
   }
 
+  public ToolchainContext getToolchainContext() {
+    return toolchainContext;
+  }
+
   private void checkAttribute(String attributeName, Mode mode) {
     Attribute attributeDefinition = attributes.getAttributeDefinition(attributeName);
     if (attributeDefinition == null) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainContext.java
new file mode 100644
index 0000000..c4c9a9f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ToolchainContext.java
@@ -0,0 +1,38 @@
+// Copyright 2017 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 com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
+import com.google.devtools.build.lib.packages.ClassObjectConstructor;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Contains toolchain-related information needed for a {@link RuleContext}. */
+public class ToolchainContext {
+  private final ImmutableMap<ClassObjectConstructor.Key, ToolchainInfo> toolchains;
+
+  public ToolchainContext(@Nullable Map<ClassObjectConstructor.Key, ToolchainInfo> toolchains) {
+    this.toolchains =
+        toolchains == null
+            ? ImmutableMap.<ClassObjectConstructor.Key, ToolchainInfo>of()
+            : ImmutableMap.copyOf(toolchains);
+  }
+
+  public SkylarkDict<ClassObjectConstructor.Key, ToolchainInfo> collectToolchains() {
+    return SkylarkDict.<ClassObjectConstructor.Key, ToolchainInfo>copyOf(null, toolchains);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index 9293b09..5a5b58d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -508,6 +508,7 @@
     private boolean supportsConstraintChecking = true;
 
     private final Map<String, Attribute> attributes = new LinkedHashMap<>();
+    private final List<ClassObjectConstructor.Key> requiredToolchains = new ArrayList<>();
 
     /**
      * Constructs a new {@code RuleClassBuilder} using all attributes from all
@@ -618,6 +619,7 @@
           ruleDefinitionEnvironmentHashCode,
           configurationFragmentPolicy.build(),
           supportsConstraintChecking,
+          requiredToolchains,
           attributes.values().toArray(new Attribute[0]));
     }
 
@@ -998,6 +1000,11 @@
       return this;
     }
 
+    public Builder addRequiredToolchain(ClassObjectConstructor.Key toolchain) {
+      this.requiredToolchains.add(toolchain);
+      return this;
+    }
+
     /**
      * Returns an Attribute.Builder object which contains a replica of the
      * same attribute in the parent rule if exists.
@@ -1118,26 +1125,27 @@
    */
   private final boolean supportsConstraintChecking;
 
+  private final ImmutableList<ClassObjectConstructor.Key> requiredToolchains;
+
   /**
-   * Constructs an instance of RuleClass whose name is 'name', attributes
-   * are 'attributes'. The {@code srcsAllowedFiles} determines which types of
-   * files are allowed as parameters to the "srcs" attribute; rules are always
-   * allowed. For the "deps" attribute, there are four cases:
+   * Constructs an instance of RuleClass whose name is 'name', attributes are 'attributes'. The
+   * {@code srcsAllowedFiles} determines which types of files are allowed as parameters to the
+   * "srcs" attribute; rules are always allowed. For the "deps" attribute, there are four cases:
+   *
    * <ul>
-   *   <li>if the parameter is a file, it is allowed if its file type is given
-   *       in {@code depsAllowedFiles},
-   *   <li>if the parameter is a rule and the rule class is accepted by
-   *       {@code depsAllowedRules}, then it is allowed,
-   *   <li>if the parameter is a rule and the rule class is not accepted by
-   *       {@code depsAllowedRules}, but accepted by
-   *       {@code depsAllowedRulesWithWarning}, then it is allowed, but
-   *       triggers a warning;
+   *   <li>if the parameter is a file, it is allowed if its file type is given in {@code
+   *       depsAllowedFiles},
+   *   <li>if the parameter is a rule and the rule class is accepted by {@code depsAllowedRules},
+   *       then it is allowed,
+   *   <li>if the parameter is a rule and the rule class is not accepted by {@code
+   *       depsAllowedRules}, but accepted by {@code depsAllowedRulesWithWarning}, then it is
+   *       allowed, but triggers a warning;
    *   <li>all other parameters trigger an error.
    * </ul>
    *
-   * <p>The {@code depsAllowedRules} predicate should have a {@code toString}
-   * method which returns a plain English enumeration of the allowed rule class
-   * names, if it does not allow all rule classes.
+   * <p>The {@code depsAllowedRules} predicate should have a {@code toString} method which returns a
+   * plain English enumeration of the allowed rule class names, if it does not allow all rule
+   * classes.
    */
   @VisibleForTesting
   RuleClass(
@@ -1165,6 +1173,7 @@
       String ruleDefinitionEnvironmentHashCode,
       ConfigurationFragmentPolicy configurationFragmentPolicy,
       boolean supportsConstraintChecking,
+      List<ClassObjectConstructor.Key> requiredToolchains,
       Attribute... attributes) {
     this.name = name;
     this.isSkylark = isSkylark;
@@ -1193,6 +1202,7 @@
     this.outputsDefaultExecutable = outputsDefaultExecutable;
     this.configurationFragmentPolicy = configurationFragmentPolicy;
     this.supportsConstraintChecking = supportsConstraintChecking;
+    this.requiredToolchains = ImmutableList.copyOf(requiredToolchains);
 
     // Create the index and collect non-configurable attributes.
     int index = 0;
@@ -2002,6 +2012,10 @@
     return outputsDefaultExecutable;
   }
 
+  public ImmutableList<ClassObjectConstructor.Key> getRequiredToolchains() {
+    return requiredToolchains;
+  }
+
   public static boolean isThirdPartyPackage(PackageIdentifier packageIdentifier) {
     if (!packageIdentifier.getRepository().isMain()) {
       return false;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ToolchainConstructor.java b/src/main/java/com/google/devtools/build/lib/packages/ToolchainConstructor.java
index fe21f43e..13a965e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ToolchainConstructor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ToolchainConstructor.java
@@ -14,13 +14,14 @@
 package com.google.devtools.build.lib.packages;
 
 import com.google.devtools.build.lib.packages.ClassObjectConstructor.Key;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 
 /**
  * Constructor that can be used to generate toolchains. The key returned by {@link #getKey()} is a
  * serializable representation of the constructor and serves to identify toolchains of the same
  * type.
  */
-public interface ToolchainConstructor {
+public interface ToolchainConstructor extends SkylarkValue {
 
   Key getKey();
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index 02b304f..083cb92 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -70,6 +70,7 @@
 import com.google.devtools.build.lib.packages.SkylarkExportable;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.packages.ToolchainConstructor;
 import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
 import com.google.devtools.build.lib.skylarkinterface.Param;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
@@ -266,122 +267,132 @@
         }
       };
 
-
   // TODO(bazel-team): implement attribute copy and other rule properties
   @SkylarkSignature(
-      name = "rule",
-      doc =
-          "Creates a new rule. Store it in a global value, so that it can be loaded and called "
-              + "from BUILD files.",
-      returnType = BaseFunction.class,
-      parameters = {
-          @Param(
-              name = "implementation",
-              type = BaseFunction.class,
-              doc =
-                  "the function implementing this rule, must have exactly one parameter: "
-                      + "<a href=\"ctx.html\">ctx</a>. The function is called during the analysis "
-                      + "phase for each instance of the rule. It can access the attributes "
-                      + "provided by the user. It must create actions to generate all the declared "
-                      + "outputs."
-          ),
-          @Param(
-              name = "test",
-              type = Boolean.class,
-              defaultValue = "False",
-              doc =
-                  "Whether this rule is a test rule. "
-                      + "If True, the rule must end with <code>_test</code> (otherwise it must "
-                      + "not), and there must be an action that generates "
-                      + "<code>ctx.outputs.executable</code>."
-          ),
-          @Param(
-              name = "attrs",
-              type = SkylarkDict.class,
-              noneable = true,
-              defaultValue = "None",
-              doc =
-                  "dictionary to declare all the attributes of the rule. It maps from an attribute "
-                      + "name to an attribute object (see <a href=\"attr.html\">attr</a> module). "
-                      + "Attributes starting with <code>_</code> are private, and can be used to "
-                      + "add an implicit dependency on a label. The attribute <code>name</code> is "
-                      + "implicitly added and must not be specified. Attributes "
-                      + "<code>visibility</code>, <code>deprecation</code>, <code>tags</code>, "
-                      + "<code>testonly</code>, and <code>features</code> are implicitly added and "
-                      + "cannot be overriden."
-          ),
-          // TODO(bazel-team): need to give the types of these builtin attributes
-          @Param(
-              name = "outputs",
-              type = SkylarkDict.class,
-              callbackEnabled = true,
-              noneable = true,
-              defaultValue = "None",
-              doc =
-                  "outputs of this rule. "
-                      + "It is a dictionary mapping from string to a template name. "
-                      + "For example: <code>{\"ext\": \"%{name}.ext\"}</code>. <br>"
-                      + "The dictionary key becomes an attribute in <code>ctx.outputs</code>. "
-                      + "Similar to computed dependency rule attributes, you can also specify the "
-                      + "name of a function that returns the dictionary. This function can access "
-                      + "all rule attributes that are listed as parameters in its function "
-                      + "signature. For example, <code>outputs = _my_func</code> with "
-                      + "<code>def _my_func(srcs, deps):</code> has access to the attributes "
-                      + "'srcs' and 'deps' (if defined)."
-          ),
-          @Param(
-              name = "executable",
-              type = Boolean.class,
-              defaultValue = "False",
-              doc =
-                  "whether this rule is marked as executable or not. If True, "
-                      + "there must be an action that generates "
-                      + "<code>ctx.outputs.executable</code>."
-          ),
-          @Param(
-              name = "output_to_genfiles",
-              type = Boolean.class,
-              defaultValue = "False",
-              doc =
-                  "If true, the files will be generated in the genfiles directory instead of the "
-                      + "bin directory. Unless you need it for compatibility with existing rules "
-                      + "(e.g. when generating header files for C++), do not set this flag."
-          ),
-          @Param(
-              name = "fragments",
-              type = SkylarkList.class,
-              generic1 = String.class,
-              defaultValue = "[]",
-              doc =
-                  "List of names of configuration fragments that the rule requires "
-                      + "in target configuration."
-          ),
-          @Param(
-              name = "host_fragments",
-              type = SkylarkList.class,
-              generic1 = String.class,
-              defaultValue = "[]",
-              doc =
-                  "List of names of configuration fragments that the rule requires "
-                      + "in host configuration."
-          ),
-          @Param(
-              name = "_skylark_testable",
-              type = Boolean.class,
-              defaultValue = "False",
-              doc =
-                  "<i>(Experimental)</i><br/><br/>"
-                      + "If true, this rule will expose its actions for inspection by rules that "
-                      + "depend on it via an <a href=\"globals.html#Actions\">Actions</a> "
-                      + "provider. The provider is also available to the rule itself by calling "
-                      + "<a href=\"ctx.html#created_actions\">ctx.created_actions()</a>."
-                      + "<br/><br/>"
-                      + "This should only be used for testing the analysis-time behavior of "
-                      + "Skylark rules. This flag may be removed in the future."
-          )
-      },
-      useAst = true,
-      useEnvironment = true
+    name = "rule",
+    doc =
+        "Creates a new rule. Store it in a global value, so that it can be loaded and called "
+            + "from BUILD files.",
+    returnType = BaseFunction.class,
+    parameters = {
+      @Param(
+        name = "implementation",
+        type = BaseFunction.class,
+        doc =
+            "the function implementing this rule, must have exactly one parameter: "
+                + "<a href=\"ctx.html\">ctx</a>. The function is called during the analysis "
+                + "phase for each instance of the rule. It can access the attributes "
+                + "provided by the user. It must create actions to generate all the declared "
+                + "outputs."
+      ),
+      @Param(
+        name = "test",
+        type = Boolean.class,
+        defaultValue = "False",
+        doc =
+            "Whether this rule is a test rule. "
+                + "If True, the rule must end with <code>_test</code> (otherwise it must "
+                + "not), and there must be an action that generates "
+                + "<code>ctx.outputs.executable</code>."
+      ),
+      @Param(
+        name = "attrs",
+        type = SkylarkDict.class,
+        noneable = true,
+        defaultValue = "None",
+        doc =
+            "dictionary to declare all the attributes of the rule. It maps from an attribute "
+                + "name to an attribute object (see <a href=\"attr.html\">attr</a> module). "
+                + "Attributes starting with <code>_</code> are private, and can be used to "
+                + "add an implicit dependency on a label. The attribute <code>name</code> is "
+                + "implicitly added and must not be specified. Attributes "
+                + "<code>visibility</code>, <code>deprecation</code>, <code>tags</code>, "
+                + "<code>testonly</code>, and <code>features</code> are implicitly added and "
+                + "cannot be overriden."
+      ),
+      // TODO(bazel-team): need to give the types of these builtin attributes
+      @Param(
+        name = "outputs",
+        type = SkylarkDict.class,
+        callbackEnabled = true,
+        noneable = true,
+        defaultValue = "None",
+        doc =
+            "outputs of this rule. "
+                + "It is a dictionary mapping from string to a template name. "
+                + "For example: <code>{\"ext\": \"%{name}.ext\"}</code>. <br>"
+                + "The dictionary key becomes an attribute in <code>ctx.outputs</code>. "
+                + "Similar to computed dependency rule attributes, you can also specify the "
+                + "name of a function that returns the dictionary. This function can access "
+                + "all rule attributes that are listed as parameters in its function "
+                + "signature. For example, <code>outputs = _my_func</code> with "
+                + "<code>def _my_func(srcs, deps):</code> has access to the attributes "
+                + "'srcs' and 'deps' (if defined)."
+      ),
+      @Param(
+        name = "executable",
+        type = Boolean.class,
+        defaultValue = "False",
+        doc =
+            "whether this rule is marked as executable or not. If True, "
+                + "there must be an action that generates "
+                + "<code>ctx.outputs.executable</code>."
+      ),
+      @Param(
+        name = "output_to_genfiles",
+        type = Boolean.class,
+        defaultValue = "False",
+        doc =
+            "If true, the files will be generated in the genfiles directory instead of the "
+                + "bin directory. Unless you need it for compatibility with existing rules "
+                + "(e.g. when generating header files for C++), do not set this flag."
+      ),
+      @Param(
+        name = "fragments",
+        type = SkylarkList.class,
+        generic1 = String.class,
+        defaultValue = "[]",
+        doc =
+            "List of names of configuration fragments that the rule requires "
+                + "in target configuration."
+      ),
+      @Param(
+        name = "host_fragments",
+        type = SkylarkList.class,
+        generic1 = String.class,
+        defaultValue = "[]",
+        doc =
+            "List of names of configuration fragments that the rule requires "
+                + "in host configuration."
+      ),
+      @Param(
+        name = "_skylark_testable",
+        type = Boolean.class,
+        defaultValue = "False",
+        doc =
+            "<i>(Experimental)</i><br/><br/>"
+                + "If true, this rule will expose its actions for inspection by rules that "
+                + "depend on it via an <a href=\"globals.html#Actions\">Actions</a> "
+                + "provider. The provider is also available to the rule itself by calling "
+                + "<a href=\"ctx.html#created_actions\">ctx.created_actions()</a>."
+                + "<br/><br/>"
+                + "This should only be used for testing the analysis-time behavior of "
+                + "Skylark rules. This flag may be removed in the future."
+      ),
+      @Param(
+        name = "toolchains",
+        type = SkylarkList.class,
+        generic1 = ToolchainConstructor.class,
+        defaultValue = "[]",
+        doc =
+            "<i>(Experimental)</i><br/><br/>"
+                + "If set, the set of toolchains this rule requires. Toolchains will be "
+                + "found by checking the current platform, and provided to the rule "
+                + "implementation via <code>ctx.toolchain</code>."
+      )
+    },
+    useAst = true,
+    useEnvironment = true
   )
   private static final BuiltinFunction rule =
       new BuiltinFunction("rule") {
@@ -397,6 +408,7 @@
             SkylarkList fragments,
             SkylarkList hostFragments,
             Boolean skylarkTestable,
+            SkylarkList<ToolchainConstructor> toolchains,
             FuncallExpression ast,
             Environment funcallEnv)
             throws EvalException, ConversionException {
@@ -455,6 +467,11 @@
               hostFragments.getContents(String.class, "host_fragments"));
           builder.setConfiguredTargetFunction(implementation);
           builder.setRuleDefinitionEnvironment(funcallEnv);
+
+          for (ToolchainConstructor toolchain : toolchains) {
+            builder.addRequiredToolchain(toolchain.getKey());
+          }
+
           return new RuleFunction(builder, type, attributes, ast.getLocation());
         }
       };
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index 009b6e4..90c121b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -36,6 +36,7 @@
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.FragmentCollection;
+import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.events.Location;
@@ -179,6 +180,7 @@
   private SkylarkRuleAttributesCollection attributesCollection;
   private SkylarkRuleAttributesCollection ruleAttributesCollection;
   private SkylarkClassObject splitAttributes;
+  private SkylarkDict<ClassObjectConstructor.Key, ToolchainInfo> toolchains;
 
   // TODO(bazel-team): we only need this because of the css_binary rule.
   private ImmutableMap<Artifact, Label> artifactsLabelMap;
@@ -260,6 +262,7 @@
               this, attributes, ruleContext, attributeValueExtractorForRule(ruleContext));
       this.splitAttributes = buildSplitAttributeInfo(attributes, ruleContext);
       this.ruleAttributesCollection = null;
+      toolchains = ruleContext.getToolchainContext().collectToolchains();
     } else { // ASPECT
       this.artifactsLabelMap = ImmutableMap.of();
       this.outputsObject = null;
@@ -276,6 +279,8 @@
               ruleContext.getRule().getAttributes(),
               ruleContext,
               attributeValueExtractorForRule(ruleContext));
+      // TODO(katre): Collect toolchains for aspects.
+      toolchains = null;
     }
 
     makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables();
@@ -297,6 +302,7 @@
     splitAttributes = null;
     artifactsLabelMap = null;
     outputsObject = null;
+    toolchains = null;
   }
 
   public void checkMutable(String attrName) throws EvalException {
@@ -810,6 +816,17 @@
     return makeVariables;
   }
 
+  @SkylarkCallable(structField = true, doc = "Toolchains required for this rule.")
+  public SkylarkDict<ClassObjectConstructor.Key, ToolchainInfo> toolchains() throws EvalException {
+    checkMutable("toolchains");
+    if (ruleAttributesCollection != null) {
+      // TODO(katre): Support toolchains on aspects.
+      throw new EvalException(
+          Location.BUILTIN, "'toolchains' is not available in aspect implementations");
+    }
+    return toolchains;
+  }
+
   @Override
   public String toString() {
     return ruleLabelCanonicalName;
diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
index b0a23b4..63ccc24 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
@@ -908,6 +908,7 @@
             .setMissingFragmentPolicy(missingFragmentPolicy)
             .build(),
         supportsConstraintChecking,
+        /*requiredToolchains=*/ ImmutableList.<ClassObjectConstructor.Key>of(),
         attributes);
   }
 
@@ -1040,4 +1041,21 @@
       // expected
     }
   }
+
+  @Test
+  public void testRequiredToolchains() throws Exception {
+    RuleClass.Builder ruleClassBuilder =
+        new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false)
+            .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
+            .add(attr("tags", STRING_LIST));
+
+    ClassObjectConstructor.Key toolchain1 = new ClassObjectConstructor.Key() {};
+    ClassObjectConstructor.Key toolchain2 = new ClassObjectConstructor.Key() {};
+    ruleClassBuilder.addRequiredToolchain(toolchain1);
+    ruleClassBuilder.addRequiredToolchain(toolchain2);
+
+    RuleClass ruleClass = ruleClassBuilder.build();
+
+    assertThat(ruleClass.getRequiredToolchains()).containsExactly(toolchain1, toolchain2).inOrder();
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/BUILD b/src/test/java/com/google/devtools/build/lib/skylark/BUILD
index 0cd4824..52a35a8 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skylark/BUILD
@@ -20,6 +20,7 @@
         "//src/main/java/com/google/devtools/build/lib:packages",
         "//src/main/java/com/google/devtools/build/lib:vfs",
         "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/rules/platform",
         "//src/test/java/com/google/devtools/build/lib:analysis_testutil",
         "//src/test/java/com/google/devtools/build/lib:foundations_testutil",
         "//src/test/java/com/google/devtools/build/lib:syntax_testutil",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 8852be8..2be126b 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -42,6 +42,7 @@
 import com.google.devtools.build.lib.packages.SkylarkClassObject;
 import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
 import com.google.devtools.build.lib.packages.SkylarkProviderIdentifier;
+import com.google.devtools.build.lib.packages.ToolchainConstructor;
 import com.google.devtools.build.lib.rules.SkylarkAttr;
 import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor;
 import com.google.devtools.build.lib.rules.SkylarkFileType;
@@ -1377,5 +1378,15 @@
           + "provided.");
     }
   }
-}
 
+  @Test
+  public void testRuleAddToolchain() throws Exception {
+    evalAndExport(
+        "my_toolchain_type = platform_common.toolchain_type()",
+        "def impl(ctx): return None",
+        "r1 = rule(impl, toolchains=[my_toolchain_type])");
+    ToolchainConstructor toolchain = (ToolchainConstructor) lookup("my_toolchain_type");
+    RuleClass c = ((RuleFunction) lookup("r1")).getRuleClass();
+    assertThat(c.getRequiredToolchains()).containsExactly(toolchain.getKey());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
index 8c86c07..e416997 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
@@ -26,12 +26,14 @@
 import com.google.devtools.build.lib.packages.PackageFactory.PackageContext;
 import com.google.devtools.build.lib.rules.SkylarkModules;
 import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.rules.platform.PlatformCommon;
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.Environment.Phase;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.SkylarkUtils;
 import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestConstants;
+import java.util.List;
 import org.junit.Before;
 
 /**
@@ -52,10 +54,15 @@
     return new EvaluationTestCase() {
       @Override
       public Environment newEnvironment() throws Exception {
+        List<Class<?>> modules =
+            new ImmutableList.Builder<Class<?>>()
+                .addAll(SkylarkModules.MODULES)
+                .add(PlatformCommon.class)
+                .build();
         Environment env =
             Environment.builder(mutability)
                 .setEventHandler(getEventHandler())
-                .setGlobals(SkylarkModules.getGlobals(SkylarkModules.MODULES))
+                .setGlobals(SkylarkModules.getGlobals(modules))
                 .setPhase(Phase.LOADING)
                 .build()
                 .setupDynamic(