// 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.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.cmdline.TargetPattern;
import com.google.devtools.build.lib.cmdline.TargetPattern.Type;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.pkgcache.FilteringPolicies;
import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternKey;
import com.google.devtools.build.lib.skyframe.TargetPatternValue.TargetPatternSkyKeyOrException;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.LegacySkyKey;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.List;
/**
* The value returned by {@link PrepareDepsOfPatternFunction}. Because that function is
* invoked only for its side effect (i.e. ensuring the graph contains targets matching the
* pattern and its transitive dependencies), this value carries no information.
*
*
Because the returned value is always equal to objects that share its type, this value and the
* {@link PrepareDepsOfPatternFunction} which computes it are incompatible with change pruning. It
* should only be requested by consumers who do not require reevaluation when
* {@link PrepareDepsOfPatternFunction} is reevaluated. Safe consumers include, e.g., top-level
* consumers, and other functions which invoke {@link PrepareDepsOfPatternFunction} solely for its
* side-effects.
*/
public class PrepareDepsOfPatternValue implements SkyValue {
// Note that this value does not guarantee singleton-like reference equality because we use Java
// deserialization. Java deserialization can create other instances.
public static final PrepareDepsOfPatternValue INSTANCE = new PrepareDepsOfPatternValue();
private PrepareDepsOfPatternValue() {
}
@Override
public boolean equals(Object o) {
return o instanceof PrepareDepsOfPatternValue;
}
@Override
public int hashCode() {
return 42;
}
/**
* Returns an iterable of {@link PrepareDepsOfPatternSkyKeyOrException}, with {@link
* TargetPatternKey} arguments. Negative target patterns of type other than {@link
* Type#TARGETS_BELOW_DIRECTORY} are not permitted. If a provided pattern fails to parse or is
* negative but not a {@link Type#TARGETS_BELOW_DIRECTORY}, an element in the returned iterable
* will throw when its {@link PrepareDepsOfPatternSkyKeyOrException#getSkyKey} method is called
* and will return the failing pattern when its {@link
* PrepareDepsOfPatternSkyKeyOrException#getOriginalPattern} method is called.
*
*
There may be fewer returned elements than patterns provided as input. This function will
* combine negative {@link Type#TARGETS_BELOW_DIRECTORY} patterns with preceding patterns to
* return an iterable of SkyKeys that avoids loading excluded directories during evaluation.
*
* @param patterns The list of patterns, e.g. [//foo/..., -//foo/biz/...]. If a pattern's first
* character is "-", it is treated as a negative pattern.
* @param offset The offset to apply to relative target patterns.
*/
@ThreadSafe
public static Iterable keys(List patterns,
String offset) {
List keysMaybe =
ImmutableList.copyOf(TargetPatternValue.keys(patterns, FilteringPolicies.NO_FILTER,
offset));
// This code path is evaluated only for query universe preloading, and the quadratic cost of
// the code below (i.e. for each pattern, consider each later pattern as a candidate for
// subdirectory exclusion) is only acceptable because all the use cases for query universe
// preloading involve short (<10 items) pattern sequences.
ImmutableList.Builder builder = ImmutableList.builder();
for (int i = 0; i < keysMaybe.size(); i++) {
TargetPatternSkyKeyOrException keyMaybe = keysMaybe.get(i);
SkyKey skyKey;
try {
skyKey = keyMaybe.getSkyKey();
} catch (TargetParsingException e) {
// keyMaybe.getSkyKey() may throw TargetParsingException if its corresponding pattern
// failed to parse. If so, wrap the exception and return it, so that our caller can
// deal with it.
skyKey = null;
builder.add(new PrepareDepsOfPatternSkyKeyException(e, keyMaybe.getOriginalPattern()));
}
if (skyKey != null) {
TargetPatternKey targetPatternKey = (TargetPatternKey) skyKey.argument();
if (targetPatternKey.isNegative()) {
if (!targetPatternKey.getParsedPattern().getType().equals(Type.TARGETS_BELOW_DIRECTORY)) {
builder.add(
new PrepareDepsOfPatternSkyKeyException(
new TargetParsingException(
"Negative target patterns of types other than \"targets below directory\""
+ " are not permitted."), targetPatternKey.toString()));
}
// Otherwise it's a negative TBD pattern which was combined with previous patterns as an
// excluded directory. These can be skipped because there's no PrepareDepsOfPattern work
// to be done for them.
} else {
builder.add(new PrepareDepsOfPatternSkyKeyValue(setExcludedDirectories(targetPatternKey,
excludedDirectoriesBeneath(targetPatternKey, i, keysMaybe))));
}
}
}
return builder.build();
}
private static TargetPatternKey setExcludedDirectories(
TargetPatternKey original, ImmutableSet excludedSubdirectories) {
return new TargetPatternKey(original.getParsedPattern(), original.getPolicy(),
original.isNegative(), original.getOffset(), excludedSubdirectories);
}
private static ImmutableSet excludedDirectoriesBeneath(
TargetPatternKey targetPatternKey,
int position,
List keysMaybe) {
ImmutableSet.Builder excludedDirectoriesBuilder = ImmutableSet.builder();
for (int j = position + 1; j < keysMaybe.size(); j++) {
TargetPatternSkyKeyOrException laterPatternMaybe = keysMaybe.get(j);
SkyKey laterSkyKey;
try {
laterSkyKey = laterPatternMaybe.getSkyKey();
} catch (TargetParsingException ignored) {
laterSkyKey = null;
}
if (laterSkyKey != null) {
TargetPatternKey laterTargetPatternKey = (TargetPatternKey) laterSkyKey.argument();
TargetPattern laterParsedPattern = laterTargetPatternKey.getParsedPattern();
if (laterTargetPatternKey.isNegative()
&& laterParsedPattern.getType() == Type.TARGETS_BELOW_DIRECTORY
&& targetPatternKey.getParsedPattern().containsDirectoryOfTBDForTBD(
laterParsedPattern)) {
excludedDirectoriesBuilder.add(
laterParsedPattern.getDirectoryForTargetsUnderDirectory().getPackageFragment());
}
}
}
return excludedDirectoriesBuilder.build();
}
/**
* Wrapper for a prepare deps of pattern {@link SkyKey} or the {@link TargetParsingException}
* thrown when trying to create it.
*/
public interface PrepareDepsOfPatternSkyKeyOrException {
/**
* Returns the stored {@link SkyKey} or throws {@link TargetParsingException} if one was thrown
* when creating the key.
*/
SkyKey getSkyKey() throws TargetParsingException;
/**
* Returns the pattern that resulted in the stored {@link SkyKey} or {@link
* TargetParsingException}.
*/
String getOriginalPattern();
}
private static class PrepareDepsOfPatternSkyKeyException implements
PrepareDepsOfPatternSkyKeyOrException {
private final TargetParsingException exception;
private final String originalPattern;
public PrepareDepsOfPatternSkyKeyException(TargetParsingException exception,
String originalPattern) {
this.exception = exception;
this.originalPattern = originalPattern;
}
@Override
public SkyKey getSkyKey() throws TargetParsingException {
throw exception;
}
@Override
public String getOriginalPattern() {
return originalPattern;
}
}
private static class PrepareDepsOfPatternSkyKeyValue implements
PrepareDepsOfPatternSkyKeyOrException {
private final TargetPatternKey targetPatternKey;
public PrepareDepsOfPatternSkyKeyValue(TargetPatternKey targetPatternKey) {
this.targetPatternKey = targetPatternKey;
}
@Override
public SkyKey getSkyKey() throws TargetParsingException {
return LegacySkyKey.create(SkyFunctions.PREPARE_DEPS_OF_PATTERN, targetPatternKey);
}
@Override
public String getOriginalPattern() {
return targetPatternKey.getPattern();
}
}
}