blob: 5917ad73051924c2d9b0dc910165462ca5d45b5b [file] [log] [blame]
// 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.skyframe;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* The result of a Skyframe {@link ParallelEvaluator#eval} call. Will contain all the successfully
* evaluated values, retrievable through {@link #get}. As well, the {@link ErrorInfo} for the first
* value that failed to evaluate (in the non-keep-going case), or any remaining values that failed
* to evaluate (in the keep-going case) will be retrievable.
*
* <p>A node can never be successfully evaluated and fail to evaluate. Thus, if {@link #get} returns
* non-null for some key, there is no stored error for that key, and vice versa.
*
* @param <T> The type of the values that the caller has requested.
*/
public class EvaluationResult<T extends SkyValue> {
@Nullable private final Exception catastrophe;
private final Map<SkyKey, T> resultMap;
private final Map<SkyKey, ErrorInfo> errorMap;
private final WalkableGraph walkableGraph;
/**
* Constructor for the "completed" case. Used only by {@link Builder}.
*/
private EvaluationResult(
Map<SkyKey, T> result,
Map<SkyKey, ErrorInfo> errorMap,
@Nullable Exception catastrophe,
@Nullable WalkableGraph walkableGraph) {
this.resultMap = Preconditions.checkNotNull(result);
this.errorMap = Preconditions.checkNotNull(errorMap);
this.catastrophe = catastrophe;
this.walkableGraph = walkableGraph;
}
/**
* Get a successfully evaluated value.
*/
public T get(SkyKey key) {
Preconditions.checkNotNull(resultMap, key);
return resultMap.get(key);
}
/**
* @return Whether or not the eval successfully evaluated all requested values. True iff
* {@link #getCatastrophe} or {@link #getError} returns non-null.
*/
public boolean hasError() {
return catastrophe != null || !errorMap.isEmpty();
}
/**
* Catastrophic error encountered during evaluation, if any. If the evaluation failed with a
* catastrophe, this will be non-null.
*/
@Nullable
public Exception getCatastrophe() {
return catastrophe;
}
/**
* @return All successfully evaluated {@link SkyValue}s.
*/
public Collection<T> values() {
return Collections.unmodifiableCollection(resultMap.values());
}
/**
* Returns {@link Map} of {@link SkyKey}s to {@link ErrorInfo}. Note that currently some of the
* returned SkyKeys may not be the ones requested by the user. Moreover, the SkyKey is not
* necessarily the cause of the error -- it is just the value that was being evaluated when the
* error was discovered.
*/
public Map<SkyKey, ErrorInfo> errorMap() {
return ImmutableMap.copyOf(errorMap);
}
/** Returns {@link ErrorInfo} for given {@code key} which must be present in errors. */
public ErrorInfo getError(SkyKey key) {
return Preconditions.checkNotNull(errorMap, key).get(key);
}
/**
* Returns some error info. Convenience method equivalent to Iterables.getFirst({@link
* #errorMap()}, null).getValue().
*/
public ErrorInfo getError() {
return Iterables.getFirst(errorMap.entrySet(), null).getValue();
}
/**
* @return Names of all values that were successfully evaluated. This collection is disjoint from
* the keys in {@link #errorMap}.
*/
public <S> Collection<? extends S> keyNames() {
return EvaluationResult.<S>getNames(resultMap.keySet());
}
@SuppressWarnings("unchecked")
private static <S> Collection<? extends S> getNames(Collection<SkyKey> keys) {
Collection<S> names = Lists.newArrayListWithCapacity(keys.size());
for (SkyKey key : keys) {
names.add((S) key.argument());
}
return names;
}
@Nullable
public WalkableGraph getWalkableGraph() {
return walkableGraph;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("catastrophe", catastrophe)
.add("errorMap", errorMap)
.add("resultMap", resultMap)
.toString();
}
public static <T extends SkyValue> Builder<T> builder() {
return new Builder<>();
}
/**
* Builder for {@link EvaluationResult}.
*
* <p>This is intended only for use in alternative {@code MemoizingEvaluator} implementations.
*/
public static class Builder<T extends SkyValue> {
private final Map<SkyKey, T> result = new HashMap<>();
private final Map<SkyKey, ErrorInfo> errors = new HashMap<>();
@Nullable private Exception catastrophe = null;
private WalkableGraph walkableGraph = null;
/** Adds a value to the result. An error for this key must not already be present. */
@CanIgnoreReturnValue
@SuppressWarnings({"unchecked", "LenientFormatStringValidation"})
public Builder<T> addResult(SkyKey key, SkyValue value) {
result.put(key, Preconditions.checkNotNull((T) value, key));
// Expected 3 args, but got 2.
Preconditions.checkState(
!errors.containsKey(key), "%s in both result and errors: %s %s", value, errors);
return this;
}
/**
* Adds an error to the result. A successful value for this key must not already be present.
* Publicly visible only for testing: should be package-private.
*/
@SuppressWarnings("LenientFormatStringValidation")
@CanIgnoreReturnValue
public Builder<T> addError(SkyKey key, ErrorInfo error) {
errors.put(key, Preconditions.checkNotNull(error, key));
// Expected 3 args, but got 2.
Preconditions.checkState(
!result.containsKey(key), "%s in both result and errors: %s %s", error, result);
if (error.isCatastrophic()) {
setCatastrophe(error.getException());
}
return this;
}
@CanIgnoreReturnValue
public Builder<T> setWalkableGraph(WalkableGraph walkableGraph) {
this.walkableGraph = walkableGraph;
return this;
}
@CanIgnoreReturnValue
public Builder<T> mergeFrom(EvaluationResult<T> otherResult) {
result.putAll(otherResult.resultMap);
errors.putAll(otherResult.errorMap);
catastrophe = otherResult.catastrophe;
return this;
}
public EvaluationResult<T> build() {
return new EvaluationResult<>(result, errors, catastrophe, walkableGraph);
}
@CanIgnoreReturnValue
public Builder<T> setCatastrophe(Exception catastrophe) {
this.catastrophe = catastrophe;
return this;
}
void maybeEnsureCatastrophe(boolean hasCatastrophe) {
if (!hasCatastrophe || catastrophe != null) {
return;
}
for (ErrorInfo errorInfo : errors.values()) {
if (errorInfo.getException() != null) {
catastrophe = errorInfo.getException();
return;
}
}
throw new IllegalStateException("Should have found exception in catastrophe: " + errors);
}
boolean isEmpty() {
return this.result.isEmpty() && this.errors.isEmpty();
}
}
}