blob: 55c693fb09de33ae37d191d37468c1e1d0307bda [file] [log] [blame]
// Copyright 2015 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.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.BatchCallback;
import com.google.devtools.build.lib.cmdline.BatchCallback.NullCallback;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.QueryExceptionMarkerInterface;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.cmdline.ResolvedTargets;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.cmdline.TargetPattern;
import com.google.devtools.build.lib.cmdline.TargetPatternResolver;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.io.InconsistentFilesystemException;
import com.google.devtools.build.lib.io.ProcessPackageDirectoryException;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.AbstractRecursivePackageProvider.MissingDepException;
import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.pkgcache.TargetPatternResolverUtil;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.util.DetailedExitCode;
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.GraphTraversingHelper;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
/**
* PrepareDepsOfPatternFunction ensures the graph loads targets matching the pattern and its
* transitive dependencies.
*/
public class PrepareDepsOfPatternFunction implements SkyFunction {
private final AtomicReference<PathPackageLocator> pkgPath;
private final boolean traverseTestSuites;
public PrepareDepsOfPatternFunction(
AtomicReference<PathPackageLocator> pkgPath, boolean traverseTestSuites) {
this.pkgPath = pkgPath;
this.traverseTestSuites = traverseTestSuites;
}
@Nullable
@Override
public SkyValue compute(SkyKey key, Environment env)
throws PrepareDepsOfPatternFunctionException, InterruptedException {
TargetPatternValue.TargetPatternKey patternKey =
((TargetPatternValue.TargetPatternKey) key.argument());
// DepsOfPatternPreparer below expects to be able to ignore the filtering policy from the
// TargetPatternKey, which should be valid because PrepareDepsOfPatternValue.keys
// unconditionally creates TargetPatternKeys with the NO_FILTER filtering policy. (Compare
// with SkyframeTargetPatternEvaluator, which can create TargetPatternKeys with other
// filtering policies like FILTER_TESTS or FILTER_MANUAL.) This check makes sure that the
// key's filtering policy is NO_FILTER as expected.
Preconditions.checkState(
patternKey.getPolicy().equals(FilteringPolicies.NO_FILTER), patternKey.getPolicy());
TargetPattern parsedPattern = patternKey.getParsedPattern();
IgnoredPackagePrefixesValue repositoryIgnoredPrefixes =
(IgnoredPackagePrefixesValue)
env.getValue(IgnoredPackagePrefixesValue.key(parsedPattern.getRepository()));
if (repositoryIgnoredPrefixes == null) {
return null;
}
// This SkyFunction is used to load the universe, so we want both the ignored directories from
// the global list of exclusions (set with .bazelignore in Bazel and set statically in other
// binaries) and the excluded directories from the TargetPatternKey itself to be embedded in the
// SkyKeys created and used by the DepsOfPatternPreparer. The DepsOfPatternPreparer ignores
// excludedSubdirectories and embeds repositoryIgnoredPatterns in SkyKeys it creates and uses.
//
// This consolidation of excluded into ignored means that parsedPattern.eval below does a bit of
// extra work when parsePattern is a TargetsBelowDirectory and there are excluded directories,
// since it has to iterate over those exclusions to see if they fully exclude the directory even
// though TargetPatternKey guarantees that the exclusions will not fully exclude the directory.
ImmutableSet<PathFragment> excludedPatterns = patternKey.getExcludedSubdirectories();
ImmutableSet<PathFragment> ignoredPatterns = repositoryIgnoredPrefixes.getPatterns();
ImmutableSet<PathFragment> repositoryIgnoredPatterns =
ImmutableSet.<PathFragment>builderWithExpectedSize(
excludedPatterns.size() + ignoredPatterns.size())
.addAll(excludedPatterns)
.addAll(ignoredPatterns)
.build();
DepsOfPatternPreparer preparer =
new DepsOfPatternPreparer(env, pkgPath.get(), traverseTestSuites);
try {
parsedPattern.eval(
preparer,
() -> repositoryIgnoredPatterns,
ImmutableSet.of(),
NullCallback.instance(),
QueryExceptionMarkerInterface.MarkerRuntimeException.class);
} catch (TargetParsingException e) {
throw new PrepareDepsOfPatternFunctionException(e);
} catch (MissingDepException e) {
// The DepsOfPatternPreparer constructed above might throw MissingDepException to signal
// when it has a dependency on a missing Environment value.
return null;
} catch (ProcessPackageDirectoryException e) {
throw new PrepareDepsOfPatternFunctionException(parsedPattern, e);
} catch (InconsistentFilesystemException e) {
throw new PrepareDepsOfPatternFunctionException(parsedPattern, e);
}
return PrepareDepsOfPatternValue.INSTANCE;
}
/**
* Used to declare all the exception types that can be wrapped in the exception thrown by {@link
* PrepareDepsOfPatternFunction#compute}.
*/
private static final class PrepareDepsOfPatternFunctionException extends SkyFunctionException {
PrepareDepsOfPatternFunctionException(TargetParsingException e) {
super(e, Transience.PERSISTENT);
}
PrepareDepsOfPatternFunctionException(
TargetPattern pattern, ProcessPackageDirectoryException e) {
super(new PrepareDepsOfPatternException(pattern, e), Transience.PERSISTENT);
}
PrepareDepsOfPatternFunctionException(
TargetPattern pattern, InconsistentFilesystemException e) {
super(new PrepareDepsOfPatternException(pattern, e), Transience.PERSISTENT);
}
}
private static final class PrepareDepsOfPatternException extends Exception
implements DetailedException {
private final DetailedExitCode detailedExitCode;
PrepareDepsOfPatternException(TargetPattern pattern, ProcessPackageDirectoryException e) {
super(
"Preparing deps of pattern '"
+ pattern.getOriginalPattern()
+ "' failed: "
+ e.getMessage(),
e);
detailedExitCode = e.getDetailedExitCode();
}
public PrepareDepsOfPatternException(TargetPattern pattern, InconsistentFilesystemException e) {
super(
"Preparing deps of pattern '"
+ pattern.getOriginalPattern()
+ "' failed: "
+ e.getMessage(),
e);
detailedExitCode =
DetailedExitCode.of(
FailureDetails.FailureDetail.newBuilder()
.setMessage(getMessage())
.setPackageLoading(
FailureDetails.PackageLoading.newBuilder()
.setCode(
FailureDetails.PackageLoading.Code
.TRANSIENT_INCONSISTENT_FILESYSTEM_ERROR))
.build());
}
@Override
public DetailedExitCode getDetailedExitCode() {
return detailedExitCode;
}
}
/**
* A {@link TargetPatternResolver} backed by an {@link
* com.google.devtools.build.skyframe.SkyFunction.Environment} whose methods do not actually
* return resolved targets, but that ensures the graph loads the matching targets <b>and</b> their
* transitive dependencies. Its methods may throw {@link MissingDepException} if the package
* values this depends on haven't been calculated and added to its environment.
*/
static class DepsOfPatternPreparer extends TargetPatternResolver<Void> {
// Because PrepareDepsOfPatternFunction's only goal is to ensure the proper Skyframe nodes and
// edges are in the graph, we don't need to worry about
// EnvironmentBackedRecursivePackageProvider#encounteredPackageErrors.
private final EnvironmentBackedRecursivePackageProvider packageProvider;
private final Environment env;
private final ImmutableList<Root> pkgRoots;
private final boolean traverseTestSuites;
DepsOfPatternPreparer(Environment env, PathPackageLocator pkgPath, boolean traverseTestSuites) {
this.env = env;
this.packageProvider = new EnvironmentBackedRecursivePackageProvider(env);
this.pkgRoots = pkgPath.getPathEntries();
this.traverseTestSuites = traverseTestSuites;
}
@Override
public void warn(String msg) {
env.getListener().handle(Event.warn(msg));
}
@Override
public Void getTargetOrNull(Label label) {
// Note:
// This method is used in just one place, TargetPattern.TargetsInPackage#getWildcardConflict.
// Returning null tells #getWildcardConflict that there is not a target with a name like
// "all" or "all-targets", which means that TargetPattern.TargetsInPackage will end up
// calling DepsOfTargetPreparer#getTargetsInPackage.
// TODO (bazel-team): Consider replacing this with an isTarget method on the interface.
return null;
}
@Override
public ResolvedTargets<Void> getExplicitTarget(Label label)
throws TargetParsingException, InterruptedException {
try {
Target target = packageProvider.getTarget(env.getListener(), label);
SkyKey key = TransitiveTraversalValue.key(target.getLabel());
SkyValue token =
env.getValueOrThrow(key, NoSuchPackageException.class, NoSuchTargetException.class);
if (token == null) {
throw new MissingDepException();
}
return ResolvedTargets.empty();
} catch (NoSuchThingException e) {
throw new TargetParsingException(e.getMessage(), e, e.getDetailedExitCode());
}
}
@Override
public Collection<Void> getTargetsInPackage(
String originalPattern, PackageIdentifier packageIdentifier, boolean rulesOnly)
throws TargetParsingException, InterruptedException {
FilteringPolicy policy =
rulesOnly ? FilteringPolicies.RULES_ONLY : FilteringPolicies.NO_FILTER;
return getTargetsInPackage(originalPattern, packageIdentifier, policy);
}
private Collection<Void> getTargetsInPackage(
String originalPattern, PackageIdentifier packageIdentifier, FilteringPolicy policy)
throws TargetParsingException, InterruptedException {
try {
Package pkg = packageProvider.getPackage(env.getListener(), packageIdentifier);
Collection<Target> packageTargets =
TargetPatternResolverUtil.resolvePackageTargets(pkg, policy);
ImmutableList.Builder<SkyKey> builder = ImmutableList.builder();
for (Target target : packageTargets) {
builder.add(TransitiveTraversalValue.key(target.getLabel()));
}
ImmutableList<SkyKey> skyKeys = builder.build();
if (GraphTraversingHelper.declareDependenciesAndCheckIfValuesMissing(
env, skyKeys, NoSuchPackageException.class, NoSuchTargetException.class)) {
throw new MissingDepException();
}
return ImmutableSet.of();
} catch (NoSuchThingException e) {
String message =
TargetPatternResolverUtil.getParsingErrorMessage(
"package contains errors", originalPattern);
throw new TargetParsingException(message, e, e.getDetailedExitCode());
}
}
@Override
public boolean isPackage(PackageIdentifier packageIdentifier)
throws InterruptedException, InconsistentFilesystemException {
return packageProvider.isPackage(env.getListener(), packageIdentifier);
}
@Override
public String getTargetKind(Void target) {
// Note:
// This method is used in just one place, TargetPattern.TargetsInPackage#getWildcardConflict.
// Because DepsOfPatternPreparer#getTargetOrNull always returns null, this method is never
// called.
throw new UnsupportedOperationException();
}
@Override
public <E extends Exception & QueryExceptionMarkerInterface> void findTargetsBeneathDirectory(
RepositoryName repository,
String originalPattern,
String directory,
boolean rulesOnly,
ImmutableSet<PathFragment> repositoryIgnoredSubdirectories,
ImmutableSet<PathFragment> excludedSubdirectories,
BatchCallback<Void, E> callback,
Class<E> exceptionClass)
throws TargetParsingException, E, InterruptedException {
PathFragment directoryPathFragment = TargetPatternResolverUtil.getPathFragment(directory);
Preconditions.checkArgument(excludedSubdirectories.isEmpty(), excludedSubdirectories);
FilteringPolicy policy =
rulesOnly ? FilteringPolicies.RULES_ONLY : FilteringPolicies.NO_FILTER;
List<Root> roots = new ArrayList<>();
if (repository.isMain()) {
roots.addAll(pkgRoots);
} else {
RepositoryDirectoryValue repositoryValue =
(RepositoryDirectoryValue) env.getValue(RepositoryDirectoryValue.key(repository));
if (repositoryValue == null) {
throw new MissingDepException();
}
if (!repositoryValue.repositoryExists()) {
// This shouldn't be possible; we're given a repository, so we assume that the caller has
// already checked for its existence.
throw new IllegalStateException(
String.format(
"No such repository '%s': %s", repository, repositoryValue.getErrorMsg()));
}
roots.add(Root.fromPath(repositoryValue.getPath()));
}
for (Root root : roots) {
RootedPath rootedPath = RootedPath.toRootedPath(root, directoryPathFragment);
if (GraphTraversingHelper.declareDependenciesAndCheckIfValuesMissing(
env, getDeps(repository, repositoryIgnoredSubdirectories, policy, rootedPath))) {
throw new MissingDepException();
}
}
}
private ImmutableList<SkyKey> getDeps(
RepositoryName repository,
ImmutableSet<PathFragment> repositoryIgnoredSubdirectories,
FilteringPolicy policy,
RootedPath rootedPath) {
List<SkyKey> keys = new ArrayList<>();
keys.add(
PrepareDepsOfTargetsUnderDirectoryValue.key(
repository, rootedPath, repositoryIgnoredSubdirectories, policy));
keys.add(
CollectPackagesUnderDirectoryValue.key(
repository, rootedPath, repositoryIgnoredSubdirectories));
if (traverseTestSuites) {
keys.add(
PrepareTestSuitesUnderDirectoryValue.key(
repository, rootedPath, repositoryIgnoredSubdirectories));
}
return ImmutableList.copyOf(keys);
}
}
}