aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/MutableActionGraph.java
blob: a67dc1796a0c0813ffea91e67fdbdd3f07d30441 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright 2014 Google Inc. 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.
   *
   * <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(Action 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(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<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");
      } 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();
    }
  }
}