Update from Google.

--
MOE_MIGRATED_REVID=85702957
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
new file mode 100644
index 0000000..2404b99
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -0,0 +1,809 @@
+// Copyright 2014 Google Inc. 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.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.Constants;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
+import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
+import com.google.devtools.build.lib.packages.CachingPackageLocator;
+import com.google.devtools.build.lib.packages.InvalidPackageNameException;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageFactory.Globber;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.PackageLoadedEvent;
+import com.google.devtools.build.lib.packages.Preprocessor;
+import com.google.devtools.build.lib.packages.RuleVisibility;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.skyframe.ASTFileLookupValue.ASTLookupInputException;
+import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
+import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.Statement;
+import com.google.devtools.build.lib.util.Clock;
+import com.google.devtools.build.lib.util.JavaClock;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+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.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrException3;
+import com.google.devtools.build.skyframe.ValueOrException4;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
+
+/**
+ * A SkyFunction for {@link PackageValue}s.
+ */
+public class PackageFunction implements SkyFunction {
+
+  private final EventHandler reporter;
+  private final PackageFactory packageFactory;
+  private final CachingPackageLocator packageLocator;
+  private final ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache;
+  private final AtomicBoolean showLoadingProgress;
+  private final AtomicReference<EventBus> eventBus;
+  private final AtomicInteger numPackagesLoaded;
+  private final Profiler profiler = Profiler.instance();
+
+  private static final PathFragment PRELUDE_FILE_FRAGMENT =
+      new PathFragment(Constants.PRELUDE_FILE_DEPOT_RELATIVE_PATH);
+
+  static final String DEFAULTS_PACKAGE_NAME = "tools/defaults";
+  public static final String EXTERNAL_PACKAGE_NAME = "external";
+
+  static {
+    Preconditions.checkArgument(!PRELUDE_FILE_FRAGMENT.isAbsolute());
+  }
+
+  public PackageFunction(Reporter reporter, PackageFactory packageFactory,
+      CachingPackageLocator pkgLocator, AtomicBoolean showLoadingProgress,
+      ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache,
+      AtomicReference<EventBus> eventBus, AtomicInteger numPackagesLoaded) {
+    this.reporter = reporter;
+
+    this.packageFactory = packageFactory;
+    this.packageLocator = pkgLocator;
+    this.showLoadingProgress = showLoadingProgress;
+    this.packageFunctionCache = packageFunctionCache;
+    this.eventBus = eventBus;
+    this.numPackagesLoaded = numPackagesLoaded;
+  }
+
+  private static void maybeThrowFilesystemInconsistency(String packageName,
+      Exception skyframeException, boolean packageWasInError)
+          throws InternalInconsistentFilesystemException {
+    if (!packageWasInError) {
+      throw new InternalInconsistentFilesystemException(packageName, "Encountered error '"
+          + skyframeException.getMessage() + "' but didn't encounter it when doing the same thing "
+          + "earlier in the build");
+    }
+  }
+
+  /**
+   * Marks the given dependencies, and returns those already present. Ignores any exception
+   * thrown while building the dependency, except for filesystem inconsistencies.
+   *
+   * <p>We need to mark dependencies implicitly used by the legacy package loading code, but we
+   * don't care about any skyframe errors since the package knows whether it's in error or not.
+   */
+  private static Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean>
+  getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(String packageName,
+      Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+          throws InternalInconsistentFilesystemException {
+    Preconditions.checkState(
+        Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE_LOOKUP)), depKeys);
+    boolean packageShouldBeInError = packageWasInError;
+    ImmutableMap.Builder<PathFragment, PackageLookupValue> builder = ImmutableMap.builder();
+    for (Map.Entry<SkyKey, ValueOrException3<BuildFileNotFoundException,
+        InconsistentFilesystemException, FileSymlinkCycleException>> entry :
+            env.getValuesOrThrow(depKeys, BuildFileNotFoundException.class,
+                InconsistentFilesystemException.class,
+                FileSymlinkCycleException.class).entrySet()) {
+      PathFragment pkgName = ((PackageIdentifier) entry.getKey().argument()).getPackageFragment();
+      try {
+        PackageLookupValue value = (PackageLookupValue) entry.getValue().get();
+        if (value != null) {
+          builder.put(pkgName, value);
+        }
+      } catch (BuildFileNotFoundException e) {
+        maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+      } catch (InconsistentFilesystemException e) {
+        throw new InternalInconsistentFilesystemException(packageName, e);
+      } catch (FileSymlinkCycleException e) {
+        // Legacy doesn't detect symlink cycles.
+        packageShouldBeInError = true;
+      }
+    }
+    return Pair.of(builder.build(), packageShouldBeInError);
+  }
+
+  private static boolean markFileDepsAndPropagateInconsistentFilesystemExceptions(
+      String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+          throws InternalInconsistentFilesystemException {
+    Preconditions.checkState(
+        Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.FILE)), depKeys);
+    boolean packageShouldBeInError = packageWasInError;
+    for (Map.Entry<SkyKey, ValueOrException3<IOException, FileSymlinkCycleException,
+        InconsistentFilesystemException>> entry : env.getValuesOrThrow(depKeys, IOException.class,
+            FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
+      try {
+        entry.getValue().get();
+      } catch (IOException e) {
+        maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+      } catch (FileSymlinkCycleException e) {
+        // Legacy doesn't detect symlink cycles.
+        packageShouldBeInError = true;
+      } catch (InconsistentFilesystemException e) {
+        throw new InternalInconsistentFilesystemException(packageName, e);
+      }
+    }
+    return packageShouldBeInError;
+  }
+
+  private static boolean markGlobDepsAndPropagateInconsistentFilesystemExceptions(
+      String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+          throws InternalInconsistentFilesystemException {
+    Preconditions.checkState(
+        Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.GLOB)), depKeys);
+    boolean packageShouldBeInError = packageWasInError;
+    for (Map.Entry<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException,
+        FileSymlinkCycleException, InconsistentFilesystemException>> entry :
+        env.getValuesOrThrow(depKeys, IOException.class, BuildFileNotFoundException.class,
+            FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
+      try {
+        entry.getValue().get();
+      } catch (IOException | BuildFileNotFoundException e) {
+        maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+      } catch (FileSymlinkCycleException e) {
+        // Legacy doesn't detect symlink cycles.
+        packageShouldBeInError = true;
+      } catch (InconsistentFilesystemException e) {
+        throw new InternalInconsistentFilesystemException(packageName, e);
+      }
+    }
+    return packageShouldBeInError;
+  }
+
+  /**
+   * Marks dependencies implicitly used by legacy package loading code, after the fact. Note that
+   * the given package might already be in error.
+   *
+   * <p>Any skyframe exceptions encountered here are ignored, as similar errors should have
+   * already been encountered by legacy package loading (if not, then the filesystem is
+   * inconsistent).
+   */
+  private static boolean markDependenciesAndPropagateInconsistentFilesystemExceptions(
+      Package pkg, Environment env, Collection<Pair<String, Boolean>> globPatterns,
+      Map<Label, Path> subincludes) throws InternalInconsistentFilesystemException {
+    boolean packageShouldBeInError = pkg.containsErrors();
+
+    // TODO(bazel-team): This means that many packages will have to be preprocessed twice. Ouch!
+    // We need a better continuation mechanism to avoid repeating work. [skyframe-loading]
+
+    // TODO(bazel-team): It would be preferable to perform I/O from the package preprocessor via
+    // Skyframe rather than add (potentially incomplete) dependencies after the fact.
+    // [skyframe-loading]
+
+    Set<SkyKey> subincludePackageLookupDepKeys = Sets.newHashSet();
+    for (Label label : pkg.getSubincludeLabels()) {
+      // Declare a dependency on the package lookup for the package giving access to the label.
+      subincludePackageLookupDepKeys.add(PackageLookupValue.key(label.getPackageFragment()));
+    }
+    Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean> subincludePackageLookupResult =
+        getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(pkg.getName(),
+            subincludePackageLookupDepKeys, env, pkg.containsErrors());
+    Map<PathFragment, PackageLookupValue> subincludePackageLookupDeps =
+        subincludePackageLookupResult.getFirst();
+    packageShouldBeInError = subincludePackageLookupResult.getSecond();
+    List<SkyKey> subincludeFileDepKeys = Lists.newArrayList();
+    for (Entry<Label, Path> subincludeEntry : subincludes.entrySet()) {
+      // Ideally, we would have a direct dependency on the target with the given label, but then
+      // subincluding a file from the same package will cause a dependency cycle, since targets
+      // depend on their containing packages.
+      Label label = subincludeEntry.getKey();
+      PackageLookupValue subincludePackageLookupValue =
+          subincludePackageLookupDeps.get(label.getPackageFragment());
+      if (subincludePackageLookupValue != null) {
+        // Declare a dependency on the actual file that was subincluded.
+        Path subincludeFilePath = subincludeEntry.getValue();
+        if (subincludeFilePath != null) {
+          if (!subincludePackageLookupValue.packageExists()) {
+            // Legacy blaze puts a non-null path when only when the package does indeed exist.
+            throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
+                "Unexpected package in %s. Was it modified during the build?", subincludeFilePath));
+          }
+          // Sanity check for consistency of Skyframe and legacy blaze.
+          Path subincludeFilePathSkyframe =
+              subincludePackageLookupValue.getRoot().getRelative(label.toPathFragment());
+          if (!subincludeFilePathSkyframe.equals(subincludeFilePath)) {
+            throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
+                "Inconsistent package location for %s: '%s' vs '%s'. "
+                + "Was the source tree modified during the build?",
+                label.getPackageFragment(), subincludeFilePathSkyframe, subincludeFilePath));
+          }
+          // The actual file may be under a different package root than the package being
+          // constructed.
+          SkyKey subincludeSkyKey =
+              FileValue.key(RootedPath.toRootedPath(subincludePackageLookupValue.getRoot(),
+                  subincludeFilePath));
+          subincludeFileDepKeys.add(subincludeSkyKey);
+        }
+      }
+    }
+    packageShouldBeInError = markFileDepsAndPropagateInconsistentFilesystemExceptions(
+        pkg.getName(), subincludeFileDepKeys, env, pkg.containsErrors());
+    // Another concern is a subpackage cutting off the subinclude label, but this is already
+    // handled by the legacy package loading code which calls into our SkyframePackageLocator.
+
+    // TODO(bazel-team): In the long term, we want to actually resolve the glob patterns within
+    // Skyframe. For now, just logging the glob requests provides correct incrementality and
+    // adequate performance.
+    PackageIdentifier packageId = pkg.getPackageIdentifier();
+    List<SkyKey> globDepKeys = Lists.newArrayList();
+    for (Pair<String, Boolean> globPattern : globPatterns) {
+      String pattern = globPattern.getFirst();
+      boolean excludeDirs = globPattern.getSecond();
+      SkyKey globSkyKey;
+      try {
+        globSkyKey = GlobValue.key(packageId, pattern, excludeDirs);
+      } catch (InvalidGlobPatternException e) {
+        // Globs that make it to pkg.getGlobPatterns() should already be filtered for errors.
+        throw new IllegalStateException(e);
+      }
+      globDepKeys.add(globSkyKey);
+    }
+    packageShouldBeInError = markGlobDepsAndPropagateInconsistentFilesystemExceptions(
+        pkg.getName(), globDepKeys, env, pkg.containsErrors());
+    return packageShouldBeInError;
+  }
+
+  /**
+   * Adds a dependency on the WORKSPACE file, representing it as a special type of package.
+   * @throws PackageFunctionException if there is an error computing the workspace file or adding
+   * its rules to the //external package.
+   */
+  private SkyValue getExternalPackage(Environment env, Path packageLookupPath)
+      throws PackageFunctionException {
+    RootedPath workspacePath = RootedPath.toRootedPath(
+        packageLookupPath, new PathFragment("WORKSPACE"));
+    SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath);
+    WorkspaceFileValue workspace = null;
+    try {
+      workspace = (WorkspaceFileValue) env.getValueOrThrow(workspaceKey, IOException.class,
+          FileSymlinkCycleException.class, InconsistentFilesystemException.class,
+          EvalException.class);
+    } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException
+        | EvalException e) {
+      throw new PackageFunctionException(new BadWorkspaceFileException(e.getMessage()),
+          Transience.PERSISTENT);
+    }
+    if (workspace == null) {
+      return null;
+    }
+
+    Package pkg = workspace.getPackage();
+    Event.replayEventsOn(env.getListener(), pkg.getEvents());
+    if (pkg.containsErrors()) {
+      throw new PackageFunctionException(new BuildFileContainsErrorsException("external",
+          "Package 'external' contains errors"),
+          pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
+    }
+
+    return new PackageValue(pkg);
+  }
+
+  @Override
+  public SkyValue compute(SkyKey key, Environment env) throws PackageFunctionException,
+      InterruptedException {
+    PackageIdentifier packageId = (PackageIdentifier) key.argument();
+    PathFragment packageNameFragment = packageId.getPackageFragment();
+    String packageName = packageNameFragment.getPathString();
+
+    SkyKey packageLookupKey = PackageLookupValue.key(packageId);
+    PackageLookupValue packageLookupValue;
+    try {
+      packageLookupValue = (PackageLookupValue)
+          env.getValueOrThrow(packageLookupKey, BuildFileNotFoundException.class,
+              InconsistentFilesystemException.class);
+    } catch (BuildFileNotFoundException e) {
+      throw new PackageFunctionException(e, Transience.PERSISTENT);
+    } catch (InconsistentFilesystemException e) {
+      // This error is not transient from the perspective of the PackageFunction.
+      throw new PackageFunctionException(
+          new InternalInconsistentFilesystemException(packageName, e), Transience.PERSISTENT);
+    }
+    if (packageLookupValue == null) {
+      return null;
+    }
+
+    if (!packageLookupValue.packageExists()) {
+      switch (packageLookupValue.getErrorReason()) {
+        case NO_BUILD_FILE:
+        case DELETED_PACKAGE:
+        case NO_EXTERNAL_PACKAGE:
+          throw new PackageFunctionException(new BuildFileNotFoundException(packageName,
+              packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
+        case INVALID_PACKAGE_NAME:
+          throw new PackageFunctionException(new InvalidPackageNameException(packageName,
+              packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
+        default:
+          // We should never get here.
+          Preconditions.checkState(false);
+      }
+    }
+
+    if (packageName.equals(EXTERNAL_PACKAGE_NAME)) {
+      return getExternalPackage(env, packageLookupValue.getRoot());
+    }
+
+    RootedPath buildFileRootedPath = RootedPath.toRootedPath(packageLookupValue.getRoot(),
+        packageNameFragment.getChild("BUILD"));
+    FileValue buildFileValue;
+    try {
+      buildFileValue = (FileValue) env.getValueOrThrow(FileValue.key(buildFileRootedPath),
+          IOException.class, FileSymlinkCycleException.class,
+          InconsistentFilesystemException.class);
+    } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
+      throw new IllegalStateException("Package lookup succeeded but encountered error when "
+          + "getting FileValue for BUILD file directly.", e);
+    }
+    if (buildFileValue == null) {
+      return null;
+    }
+    Preconditions.checkState(buildFileValue.exists(),
+        "Package lookup succeeded but BUILD file doesn't exist");
+    Path buildFilePath = buildFileRootedPath.asPath();
+
+    String replacementContents = null;
+    if (packageName.equals(DEFAULTS_PACKAGE_NAME)) {
+      replacementContents = PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.get(env);
+      if (replacementContents == null) {
+        return null;
+      }
+    }
+
+    RuleVisibility defaultVisibility = PrecomputedValue.DEFAULT_VISIBILITY.get(env);
+    if (defaultVisibility == null) {
+      return null;
+    }
+
+    ASTFileLookupValue astLookupValue = null;
+    SkyKey astLookupKey = null;
+    try {
+      astLookupKey = ASTFileLookupValue.key(PRELUDE_FILE_FRAGMENT);
+    } catch (ASTLookupInputException e) {
+      // There's a static check ensuring that PRELUDE_FILE_FRAGMENT is relative.
+      throw new IllegalStateException(e);
+    }
+    try {
+      astLookupValue = (ASTFileLookupValue) env.getValueOrThrow(astLookupKey,
+          ErrorReadingSkylarkExtensionException.class, InconsistentFilesystemException.class);
+    } catch (ErrorReadingSkylarkExtensionException | InconsistentFilesystemException e) {
+      throw new PackageFunctionException(new BadPreludeFileException(packageName, e.getMessage()),
+          Transience.PERSISTENT);
+    }
+    if (astLookupValue == null) {
+      return null;
+    }
+    List<Statement> preludeStatements = astLookupValue == ASTFileLookupValue.NO_FILE
+        ? ImmutableList.<Statement>of() : astLookupValue.getAST().getStatements();
+
+    // Load the BUILD file AST and handle Skylark dependencies. This way BUILD files are
+    // only loaded twice if there are unavailable Skylark or package dependencies or an
+    // IOException occurs. Note that the BUILD files are still parsed two times.
+    ParserInputSource inputSource;
+    try {
+      if (showLoadingProgress.get() && !packageFunctionCache.containsKey(packageId)) {
+        // TODO(bazel-team): don't duplicate the loading message if there are unavailable
+        // Skylark dependencies.
+        reporter.handle(Event.progress("Loading package: " + packageName));
+      }
+      inputSource = ParserInputSource.create(buildFilePath);
+    } catch (IOException e) {
+      env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
+      // Note that we did this work, so we should conservatively report this error as transient.
+      throw new PackageFunctionException(new BuildFileContainsErrorsException(
+          packageName, e.getMessage()), Transience.TRANSIENT);
+    }
+    SkylarkImportResult importResult = fetchImportsFromBuildFile(
+        buildFilePath, packageId.getRepository(), preludeStatements, inputSource, packageName, env);
+    if (importResult == null) {
+      return null;
+    }
+
+    Package.LegacyBuilder legacyPkgBuilder = loadPackage(inputSource, replacementContents,
+        packageId, buildFilePath, defaultVisibility, preludeStatements, importResult);
+    legacyPkgBuilder.buildPartial();
+    try {
+      handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
+          packageLookupValue.getRoot(), packageId, legacyPkgBuilder, env);
+    } catch (InternalInconsistentFilesystemException e) {
+      packageFunctionCache.remove(packageId);
+      throw new PackageFunctionException(e,
+          e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
+    }
+    if (env.valuesMissing()) {
+      // The package we just loaded will be in the {@code packageFunctionCache} next when this
+      // SkyFunction is called again.
+      return null;
+    }
+    Collection<Pair<String, Boolean>> globPatterns = legacyPkgBuilder.getGlobPatterns();
+    Map<Label, Path> subincludes = legacyPkgBuilder.getSubincludes();
+    Package pkg = legacyPkgBuilder.finishBuild();
+    Event.replayEventsOn(env.getListener(), pkg.getEvents());
+    boolean packageShouldBeConsideredInError = pkg.containsErrors();
+    try {
+      packageShouldBeConsideredInError =
+          markDependenciesAndPropagateInconsistentFilesystemExceptions(pkg, env,
+              globPatterns, subincludes);
+    } catch (InternalInconsistentFilesystemException e) {
+      packageFunctionCache.remove(packageId);
+      throw new PackageFunctionException(e,
+          e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
+    }
+
+    if (env.valuesMissing()) {
+      return null;
+    }
+    // We know this SkyFunction will not be called again, so we can remove the cache entry.
+    packageFunctionCache.remove(packageId);
+
+    if (packageShouldBeConsideredInError) {
+      throw new PackageFunctionException(new BuildFileContainsErrorsException(pkg,
+          "Package '" + packageName + "' contains errors"),
+          pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
+    }
+    return new PackageValue(pkg);
+  }
+
+  private SkylarkImportResult fetchImportsFromBuildFile(Path buildFilePath, RepositoryName repo,
+      List<Statement> preludeStatements, ParserInputSource inputSource,
+      String packageName, Environment env) throws PackageFunctionException {
+    StoredEventHandler eventHandler = new StoredEventHandler();
+    BuildFileAST buildFileAST = BuildFileAST.parseBuildFile(
+          inputSource, preludeStatements, eventHandler, null, true);
+
+    if (eventHandler.hasErrors()) {
+      // In case of Python preprocessing, errors have already been reported (see checkSyntax).
+      // In other cases, errors will be reported later.
+      // TODO(bazel-team): maybe we could get rid of checkSyntax and always report errors here?
+      return new SkylarkImportResult(
+          ImmutableMap.<PathFragment, SkylarkEnvironment>of(),
+          ImmutableList.<Label>of());
+    }
+
+    ImmutableCollection<PathFragment> imports = buildFileAST.getImports();
+    Map<PathFragment, SkylarkEnvironment> importMap = new HashMap<>();
+    ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
+    try {
+      for (PathFragment importFile : imports) {
+        SkyKey importsLookupKey = SkylarkImportLookupValue.key(repo, importFile);
+        SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue)
+            env.getValueOrThrow(importsLookupKey, SkylarkImportFailedException.class,
+                InconsistentFilesystemException.class, ASTLookupInputException.class,
+                BuildFileNotFoundException.class);
+        if (importLookupValue != null) {
+          importMap.put(importFile, importLookupValue.getImportedEnvironment());
+          fileDependencies.add(importLookupValue.getDependency());
+        }
+      }
+    } catch (SkylarkImportFailedException e) {
+      env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
+      throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
+          e.getMessage()), Transience.PERSISTENT);
+    } catch (InconsistentFilesystemException e) {
+      throw new PackageFunctionException(new InternalInconsistentFilesystemException(packageName,
+          e), Transience.PERSISTENT);
+    } catch (ASTLookupInputException e) {
+      // The load syntax is bad in the BUILD file so BuildFileContainsErrorsException is OK.
+      throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
+          e.getMessage()), Transience.PERSISTENT);
+    } catch (BuildFileNotFoundException e) {
+      throw new PackageFunctionException(e, Transience.PERSISTENT);
+    }
+    if (env.valuesMissing()) {
+      // There are unavailable Skylark dependencies.
+      return null;
+    }
+    return new SkylarkImportResult(importMap, transitiveClosureOfLabels(fileDependencies.build()));
+  }
+
+  private ImmutableList<Label> transitiveClosureOfLabels(
+      ImmutableList<SkylarkFileDependency> immediateDeps) {
+    Set<Label> transitiveClosure = Sets.newHashSet();
+    transitiveClosureOfLabels(immediateDeps, transitiveClosure);
+    return ImmutableList.copyOf(transitiveClosure);
+  }
+
+  private void transitiveClosureOfLabels(
+      ImmutableList<SkylarkFileDependency> immediateDeps, Set<Label> transitiveClosure) {
+    for (SkylarkFileDependency dep : immediateDeps) {
+      if (!transitiveClosure.contains(dep.getLabel())) {
+        transitiveClosure.add(dep.getLabel());
+        transitiveClosureOfLabels(dep.getDependencies(), transitiveClosure);
+      }
+    }
+  }
+
+  @Nullable
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  private static void handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
+      Path pkgRoot, PackageIdentifier pkgId, Package.LegacyBuilder pkgBuilder, Environment env)
+          throws InternalInconsistentFilesystemException {
+    Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet();
+    Map<Target, SkyKey> targetToKey = new HashMap<>();
+    for (Target target : pkgBuilder.getTargets()) {
+      PathFragment dir = target.getLabel().toPathFragment().getParentDirectory();
+      PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
+      if (dir.equals(pkgId.getPackageFragment())) {
+        continue;
+      }
+      SkyKey key = ContainingPackageLookupValue.key(dirId);
+      targetToKey.put(target, key);
+      containingPkgLookupKeys.add(key);
+    }
+    Map<Label, SkyKey> subincludeToKey = new HashMap<>();
+    for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
+      PathFragment dir = subincludeLabel.toPathFragment().getParentDirectory();
+      PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
+      if (dir.equals(pkgId.getPackageFragment())) {
+        continue;
+      }
+      SkyKey key = ContainingPackageLookupValue.key(dirId);
+      subincludeToKey.put(subincludeLabel, key);
+      containingPkgLookupKeys.add(ContainingPackageLookupValue.key(dirId));
+    }
+    Map<SkyKey, ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
+        FileSymlinkCycleException>> containingPkgLookupValues = env.getValuesOrThrow(
+            containingPkgLookupKeys, BuildFileNotFoundException.class,
+            InconsistentFilesystemException.class, FileSymlinkCycleException.class);
+    if (env.valuesMissing()) {
+      return;
+    }
+    for (Target target : ImmutableSet.copyOf(pkgBuilder.getTargets())) {
+      SkyKey key = targetToKey.get(target);
+      if (!containingPkgLookupValues.containsKey(key)) {
+        continue;
+      }
+      ContainingPackageLookupValue containingPackageLookupValue =
+          getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
+              pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
+      if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, target.getLabel(),
+          target.getLocation(), containingPackageLookupValue)) {
+        pkgBuilder.removeTarget(target);
+        pkgBuilder.setContainsErrors();
+      }
+    }
+    for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
+      SkyKey key = subincludeToKey.get(subincludeLabel);
+      if (!containingPkgLookupValues.containsKey(key)) {
+        continue;
+      }
+      ContainingPackageLookupValue containingPackageLookupValue =
+          getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
+              pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
+      if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, subincludeLabel,
+          /*location=*/null, containingPackageLookupValue)) {
+        pkgBuilder.setContainsErrors();
+      }
+    }
+  }
+
+  @Nullable
+  private static ContainingPackageLookupValue
+  getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(String packageName,
+      ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
+      FileSymlinkCycleException> containingPkgLookupValueOrException, Environment env)
+          throws InternalInconsistentFilesystemException {
+    try {
+      return (ContainingPackageLookupValue) containingPkgLookupValueOrException.get();
+    } catch (BuildFileNotFoundException | FileSymlinkCycleException e) {
+      env.getListener().handle(Event.error(null, e.getMessage()));
+      return null;
+    } catch (InconsistentFilesystemException e) {
+      throw new InternalInconsistentFilesystemException(packageName, e);
+    }
+  }
+
+  private static boolean maybeAddEventAboutLabelCrossingSubpackage(
+      Package.LegacyBuilder pkgBuilder, Path pkgRoot, Label label, @Nullable Location location,
+      @Nullable ContainingPackageLookupValue containingPkgLookupValue) {
+    if (containingPkgLookupValue == null) {
+      return true;
+    }
+    if (!containingPkgLookupValue.hasContainingPackage()) {
+      // The missing package here is a problem, but it's not an error from the perspective of
+      // PackageFunction.
+      return false;
+    }
+    PackageIdentifier containingPkg = containingPkgLookupValue.getContainingPackageName();
+    if (containingPkg.equals(label.getPackageIdentifier())) {
+      // The label does not cross a subpackage boundary.
+      return false;
+    }
+    if (!containingPkg.getPackageFragment().startsWith(label.getPackageFragment())) {
+      // This label is referencing an imaginary package, because the containing package should
+      // extend the label's package: if the label is //a/b:c/d, the containing package could be
+      // //a/b/c or //a/b, but should never be //a. Usually such errors will be caught earlier, but
+      // in some exceptional cases (such as a Python-aware BUILD file catching its own io
+      // exceptions), it reaches here, and we tolerate it.
+      return false;
+    }
+    PathFragment labelNameFragment = new PathFragment(label.getName());
+    String message = String.format("Label '%s' crosses boundary of subpackage '%s'",
+        label, containingPkg);
+    Path containingRoot = containingPkgLookupValue.getContainingPackageRoot();
+    if (pkgRoot.equals(containingRoot)) {
+      PathFragment labelNameInContainingPackage = labelNameFragment.subFragment(
+          containingPkg.getPackageFragment().segmentCount()
+              - label.getPackageFragment().segmentCount(),
+          labelNameFragment.segmentCount());
+      message += " (perhaps you meant to put the colon here: "
+          + "'//" + containingPkg + ":" + labelNameInContainingPackage + "'?)";
+    } else {
+      message += " (have you deleted " + containingPkg + "/BUILD? "
+          + "If so, use the --deleted_packages=" + containingPkg + " option)";
+    }
+    pkgBuilder.addEvent(Event.error(location, message));
+    return true;
+  }
+
+  /**
+   * Constructs a {@link Package} object for the given package using legacy package loading.
+   * Note that the returned package may be in error.
+   */
+  private Package.LegacyBuilder loadPackage(ParserInputSource inputSource,
+      @Nullable String replacementContents,
+      PackageIdentifier packageId, Path buildFilePath, RuleVisibility defaultVisibility,
+      List<Statement> preludeStatements, SkylarkImportResult importResult)
+          throws InterruptedException {
+    ParserInputSource replacementSource = replacementContents == null ? null
+        : ParserInputSource.create(replacementContents, buildFilePath);
+    Package.LegacyBuilder pkgBuilder = packageFunctionCache.get(packageId);
+    if (pkgBuilder == null) {
+      Clock clock = new JavaClock();
+      long startTime = clock.nanoTime();
+      profiler.startTask(ProfilerTask.CREATE_PACKAGE, packageId.toString());
+      try {
+        Globber globber = packageFactory.createLegacyGlobber(buildFilePath.getParentDirectory(),
+            packageId, packageLocator);
+        StoredEventHandler localReporter = new StoredEventHandler();
+        Preprocessor.Result preprocessingResult = replacementSource == null
+            ? packageFactory.preprocess(packageId, buildFilePath, inputSource, globber,
+                localReporter)
+                : Preprocessor.Result.noPreprocessing(replacementSource);
+        pkgBuilder = packageFactory.createPackageFromPreprocessingResult(packageId, buildFilePath,
+            preprocessingResult, localReporter.getEvents(), preludeStatements,
+            importResult.importMap, importResult.fileDependencies, packageLocator,
+            defaultVisibility, globber);
+        if (eventBus.get() != null) {
+          eventBus.get().post(new PackageLoadedEvent(packageId.toString(),
+              (clock.nanoTime() - startTime) / (1000 * 1000),
+              // It's impossible to tell if the package was loaded before, so we always pass false.
+              /*reloading=*/false,
+              // This isn't completely correct since we may encounter errors later (e.g. filesystem
+              // inconsistencies)
+              !pkgBuilder.containsErrors()));
+        }
+        numPackagesLoaded.incrementAndGet();
+        packageFunctionCache.put(packageId, pkgBuilder);
+      } finally {
+        profiler.completeTask(ProfilerTask.CREATE_PACKAGE);
+      }
+    }
+    return pkgBuilder;
+  }
+
+  private static class InternalInconsistentFilesystemException extends NoSuchPackageException {
+    private boolean isTransient;
+
+    /**
+     * Used to represent a filesystem inconsistency discovered outside the
+     * {@link PackageFunction}.
+     */
+    public InternalInconsistentFilesystemException(String packageName,
+        InconsistentFilesystemException e) {
+      super(packageName, e.getMessage(), e);
+      // This is not a transient error from the perspective of the PackageFunction.
+      this.isTransient = false;
+    }
+
+    /** Used to represent a filesystem inconsistency discovered by the {@link PackageFunction}. */
+    public InternalInconsistentFilesystemException(String packageName,
+        String inconsistencyMessage) {
+      this(packageName, new InconsistentFilesystemException(inconsistencyMessage));
+      this.isTransient = true;
+    }
+
+    public boolean isTransient() {
+      return isTransient;
+    }
+  }
+
+  private static class BadWorkspaceFileException extends NoSuchPackageException {
+    private BadWorkspaceFileException(String message) {
+      super("external", "Error encountered while dealing with the WORKSPACE file: " + message);
+    }
+  }
+
+  private static class BadPreludeFileException extends NoSuchPackageException {
+    private BadPreludeFileException(String packageName, String message) {
+      super(packageName, "Error encountered while reading the prelude file: " + message);
+    }
+  }
+
+  /**
+   * Used to declare all the exception types that can be wrapped in the exception thrown by
+   * {@link PackageFunction#compute}.
+   */
+  private static class PackageFunctionException extends SkyFunctionException {
+    public PackageFunctionException(NoSuchPackageException e, Transience transience) {
+      super(e, transience);
+    }
+  }
+
+  /** A simple value class to store the result of the Skylark imports.*/
+  private static final class SkylarkImportResult {
+    private final Map<PathFragment, SkylarkEnvironment> importMap;
+    private final ImmutableList<Label> fileDependencies;
+    private SkylarkImportResult(Map<PathFragment, SkylarkEnvironment> importMap,
+        ImmutableList<Label> fileDependencies) {
+      this.importMap = importMap;
+      this.fileDependencies = fileDependencies;
+    }
+  }
+}