// 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.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 com.google.devtools.build.lib.util.StringUtil; 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. * *

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(Action action) throws ActionConflictException; /** * Removes an action from this action graph if it is present. * *

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(Action 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 Action previousAction; private final Action attemptedAction; public ActionConflictException(Artifact artifact, Action previousAction, Action attemptedAction) { super(String.format("for %s, previous action: %s, attempted action: %s", artifact, previousAction, attemptedAction)); 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 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 void addListDetail(StringBuilder sb, String key, Iterable valueA, Iterable valueB) { Set setA = ImmutableSet.copyOf(valueA); Set setB = ImmutableSet.copyOf(valueB); SetView diffA = Sets.difference(setA, setB); SetView diffB = Sets.difference(setB, setA); sb.append(key).append(": "); if (diffA.isEmpty() && diffB.isEmpty()) { sb.append("are equal"); } else { if (!diffA.isEmpty() && !diffB.isEmpty()) { sb.append("attempted action contains artifacts not in previous action and " + "previous action contains artifacts not in attempted action: " + diffA + ", " + diffB); } else if (!diffA.isEmpty()) { sb.append("attempted action contains artifacts not in previous action: "); sb.append(StringUtil.joinEnglishList(diffA, "and")); } else if (!diffB.isEmpty()) { sb.append("previous action contains artifacts not in attempted action: "); sb.append(StringUtil.joinEnglishList(diffB, "and")); } } sb.append("\n"); } // See also Actions.canBeShared() private String suffix(Action a, Action 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, "Progress message", a.getProgressMessage(), b.getProgressMessage()); addListDetail(sb, "MandatoryInputs", a.getMandatoryInputs(), b.getMandatoryInputs()); addListDetail(sb, "Outputs", a.getOutputs(), b.getOutputs()); return sb.toString(); } } }