Add an intermediate SkyFunction for resolving the external package

The WORKSPACE file parsing needs to be separated into several parts
to enable load of labels in the WORKSPACE file.

This change adds an intermediate SkyFunction, ExternalPackageFunction,
that requires all the WORKSPACE file part to be parsed to resolve
//external: labels.

Issue #824 Step 1.

--
MOS_MIGRATED_REVID=113984026
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryLoaderFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryLoaderFunction.java
index 2753346..a225672 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryLoaderFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryLoaderFunction.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skyframe.ExternalPackageFunction;
 import com.google.devtools.build.lib.skyframe.PackageValue;
 import com.google.devtools.build.lib.skyframe.RepositoryValue;
 import com.google.devtools.build.lib.vfs.Path;
@@ -48,7 +49,7 @@
       return null;
     }
 
-    SkyKey workspaceKey = PackageValue.workspaceKey(
+    SkyKey workspaceKey = ExternalPackageFunction.key(
         RootedPath.toRootedPath(repository.getPath(), new PathFragment("WORKSPACE")));
     PackageValue workspacePackage = (PackageValue) env.getValue(workspaceKey);
     if (workspacePackage == null) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ExternalPackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ExternalPackageFunction.java
new file mode 100644
index 0000000..283e1a6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ExternalPackageFunction.java
@@ -0,0 +1,65 @@
+// Copyright 2016 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.skyframe;
+
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import javax.annotation.Nullable;
+
+/**
+ * A SkyFunction for resolving //external:* bindings.
+ *
+ * <p>This function iterates through the WorkspaceFileValue-s to get the last WorkspaceFileValue
+ * that will contains all the bind statements from the workspace file.
+ */
+public class ExternalPackageFunction implements SkyFunction {
+
+  @Nullable
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env)
+      throws SkyFunctionException, InterruptedException {
+    RootedPath workspacePath = (RootedPath) skyKey.argument();
+    SkyKey key = WorkspaceFileValue.key(workspacePath);
+    WorkspaceFileValue value = (WorkspaceFileValue) env.getValue(key);
+    if (value == null) {
+      return null;
+    }
+    // Walk to the last WorkspaceFileValue that accumulate all the bindings of the WORKSPACE
+    // file.
+    while (value.next() != null) {
+      value = (WorkspaceFileValue) env.getValue(value.next());
+      if (value == null) {
+        return null;
+      }
+    }
+    return new PackageValue(value.getPackage());
+  }
+
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  /**
+   * Returns a SkyKey to find the WORKSPACE file at the given path.
+   */
+  public static SkyKey key(RootedPath workspacePath) {
+    return new SkyKey(SkyFunctions.EXTERNAL_PACKAGE, workspacePath);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index d762190..fc53e9e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -340,7 +340,7 @@
       throws PackageFunctionException {
     RootedPath workspacePath = RootedPath.toRootedPath(
         packageLookupPath, new PathFragment("WORKSPACE"));
-    SkyKey workspaceKey = PackageValue.workspaceKey(workspacePath);
+    SkyKey workspaceKey = ExternalPackageFunction.key(workspacePath);
     PackageValue workspace = null;
     try {
       workspace = (PackageValue) env.getValueOrThrow(workspaceKey, IOException.class,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
index 4e04eec..01e4af5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.NotComparableSkyValue;
 import com.google.devtools.build.skyframe.SkyKey;
 
@@ -66,10 +65,4 @@
     return keys;
   }
 
-  /**
-   * Returns a SkyKey to find the WORKSPACE file at the given path.
-   */
-  public static SkyKey workspaceKey(RootedPath workspacePath) {
-    return new SkyKey(SkyFunctions.WORKSPACE_FILE, workspacePath);
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index 0a04b85..3b393a9b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -238,7 +238,8 @@
           SkyFunctions.DIRECTORY_LISTING_STATE,
           SkyFunctions.TARGET_PATTERN,
           SkyFunctions.PREPARE_DEPS_OF_PATTERN,
-          SkyFunctions.WORKSPACE_FILE);
+          SkyFunctions.WORKSPACE_FILE,
+          SkyFunctions.EXTERNAL_PACKAGE);
 
   @Override
   protected void onNewPackageLocator(PathPackageLocator oldLocator, PathPackageLocator pkgLocator) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
index ed3508f..e6df127 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -99,6 +99,7 @@
   public static final SkyFunctionName REPOSITORY_DIRECTORY =
       SkyFunctionName.create("REPOSITORY_DIRECTORY");
   public static final SkyFunctionName WORKSPACE_AST = SkyFunctionName.create("WORKSPACE_AST");
+  public static final SkyFunctionName EXTERNAL_PACKAGE = SkyFunctionName.create("EXTERNAL_PACKAGE");
 
   public static Predicate<SkyKey> isSkyFunction(final SkyFunctionName functionName) {
     return new Predicate<SkyKey>() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index cd26d3f..291730a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -385,6 +385,7 @@
     map.put(
         SkyFunctions.WORKSPACE_FILE,
         new WorkspaceFileFunction(ruleClassProvider, pkgFactory, directories));
+    map.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
     map.put(SkyFunctions.TARGET_COMPLETION, CompletionFunction.targetCompletionFunction(eventBus));
     map.put(SkyFunctions.ASPECT_COMPLETION, CompletionFunction.aspectCompletionFunction(eventBus));
     map.put(SkyFunctions.TEST_COMPLETION, new TestCompletionFunction());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
index dc2b0c2..fd948ee 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.packages.WorkspaceFactory;
 import com.google.devtools.build.lib.skyframe.PackageFunction.PackageFunctionException;
+import com.google.devtools.build.lib.skyframe.WorkspaceFileValue.WorkspaceFileKey;
 import com.google.devtools.build.lib.syntax.BuildFileAST;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.vfs.Path;
@@ -53,7 +54,7 @@
   public SkyValue compute(SkyKey skyKey, Environment env) throws WorkspaceFileFunctionException,
       InterruptedException {
 
-    RootedPath workspaceRoot = (RootedPath) skyKey.argument();
+    RootedPath workspaceRoot = ((WorkspaceFileKey) skyKey.argument()).getPath();
     WorkspaceASTValue workspaceASTValue =
         (WorkspaceASTValue) env.getValue(new SkyKey(SkyFunctions.WORKSPACE_AST, workspaceRoot));
     if (workspaceASTValue == null) {
@@ -88,7 +89,7 @@
       }
     }
 
-    return new PackageValue(builder.build());
+    return new WorkspaceFileValue(builder.build(), workspaceRoot, 0, false);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileValue.java
new file mode 100644
index 0000000..877d7c5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileValue.java
@@ -0,0 +1,152 @@
+// Copyright 2016 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.skyframe;
+
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.util.Objects;
+
+/**
+ * A SkyValue that contains the result of the parsing of one part of the WORKSPACE file. The parsing
+ * of the WORKSPACE file is split before each series of load statement because we need to resolve
+ * repositories before being able to load from those repositories.
+ */
+public class WorkspaceFileValue implements SkyValue {
+
+  /**
+   * Argument for the SkyKey to request a WorkspaceFileValue.
+   */
+  @Immutable
+  public static class WorkspaceFileKey {
+    private final RootedPath path;
+    private final int idx;
+
+    /**
+     * Creates a Key for the WorkspaceFileFunction. The path to the workspace file is specified
+     * by {@code path}. This key will ask WorkspaceFileFunction to get the {@code idx+1}-th part of
+     * the workspace file (so idx = 0 represents the first part, idx = 1, the second part, etc...).
+     */
+    public WorkspaceFileKey(RootedPath path, int idx) {
+      this.path = path;
+      this.idx = idx;
+    }
+
+    public RootedPath getPath() {
+      return path;
+    }
+
+    public int getIndex() {
+      return idx;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (!(obj instanceof WorkspaceFileKey)) {
+        return false;
+      }
+      WorkspaceFileKey other = (WorkspaceFileKey) obj;
+      return Objects.equals(path, other.path) && idx == other.idx;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(path.hashCode(), idx);
+    }
+
+    @Override
+    public String toString() {
+      return path + ", " + idx;
+    }
+  }
+
+  private final Package pkg;
+  private final int idx;
+  private final RootedPath path;
+  private final boolean hasNext;
+
+  public WorkspaceFileValue(Package pkg, RootedPath path, int idx, boolean hasNext) {
+    this.pkg = Preconditions.checkNotNull(pkg);
+    this.idx = idx;
+    this.path = path;
+    this.hasNext = hasNext;
+  }
+
+  /**
+   * Returns the package. This package may contain errors, in which case the caller should throw
+   * a {@link BuildFileContainsErrorsException}.
+   */
+  public Package getPackage() {
+    return pkg;
+  }
+
+  @Override
+  public String toString() {
+    return "<WorkspaceFileValue idx=" + idx + ">";
+  }
+
+  private static SkyKey key(RootedPath path, int idx) {
+    return new SkyKey(SkyFunctions.WORKSPACE_FILE, new WorkspaceFileKey(path, idx));
+  }
+
+  public static SkyKey key(RootedPath path) {
+    return key(path, 0);
+  }
+
+  /**
+   * Get the key for the next WorkspaceFileValue or null if this value is the last part of the
+   * workspace file.
+   */
+  public SkyKey next() {
+    if (hasNext) {
+      return key(path, idx + 1);
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * The workspace file parsing is cut in several parts and this function returns the index of the
+   * part of the workspace file that this value holds. For the first part, this index will be 0, for
+   * the second part, it will be 1 and so on.
+   */
+  public int getIndex() {
+    return idx;
+  }
+
+  /**
+   * The workspace file parsing is cut in several parts and this function returns true if there is
+   * a part following the part holds by this value (or false if this is the last part of the
+   * WORKSPACE file.
+   *
+   * <p>This method is public for serialization of the WorkspaceFileValue, #next() should be used
+   * to iterate instead of this method.
+   */
+  public boolean hasNext() {
+    return hasNext;
+  }
+
+  public RootedPath getPath() {
+    return path;
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index 4870806..10d05ff 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -114,15 +114,20 @@
                 .put(SkyFunctions.FILE, new FileFunction(pkgLocator))
                 .put(SkyFunctions.ARTIFACT, new ArtifactFunction(allowedMissingInputsPredicate))
                 .put(SkyFunctions.ACTION_EXECUTION, new SimpleActionExecutionFunction())
-                .put(SkyFunctions.PACKAGE,
+                .put(
+                    SkyFunctions.PACKAGE,
                     new PackageFunction(null, null, null, null, null, null, null))
                 .put(SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction(null))
-                .put(SkyFunctions.WORKSPACE_AST,
+                .put(
+                    SkyFunctions.WORKSPACE_AST,
                     new WorkspaceASTFunction(TestRuleClassProvider.getRuleClassProvider()))
-                .put(SkyFunctions.WORKSPACE_FILE,
-                    new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
+                .put(
+                    SkyFunctions.WORKSPACE_FILE,
+                    new WorkspaceFileFunction(
+                        TestRuleClassProvider.getRuleClassProvider(),
                         new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
                         new BlazeDirectories(root, root, root)))
+                .put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction())
                 .build(),
             differencer);
     driver = new SequentialBuildDriver(evaluator);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
index 7780f7f..ed239f3 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
@@ -130,22 +130,30 @@
         new InMemoryMemoizingEvaluator(
             ImmutableMap.<SkyFunctionName, SkyFunction>builder()
                 .put(SkyFunctions.FILE_STATE, new FileStateFunction(tsgm, externalFilesHelper))
-                .put(SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS,
+                .put(
+                    SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS,
                     new FileSymlinkCycleUniquenessFunction())
-                .put(SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+                .put(
+                    SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
                     new FileSymlinkInfiniteExpansionUniquenessFunction())
                 .put(SkyFunctions.FILE, new FileFunction(pkgLocatorRef))
-                .put(SkyFunctions.PACKAGE,
+                .put(
+                    SkyFunctions.PACKAGE,
                     new PackageFunction(null, null, null, null, null, null, null))
-                .put(SkyFunctions.PACKAGE_LOOKUP,
-                    new PackageLookupFunction(new AtomicReference<>(
-                        ImmutableSet.<PackageIdentifier>of())))
-                .put(SkyFunctions.WORKSPACE_AST,
+                .put(
+                    SkyFunctions.PACKAGE_LOOKUP,
+                    new PackageLookupFunction(
+                        new AtomicReference<>(ImmutableSet.<PackageIdentifier>of())))
+                .put(
+                    SkyFunctions.WORKSPACE_AST,
                     new WorkspaceASTFunction(TestRuleClassProvider.getRuleClassProvider()))
-                .put(SkyFunctions.WORKSPACE_FILE,
-                    new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
+                .put(
+                    SkyFunctions.WORKSPACE_FILE,
+                    new WorkspaceFileFunction(
+                        TestRuleClassProvider.getRuleClassProvider(),
                         new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
                         new BlazeDirectories(pkgRoot, outputBase, pkgRoot)))
+                .put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction())
                 .build(),
             differencer);
     PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID());
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index 3b0380e..95cb020 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -119,6 +119,7 @@
         new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
             new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
             new BlazeDirectories(pkgRoot, pkgRoot, pkgRoot)));
+    skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
 
     differencer = new RecordingDifferencer();
     evaluator = new InMemoryMemoizingEvaluator(skyFunctions.build(), differencer);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
index 4195a3d..39b038f 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
@@ -97,6 +97,7 @@
             new PackageFactory(
                 ruleClassProvider, new BazelRulesModule().getPackageEnvironmentExtension()),
             directories));
+    skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
     differencer = new RecordingDifferencer();
     evaluator = new InMemoryMemoizingEvaluator(skyFunctions, differencer);
     driver = new SequentialBuildDriver(evaluator);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
index 62a375f..5e2ebfd 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
@@ -114,6 +114,7 @@
         new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
             new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
             new BlazeDirectories(rootDirectory, outputBase, rootDirectory)));
+    skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
 
     progressReceiver = new RecordingEvaluationProgressReceiver();
     differencer = new RecordingDifferencer();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index 995ece5..2218684 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -155,19 +155,26 @@
             ImmutableMap.<SkyFunctionName, SkyFunction>builder()
                 .put(SkyFunctions.FILE_STATE, new FileStateFunction(tsgm, externalFilesHelper))
                 .put(SkyFunctions.FILE, new FileFunction(pkgLocator))
-                .put(SkyFunctions.ARTIFACT,
+                .put(
+                    SkyFunctions.ARTIFACT,
                     new ArtifactFunction(Predicates.<PathFragment>alwaysFalse()))
-                .put(SkyFunctions.ACTION_EXECUTION,
+                .put(
+                    SkyFunctions.ACTION_EXECUTION,
                     new ActionExecutionFunction(skyframeActionExecutor, tsgm))
-                .put(SkyFunctions.PACKAGE,
+                .put(
+                    SkyFunctions.PACKAGE,
                     new PackageFunction(null, null, null, null, null, null, null))
                 .put(SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction(null))
-                .put(SkyFunctions.WORKSPACE_AST,
+                .put(
+                    SkyFunctions.WORKSPACE_AST,
                     new WorkspaceASTFunction(TestRuleClassProvider.getRuleClassProvider()))
-                .put(SkyFunctions.WORKSPACE_FILE,
-                    new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
+                .put(
+                    SkyFunctions.WORKSPACE_FILE,
+                    new WorkspaceFileFunction(
+                        TestRuleClassProvider.getRuleClassProvider(),
                         new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
                         new BlazeDirectories(rootDirectory, outputBase, rootDirectory)))
+                .put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction())
                 .build(),
             differencer,
             evaluationProgressReceiver);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java
index 3f7c0d5..0f4ce40 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunctionTest.java
@@ -55,7 +55,8 @@
 @RunWith(JUnit4.class)
 public class WorkspaceFileFunctionTest extends BuildViewTestCase {
 
-  private WorkspaceFileFunction skyFunc;
+  private WorkspaceFileFunction workspaceSkyFunc;
+  private ExternalPackageFunction externalSkyFunc;
   private WorkspaceASTFunction astSkyFunc;
   private FakeFileValue fakeWorkspaceFileValue;
 
@@ -101,12 +102,13 @@
   @Before
   public final void setUp() throws Exception {
     ConfiguredRuleClassProvider ruleClassProvider = TestRuleClassProvider.getRuleClassProvider();
-    skyFunc = 
+    workspaceSkyFunc =
         new WorkspaceFileFunction(
             ruleClassProvider,
-            new PackageFactory(ruleClassProvider,
-                new BazelRulesModule().getPackageEnvironmentExtension()),
+            new PackageFactory(
+                ruleClassProvider, new BazelRulesModule().getPackageEnvironmentExtension()),
             directories);
+    externalSkyFunc = new ExternalPackageFunction();
     astSkyFunc = new WorkspaceASTFunction(ruleClassProvider);
     fakeWorkspaceFileValue = new FakeFileValue();
   }
@@ -140,19 +142,29 @@
     @Override
     public void describeTo(Description description) {}
   }
-  
+
   private SkyFunction.Environment getEnv() {
     SkyFunction.Environment env = Mockito.mock(SkyFunction.Environment.class);
     Mockito.when(env.getValue(Matchers.argThat(new SkyKeyMatchers(SkyFunctions.FILE))))
         .thenReturn(fakeWorkspaceFileValue);
+    Mockito.when(env.getValue(Matchers.argThat(new SkyKeyMatchers(SkyFunctions.WORKSPACE_FILE))))
+        .then(
+            new Answer<SkyValue>() {
+              @Override
+              public SkyValue answer(InvocationOnMock invocation) throws Throwable {
+                SkyKey key = (SkyKey) invocation.getArguments()[0];
+                return workspaceSkyFunc.compute(key, getEnv());
+              }
+            });
     Mockito.when(env.getValue(Matchers.argThat(new SkyKeyMatchers(SkyFunctions.WORKSPACE_AST))))
-    .then(new Answer<SkyValue>() {
-      @Override
-      public SkyValue answer(InvocationOnMock invocation) throws Throwable {
-        SkyKey key = (SkyKey) invocation.getArguments()[0];
-        return astSkyFunc.compute(key, getEnv());
-      }
-    });
+        .then(
+            new Answer<SkyValue>() {
+              @Override
+              public SkyValue answer(InvocationOnMock invocation) throws Throwable {
+                SkyKey key = (SkyKey) invocation.getArguments()[0];
+                return astSkyFunc.compute(key, getEnv());
+              }
+            });
     return env;
   }
 
@@ -160,7 +172,8 @@
   public void testInvalidRepo() throws Exception {
     RootedPath workspacePath = createWorkspaceFile("workspace(name = 'foo$')");
     PackageValue value =
-        (PackageValue) skyFunc.compute(PackageValue.workspaceKey(workspacePath), getEnv());
+        (PackageValue) externalSkyFunc
+            .compute(ExternalPackageFunction.key(workspacePath), getEnv());
     Package pkg = value.getPackage();
     assertTrue(pkg.containsErrors());
     MoreAsserts.assertContainsEvent(pkg.getEvents(), "foo$ is not a legal workspace name");
@@ -171,8 +184,8 @@
     String lines[] = {"bind(name = 'foo/bar',", "actual = '//foo:bar')"};
     RootedPath workspacePath = createWorkspaceFile(lines);
 
-    SkyKey key = PackageValue.workspaceKey(workspacePath);
-    PackageValue value = (PackageValue) skyFunc.compute(key, getEnv());
+    SkyKey key = ExternalPackageFunction.key(workspacePath);
+    PackageValue value = (PackageValue) externalSkyFunc.compute(key, getEnv());
     Package pkg = value.getPackage();
     assertEquals(Label.parseAbsolute("//foo:bar"), getLabelMapping(pkg, "foo/bar"));
     MoreAsserts.assertNoEvents(pkg.getEvents());
@@ -183,8 +196,8 @@
     String lines[] = {"bind(actual = '//foo:bar', name = 'foo/bar')"};
     RootedPath workspacePath = createWorkspaceFile(lines);
 
-    SkyKey key = PackageValue.workspaceKey(workspacePath);
-    PackageValue value = (PackageValue) skyFunc.compute(key, getEnv());
+    SkyKey key = ExternalPackageFunction.key(workspacePath);
+    PackageValue value = (PackageValue) externalSkyFunc.compute(key, getEnv());
     Package pkg = value.getPackage();
     assertEquals(Label.parseAbsolute("//foo:bar"), getLabelMapping(pkg, "foo/bar"));
     MoreAsserts.assertNoEvents(pkg.getEvents());
@@ -197,7 +210,8 @@
     RootedPath workspacePath = createWorkspaceFile(lines);
 
     PackageValue value =
-        (PackageValue) skyFunc.compute(PackageValue.workspaceKey(workspacePath), getEnv());
+        (PackageValue) externalSkyFunc
+            .compute(ExternalPackageFunction.key(workspacePath), getEnv());
     Package pkg = value.getPackage();
     assertTrue(pkg.containsErrors());
     MoreAsserts.assertContainsEvent(pkg.getEvents(), "target names may not contain ':'");
@@ -210,7 +224,8 @@
     RootedPath workspacePath = createWorkspaceFile(lines);
 
     PackageValue value =
-        (PackageValue) skyFunc.compute(PackageValue.workspaceKey(workspacePath), getEnv());
+        (PackageValue) externalSkyFunc
+            .compute(ExternalPackageFunction.key(workspacePath), getEnv());
     Package pkg = value.getPackage();
     assertTrue(pkg.containsErrors());
     MoreAsserts.assertContainsEvent(pkg.getEvents(), "target names may not contain ':'");
@@ -224,7 +239,8 @@
     fakeWorkspaceFileValue.setExists(false);
 
     PackageValue value =
-        (PackageValue) skyFunc.compute(PackageValue.workspaceKey(workspacePath), getEnv());
+        (PackageValue) externalSkyFunc
+            .compute(ExternalPackageFunction.key(workspacePath), getEnv());
     Package pkg = value.getPackage();
     assertFalse(pkg.containsErrors());
     MoreAsserts.assertNoEvents(pkg.getEvents());
@@ -236,8 +252,8 @@
         "L = ['foo', 'bar']", "bind(name = '%s/%s' % (L[0], L[1]),", "actual = '//foo:bar')"};
     RootedPath workspacePath = createWorkspaceFile(lines);
 
-    SkyKey key = PackageValue.workspaceKey(workspacePath);
-    PackageValue value = (PackageValue) skyFunc.compute(key, getEnv());
+    SkyKey key = ExternalPackageFunction.key(workspacePath);
+    PackageValue value = (PackageValue) externalSkyFunc.compute(key, getEnv());
     Package pkg = value.getPackage();
     assertEquals(Label.parseAbsolute("//foo:bar"), getLabelMapping(pkg, "foo/bar"));
     MoreAsserts.assertNoEvents(pkg.getEvents());