// 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 com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; 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 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. */ public static final class ActionConflictException extends Exception { private final Artifact artifact; private final ActionAnalysisMetadata previousAction; private final ActionAnalysisMetadata attemptedAction; private static final int MAX_DIFF_ARTIFACTS_TO_REPORT = 5; public ActionConflictException(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.previousAction = previousAction; this.attemptedAction = attemptedAction; } public Artifact getArtifact() { return artifact; } public void reportTo(EventHandler eventListener) { String msg = "file '" + artifact.prettyPrint() + "' is generated by these conflicting actions:\n" + suffix(attemptedAction, previousAction); 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> setA = ImmutableSet.copyOf(valueA); Set<Artifact> setB = ImmutableSet.copyOf(valueB); SetView<Artifact> diffA = Sets.difference(setA, setB); SetView<Artifact> diffB = Sets.difference(setB, setA); 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); } } } /** Pretty print action diffs (at most {@code MAX_DIFF_ARTIFACTS_TO_REPORT} lines). */ private static void prettyPrintArtifactDiffs(StringBuilder sb, SetView<Artifact> diff) { for (Artifact artifact : Iterables.limit(diff, MAX_DIFF_ARTIFACTS_TO_REPORT)) { sb.append("\t" + artifact.prettyPrint() + "\n"); } } private static String getKey(ActionAnalysisMetadata action) { return action instanceof Action ? ((Action) action).getKey() : null; } // See also Actions.canBeShared() private static String suffix(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(a), getKey(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 != bPrimaryOutput) { sb.append("Primary outputs are different objects: ") .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(), b.getMandatoryInputs()); addListDetail(sb, "Outputs", a.getOutputs(), b.getOutputs()); } return sb.toString(); } } }