| // 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(); |
| } |
| } |
| } |