| // 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.lib.actions; |
| |
| import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap; |
| |
| import com.google.common.base.Functions; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Streams; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.skyframe.SaneAnalysisException; |
| import java.util.Set; |
| |
| /** |
| * A mutable action graph. Implementations of this interface must be thread-safe. |
| */ |
| public interface MutableActionGraph extends ActionGraph { |
| |
| /** |
| * Attempts to register the action. If any of the action's outputs already has a generating |
| * action, and the two actions are not compatible, then an {@link ActionConflictException} is |
| * thrown. The internal data structure may be partially modified when that happens; it is not |
| * guaranteed that all potential conflicts are detected, but at least one of them is. |
| * |
| * <p>For example, take three actions A, B, and C, where A creates outputs a and b, B creates just |
| * b, and C creates c and b. There are two potential conflicts in this case, between A and B, and |
| * between B and C. Depending on the ordering of calls to this method and the ordering of outputs |
| * in the action output lists, either one or two conflicts are detected: if B is registered first, |
| * then both conflicts are detected; if either A or C is registered first, then only one conflict |
| * is detected. |
| */ |
| void registerAction(ActionAnalysisMetadata action) throws ActionConflictException; |
| |
| /** |
| * Removes an action from this action graph if it is present. |
| * |
| * <p>Throws {@link IllegalStateException} if one of the outputs of the action is in fact |
| * generated by a different {@link Action} instance (even if they are sharable). |
| */ |
| void unregisterAction(ActionAnalysisMetadata action); |
| |
| /** |
| * Clear the action graph. |
| */ |
| void clear(); |
| |
| /** |
| * This exception is thrown when a conflict between actions is detected. It contains information |
| * about the artifact for which the conflict is found, and data about the two conflicting actions |
| * and their owners. |
| */ |
| final class ActionConflictException extends Exception implements SaneAnalysisException { |
| |
| private final Artifact artifact; |
| private final String suffix; |
| |
| private static final int MAX_DIFF_ARTIFACTS_TO_REPORT = 5; |
| |
| public ActionConflictException( |
| ActionKeyContext actionKeyContext, |
| Artifact artifact, |
| ActionAnalysisMetadata previousAction, |
| ActionAnalysisMetadata attemptedAction) { |
| super( |
| String.format( |
| "for %s, previous action: %s, attempted action: %s", |
| artifact.prettyPrint(), |
| previousAction.prettyPrint(), |
| attemptedAction.prettyPrint())); |
| this.artifact = artifact; |
| this.suffix = suffix(actionKeyContext, attemptedAction, previousAction); |
| } |
| |
| public Artifact getArtifact() { |
| return artifact; |
| } |
| |
| public void reportTo(EventHandler eventListener) { |
| String msg = |
| "file '" |
| + artifact.prettyPrint() |
| + "' is generated by these conflicting actions:\n" |
| + suffix; |
| eventListener.handle(Event.error(msg)); |
| } |
| |
| private static void addStringDetail( |
| StringBuilder sb, String key, String valueA, String valueB) { |
| valueA = valueA != null ? valueA : "(null)"; |
| valueB = valueB != null ? valueB : "(null)"; |
| |
| sb.append(key).append(": ").append(valueA); |
| if (!valueA.equals(valueB)) { |
| sb.append(", ").append(valueB); |
| } |
| sb.append("\n"); |
| } |
| |
| private static void addListDetail( |
| StringBuilder sb, String key, Iterable<Artifact> valueA, Iterable<Artifact> valueB) { |
| Set<Artifact> diffA = differenceWithoutOwner(valueA, valueB); |
| Set<Artifact> diffB = differenceWithoutOwner(valueB, valueA); |
| |
| sb.append(key).append(": "); |
| if (diffA.isEmpty() && diffB.isEmpty()) { |
| sb.append("are equal\n"); |
| } else { |
| if (!diffA.isEmpty()) { |
| sb.append( |
| "Attempted action contains artifacts not in previous action (first " |
| + MAX_DIFF_ARTIFACTS_TO_REPORT |
| + "): \n"); |
| prettyPrintArtifactDiffs(sb, diffA); |
| } |
| |
| if (!diffB.isEmpty()) { |
| sb.append( |
| "Previous action contains artifacts not in attempted action (first " |
| + MAX_DIFF_ARTIFACTS_TO_REPORT |
| + "): \n"); |
| prettyPrintArtifactDiffs(sb, diffB); |
| } |
| } |
| } |
| |
| /** Returns items in {@code valueA} that are not in {@code valueB}, ignoring the owner. */ |
| private static Set<Artifact> differenceWithoutOwner( |
| Iterable<Artifact> valueA, Iterable<Artifact> valueB) { |
| ImmutableSet.Builder<Artifact> diff = new ImmutableSet.Builder<>(); |
| |
| // Group valueB by exec path for easier checks. |
| ImmutableListMultimap<String, Artifact> mapB = |
| Streams.stream(valueB) |
| .collect(toImmutableListMultimap(Artifact::getExecPathString, Functions.identity())); |
| for (Artifact a : valueA) { |
| boolean found = false; |
| for (Artifact b : mapB.get(a.getExecPathString())) { |
| if (a.equalsWithoutOwner(b)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| diff.add(a); |
| } |
| } |
| |
| return diff.build(); |
| } |
| |
| /** Pretty print action diffs (at most {@code MAX_DIFF_ARTIFACTS_TO_REPORT} lines). */ |
| private static void prettyPrintArtifactDiffs(StringBuilder sb, Set<Artifact> diff) { |
| for (Artifact artifact : Iterables.limit(diff, MAX_DIFF_ARTIFACTS_TO_REPORT)) { |
| sb.append("\t" + artifact.prettyPrint() + "\n"); |
| } |
| } |
| |
| private static String getKey(ActionKeyContext actionKeyContext, ActionAnalysisMetadata action) { |
| return action instanceof Action ? ((Action) action).getKey(actionKeyContext) : null; |
| } |
| |
| // See also Actions.canBeShared() |
| private static String suffix( |
| ActionKeyContext actionKeyContext, ActionAnalysisMetadata a, ActionAnalysisMetadata b) { |
| // Note: the error message reveals to users the names of intermediate files that are not |
| // documented in the BUILD language. This error-reporting logic is rather elaborate but it |
| // does help to diagnose some tricky situations. |
| StringBuilder sb = new StringBuilder(); |
| ActionOwner aOwner = a.getOwner(); |
| ActionOwner bOwner = b.getOwner(); |
| boolean aNull = aOwner == null; |
| boolean bNull = bOwner == null; |
| |
| addStringDetail(sb, "Label", aNull ? null : Label.print(aOwner.getLabel()), |
| bNull ? null : Label.print(bOwner.getLabel())); |
| addStringDetail(sb, "RuleClass", aNull ? null : aOwner.getTargetKind(), |
| bNull ? null : bOwner.getTargetKind()); |
| addStringDetail(sb, "Configuration", aNull ? null : aOwner.getConfigurationChecksum(), |
| bNull ? null : bOwner.getConfigurationChecksum()); |
| addStringDetail(sb, "Mnemonic", a.getMnemonic(), b.getMnemonic()); |
| addStringDetail(sb, "Action key", getKey(actionKeyContext, a), getKey(actionKeyContext, b)); |
| |
| if ((a instanceof ActionExecutionMetadata) && (b instanceof ActionExecutionMetadata)) { |
| addStringDetail( |
| sb, |
| "Progress message", |
| ((ActionExecutionMetadata) a).getProgressMessage(), |
| ((ActionExecutionMetadata) b).getProgressMessage()); |
| } |
| |
| Artifact aPrimaryInput = a.getPrimaryInput(); |
| Artifact bPrimaryInput = b.getPrimaryInput(); |
| addStringDetail( |
| sb, |
| "PrimaryInput", |
| aPrimaryInput == null ? null : aPrimaryInput.toString(), |
| bPrimaryInput == null ? null : bPrimaryInput.toString()); |
| addStringDetail( |
| sb, "PrimaryOutput", a.getPrimaryOutput().toString(), b.getPrimaryOutput().toString()); |
| |
| // Only add list details if the primary input of A matches the input of B. Otherwise |
| // the above information is enough and list diff detail is not needed. |
| if ((aPrimaryInput == null && bPrimaryInput == null) |
| || (aPrimaryInput != null |
| && bPrimaryInput != null |
| && aPrimaryInput.toString().equals(bPrimaryInput.toString()))) { |
| Artifact aPrimaryOutput = a.getPrimaryOutput(); |
| Artifact bPrimaryOutput = b.getPrimaryOutput(); |
| if (!aPrimaryOutput.equalsWithoutOwner(bPrimaryOutput)) { |
| sb.append("Primary outputs are different: ") |
| .append(System.identityHashCode(aPrimaryOutput)) |
| .append(", ") |
| .append(System.identityHashCode(bPrimaryOutput)) |
| .append('\n'); |
| } |
| ArtifactOwner aArtifactOwner = aPrimaryOutput.getArtifactOwner(); |
| ArtifactOwner bArtifactOwner = bPrimaryOutput.getArtifactOwner(); |
| addStringDetail( |
| sb, "Owner information", aArtifactOwner.toString(), bArtifactOwner.toString()); |
| addListDetail( |
| sb, |
| "MandatoryInputs", |
| a.getMandatoryInputs().toList(), |
| b.getMandatoryInputs().toList()); |
| addListDetail(sb, "Outputs", a.getOutputs(), b.getOutputs()); |
| } |
| |
| return sb.toString(); |
| } |
| } |
| } |