// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.skyframe.RecursivePkgValue.RecursivePkgKey; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.skyframe.LegacySkyKey; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.Objects; import javax.annotation.Nullable; /** * The value computed by {@link CollectPackagesUnderDirectoryFunction}. Contains a mapping for all * its non-excluded directories to whether there are packages or error messages beneath them. * * <p>This value is used by {@link GraphBackedRecursivePackageProvider#getPackagesUnderDirectory} to * help it traverse the graph and find the set of packages under a directory, recursively by {@link * CollectPackagesUnderDirectoryFunction} which computes a value for a directory by aggregating * results calculated from its subdirectories, and by {@link * PrepareDepsOfTargetsUnderDirectoryFunction} which uses this value to find transitive targets to * load. * * <p>Note that even though the {@link CollectPackagesUnderDirectoryFunction} is evaluated in part * because of its side-effects (i.e. loading transitive dependencies of targets), this value * interacts safely with change pruning, despite the fact that this value is a lossy representation * of the packages beneath a directory (i.e. it doesn't care <b>which</b> packages are under a * directory, just whether there are any). When the targets in a package change, the {@link * PackageValue} that {@link CollectPackagesUnderDirectoryFunction} depends on will be invalidated, * and the PrepareDeps function for that package's directory will be reevaluated, loading any new * transitive dependencies. Change pruning may prevent the reevaluation of PrepareDeps for * directories above that one, but they don't need to be re-run. */ public abstract class CollectPackagesUnderDirectoryValue implements SkyValue { private final ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors; CollectPackagesUnderDirectoryValue( ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors) { this.subdirectoryTransitivelyContainsPackagesOrErrors = Preconditions.checkNotNull(subdirectoryTransitivelyContainsPackagesOrErrors); } /** Represents a successfully loaded package or a directory without a BUILD file. */ public static class NoErrorCollectPackagesUnderDirectoryValue extends CollectPackagesUnderDirectoryValue { public static final NoErrorCollectPackagesUnderDirectoryValue EMPTY = new NoErrorCollectPackagesUnderDirectoryValue( false, ImmutableMap.<RootedPath, Boolean>of()); private final boolean isDirectoryPackage; private NoErrorCollectPackagesUnderDirectoryValue( boolean isDirectoryPackage, ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors) { super(subdirectoryTransitivelyContainsPackagesOrErrors); this.isDirectoryPackage = isDirectoryPackage; } @Override public boolean isDirectoryPackage() { return isDirectoryPackage; } @Nullable @Override public String getErrorMessage() { return null; } @Override public int hashCode() { return Objects.hash( isDirectoryPackage, getSubdirectoryTransitivelyContainsPackagesOrErrors()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof NoErrorCollectPackagesUnderDirectoryValue)) { return false; } NoErrorCollectPackagesUnderDirectoryValue that = (NoErrorCollectPackagesUnderDirectoryValue) o; return this.isDirectoryPackage == that.isDirectoryPackage && Objects.equals( this.getSubdirectoryTransitivelyContainsPackagesOrErrors(), that.getSubdirectoryTransitivelyContainsPackagesOrErrors()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("isDirectoryPackage", isDirectoryPackage) .add( "subdirectoryTransitivelyContainsPackagesOrErrors", getSubdirectoryTransitivelyContainsPackagesOrErrors()) .toString(); } } /** Represents a directory with a BUILD file that failed to load. */ public static class ErrorCollectPackagesUnderDirectoryValue extends CollectPackagesUnderDirectoryValue { private final String errorMessage; private ErrorCollectPackagesUnderDirectoryValue( String errorMessage, ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors) { super(subdirectoryTransitivelyContainsPackagesOrErrors); this.errorMessage = Preconditions.checkNotNull(errorMessage); } @Override public boolean isDirectoryPackage() { return false; } @Override public String getErrorMessage() { return errorMessage; } @Override public int hashCode() { return Objects.hash(errorMessage, getSubdirectoryTransitivelyContainsPackagesOrErrors()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ErrorCollectPackagesUnderDirectoryValue)) { return false; } ErrorCollectPackagesUnderDirectoryValue that = (ErrorCollectPackagesUnderDirectoryValue) o; return Objects.equals(this.errorMessage, that.errorMessage) && Objects.equals( this.getSubdirectoryTransitivelyContainsPackagesOrErrors(), that.getSubdirectoryTransitivelyContainsPackagesOrErrors()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("errorMessage", errorMessage) .add( "subdirectoryTransitivelyContainsPackagesOrErrors", getSubdirectoryTransitivelyContainsPackagesOrErrors()) .toString(); } } /** * Constructs a {@link CollectPackagesUnderDirectoryValue} for a directory with a BUILD file that * failed to load as a package. */ public static CollectPackagesUnderDirectoryValue ofError( String errorMessage, ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors) { Preconditions.checkNotNull(errorMessage, "errorMessage"); return new ErrorCollectPackagesUnderDirectoryValue( errorMessage, subdirectoryTransitivelyContainsPackagesOrErrors); } /** * Constructs a {@link CollectPackagesUnderDirectoryValue} for a directory without a BUILD file or * that has a BUILD file that successfully loads as a package. */ public static CollectPackagesUnderDirectoryValue ofNoError( boolean isDirectoryPackage, ImmutableMap<RootedPath, Boolean> subdirectoryTransitivelyContainsPackagesOrErrors) { if (!isDirectoryPackage && subdirectoryTransitivelyContainsPackagesOrErrors.isEmpty()) { return NoErrorCollectPackagesUnderDirectoryValue.EMPTY; } return new NoErrorCollectPackagesUnderDirectoryValue( isDirectoryPackage, subdirectoryTransitivelyContainsPackagesOrErrors); } /** * Returns whether there is a BUILD file in this directory that can be loaded as a package. If * this returns {@code true}, then {@link #getErrorMessage()} returns {@code null}. */ public abstract boolean isDirectoryPackage(); /** * Returns an error describing why the BUILD file in this directory cannot be loaded as a package, * if there is one and it can't be. Otherwise returns {@code null}. If this returns non-{@code * null}, then {@link #isDirectoryPackage()} returns {@code false}. */ @Nullable public abstract String getErrorMessage(); /** * Returns an {@link ImmutableMap} describing each immediate subdirectory of this directory and * whether there are any packages, or BUILD files that couldn't be loaded, in or beneath that * subdirectory. */ public final ImmutableMap<RootedPath, Boolean> getSubdirectoryTransitivelyContainsPackagesOrErrors() { return subdirectoryTransitivelyContainsPackagesOrErrors; } /** Create a collect packages under directory request. */ @ThreadSafe static SkyKey key( RepositoryName repository, RootedPath rootedPath, ImmutableSet<PathFragment> excludedPaths) { return key(new RecursivePkgKey(repository, rootedPath, excludedPaths)); } static SkyKey key(RecursivePkgKey recursivePkgKey) { return LegacySkyKey.create(SkyFunctions.COLLECT_PACKAGES_UNDER_DIRECTORY, recursivePkgKey); } }