com.google.devtools.build.lib.skyframe.PackageFunction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.skyframe.PackageFunction.java

Source

// Copyright 2014 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.common.cache.Cache;
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.Maps;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
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.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
import com.google.devtools.build.lib.packages.CachingPackageLocator;
import com.google.devtools.build.lib.packages.Globber;
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.LegacyGlobber;
import com.google.devtools.build.lib.packages.Preprocessor;
import com.google.devtools.build.lib.packages.Preprocessor.AstAfterPreprocessing;
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.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.Environment.Extension;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.syntax.SkylarkImport;
import com.google.devtools.build.lib.syntax.Statement;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
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.ValueOrException2;
import com.google.devtools.build.skyframe.ValueOrException3;
import com.google.devtools.build.skyframe.ValueOrException4;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;

/**
 * A SkyFunction for {@link PackageValue}s.
 */
public class PackageFunction implements SkyFunction {

    private final PackageFactory packageFactory;
    private final CachingPackageLocator packageLocator;
    private final Cache<PackageIdentifier, CacheEntryWithGlobDeps<Package.Builder>> packageFunctionCache;
    private final Cache<PackageIdentifier, CacheEntryWithGlobDeps<AstAfterPreprocessing>> astCache;
    private final AtomicBoolean showLoadingProgress;
    private final AtomicInteger numPackagesLoaded;
    private final PackageProgressReceiver packageProgress;
    private final Profiler profiler = Profiler.instance();
    private final Label preludeLabel;

    // Not final only for testing.
    @Nullable
    private SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining;

    static final PathFragment DEFAULTS_PACKAGE_NAME = new PathFragment("tools/defaults");

    public PackageFunction(PackageFactory packageFactory, CachingPackageLocator pkgLocator,
            AtomicBoolean showLoadingProgress,
            Cache<PackageIdentifier, CacheEntryWithGlobDeps<Package.Builder>> packageFunctionCache,
            Cache<PackageIdentifier, CacheEntryWithGlobDeps<AstAfterPreprocessing>> astCache,
            AtomicInteger numPackagesLoaded,
            @Nullable SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining,
            @Nullable PackageProgressReceiver packageProgress) {
        this.skylarkImportLookupFunctionForInlining = skylarkImportLookupFunctionForInlining;
        // Can be null in tests.
        this.preludeLabel = packageFactory == null ? null : packageFactory.getRuleClassProvider().getPreludeLabel();
        this.packageFactory = packageFactory;
        this.packageLocator = pkgLocator;
        this.showLoadingProgress = showLoadingProgress;
        this.packageFunctionCache = packageFunctionCache;
        this.astCache = astCache;
        this.numPackagesLoaded = numPackagesLoaded;
        this.packageProgress = packageProgress;
    }

    public PackageFunction(PackageFactory packageFactory, CachingPackageLocator pkgLocator,
            AtomicBoolean showLoadingProgress,
            Cache<PackageIdentifier, CacheEntryWithGlobDeps<Package.Builder>> packageFunctionCache,
            Cache<PackageIdentifier, CacheEntryWithGlobDeps<AstAfterPreprocessing>> astCache,
            AtomicInteger numPackagesLoaded,
            @Nullable SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining) {
        this(packageFactory, pkgLocator, showLoadingProgress, packageFunctionCache, astCache, numPackagesLoaded,
                skylarkImportLookupFunctionForInlining, null);
    }

    public void setSkylarkImportLookupFunctionForInliningForTesting(
            SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining) {
        this.skylarkImportLookupFunctionForInlining = skylarkImportLookupFunctionForInlining;
    }

    /** An entry in {@link PackageFunction}'s internal caches. */
    public static class CacheEntryWithGlobDeps<T> {
        private final T value;
        private final Set<SkyKey> globDepKeys;
        @Nullable
        private final LegacyGlobber legacyGlobber;

        private CacheEntryWithGlobDeps(T value, Set<SkyKey> globDepKeys, @Nullable LegacyGlobber legacyGlobber) {
            this.value = value;
            this.globDepKeys = globDepKeys;
            this.legacyGlobber = legacyGlobber;
        }
    }

    private static void maybeThrowFilesystemInconsistency(PackageIdentifier packageIdentifier,
            Exception skyframeException, boolean packageWasInError) throws InternalInconsistentFilesystemException {
        if (!packageWasInError) {
            throw new InternalInconsistentFilesystemException(packageIdentifier,
                    "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(
            PackageIdentifier packageIdentifier, Iterable<SkyKey> depKeys, Environment env,
            boolean packageWasInError) throws InternalInconsistentFilesystemException, InterruptedException {
        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, FileSymlinkException>> entry : env
                .getValuesOrThrow(depKeys, BuildFileNotFoundException.class, InconsistentFilesystemException.class,
                        FileSymlinkException.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(packageIdentifier, e, packageWasInError);
            } catch (InconsistentFilesystemException e) {
                throw new InternalInconsistentFilesystemException(packageIdentifier, e);
            } catch (FileSymlinkException e) {
                // Legacy doesn't detect symlink cycles.
                packageShouldBeInError = true;
            }
        }
        return Pair.of(builder.build(), packageShouldBeInError);
    }

    private static boolean markFileDepsAndPropagateFilesystemExceptions(PackageIdentifier packageIdentifier,
            Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
            throws InternalInconsistentFilesystemException, InterruptedException {
        Preconditions.checkState(Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.FILE)), depKeys);
        boolean packageShouldBeInError = packageWasInError;
        for (Map.Entry<SkyKey, ValueOrException3<IOException, FileSymlinkException, InconsistentFilesystemException>> entry : env
                .getValuesOrThrow(depKeys, IOException.class, FileSymlinkException.class,
                        InconsistentFilesystemException.class)
                .entrySet()) {
            try {
                entry.getValue().get();
            } catch (IOException e) {
                maybeThrowFilesystemInconsistency(packageIdentifier, e, packageWasInError);
            } catch (FileSymlinkException e) {
                // Legacy doesn't detect symlink cycles.
                packageShouldBeInError = true;
            } catch (InconsistentFilesystemException e) {
                throw new InternalInconsistentFilesystemException(packageIdentifier, e);
            }
        }
        return packageShouldBeInError;
    }

