aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/MutableActionGraph.java
blob: 03ac629dc889efc1101b4a9536af7b58fa876ffb (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// 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.
   */
  final class ActionConflictException extends Exception {

    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> 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(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.equals(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(), b.getMandatoryInputs());
        addListDetail(sb, "Outputs", a.getOutputs(), b.getOutputs());
      }

      return sb.toString();
    }
  }
}