Skylark: only allow rules that are exported

For the purpose of package serialization (that will be necessary for caching),
only accept to use RuleFunction-s (as defined by skylark's rule() function)
that have been exported from a .bzl file with foo = rule(...), using
a finalization pass that walks exported identifiers and blesses RuleFunction-s.

--
MOS_MIGRATED_REVID=97236441
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 f05d222..6fcf85f 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
@@ -73,6 +73,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkSignature;
 import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
 import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
+import com.google.devtools.build.lib.vfs.PathFragment;
 
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
@@ -284,6 +285,8 @@
     // This is fine since we don't modify the builder from here.
     private final RuleClass.Builder builder;
     private final RuleClassType type;
+    private PathFragment skylarkFile;
+    private String ruleClassName;
 
     public RuleFunction(Builder builder, RuleClassType type) {
       super("rule", FunctionSignature.KWARGS);
@@ -297,7 +300,10 @@
     public Object call(Object[] args, FuncallExpression ast, Environment env)
         throws EvalException, InterruptedException, ConversionException {
       try {
-        String ruleClassName = ast.getFunction().getName();
+        if (ruleClassName == null || skylarkFile == null) {
+          throw new EvalException(ast.getLocation(),
+              "Invalid rule class hasn't been exported by a Skylark file");
+        }
         if (ruleClassName.startsWith("_")) {
           throw new EvalException(ast.getLocation(), "Invalid rule class name '" + ruleClassName
               + "', cannot be private");
@@ -315,12 +321,36 @@
       }
     }
 
+    /**
+     * Export a RuleFunction from a Skylark file with a given name.
+     */
+    void export(PathFragment skylarkFile, String ruleClassName) {
+      this.skylarkFile = skylarkFile;
+      this.ruleClassName = ruleClassName;
+    }
+
     @VisibleForTesting
     RuleClass.Builder getBuilder() {
       return builder;
     }
   }
 
+  public static void exportRuleFunctions(SkylarkEnvironment env, PathFragment skylarkFile) {
+    for (String name : env.getDirectVariableNames()) {
+      try {
+        Object value = env.lookup(name);
+        if (value instanceof RuleFunction) {
+          RuleFunction function = (RuleFunction) value;
+          if (function.skylarkFile == null) {
+            function.export(skylarkFile, name);
+          }
+        }
+      } catch (NoSuchVariableException e) {
+        throw new AssertionError(e);
+      }
+    }
+  }
+
   @SkylarkSignature(name = "Label", doc = "Creates a Label referring to a BUILD target. Use "
       + "this function only when you want to give a default value for the label attributes. "
       + "Example: <br><pre class=language-python>Label(\"//tools:default\")</pre>",