    /**
     * These deps have already been marked (see {@link SkyframeHybridGlobber}) but we need to properly
     * handle some errors that legacy package loading can't handle gracefully.
     */
    private static boolean handleGlobDepsAndPropagateFilesystemExceptions(PackageIdentifier packageIdentifier,
            Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
            throws InternalInconsistentFilesystemException, InterruptedException {
        Preconditions.checkState(Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.GLOB)), depKeys);
        boolean packageShouldBeInError = packageWasInError;
        for (Map.Entry<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkException, InconsistentFilesystemException>> entry : env
                .getValuesOrThrow(depKeys, IOException.class, BuildFileNotFoundException.class,
                        FileSymlinkException.class, InconsistentFilesystemException.class)
                .entrySet()) {
            try {
                entry.getValue().get();
            } catch (IOException | BuildFileNotFoundException e) {
                maybeThrowFilesystemInconsistency(packageIdentifier, e, packageWasInError);
            } catch (FileSymlinkException e) {
                // Legacy doesn't detect symlink cycles.
                packageShouldBeInError = true;
            } catch (InconsistentFilesystemException e) {
                throw new InternalInconsistentFilesystemException(packageIdentifier, 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>Most 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). Some
     * exceptions that Skyframe is stricter about (disallowed access to files outside package roots)
     * are propagated.
     */
    private static boolean markDependenciesAndPropagateFilesystemExceptions(Environment env,
            Set<SkyKey> globDepKeys, Map<Label, Path> subincludes, PackageIdentifier packageIdentifier,
            boolean containsErrors) throws InternalInconsistentFilesystemException, InterruptedException {
        boolean packageShouldBeInError = 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 : subincludes.keySet()) {
            // Declare a dependency on the package lookup for the package giving access to the label.
            subincludePackageLookupDepKeys.add(PackageLookupValue.key(label.getPackageIdentifier()));
        }
        Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean> subincludePackageLookupResult = getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(
                packageIdentifier, subincludePackageLookupDepKeys, env, 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 && !subincludePackageLookupValue.packageExists()) {
                    // Legacy blaze puts a non-null path when only when the package does indeed exist.
                    throw new InternalInconsistentFilesystemException(packageIdentifier, String.format(
                            "Unexpected package in %s. Was it modified during the build?", subincludeFilePath));
                }
                if (subincludePackageLookupValue.packageExists()) {
                    // Sanity check for consistency of Skyframe and legacy blaze.
                    Path subincludeFilePathSkyframe = subincludePackageLookupValue.getRoot()
                            .getRelative(label.toPathFragment());
                    if (subincludeFilePath != null && !subincludeFilePathSkyframe.equals(subincludeFilePath)) {
                        throw new InternalInconsistentFilesystemException(packageIdentifier, 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(),
                                    label.getPackageFragment().getRelative(label.getName())));
                    subincludeFileDepKeys.add(subincludeSkyKey);
                }
            }
        }
        packageShouldBeInError |= markFileDepsAndPropagateFilesystemExceptions(packageIdentifier,
                subincludeFileDepKeys, env, containsErrors);

        packageShouldBeInError |= handleGlobDepsAndPropagateFilesystemExceptions(packageIdentifier, globDepKeys,
                env, 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, InterruptedException {
        RootedPath workspacePath = RootedPath.toRootedPath(packageLookupPath, Label.EXTERNAL_PACKAGE_FILE_NAME);
        SkyKey workspaceKey = ExternalPackageFunction.key(workspacePath);
        PackageValue workspace = null;
        try {
            // This may throw a NoSuchPackageException if the WORKSPACE file was malformed or had other
            // problems. Since this function can't add much context, we silently bubble it up.
            workspace = (PackageValue) env.getValueOrThrow(workspaceKey, IOException.class,
                    FileSymlinkException.class, InconsistentFilesystemException.class, EvalException.class,
                    SkylarkImportFailedException.class);
        } catch (IOException | FileSymlinkException | InconsistentFilesystemException | EvalException
                | SkylarkImportFailedException e) {
            throw new PackageFunctionException(
                    new NoSuchPackageException(Label.EXTERNAL_PACKAGE_IDENTIFIER,
                            "Error encountered while dealing with the WORKSPACE file: " + e.getMessage()),
                    Transience.PERSISTENT);
        }
        if (workspace == null) {
            return null;
        }

        Package pkg = workspace.getPackage();
        Event.replayEventsOn(env.getListener(), pkg.getEvents());

        packageFactory.afterDoneLoadingPackage(pkg);
        return new PackageValue(pkg);
    }

    @Override
    public SkyValue compute(SkyKey key, Environment env) throws PackageFunctionException, InterruptedException {
        PackageIdentifier packageId = (PackageIdentifier) key.argument();

        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 NoSuchPackageException(packageId, e.getMessage(), e),
                    Transience.PERSISTENT);
        }
        if (packageLookupValue == null) {
            return null;
        }

