Enable load() statement in the WORKSPACE file.

RELNOTES[NEW]: Skylark macros are now enabled in WORKSPACE file.

Design document at https://docs.google.com/document/d/1jKbNXOVp2T1zJD_iRnVr8k5D0xZKgO8blMVDlXOksJg/preview

Fixes #337

--
MOS_MIGRATED_REVID=108860301
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 579e060..a9ad155 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
@@ -688,6 +688,16 @@
     }
 
     /**
+     * Derive a LegacyBuilder from a normal Builder.
+     */
+    LegacyBuilder(Builder builder) {
+      super(builder.pkg);
+      if (builder.getFilename() != null) {
+        setFilename(builder.getFilename());
+      }
+    }
+
+    /**
      * Sets the globber used for this package's glob expansions.
      */
     LegacyBuilder setGlobber(Globber globber) {
@@ -717,8 +727,8 @@
     }
   }
 
-  public static Builder newExternalPackageBuilder(Path workspacePath, String runfilesPrefix) {
-    Builder b = new Builder(new Package(EXTERNAL_PACKAGE_IDENTIFIER, runfilesPrefix));
+  public static LegacyBuilder newExternalPackageBuilder(Path workspacePath, String runfilesPrefix) {
+    LegacyBuilder b = new LegacyBuilder(EXTERNAL_PACKAGE_IDENTIFIER, runfilesPrefix);
     b.setFilename(workspacePath);
     b.setMakeEnv(new MakeEnvironment.Builder());
     return b;
@@ -799,6 +809,11 @@
       return pkg.getPackageIdentifier();
     }
 
+    /** Determine if we are in the WORKSPACE file or not */
+    public boolean isWorkspace() {
+      return pkg.getPackageIdentifier().equals(EXTERNAL_PACKAGE_IDENTIFIER);
+    }
+
     /**
      * Sets the name of this package's BUILD file.
      */
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 987b572..6093a40 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -390,6 +390,7 @@
     this.environmentExtensions = ImmutableList.copyOf(environmentExtensions);
     this.packageArguments = createPackageArguments();
     this.nativeModule = newNativeModule();
+    this.workspaceNativeModule = WorkspaceFactory.newNativeModule(ruleClassProvider);
   }
 
   /**
@@ -1223,10 +1224,11 @@
   }
 
   private final ClassObject nativeModule;
+  private final ClassObject workspaceNativeModule;
 
   /** @return the Skylark struct to bind to "native" */
