|  | // 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.annotations.VisibleForTesting; | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.base.Throwables; | 
|  | 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.actions.FileValue; | 
|  | import com.google.devtools.build.lib.actions.InconsistentFilesystemException; | 
|  | import com.google.devtools.build.lib.clock.BlazeClock; | 
|  | 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.cmdline.RepositoryName; | 
|  | import com.google.devtools.build.lib.collect.nestedset.NestedSet; | 
|  | import com.google.devtools.build.lib.events.Event; | 
|  | import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable; | 
|  | import com.google.devtools.build.lib.events.Location; | 
|  | import com.google.devtools.build.lib.events.StoredEventHandler; | 
|  | import com.google.devtools.build.lib.packages.AstParseResult; | 
|  | 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.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.profiler.SilentCloseable; | 
|  | import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException; | 
|  | import com.google.devtools.build.lib.skyframe.PackageFunction.ActionOnIOExceptionReadingBuildFile; | 
|  | 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.SkylarkSemantics; | 
|  | import com.google.devtools.build.lib.syntax.Statement; | 
|  | import com.google.devtools.build.lib.util.Pair; | 
|  | 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.Root; | 
|  | 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 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.LinkedHashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | 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, LoadedPackageCacheEntry> packageFunctionCache; | 
|  | private final Cache<PackageIdentifier, AstParseResult> astCache; | 
|  | private final AtomicBoolean showLoadingProgress; | 
|  | private final AtomicInteger numPackagesLoaded; | 
|  | @Nullable private final PackageProgressReceiver packageProgress; | 
|  | private final Profiler profiler = Profiler.instance(); | 
|  | private final Label preludeLabel; | 
|  |  | 
|  | // Not final only for testing. | 
|  | @Nullable private SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining; | 
|  |  | 
|  | private final ActionOnIOExceptionReadingBuildFile actionOnIOExceptionReadingBuildFile; | 
|  |  | 
|  | private final IncrementalityIntent incrementalityIntent; | 
|  |  | 
|  | static final PathFragment DEFAULTS_PACKAGE_NAME = PathFragment.create("tools/defaults"); | 
|  |  | 
|  | public PackageFunction( | 
|  | PackageFactory packageFactory, | 
|  | CachingPackageLocator pkgLocator, | 
|  | AtomicBoolean showLoadingProgress, | 
|  | Cache<PackageIdentifier, LoadedPackageCacheEntry> packageFunctionCache, | 
|  | Cache<PackageIdentifier, AstParseResult> astCache, | 
|  | AtomicInteger numPackagesLoaded, | 
|  | @Nullable SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining, | 
|  | @Nullable PackageProgressReceiver packageProgress, | 
|  | ActionOnIOExceptionReadingBuildFile actionOnIOExceptionReadingBuildFile, | 
|  | IncrementalityIntent incrementalityIntent) { | 
|  | 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; | 
|  | this.actionOnIOExceptionReadingBuildFile = actionOnIOExceptionReadingBuildFile; | 
|  | this.incrementalityIntent = incrementalityIntent; | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | public PackageFunction( | 
|  | PackageFactory packageFactory, | 
|  | CachingPackageLocator pkgLocator, | 
|  | AtomicBoolean showLoadingProgress, | 
|  | Cache<PackageIdentifier, LoadedPackageCacheEntry> packageFunctionCache, | 
|  | Cache<PackageIdentifier, AstParseResult> astCache, | 
|  | AtomicInteger numPackagesLoaded, | 
|  | @Nullable SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining) { | 
|  | this( | 
|  | packageFactory, | 
|  | pkgLocator, | 
|  | showLoadingProgress, | 
|  | packageFunctionCache, | 
|  | astCache, | 
|  | numPackagesLoaded, | 
|  | skylarkImportLookupFunctionForInlining, | 
|  | /*packageProgress=*/ null, | 
|  | ActionOnIOExceptionReadingBuildFile.UseOriginalIOException.INSTANCE, | 
|  | IncrementalityIntent.INCREMENTAL); | 
|  | } | 
|  |  | 
|  | public void setSkylarkImportLookupFunctionForInliningForTesting( | 
|  | SkylarkImportLookupFunction skylarkImportLookupFunctionForInlining) { | 
|  | this.skylarkImportLookupFunctionForInlining = skylarkImportLookupFunctionForInlining; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * What to do when encountering an {@link IOException} trying to read the contents of a BUILD | 
|  | * file. | 
|  | * | 
|  | * <p>Any choice besides | 
|  | * {@link ActionOnIOExceptionReadingBuildFile.UseOriginalIOException#INSTANCE} is potentially | 
|  | * incrementally unsound: if the initial {@link IOException} is transient, then Blaze will | 
|  | * "incorrectly" not attempt to redo package loading for this BUILD file on incremental builds. | 
|  | * | 
|  | * <p>The fact that this behavior is configurable and potentially unsound is a concession to | 
|  | * certain desired use cases with fancy filesystems. | 
|  | */ | 
|  | public interface ActionOnIOExceptionReadingBuildFile { | 
|  | /** | 
|  | * Given the {@link IOException} encountered when reading the contents of the given BUILD file, | 
|  | * returns the contents that should be used, or {@code null} if the original {@link IOException} | 
|  | * should be respected (that is, we should error-out with a package loading error). | 
|  | */ | 
|  | @Nullable | 
|  | byte[] maybeGetBuildFileContentsToUse( | 
|  | PathFragment buildFilePathFragment, IOException originalExn); | 
|  |  | 
|  | /** | 
|  | * A {@link ActionOnIOExceptionReadingBuildFile} whose {@link #maybeGetBuildFileContentsToUse} | 
|  | * has the sensible behavior of always respecting the initial {@link IOException}. | 
|  | */ | 
|  | public static class UseOriginalIOException implements ActionOnIOExceptionReadingBuildFile { | 
|  | public static final UseOriginalIOException INSTANCE = new UseOriginalIOException(); | 
|  |  | 
|  | private UseOriginalIOException() { | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public byte[] maybeGetBuildFileContentsToUse( | 
|  | PathFragment buildFilePathFragment, IOException originalExn) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** An entry in {@link PackageFunction} internal cache. */ | 
|  | public static class LoadedPackageCacheEntry { | 
|  | private final Package.Builder builder; | 
|  | private final Set<SkyKey> globDepKeys; | 
|  | private final long loadTimeNanos; | 
|  |  | 
|  | private LoadedPackageCacheEntry( | 
|  | Package.Builder builder, Set<SkyKey> globDepKeys, long loadTimeNanos) { | 
|  | this.builder = builder; | 
|  | this.globDepKeys = globDepKeys; | 
|  | this.loadTimeNanos = loadTimeNanos; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A declaration to {@link PackageFunction} about how it will be used, for the sake of making | 
|  | * use-case-driven performance optimizations. | 
|  | */ | 
|  | public enum IncrementalityIntent { | 
|  | /** | 
|  | * {@link PackageFunction} will be used to load packages incrementally (e.g. on both clean | 
|  | * builds and incremental builds, perhaps with cached globs). This is Bazel's normal use-case. | 
|  | */ | 
|  | INCREMENTAL, | 
|  |  | 
|  | /** | 
|  | * {@link PackageFunction} will never be used to load packages incrementally. | 
|  | * | 
|  | * <p>Do not use this unless you know what you are doing; Bazel will be intentionally | 
|  | * incrementally incorrect! | 
|  | */ | 
|  | // TODO(nharmata): Consider using this when --track_incremental_state=false. | 
|  | NON_INCREMENTAL | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 packageShouldBeInErrorFromGlobDeps = false; | 
|  | for (Map.Entry<SkyKey, ValueOrException2<IOException, BuildFileNotFoundException>> entry : | 
|  | env.getValuesOrThrow( | 
|  | depKeys, IOException.class, BuildFileNotFoundException.class).entrySet()) { | 
|  | try { | 
|  | entry.getValue().get(); | 
|  | } catch (InconsistentFilesystemException e) { | 
|  | throw new InternalInconsistentFilesystemException(packageIdentifier, e); | 
|  | } catch (FileSymlinkException e) { | 
|  | // Legacy doesn't detect symlink cycles. | 
|  | packageShouldBeInErrorFromGlobDeps = true; | 
|  | } catch (IOException | BuildFileNotFoundException e) { | 
|  | maybeThrowFilesystemInconsistency(packageIdentifier, e, packageWasInError); | 
|  | } | 
|  | } | 
|  | return packageShouldBeInErrorFromGlobDeps; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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, Root packageLookupPath) | 
|  | throws PackageFunctionException, InterruptedException { | 
|  | SkylarkSemantics skylarkSemantics = PrecomputedValue.SKYLARK_SEMANTICS.get(env); | 
|  | if (skylarkSemantics == null) { | 
|  | return null; | 
|  | } | 
|  | RootedPath workspacePath = RootedPath.toRootedPath( | 
|  | packageLookupPath, Label.WORKSPACE_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, | 
|  | EvalException.class, | 
|  | SkylarkImportFailedException.class); | 
|  | } catch (IOException | 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()); | 
|  | for (Postable post : pkg.getPosts()) { | 
|  | env.getListener().post(post); | 
|  | } | 
|  |  | 
|  | if (packageFactory != null) { | 
|  | packageFactory.afterDoneLoadingPackage( | 
|  | pkg, | 
|  | skylarkSemantics, | 
|  | // This is a lie. | 
|  | /*loadTimeNanos=*/0L); | 
|  | } | 
|  | 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: | 
|  | case REPOSITORY_NOT_FOUND: | 
|  | 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()); | 
|  | } | 
|  | WorkspaceNameValue workspaceNameValue = | 
|  | (WorkspaceNameValue) env.getValue(WorkspaceNameValue.key()); | 
|  | if (workspaceNameValue == null) { | 
|  | return null; | 
|  | } | 
|  | String workspaceName = workspaceNameValue.getName(); | 
|  |  | 
|  | RepositoryMappingValue repositoryMappingValue = | 
|  | (RepositoryMappingValue) | 
|  | env.getValue(RepositoryMappingValue.key(packageId.getRepository())); | 
|  | if (repositoryMappingValue == null) { | 
|  | return null; | 
|  | } | 
|  | ImmutableMap<RepositoryName, RepositoryName> repositoryMapping = | 
|  | repositoryMappingValue.getRepositoryMapping(); | 
|  |  | 
|  | RootedPath buildFileRootedPath = packageLookupValue.getRootedPath(packageId); | 
|  | FileValue buildFileValue = null; | 
|  | Path buildFilePath = buildFileRootedPath.asPath(); | 
|  | String replacementContents = null; | 
|  |  | 
|  | if (isDefaultsPackage(packageId) && PrecomputedValue.isInMemoryToolsDefaults(env)) { | 
|  | replacementContents = PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.get(env); | 
|  | if (replacementContents == null) { | 
|  | return null; | 
|  | } | 
|  | } else { | 
|  | buildFileValue = getBuildFileValue(env, buildFileRootedPath); | 
|  | if (buildFileValue == null) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | RuleVisibility defaultVisibility = PrecomputedValue.DEFAULT_VISIBILITY.get(env); | 
|  | if (defaultVisibility == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | SkylarkSemantics skylarkSemantics = PrecomputedValue.SKYLARK_SEMANTICS.get(env); | 
|  | if (skylarkSemantics == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // Load the prelude from the same repository as the package being loaded.  Can't use | 
|  | // Label.resolveRepositoryRelative because preludeLabel is in the main repository, not the | 
|  | // default one, so it is resolved to itself. | 
|  | Label pkgPreludeLabel = | 
|  | Label.createUnvalidated( | 
|  | PackageIdentifier.create(packageId.getRepository(), preludeLabel.getPackageFragment()), | 
|  | preludeLabel.getName()); | 
|  | SkyKey astLookupKey = ASTFileLookupValue.key(pkgPreludeLabel); | 
|  | 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(); | 
|  | LoadedPackageCacheEntry packageCacheEntry = | 
|  | loadPackage( | 
|  | workspaceName, | 
|  | repositoryMapping, | 
|  | replacementContents, | 
|  | packageId, | 
|  | buildFilePath, | 
|  | buildFileValue, | 
|  | defaultVisibility, | 
|  | skylarkSemantics, | 
|  | preludeStatements, | 
|  | packageLookupValue.getRoot(), | 
|  | env); | 
|  | if (packageCacheEntry == null) { | 
|  | return null; | 
|  | } | 
|  | Package.Builder pkgBuilder = packageCacheEntry.builder; | 
|  | try { | 
|  | pkgBuilder.buildPartial(); | 
|  | } catch (NoSuchPackageException e) { | 
|  | throw new PackageFunctionException( | 
|  | e, | 
|  | e.getCause() instanceof SkyframeGlobbingIOException | 
|  | ? Transience.PERSISTENT | 
|  | : Transience.TRANSIENT); | 
|  | } | 
|  | try { | 
|  | // Since the Skyframe dependencies we request below in | 
|  | // handleGlobDepsAndPropagateFilesystemExceptions 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 = packageCacheEntry.globDepKeys; | 
|  | boolean packageShouldBeConsideredInErrorFromGlobDeps; | 
|  | try { | 
|  | packageShouldBeConsideredInErrorFromGlobDeps = | 
|  | handleGlobDepsAndPropagateFilesystemExceptions( | 
|  | packageId, globKeys, env, pkgBuilder.containsErrors()); | 
|  | } catch (InternalInconsistentFilesystemException e) { | 
|  | packageFunctionCache.invalidate(packageId); | 
|  | throw new PackageFunctionException( | 
|  | e.toNoSuchPackageException(), | 
|  | e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT); | 
|  | } | 
|  | if (env.valuesMissing()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (pkgBuilder.containsErrors() || packageShouldBeConsideredInErrorFromGlobDeps) { | 
|  | pkgBuilder.setContainsErrors(); | 
|  | } | 
|  | Package pkg = pkgBuilder.finishBuild(); | 
|  |  | 
|  | Event.replayEventsOn(env.getListener(), pkgBuilder.getEvents()); | 
|  | for (Postable post : pkgBuilder.getPosts()) { | 
|  | env.getListener().post(post); | 
|  | } | 
|  |  | 
|  | // We know this SkyFunction will not be called again, so we can remove the cache entry. | 
|  | packageFunctionCache.invalidate(packageId); | 
|  |  | 
|  | packageFactory.afterDoneLoadingPackage(pkg, skylarkSemantics, packageCacheEntry.loadTimeNanos); | 
|  | 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); | 
|  | } catch (IOException 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; | 
|  | } | 
|  |  | 
|  | private static BuildFileContainsErrorsException propagateSkylarkImportFailedException( | 
|  | PackageIdentifier packageId, SkylarkImportFailedException e) | 
|  | throws BuildFileContainsErrorsException { | 
|  | Throwable rootCause = Throwables.getRootCause(e); | 
|  | throw (rootCause instanceof IOException) | 
|  | ? new BuildFileContainsErrorsException( | 
|  | packageId, e.getMessage(), (IOException) rootCause) | 
|  | : new BuildFileContainsErrorsException(packageId, e.getMessage()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 NoSuchPackageException, 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 propagateSkylarkImportFailedException(packageId, e); | 
|  | } | 
|  |  | 
|  | // 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 | 
|  | for (SkyKey importLookupKey : importLookupKeys) { | 
|  | SkyValue skyValue = | 
|  | skylarkImportLookupFunctionForInlining.computeWithInlineCalls( | 
|  | importLookupKey, env, importLookupKeys.size()); | 
|  | 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 propagateSkylarkImportFailedException(packageId, e); | 
|  | } catch (InconsistentFilesystemException e) { | 
|  | throw new NoSuchPackageException(packageId, e.getMessage(), e); | 
|  | } | 
|  |  | 
|  | if (valuesMissing) { | 
|  | // Some imports are unavailable. | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // Process the loaded imports. | 
|  | for (Map.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( | 
|  | Root pkgRoot, PackageIdentifier pkgId, Package.Builder pkgBuilder, Environment env) | 
|  | throws InternalInconsistentFilesystemException, InterruptedException { | 
|  | PathFragment pkgDir = pkgId.getPackageFragment(); | 
|  | Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet(); | 
|  | Map<Target, SkyKey> targetToKey = new HashMap<>(); | 
|  | for (Target target : pkgBuilder.getTargets()) { | 
|  | PathFragment dir = getContainingDirectory(target.getLabel()); | 
|  | if (dir == null) { | 
|  | throw new IllegalStateException( | 
|  | String.format( | 
|  | "Null pkg for label %s as path fragment %s in pkg %s", | 
|  | target.getLabel(), target.getLabel().getPackageFragment(), pkgId)); | 
|  | } | 
|  | if (dir.equals(pkgDir)) { | 
|  | continue; | 
|  | } | 
|  | PackageIdentifier dirId = PackageIdentifier.create(pkgId.getRepository(), dir); | 
|  | SkyKey key = ContainingPackageLookupValue.key(dirId); | 
|  | targetToKey.put(target, key); | 
|  | containingPkgLookupKeys.add(key); | 
|  | } | 
|  | 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(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static PathFragment getContainingDirectory(Label label) { | 
|  | PathFragment pkg = label.getPackageFragment(); | 
|  | String name = label.getName(); | 
|  | if (name.equals(".")) { | 
|  | return pkg; | 
|  | } | 
|  | if (PathFragment.isNormalizedRelativePath(name) && !PathFragment.containsSeparator(name)) { | 
|  | // Optimize for the common case of a label like '//pkg:target'. | 
|  | return pkg; | 
|  | } | 
|  | return pkg.getRelative(name).getParentDirectory(); | 
|  | } | 
|  |  | 
|  | @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, | 
|  | Root 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 = PathFragment.create(label.getName()); | 
|  | String message = String.format("Label '%s' crosses boundary of subpackage '%s'", | 
|  | label, containingPkg); | 
|  | Root 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; | 
|  | } | 
|  |  | 
|  | private interface GlobberWithSkyframeGlobDeps extends Globber { | 
|  | Set<SkyKey> getGlobDepsRequested(); | 
|  | } | 
|  |  | 
|  | private static class LegacyGlobberWithNoGlobDeps implements GlobberWithSkyframeGlobDeps { | 
|  | private final LegacyGlobber delegate; | 
|  |  | 
|  | private LegacyGlobberWithNoGlobDeps(LegacyGlobber delegate) { | 
|  | this.delegate = delegate; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Set<SkyKey> getGlobDepsRequested() { | 
|  | return ImmutableSet.of(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Token runAsync(List<String> includes, List<String> excludes, boolean excludeDirs) | 
|  | throws BadGlobException, InterruptedException { | 
|  | return delegate.runAsync(includes, excludes, excludeDirs); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public List<String> fetch(Token token) throws IOException, InterruptedException { | 
|  | return delegate.fetch(token); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onInterrupt() { | 
|  | delegate.onInterrupt(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onCompletion() { | 
|  | delegate.onCompletion(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 GlobberWithSkyframeGlobDeps { | 
|  | private final PackageIdentifier packageId; | 
|  | private final Root packageRoot; | 
|  | private final Environment env; | 
|  | private final LegacyGlobber legacyGlobber; | 
|  | private final Set<SkyKey> globDepsRequested = Sets.newConcurrentHashSet(); | 
|  |  | 
|  | private SkyframeHybridGlobber( | 
|  | PackageIdentifier packageId, | 
|  | Root packageRoot, | 
|  | Environment env, | 
|  | LegacyGlobber legacyGlobber) { | 
|  | this.packageId = packageId; | 
|  | this.packageRoot = packageRoot; | 
|  | this.env = env; | 
|  | this.legacyGlobber = legacyGlobber; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public 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, ValueOrException3<IOException, BuildFileNotFoundException, | 
|  | FileSymlinkCycleException>> globValueMap = | 
|  | env.getValuesOrThrow(globKeys, IOException.class, BuildFileNotFoundException.class, | 
|  | FileSymlinkCycleException.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, ValueOrException3<IOException, BuildFileNotFoundException, | 
|  | FileSymlinkCycleException>> globValueMap) { | 
|  | List<SkyKey> missingKeys = new ArrayList<>(globKeys.size()); | 
|  | for (SkyKey globKey : globKeys) { | 
|  | ValueOrException3<IOException, BuildFileNotFoundException, FileSymlinkCycleException> | 
|  | valueOrException = globValueMap.get(globKey); | 
|  | if (valueOrException == null) { | 
|  | missingKeys.add(globKey); | 
|  | } | 
|  | try { | 
|  | if (valueOrException.get() == null) { | 
|  | missingKeys.add(globKey); | 
|  | } | 
|  | } catch (IOException | BuildFileNotFoundException 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, ValueOrException3<IOException, BuildFileNotFoundException, | 
|  | FileSymlinkCycleException>> 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, ValueOrException3<IOException, BuildFileNotFoundException, | 
|  | FileSymlinkCycleException>> 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, | 
|  | ValueOrException3< | 
|  | IOException, BuildFileNotFoundException, FileSymlinkCycleException>> | 
|  | globValueMap) | 
|  | throws SkyframeGlobbingIOException { | 
|  | ValueOrException3<IOException, BuildFileNotFoundException, FileSymlinkCycleException> | 
|  | 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 e) { | 
|  | // Legacy package loading is only able to handle an IOException, so a rethrow here is the | 
|  | // best we can do. | 
|  | throw new SkyframeGlobbingIOException(e); | 
|  | } catch (IOException e) { | 
|  | throw new SkyframeGlobbingIOException(e); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class SkyframeGlobbingIOException extends IOException { | 
|  | private SkyframeGlobbingIOException(Exception cause) { | 
|  | super(cause); | 
|  | } | 
|  | } | 
|  |  | 
|  | private GlobberWithSkyframeGlobDeps makeGlobber( | 
|  | Path buildFilePath, | 
|  | PackageIdentifier packageId, | 
|  | Root packageRoot, | 
|  | SkyFunction.Environment env) { | 
|  | LegacyGlobber legacyGlobber = packageFactory.createLegacyGlobber( | 
|  | buildFilePath.getParentDirectory(), packageId, packageLocator); | 
|  | switch (incrementalityIntent) { | 
|  | case INCREMENTAL: | 
|  | return new SkyframeHybridGlobber(packageId, packageRoot, env, legacyGlobber); | 
|  | case NON_INCREMENTAL: | 
|  | // Skyframe globbing is only useful for incremental correctness and performance. The | 
|  | // first time Bazel loads a package ever, Skyframe globbing is actually pure overhead | 
|  | // (SkyframeHybridGlobber will make full use of LegacyGlobber). | 
|  | return new LegacyGlobberWithNoGlobDeps(legacyGlobber); | 
|  | default: | 
|  | throw new IllegalStateException(incrementalityIntent.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Constructs a {@link Package} object for the given package. 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 read its contents. | 
|  | */ | 
|  | @Nullable | 
|  | private LoadedPackageCacheEntry loadPackage( | 
|  | String workspaceName, | 
|  | ImmutableMap<RepositoryName, RepositoryName> repositoryMapping, | 
|  | @Nullable String replacementContents, | 
|  | PackageIdentifier packageId, | 
|  | Path buildFilePath, | 
|  | @Nullable FileValue buildFileValue, | 
|  | RuleVisibility defaultVisibility, | 
|  | SkylarkSemantics skylarkSemantics, | 
|  | List<Statement> preludeStatements, | 
|  | Root packageRoot, | 
|  | Environment env) | 
|  | throws InterruptedException, PackageFunctionException { | 
|  | LoadedPackageCacheEntry packageCacheEntry = packageFunctionCache.getIfPresent(packageId); | 
|  | if (packageCacheEntry == null) { | 
|  | if (packageProgress != null) { | 
|  | packageProgress.startReadPackage(packageId); | 
|  | } | 
|  | try (SilentCloseable c = | 
|  | Profiler.instance().profile(ProfilerTask.CREATE_PACKAGE, packageId.toString())) { | 
|  | AstParseResult astParseResult = astCache.getIfPresent(packageId); | 
|  | if (astParseResult == null) { | 
|  | if (showLoadingProgress.get()) { | 
|  | env.getListener().handle(Event.progress("Loading package: " + packageId)); | 
|  | } | 
|  | ParserInputSource input; | 
|  | if (replacementContents == null) { | 
|  | Preconditions.checkNotNull(buildFileValue, packageId); | 
|  | byte[] buildFileBytes = null; | 
|  | try { | 
|  | buildFileBytes = | 
|  | buildFileValue.isSpecialFile() | 
|  | ? FileSystemUtils.readContent(buildFilePath) | 
|  | : FileSystemUtils.readWithKnownFileSize( | 
|  | buildFilePath, buildFileValue.getSize()); | 
|  | } catch (IOException e) { | 
|  | buildFileBytes = actionOnIOExceptionReadingBuildFile.maybeGetBuildFileContentsToUse( | 
|  | buildFilePath.asFragment(), e); | 
|  | if (buildFileBytes == null) { | 
|  | // Note that we did the work that led to this IOException, so we should | 
|  | // conservatively report this error as transient. | 
|  | throw new PackageFunctionException(new BuildFileContainsErrorsException( | 
|  | packageId, e.getMessage(), e), Transience.TRANSIENT); | 
|  | } | 
|  | // If control flow reaches here, we're in territory that is deliberately unsound. | 
|  | // See the javadoc for ActionOnIOExceptionReadingBuildFile. | 
|  | } | 
|  | input = | 
|  | ParserInputSource.create( | 
|  | FileSystemUtils.convertFromLatin1(buildFileBytes), | 
|  | buildFilePath.asFragment()); | 
|  | } else { | 
|  | input = ParserInputSource.create(replacementContents, buildFilePath.asFragment()); | 
|  | } | 
|  | StoredEventHandler astParsingEventHandler = new StoredEventHandler(); | 
|  | BuildFileAST ast = | 
|  | PackageFactory.parseBuildFile( | 
|  | packageId, input, preludeStatements, repositoryMapping, astParsingEventHandler); | 
|  | astParseResult = new AstParseResult(ast, astParsingEventHandler); | 
|  | astCache.put(packageId, astParseResult); | 
|  | } | 
|  | SkylarkImportResult importResult; | 
|  | try { | 
|  | importResult = | 
|  | fetchImportsFromBuildFile( | 
|  | buildFilePath, | 
|  | packageId, | 
|  | astParseResult.ast, | 
|  | env, | 
|  | skylarkImportLookupFunctionForInlining); | 
|  | } catch (NoSuchPackageException e) { | 
|  | throw new PackageFunctionException(e, Transience.PERSISTENT); | 
|  | } catch (InterruptedException e) { | 
|  | astCache.invalidate(packageId); | 
|  | throw e; | 
|  | } | 
|  | if (importResult == null) { | 
|  | return null; | 
|  | } | 
|  | astCache.invalidate(packageId); | 
|  | GlobberWithSkyframeGlobDeps globberWithSkyframeGlobDeps = | 
|  | makeGlobber(buildFilePath, packageId, packageRoot, env); | 
|  | long startTimeNanos = BlazeClock.nanoTime(); | 
|  | Package.Builder pkgBuilder = | 
|  | packageFactory.createPackageFromAst( | 
|  | workspaceName, | 
|  | repositoryMapping, | 
|  | packageId, | 
|  | buildFilePath, | 
|  | astParseResult, | 
|  | importResult.importMap, | 
|  | importResult.fileDependencies, | 
|  | defaultVisibility, | 
|  | skylarkSemantics, | 
|  | globberWithSkyframeGlobDeps); | 
|  | long loadTimeNanos = Math.max(BlazeClock.nanoTime() - startTimeNanos, 0L); | 
|  | packageCacheEntry = new LoadedPackageCacheEntry( | 
|  | pkgBuilder, | 
|  | globberWithSkyframeGlobDeps.getGlobDepsRequested(), | 
|  | loadTimeNanos); | 
|  | numPackagesLoaded.incrementAndGet(); | 
|  | if (packageProgress != null) { | 
|  | packageProgress.doneReadPackage(packageId); | 
|  | } | 
|  | packageFunctionCache.put(packageId, packageCacheEntry); | 
|  | } | 
|  | } | 
|  | return packageCacheEntry; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | public static boolean isDefaultsPackage(PackageIdentifier packageIdentifier) { | 
|  | return packageIdentifier.getRepository().isMain() | 
|  | && packageIdentifier.getPackageFragment().equals(DEFAULTS_PACKAGE_NAME); | 
|  | } | 
|  | } |