        if (!packageLookupValue.packageExists()) {
            switch (packageLookupValue.getErrorReason()) {
            case NO_BUILD_FILE:
            case DELETED_PACKAGE:
                throw new PackageFunctionException(
                        new BuildFileNotFoundException(packageId, packageLookupValue.getErrorMsg()),
                        Transience.PERSISTENT);
            case INVALID_PACKAGE_NAME:
                throw new PackageFunctionException(
                        new InvalidPackageNameException(packageId, packageLookupValue.getErrorMsg()),
                        Transience.PERSISTENT);
            default:
                // We should never get here.
                throw new IllegalStateException();
            }
        }

        if (packageId.equals(Label.EXTERNAL_PACKAGE_IDENTIFIER)) {
            return getExternalPackage(env, packageLookupValue.getRoot());
        }
        SkyKey externalPackageKey = PackageValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER);
        PackageValue externalPackage = (PackageValue) env.getValue(externalPackageKey);
        if (externalPackage == null) {
            return null;
        }
        Package externalPkg = externalPackage.getPackage();
        if (externalPkg.containsErrors()) {
            throw new PackageFunctionException(
                    new BuildFileContainsErrorsException(Label.EXTERNAL_PACKAGE_IDENTIFIER), Transience.PERSISTENT);
        }

        RootedPath buildFileRootedPath = packageLookupValue.getRootedPath(packageId);
        FileValue buildFileValue = null;
        Path buildFilePath = buildFileRootedPath.asPath();
        String replacementContents = null;

        if (!isDefaultsPackage(packageId)) {
            buildFileValue = getBuildFileValue(env, buildFileRootedPath);
            if (buildFileValue == null) {
                return null;
            }
        } else {
            replacementContents = PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.get(env);
            if (replacementContents == null) {
                return null;
            }
        }

        RuleVisibility defaultVisibility = PrecomputedValue.DEFAULT_VISIBILITY.get(env);
        if (defaultVisibility == null) {
            return null;
        }

        SkyKey astLookupKey = ASTFileLookupValue.key(preludeLabel);
        ASTFileLookupValue astLookupValue = null;
        try {
            astLookupValue = (ASTFileLookupValue) env.getValueOrThrow(astLookupKey,
                    ErrorReadingSkylarkExtensionException.class, InconsistentFilesystemException.class);
        } catch (ErrorReadingSkylarkExtensionException | InconsistentFilesystemException e) {
            throw new PackageFunctionException(
                    new NoSuchPackageException(packageId,
                            "Error encountered while reading the prelude file: " + e.getMessage()),
                    Transience.PERSISTENT);
        }
        if (astLookupValue == null) {
            return null;
        }
        // The prelude file doesn't have to exist. If not, we substitute an empty statement list.
        List<Statement> preludeStatements = astLookupValue.lookupSuccessful()
                ? astLookupValue.getAST().getStatements()
                : ImmutableList.<Statement>of();
        CacheEntryWithGlobDeps<Package.Builder> packageBuilderAndGlobDeps = loadPackage(externalPkg,
                replacementContents, packageId, buildFilePath, buildFileValue, defaultVisibility, preludeStatements,
                packageLookupValue.getRoot(), env);
        if (packageBuilderAndGlobDeps == null) {
            return null;
        }
        Package.Builder pkgBuilder = packageBuilderAndGlobDeps.value;
        pkgBuilder.buildPartial();
        try {
            // Since the Skyframe dependencies we request below in
            // markDependenciesAndPropagateFilesystemExceptions are requested independently of
            // the ones requested here in
            // handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions, we don't
            // bother checking for missing values and instead piggyback on the env.missingValues() call
            // for the former. This avoids a Skyframe restart.
            handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
                    packageLookupValue.getRoot(), packageId, pkgBuilder, env);
        } catch (InternalInconsistentFilesystemException e) {
            packageFunctionCache.invalidate(packageId);
            throw new PackageFunctionException(e.toNoSuchPackageException(),
                    e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
        }
        Set<SkyKey> globKeys = packageBuilderAndGlobDeps.globDepKeys;
        Map<Label, Path> subincludes = pkgBuilder.getSubincludes();
        boolean packageShouldBeConsideredInError;
        try {
            packageShouldBeConsideredInError = markDependenciesAndPropagateFilesystemExceptions(env, globKeys,
                    subincludes, packageId, pkgBuilder.containsErrors());
        } catch (InternalInconsistentFilesystemException e) {
            packageFunctionCache.invalidate(packageId);
            throw new PackageFunctionException(e.toNoSuchPackageException(),
                    e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
        }
        if (env.valuesMissing()) {
            return null;
        }

        Event.replayEventsOn(env.getListener(), pkgBuilder.getEvents());

        if (packageShouldBeConsideredInError) {
            pkgBuilder.setContainsErrors();
        }
        Package pkg = pkgBuilder.finishBuild();

        // We know this SkyFunction will not be called again, so we can remove the cache entry.
        packageFunctionCache.invalidate(packageId);

        packageFactory.afterDoneLoadingPackage(pkg);
        return new PackageValue(pkg);
    }

    private static FileValue getBuildFileValue(Environment env, RootedPath buildFileRootedPath)
            throws InterruptedException {
        FileValue buildFileValue;
        try {
            buildFileValue = (FileValue) env.getValueOrThrow(FileValue.key(buildFileRootedPath), IOException.class,
                    FileSymlinkException.class, InconsistentFilesystemException.class);
        } catch (IOException | FileSymlinkException | 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");
        return buildFileValue;
    }

    /**
     * Fetch the skylark loads for this BUILD file. If any of them haven't been computed yet,
     * returns null.
     */
    @Nullable
    static SkylarkImportResult fetchImportsFromBuildFile(Path buildFilePath, PackageIdentifier packageId,
            BuildFileAST buildFileAST, Environment env,
            SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining)
            throws PackageFunctionException, InterruptedException {
        Preconditions.checkArgument(!packageId.getRepository().isDefault());

        ImmutableList<SkylarkImport> imports = buildFileAST.getImports();
        Map<String, Extension> importMap = Maps.newHashMapWithExpectedSize(imports.size());
        ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
        ImmutableMap<String, Label> importPathMap;

        // Find the labels corresponding to the load statements.
        Label labelForCurrBuildFile;
        try {
            labelForCurrBuildFile = Label.create(packageId, "BUILD");
        } catch (LabelSyntaxException e) {
            // Shouldn't happen; the Label is well-formed by construction.
            throw new IllegalStateException(e);
        }
        try {
            importPathMap = SkylarkImportLookupFunction.findLabelsForLoadStatements(imports, labelForCurrBuildFile,
                    env);
            if (importPathMap == null) {
                return null;
            }
        } catch (SkylarkImportFailedException e) {
            throw new PackageFunctionException(new BuildFileContainsErrorsException(packageId, e.getMessage()),
                    Transience.PERSISTENT);
        }

        // Look up and load the imports.
        ImmutableCollection<Label> importLabels = importPathMap.values();
        List<SkyKey> importLookupKeys = Lists.newArrayListWithExpectedSize(importLabels.size());
        boolean inWorkspace = buildFilePath.getBaseName().endsWith("WORKSPACE");
        for (Label importLabel : importLabels) {
            importLookupKeys.add(SkylarkImportLookupValue.key(importLabel, inWorkspace));
        }
        Map<SkyKey, SkyValue> skylarkImportMap = Maps.newHashMapWithExpectedSize(importPathMap.size());
        boolean valuesMissing = false;

        try {
            if (skylarkImportLookupFunctionForInlining == null) {
                // Not inlining
                Map<SkyKey, ValueOrException2<SkylarkImportFailedException, InconsistentFilesystemException>> skylarkLookupResults = env
                        .getValuesOrThrow(importLookupKeys, SkylarkImportFailedException.class,
                                InconsistentFilesystemException.class);
                valuesMissing = env.valuesMissing();
                for (Map.Entry<SkyKey, ValueOrException2<SkylarkImportFailedException, InconsistentFilesystemException>> entry : skylarkLookupResults
                        .entrySet()) {
                    // Fetching the value will raise any deferred exceptions
                    skylarkImportMap.put(entry.getKey(), entry.getValue().get());
                }
            } else {
                // Inlining calls to SkylarkImportLookupFunction
                LinkedHashMap<Label, SkylarkImportLookupValue> alreadyVisitedImports = Maps
                        .newLinkedHashMapWithExpectedSize(importLookupKeys.size());
                for (SkyKey importLookupKey : importLookupKeys) {
                    SkyValue skyValue = skylarkImportLookupFunctionForInlining
                            .computeWithInlineCalls(importLookupKey, env, alreadyVisitedImports);
                    if (skyValue == null) {
                        Preconditions.checkState(env.valuesMissing(), "no skylark import value for %s",
                                importLookupKey);
                        // We continue making inline calls even if some requested values are missing, to
                        // maximize the number of dependent (non-inlined) SkyFunctions that are requested, thus
                        // avoiding a quadratic number of restarts.
                        valuesMissing = true;
                    } else {
                        skylarkImportMap.put(importLookupKey, skyValue);
                    }
                }

            }
        } catch (SkylarkImportFailedException e) {
            throw new PackageFunctionException(new BuildFileContainsErrorsException(packageId, e.getMessage()),
                    Transience.PERSISTENT);
        } catch (InconsistentFilesystemException e) {
            throw new PackageFunctionException(new NoSuchPackageException(packageId, e.getMessage(), e),
                    Transience.PERSISTENT);
        }

        if (valuesMissing) {
            // Some imports are unavailable.
            return null;
        }

        // Process the loaded imports.
        for (Entry<String, Label> importEntry : importPathMap.entrySet()) {
            String importString = importEntry.getKey();
            Label importLabel = importEntry.getValue();
            SkyKey keyForLabel = SkylarkImportLookupValue.key(importLabel, inWorkspace);
            SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue) skylarkImportMap
                    .get(keyForLabel);
            importMap.put(importString, importLookupValue.getEnvironmentExtension());
            fileDependencies.add(importLookupValue.getDependency());
        }

        return new SkylarkImportResult(importMap, transitiveClosureOfLabels(fileDependencies.build()));
    }

    private static ImmutableList<Label> transitiveClosureOfLabels(
            ImmutableList<SkylarkFileDependency> immediateDeps) {
        Set<Label> transitiveClosure = Sets.newHashSet();
        transitiveClosureOfLabels(immediateDeps, transitiveClosure);
        return ImmutableList.copyOf(transitiveClosure);
    }

    private static void transitiveClosureOfLabels(ImmutableList<SkylarkFileDependency> immediateDeps,
            Set<Label> transitiveClosure) {
        for (SkylarkFileDependency dep : immediateDeps) {
            if (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.Builder pkgBuilder, Environment env)
            throws InternalInconsistentFilesystemException, InterruptedException {
        Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet();
        Map<Target, SkyKey> targetToKey = new HashMap<>();
        for (Target target : pkgBuilder.getTargets()) {
            PathFragment dir = target.getLabel().toPathFragment().getParentDirectory();
            PackageIdentifier dirId = PackageIdentifier.create(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 = PackageIdentifier.create(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, FileSymlinkException>> containingPkgLookupValues = env
                .getValuesOrThrow(containingPkgLookupKeys, BuildFileNotFoundException.class,
                        InconsistentFilesystemException.class, FileSymlinkException.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, 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, containingPkgLookupValues.get(key), env);
            if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, subincludeLabel, /*location=*/null,
                    containingPackageLookupValue)) {
                pkgBuilder.setContainsErrors();
            }
        }
    }

    @Nullable
    private static ContainingPackageLookupValue getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
            PackageIdentifier packageIdentifier,
            ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException, FileSymlinkException> containingPkgLookupValueOrException,
            Environment env) throws InternalInconsistentFilesystemException {
        try {
            return (ContainingPackageLookupValue) containingPkgLookupValueOrException.get();
        } catch (BuildFileNotFoundException | FileSymlinkException e) {
            env.getListener().handle(Event.error(null, e.getMessage()));
            return null;
        } catch (InconsistentFilesystemException e) {
            throw new InternalInconsistentFilesystemException(packageIdentifier, e);
        }
    }

    private static boolean maybeAddEventAboutLabelCrossingSubpackage(Package.Builder 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.getSourceRoot().startsWith(label.getPackageIdentifier().getSourceRoot())) {
            // 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: '";
            if (containingPkg.getRepository().isDefault() || containingPkg.getRepository().isMain()) {
                message += "//";
            }
            message += 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;
    }

    /**
     * A {@link Globber} implemented on top of skyframe that falls back to a
     * {@link PackageFactory.LegacyGlobber} on a skyframe cache-miss. This way we don't require a
     * skyframe restart after a call to {@link Globber#runAsync} and before/during a call to
     * {@link Globber#fetch}.
     *
     * <p>There are three advantages to this hybrid approach over the more obvious approach of solely
     * using a {@link PackageFactory.LegacyGlobber}:
     * <ul>
     * <li>We trivially have the proper Skyframe {@link GlobValue} deps, whereas we would need to
     * request them after-the-fact if we solely used a {@link PackageFactory.LegacyGlobber}.
     * <li>We don't need to re-evaluate globs whose expression hasn't changed (e.g. in the common case
     * of a BUILD file edit that doesn't change a glob expression), whereas legacy package loading
     * with a {@link PackageFactory.LegacyGlobber} would naively re-evaluate globs when re-evaluating
     * the BUILD file.
     * <li>We don't need to re-evaluate invalidated globs *twice* (the single re-evaluation via our
     * GlobValue deps is sufficient and optimal). See above for why the second evaluation would
     * happen.
     * </ul>
     */
    private static class SkyframeHybridGlobber implements Globber {
        private final PackageIdentifier packageId;
        private final Path packageRoot;
        private final Environment env;
        private final LegacyGlobber legacyGlobber;
        private final Set<SkyKey> globDepsRequested = Sets.newConcurrentHashSet();

        private SkyframeHybridGlobber(PackageIdentifier packageId, Path packageRoot, Environment env,
                LegacyGlobber legacyGlobber) {
            this.packageId = packageId;
            this.packageRoot = packageRoot;
            this.env = env;
            this.legacyGlobber = legacyGlobber;
        }

        private Set<SkyKey> getGlobDepsRequested() {
            return ImmutableSet.copyOf(globDepsRequested);
        }

        private SkyKey getGlobKey(String pattern, boolean excludeDirs) throws BadGlobException {
            try {
                return GlobValue.key(packageId, packageRoot, pattern, excludeDirs, PathFragment.EMPTY_FRAGMENT);
            } catch (InvalidGlobPatternException e) {
                throw new BadGlobException(e.getMessage());
            }
        }

        @Override
        public Token runAsync(List<String> includes, List<String> excludes, boolean excludeDirs)
                throws BadGlobException, InterruptedException {
            List<SkyKey> globKeys = new ArrayList<>(includes.size() + excludes.size());
            LinkedHashSet<SkyKey> includesKeys = Sets.newLinkedHashSetWithExpectedSize(includes.size());
            LinkedHashSet<SkyKey> excludesKeys = Sets.newLinkedHashSetWithExpectedSize(excludes.size());
            Map<SkyKey, String> globKeyToIncludeStringMap = Maps.newHashMapWithExpectedSize(includes.size());
            Map<SkyKey, String> globKeyToExcludeStringMap = Maps.newHashMapWithExpectedSize(excludes.size());

            for (String pattern : includes) {
                SkyKey globKey = getGlobKey(pattern, excludeDirs);
                globKeys.add(globKey);
                includesKeys.add(globKey);
                globKeyToIncludeStringMap.put(globKey, pattern);
            }
            for (String pattern : excludes) {
                SkyKey globKey = getGlobKey(pattern, excludeDirs);
                globKeys.add(globKey);
                excludesKeys.add(globKey);
                globKeyToExcludeStringMap.put(globKey, pattern);
            }
            globDepsRequested.addAll(globKeys);

            Map<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException>> globValueMap = env
                    .getValuesOrThrow(globKeys, IOException.class, BuildFileNotFoundException.class,
                            FileSymlinkCycleException.class, InconsistentFilesystemException.class);

            // For each missing glob, evaluate it asychronously via the delegate.
            //
            // TODO(bazel-team): Consider not delegating missing globs during glob prefetching - a
            // single skyframe restart after the prefetch step is probably tolerable.
            Collection<SkyKey> missingKeys = getMissingKeys(globKeys, globValueMap);
            List<String> includesToDelegate = new ArrayList<>(missingKeys.size());
            List<String> excludesToDelegate = new ArrayList<>(missingKeys.size());
            for (SkyKey missingKey : missingKeys) {
                String missingIncludePattern = globKeyToIncludeStringMap.get(missingKey);
                if (missingIncludePattern != null) {
                    includesToDelegate.add(missingIncludePattern);
                    includesKeys.remove(missingKey);
                }
                String missingExcludePattern = globKeyToExcludeStringMap.get(missingKey);
                if (missingExcludePattern != null) {
                    excludesToDelegate.add(missingExcludePattern);
                    excludesKeys.remove(missingKey);
                }
            }
            Token legacyIncludesToken = legacyGlobber.runAsync(includesToDelegate, ImmutableList.<String>of(),
                    excludeDirs);
            // See the HybridToken class-comment for why we pass excludesToDelegate as the includes
            // parameter here.
            Token legacyExcludesToken = legacyGlobber.runAsync(excludesToDelegate, ImmutableList.<String>of(),
                    excludeDirs);

            return new HybridToken(globValueMap, includesKeys, excludesKeys, legacyIncludesToken,
                    legacyExcludesToken);
        }

        private Collection<SkyKey> getMissingKeys(Collection<SkyKey> globKeys,
                Map<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException>> globValueMap) {
            List<SkyKey> missingKeys = new ArrayList<>(globKeys.size());
            for (SkyKey globKey : globKeys) {
                ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException> valueOrException = globValueMap
                        .get(globKey);
                if (valueOrException == null) {
                    missingKeys.add(globKey);
                }
                try {
                    if (valueOrException.get() == null) {
                        missingKeys.add(globKey);
                    }
                } catch (IOException | BuildFileNotFoundException | FileSymlinkCycleException
                        | InconsistentFilesystemException doesntMatter) {
                    continue;
                }
            }
            return missingKeys;
        }

        @Override
        public List<String> fetch(Token token) throws IOException, InterruptedException {
            HybridToken hybridToken = (HybridToken) token;
            return hybridToken.resolve(legacyGlobber);
        }

        @Override
        public void onInterrupt() {
            legacyGlobber.onInterrupt();
        }

        @Override
        public void onCompletion() {
            legacyGlobber.onCompletion();
        }

        /**
         * A {@link Globber.Token} that encapsulates the result of a single {@link Globber#runAsync}
         * call via the fetching of some globs from skyframe, and some other globs via a
         * {@link PackageFactory.LegacyGlobber}. We take care to properly handle 'includes' vs
         * 'excludes'.
         *
         * <p>That is, we evaluate {@code glob(includes, excludes)} by partitioning {@code includes} and
         * {@code excludes}.
         *
         * <pre>
         * {@code
         * includes = includes_sky U includes_leg
         * excludes = excludes_sky U excludes_leg
         * }
         * </pre>
         *
         * <p>and then noting
         *
         * <pre>
         * {@code
         * glob(includes, excludes) =
         *     (glob(includes_sky, []) U glob(includes_leg, []))
         *   - (glob(excludes_sky, []) U glob(excludes_leg, []))
         * }
         * </pre>
         *
         * <p>Importantly, we pass excludes=[] in all cases; otherwise we'd be incorrectly not
         * subtracting excluded glob matches from the overall list of matches. In other words, we
         * implement the subtractive nature of excludes ourselves in {@link #resolve}.
         */
        private static class HybridToken extends Globber.Token {
            // The result of the Skyframe lookup for all the needed glob patterns.
            private final Map<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException>> globValueMap;
            // The skyframe keys corresponding to the 'includes' patterns fetched from Skyframe
            // (this is includes_sky above).
            private final Iterable<SkyKey> includesGlobKeys;
            // The skyframe keys corresponding to the 'excludes' patterns fetched from Skyframe
            // (this is excludes_sky above).
            private final Iterable<SkyKey> excludesGlobKeys;
            // A token for computing includes_leg.
            private final Token legacyIncludesToken;
            // A token for computing excludes_leg.
            private final Token legacyExcludesToken;

            private HybridToken(
                    Map<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException>> globValueMap,
                    Iterable<SkyKey> includesGlobKeys, Iterable<SkyKey> excludesGlobKeys,
                    Token delegateIncludesToken, Token delegateExcludesToken) {
                this.globValueMap = globValueMap;
                this.includesGlobKeys = includesGlobKeys;
                this.excludesGlobKeys = excludesGlobKeys;
                this.legacyIncludesToken = delegateIncludesToken;
                this.legacyExcludesToken = delegateExcludesToken;
            }

            private List<String> resolve(Globber delegate) throws IOException, InterruptedException {
                HashSet<String> matches = new HashSet<>();
                for (SkyKey includeGlobKey : includesGlobKeys) {
                    // TODO(bazel-team): NestedSet expansion here is suboptimal.
                    for (PathFragment match : getGlobMatches(includeGlobKey, globValueMap)) {
                        matches.add(match.getPathString());
                    }
                }
                matches.addAll(delegate.fetch(legacyIncludesToken));
                for (SkyKey excludeGlobKey : excludesGlobKeys) {
                    for (PathFragment match : getGlobMatches(excludeGlobKey, globValueMap)) {
                        matches.remove(match.getPathString());
                    }
                }
                for (String delegateExcludeMatch : delegate.fetch(legacyExcludesToken)) {
                    matches.remove(delegateExcludeMatch);
                }
                List<String> result = new ArrayList<>(matches);
                // Skyframe glob results are unsorted. And we used a LegacyGlobber that doesn't sort.
                // Therefore, we want to unconditionally sort here.
                Collections.sort(result);
                return result;
            }

            private static NestedSet<PathFragment> getGlobMatches(SkyKey globKey,
                    Map<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException>> globValueMap)
                    throws IOException {
                ValueOrException4<IOException, BuildFileNotFoundException, FileSymlinkCycleException, InconsistentFilesystemException> valueOrException = Preconditions
                        .checkNotNull(globValueMap.get(globKey), "%s should not be missing", globKey);
                try {
                    return Preconditions
                            .checkNotNull((GlobValue) valueOrException.get(), "%s should not be missing", globKey)
                            .getMatches();
                } catch (BuildFileNotFoundException | FileSymlinkCycleException
                        | InconsistentFilesystemException e) {
                    // Legacy package loading is only able to handle an IOException, so a rethrow here is the
                    // best we can do. But after legacy package loading, PackageFunction will go through all
                    // the skyframe deps and properly handle InconsistentFilesystemExceptions.
                    throw new IOException(e.getMessage());
                }
            }
        }
    }

    /**
     * Constructs a {@link Package} object for the given package using legacy package loading.
     * Note that the returned package may be in error.
     *
     * <p>May return null if the computation has to be restarted.
     *
     * <p>Exactly one of {@code replacementContents} and {@code buildFileValue} will be
     * non-{@code null}. The former indicates that we have a faux BUILD file with the given contents
     * and the latter indicates that we have a legitimate BUILD file and should actually do
     * preprocessing.
     */
    @Nullable
    private CacheEntryWithGlobDeps<Package.Builder> loadPackage(Package externalPkg,
            @Nullable String replacementContents, PackageIdentifier packageId, Path buildFilePath,
            @Nullable FileValue buildFileValue, RuleVisibility defaultVisibility, List<Statement> preludeStatements,
            Path packageRoot, Environment env) throws InterruptedException, PackageFunctionException {
        CacheEntryWithGlobDeps<Package.Builder> packageFunctionCacheEntry = packageFunctionCache
                .getIfPresent(packageId);
        if (packageFunctionCacheEntry == null) {
            profiler.startTask(ProfilerTask.CREATE_PACKAGE, packageId.toString());
            if (packageProgress != null) {
                packageProgress.startReadPackage(packageId);
            }
            try {
                CacheEntryWithGlobDeps<AstAfterPreprocessing> astCacheEntry = astCache.getIfPresent(packageId);
                if (astCacheEntry == null) {
                    if (showLoadingProgress.get()) {
                        env.getListener().handle(Event.progress("Loading package: " + packageId));
                    }
                    // We use a LegacyGlobber that doesn't sort the matches for each individual glob pattern,
                    // since we want to sort the final result anyway.
                    LegacyGlobber legacyGlobber = packageFactory.createLegacyGlobberThatDoesntSort(
                            buildFilePath.getParentDirectory(), packageId, packageLocator);
                    SkyframeHybridGlobber skyframeGlobber = new SkyframeHybridGlobber(packageId, packageRoot, env,
                            legacyGlobber);
                    Preprocessor.Result preprocessingResult;
                    if (replacementContents == null) {
                        Preconditions.checkNotNull(buildFileValue, packageId);
                        byte[] buildFileBytes;
                        try {
                            buildFileBytes = buildFileValue.isSpecialFile()
                                    ? FileSystemUtils.readContent(buildFilePath)
                                    : FileSystemUtils.readWithKnownFileSize(buildFilePath,
                                            buildFileValue.getSize());
                        } catch (IOException e) {
                            // Note that we did this work, so we should conservatively report this error as
                            // transient.
                            throw new PackageFunctionException(
                                    new BuildFileContainsErrorsException(packageId, e.getMessage()),
                                    Transience.TRANSIENT);
                        }
                        try {
                            preprocessingResult = packageFactory.preprocess(buildFilePath, packageId,
                                    buildFileBytes, skyframeGlobber);
                        } catch (IOException e) {
                            throw new PackageFunctionException(new BuildFileContainsErrorsException(packageId,
                                    "preprocessing failed" + e.getMessage(), e), Transience.TRANSIENT);
                        }
                    } else {
                        ParserInputSource replacementSource = ParserInputSource.create(replacementContents,
                                buildFilePath.asFragment());
                        preprocessingResult = Preprocessor.Result.noPreprocessing(replacementSource);
                    }
                    StoredEventHandler astParsingEventHandler = new StoredEventHandler();
                    BuildFileAST ast = PackageFactory.parseBuildFile(packageId, preprocessingResult.result,
                            preludeStatements, astParsingEventHandler);
                    // If no globs were fetched during preprocessing, then there's no need to reuse the
                    // legacy globber instance during BUILD file evaluation since the performance argument
                    // below does not apply.
                    Set<SkyKey> globDepsRequested = skyframeGlobber.getGlobDepsRequested();
                    LegacyGlobber legacyGlobberToStore = globDepsRequested.isEmpty() ? null : legacyGlobber;
                    astCacheEntry = new CacheEntryWithGlobDeps<>(
                            new AstAfterPreprocessing(preprocessingResult, ast, astParsingEventHandler),
                            globDepsRequested, legacyGlobberToStore);
                    astCache.put(packageId, astCacheEntry);
                }
                AstAfterPreprocessing astAfterPreprocessing = astCacheEntry.value;
                Set<SkyKey> globDepsRequestedDuringPreprocessing = astCacheEntry.globDepKeys;
                SkylarkImportResult importResult;
                try {
                    importResult = fetchImportsFromBuildFile(buildFilePath, packageId, astAfterPreprocessing.ast,
                            env, skylarkImportLookupFunctionForInlining);
                } catch (PackageFunctionException | InterruptedException e) {
                    astCache.invalidate(packageId);
                    throw e;
                }
                if (importResult == null) {
                    return null;
                }
                astCache.invalidate(packageId);
                // If a legacy globber was used to evaluate globs during preprocessing, it's important that
                // we reuse that globber during BUILD file evaluation for performance, in the case that
                // globs were fetched lazily during preprocessing. See Preprocessor.Factory#considersGlobs.
                LegacyGlobber legacyGlobber = astCacheEntry.legacyGlobber != null ? astCacheEntry.legacyGlobber
                        : packageFactory.createLegacyGlobber(buildFilePath.getParentDirectory(), packageId,
                                packageLocator);
                SkyframeHybridGlobber skyframeGlobber = new SkyframeHybridGlobber(packageId, packageRoot, env,
                        legacyGlobber);
                Package.Builder pkgBuilder = packageFactory.createPackageFromPreprocessingAst(externalPkg,
                        packageId, buildFilePath, astAfterPreprocessing, importResult.importMap,
                        importResult.fileDependencies, defaultVisibility, skyframeGlobber);
                Set<SkyKey> globDepsRequested = ImmutableSet.<SkyKey>builder()
                        .addAll(globDepsRequestedDuringPreprocessing).addAll(skyframeGlobber.getGlobDepsRequested())
                        .build();
                packageFunctionCacheEntry = new CacheEntryWithGlobDeps<>(pkgBuilder, globDepsRequested, null);
                numPackagesLoaded.incrementAndGet();
                if (packageProgress != null) {
                    packageProgress.doneReadPackage(packageId);
                }
                packageFunctionCache.put(packageId, packageFunctionCacheEntry);
            } finally {
                profiler.completeTask(ProfilerTask.CREATE_PACKAGE);
            }
        }
        return packageFunctionCacheEntry;
    }

    private static class InternalInconsistentFilesystemException extends Exception {
        private boolean isTransient;

        private PackageIdentifier packageIdentifier;

        /**
         * Used to represent a filesystem inconsistency discovered outside the
         * {@link PackageFunction}.
         */
        public InternalInconsistentFilesystemException(PackageIdentifier packageIdentifier,
                InconsistentFilesystemException e) {
            super(e.getMessage(), e);
            this.packageIdentifier = packageIdentifier;
            // 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(PackageIdentifier packageIdentifier,
                String inconsistencyMessage) {
            this(packageIdentifier, new InconsistentFilesystemException(inconsistencyMessage));
            this.isTransient = true;
        }

        public boolean isTransient() {
            return isTransient;
        }

        private NoSuchPackageException toNoSuchPackageException() {
            return new NoSuchPackageException(packageIdentifier, this.getMessage(), (Exception) this.getCause());
        }
    }

    /**
     * Used to declare all the exception types that can be wrapped in the exception thrown by
     * {@link PackageFunction#compute}.
     */
    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.*/
    static final class SkylarkImportResult {
        final Map<String, Extension> importMap;
        final ImmutableList<Label> fileDependencies;

        private SkylarkImportResult(Map<String, Extension> importMap, ImmutableList<Label> fileDependencies) {
            this.importMap = importMap;
            this.fileDependencies = fileDependencies;
        }
    }

    static boolean isDefaultsPackage(PackageIdentifier packageIdentifier) {
        return packageIdentifier.getRepository().isMain()
                && packageIdentifier.getPackageFragment().equals(DEFAULTS_PACKAGE_NAME);
    }
}