-  public ClassObject getNativeModule() {
-    return nativeModule;
+  public ClassObject getNativeModule(boolean workspace) {
+    return workspace ? workspaceNativeModule : nativeModule;
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
index d15994a..684289f 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java
@@ -101,8 +101,7 @@
     } catch (LabelSyntaxException e) {
       throw new InvalidRuleException("illegal rule name: " + name + ": " + e.getMessage());
     }
-    boolean inWorkspaceFile =
-        location.getPath() != null && location.getPath().getBaseName().contains("WORKSPACE");
+    boolean inWorkspaceFile = pkgBuilder.isWorkspace();
     if (ruleClass.getWorkspaceOnly() && !inWorkspaceFile) {
       throw new RuleFactory.InvalidRuleException(
           ruleClass + " must be in the WORKSPACE file " + "(used by " + label + ")");
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 4527f5e..4a089c3 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
@@ -17,24 +17,29 @@
 import static com.google.devtools.build.lib.syntax.Runtime.NONE;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.LabelValidator;
 import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.events.StoredEventHandler;
 import com.google.devtools.build.lib.packages.Package.Builder;
+import com.google.devtools.build.lib.packages.Package.LegacyBuilder;
 import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.BuildFileAST;
 import com.google.devtools.build.lib.syntax.BuiltinFunction;
+import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
 import com.google.devtools.build.lib.syntax.FunctionSignature;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.Runtime;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
 
 import java.io.File;
 import java.util.Map;
@@ -45,8 +50,17 @@
  * Parser for WORKSPACE files.  Fills in an ExternalPackage.Builder
  */
 public class WorkspaceFactory {
-  private final Builder builder;
-  private final Environment environment;
+  public static final String BIND = "bind";
+
+  private final LegacyBuilder builder;
+  private final StoredEventHandler localReporter;
+
+  private final Path installDir;
+  private final Path workspaceDir;
+  private final Mutability mutability;
+
+  private final ImmutableMap<String, BaseFunction> workspaceFunctions;
+  private final ImmutableList<EnvironmentExtension> environmentExtensions;
 
   /**
    * @param builder a builder for the Workspace
@@ -54,8 +68,10 @@
    * @param mutability the Mutability for the current evaluation context
    */
   public WorkspaceFactory(
-      Builder builder, RuleClassProvider ruleClassProvider,
-      ImmutableList<EnvironmentExtension> environmentExtensions, Mutability mutability) {
+      LegacyBuilder builder,
+      RuleClassProvider ruleClassProvider,
+      ImmutableList<EnvironmentExtension> environmentExtensions,
+      Mutability mutability) {
     this(builder, ruleClassProvider, environmentExtensions, mutability, null, null);
   }
 
@@ -69,62 +85,106 @@
    * @param workspaceDir the workspace directory
    */
   public WorkspaceFactory(
-      Builder builder,
+      LegacyBuilder builder,
       RuleClassProvider ruleClassProvider,
       ImmutableList<EnvironmentExtension> environmentExtensions,
       Mutability mutability,
       @Nullable Path installDir,
       @Nullable Path workspaceDir) {
     this.builder = builder;
-    this.environment = createWorkspaceEnv(builder, ruleClassProvider, environmentExtensions,
-        mutability, installDir, workspaceDir);
+    this.localReporter = new StoredEventHandler();
+    this.mutability = mutability;
+    this.installDir = installDir;
+    this.workspaceDir = workspaceDir;
+    this.environmentExtensions = environmentExtensions;
+    this.workspaceFunctions = createWorkspaceFunctions(ruleClassProvider);
   }
 
-  public void parse(ParserInputSource source)
-      throws InterruptedException {
-    StoredEventHandler localReporter = new StoredEventHandler();
-    BuildFileAST buildFileAST;
+  // State while parsing the WORKSPACE file.
+  // We store them so we can pause the parsing and load skylark imports from the
+  // WorkspaceFileFunction. Loading skylark imports require access to the .skyframe package
+  // which this package cannot depends on.
+  private BuildFileAST buildFileAST = null;
+  private Environment.Builder environmentBuilder = null;
+  private ParserInputSource source = null;
+
+  // Called by com.google.devtools.build.workspace.Resolver from //src/tools/generate_workspace.
+  public void parse(ParserInputSource source) throws InterruptedException {
+    // This method is split in 2 so WorkspaceFileFunction can call the two parts separately and
+    // do the Skylark load imports in between.
+    parseBuildFile(source);
+    execute();
+  }
+
+  public void parseBuildFile(ParserInputSource source) {
+    this.source = source;
     buildFileAST = BuildFileAST.parseBuildFile(source, localReporter, false);
     if (buildFileAST.containsErrors()) {
+      environmentBuilder = null;
       localReporter.handle(Event.error("WORKSPACE file could not be parsed"));
     } else {
-      if (!buildFileAST.exec(environment, localReporter)) {
+      environmentBuilder =
+          Environment.builder(mutability)
+              .setGlobals(Environment.BUILD)
+              .setEventHandler(localReporter);
+    }
+  }
+
+  public void execute() throws InterruptedException {
+    if (environmentBuilder != null) {
+      Environment workspaceEnv = environmentBuilder.setLoadingPhase().build();
+      addWorkspaceFunctions(workspaceEnv);
+      if (!buildFileAST.exec(workspaceEnv, localReporter)) {
         localReporter.handle(Event.error("Error evaluating WORKSPACE file " + source.getPath()));
       }
     }
+    environmentBuilder = null;
+    buildFileAST = null;
+    source = null;
 
     builder.addEvents(localReporter.getEvents());
     if (localReporter.hasErrors()) {
       builder.setContainsErrors();
     }
+    localReporter.clear();
+  }
+
+  public BuildFileAST getBuildFileAST() {
+    return buildFileAST;
+  }
+
+  public void setImportedExtensions(Map<PathFragment, Extension> importedExtensions) {
+    environmentBuilder.setImportedExtensions(importedExtensions);
   }
 
   // TODO(bazel-team): use @SkylarkSignature annotations on a BuiltinFunction.Factory
   // for signature + documentation of this and other functions in this file.
-  private static BuiltinFunction newWorkspaceNameFunction(final Builder builder) {
-    return new BuiltinFunction("workspace",
-        FunctionSignature.namedOnly("name"), BuiltinFunction.USE_LOC) {
-      public Object invoke(String name, Location loc) throws EvalException {
+  private static BuiltinFunction newWorkspaceNameFunction() {
+    return new BuiltinFunction(
+        "workspace", FunctionSignature.namedOnly("name"), BuiltinFunction.USE_AST_ENV) {
+      public Object invoke(String name, FuncallExpression ast, Environment env)
+          throws EvalException {
         String errorMessage = LabelValidator.validateTargetName(name);
         if (errorMessage != null) {
-          throw new EvalException(loc, errorMessage);
+          throw new EvalException(ast.getLocation(), errorMessage);
         }
-        builder.setWorkspaceName(name);
+
+        PackageFactory.getContext(env, ast).pkgBuilder.setWorkspaceName(name);
         return NONE;
       }
     };
   }
 
-  private static BuiltinFunction newBindFunction(
-      final RuleFactory ruleFactory, final Builder builder) {
+  private static BuiltinFunction newBindFunction(final RuleFactory ruleFactory) {
     return new BuiltinFunction(
-        "bind", FunctionSignature.namedOnly(1, "name", "actual"), BuiltinFunction.USE_LOC) {
-      public Object invoke(String name, String actual, Location loc)
+        "bind", FunctionSignature.namedOnly(1, "name", "actual"), BuiltinFunction.USE_AST_ENV) {
+      public Object invoke(String name, String actual, FuncallExpression ast, Environment env)
           throws EvalException, InterruptedException {
         Label nameLabel = null;
         try {
           nameLabel = Label.parseAbsolute("//external:" + name);
           try {
+            LegacyBuilder builder = PackageFactory.getContext(env, ast).pkgBuilder;
             RuleClass ruleClass = ruleFactory.getRuleClass("bind");
             builder
                 .externalPackageData()
@@ -133,16 +193,16 @@
                     ruleClass,
                     nameLabel,
                     actual == null ? null : Label.parseAbsolute(actual),
-                    loc);
+                    ast.getLocation());
           } catch (
               RuleFactory.InvalidRuleException | Package.NameConflictException
                       | LabelSyntaxException
                   e) {
-            throw new EvalException(loc, e.getMessage());
+            throw new EvalException(ast.getLocation(), e.getMessage());
           }
 
         } catch (LabelSyntaxException e) {
-          throw new EvalException(loc, e.getMessage());
+          throw new EvalException(ast.getLocation(), e.getMessage());
         }
         return NONE;
       }
@@ -154,12 +214,13 @@
    * specified package context.
    */
   private static BuiltinFunction newRuleFunction(
-      final RuleFactory ruleFactory, final Builder builder, final String ruleClassName) {
+      final RuleFactory ruleFactory, final String ruleClassName) {
     return new BuiltinFunction(
         ruleClassName, FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) {
       public Object invoke(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
           throws EvalException, InterruptedException {
         try {
+          Builder builder = PackageFactory.getContext(env, ast).pkgBuilder;
           RuleClass ruleClass = ruleFactory.getRuleClass(ruleClassName);
           RuleClass bindRuleClass = ruleFactory.getRuleClass("bind");
           builder
@@ -175,22 +236,25 @@
     };
   }
 
-  private Environment createWorkspaceEnv(
-      Builder builder,
-      RuleClassProvider ruleClassProvider,
-      ImmutableList<EnvironmentExtension> environmentExtensions,
-      Mutability mutability,
-      Path installDir,
-      Path workspaceDir) {
-    Environment workspaceEnv = Environment.builder(mutability)
-        .setGlobals(Environment.BUILD)
-        .setLoadingPhase()
-        .build();
+  private static ImmutableMap<String, BaseFunction> createWorkspaceFunctions(
+      RuleClassProvider ruleClassProvider) {
+    ImmutableMap.Builder<String, BaseFunction> mapBuilder = ImmutableMap.builder();
     RuleFactory ruleFactory = new RuleFactory(ruleClassProvider);
+    mapBuilder.put(BIND, newBindFunction(ruleFactory));
+    for (String ruleClass : ruleFactory.getRuleClassNames()) {
+      if (!ruleClass.equals(BIND)) {
+        BaseFunction ruleFunction = newRuleFunction(ruleFactory, ruleClass);
+        mapBuilder.put(ruleClass, ruleFunction);
+      }
+    }
+    return mapBuilder.build();
+  }
+
+  private void addWorkspaceFunctions(Environment workspaceEnv) {
     try {
-      for (String ruleClass : ruleFactory.getRuleClassNames()) {
-        BaseFunction ruleFunction = newRuleFunction(ruleFactory, builder, ruleClass);
-        workspaceEnv.update(ruleClass, ruleFunction);
+      workspaceEnv.update("workspace", newWorkspaceNameFunction());
+      for (Map.Entry<String, BaseFunction> function : workspaceFunctions.entrySet()) {
+        workspaceEnv.update(function.getKey(), function.getValue());
       }
       if (installDir != null) {
         workspaceEnv.update("__embedded_dir__", installDir.getPathString());
@@ -200,16 +264,32 @@
       }
       File jreDirectory = new File(System.getProperty("java.home"));
       workspaceEnv.update("DEFAULT_SERVER_JAVABASE", jreDirectory.getParentFile().toString());
-      workspaceEnv.update("bind", newBindFunction(ruleFactory, builder));
-      workspaceEnv.update("workspace", newWorkspaceNameFunction(builder));
 
       for (EnvironmentExtension extension : environmentExtensions) {
         extension.updateWorkspace(workspaceEnv);
       }
-
-      return workspaceEnv;
+      workspaceEnv.setupDynamic(
+          PackageFactory.PKG_CONTEXT,
+          new PackageFactory.PackageContext(builder, null, localReporter));
     } catch (EvalException e) {
       throw new AssertionError(e);
     }
   }
+
+  private static ClassObject newNativeModule(
+      ImmutableMap<String, BaseFunction> workspaceFunctions) {
+    ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
+    for (String nativeFunction : Runtime.getFunctionNames(SkylarkNativeModule.class)) {
+      builder.put(nativeFunction, Runtime.getFunction(SkylarkNativeModule.class, nativeFunction));
+    }
+    for (Map.Entry<String, BaseFunction> function : workspaceFunctions.entrySet()) {
+      builder.put(function.getKey(), function.getValue());
+    }
+
+    return new ClassObject.SkylarkClassObject(builder.build(), "no native function or rule '%s'");
+  }
+
+  public static ClassObject newNativeModule(RuleClassProvider ruleClassProvider) {
+    return newNativeModule(createWorkspaceFunctions(ruleClassProvider));
+  }
 }