aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/MiddlemanFactory.java
blob: 43a9e372dcc368a10c186969e5879c2d52975901 (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
// 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.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Iterator;
import javax.annotation.Nullable;

/**
 * A factory to create middleman objects.
 */
@ThreadSafe
public final class MiddlemanFactory {

  private final ArtifactFactory artifactFactory;
  private final ActionRegistry actionRegistry;

  public MiddlemanFactory(
      ArtifactFactory artifactFactory, ActionRegistry actionRegistry) {
    this.artifactFactory = Preconditions.checkNotNull(artifactFactory);
    this.actionRegistry = Preconditions.checkNotNull(actionRegistry);
  }

  /**
   * Creates a {@link MiddlemanType#AGGREGATING_MIDDLEMAN aggregating} middleman.
   *
   * @param owner the owner of the action that will be created; must not be null
   * @param purpose the purpose for which this middleman is created. This should be a string which
   *     is suitable for use as a filename. A single rule may have many middlemen with distinct
   *     purposes.
   * @param inputs the set of artifacts for which the created artifact is to be the middleman.
   * @param middlemanDir the directory in which to place the middleman.
   * @return null iff {@code inputs} is empty; the single element of {@code inputs} if there's only
   *     one; a new aggregating middleman for the {@code inputs} otherwise
   */
  public Artifact createAggregatingMiddleman(
      ActionOwner owner, String purpose, Iterable<Artifact> inputs, ArtifactRoot middlemanDir) {
    if (hasExactlyOneInput(inputs)) { // Optimization: No middleman for just one input.
      return Iterables.getOnlyElement(inputs);
    }
    Pair<Artifact, Action> result = createMiddleman(
        owner, Label.print(owner.getLabel()), purpose, inputs, middlemanDir,
        MiddlemanType.AGGREGATING_MIDDLEMAN);
    return result == null ? null : result.getFirst();
  }

  /**
   * Returns <code>null</code> iff inputs is empty. Returns the sole element of inputs iff <code>
   * inputs.size()==1</code>. Otherwise, returns a middleman artifact and creates a middleman action
   * that generates that artifact.
   *
   * @param owner the owner of the action that will be created.
   * @param owningArtifact the artifact of the file for which the runfiles should be created. There
   *     may be at most one set of runfiles for an owning artifact, unless the owning artifact is
   *     null. There may be at most one set of runfiles per owner with a null owning artifact.
   *     Further, if the owning Artifact is non-null, the owning Artifacts' root-relative path must
   *     be unique and the artifact must be part of the runfiles tree for which this middleman is
   *     created. Usually this artifact will be an executable program.
   * @param inputs the set of artifacts for which the created artifact is to be the middleman.
   * @param middlemanDir the directory in which to place the middleman.
   */
  public Artifact createRunfilesMiddleman(
      ActionOwner owner,
      @Nullable Artifact owningArtifact,
      Iterable<Artifact> inputs,
      ArtifactRoot middlemanDir,
      String tag) {
    Preconditions.checkArgument(middlemanDir.isMiddlemanRoot());
    if (hasExactlyOneInput(inputs)) { // Optimization: No middleman for just one input.
      return Iterables.getOnlyElement(inputs);
    }
    String middlemanPath = owningArtifact == null
       ? Label.print(owner.getLabel())
       : owningArtifact.getRootRelativePath().getPathString();
    return createMiddleman(owner, middlemanPath, tag, inputs, middlemanDir,
        MiddlemanType.RUNFILES_MIDDLEMAN).getFirst();
  }

  private <T> boolean hasExactlyOneInput(Iterable<T> iterable) {
    if (iterable instanceof NestedSet) {
      return ((NestedSet) iterable).isSingleton();
    }
    Iterator<T> it = iterable.iterator();
    if (!it.hasNext()) {
      return false;
    }
    it.next();
    return !it.hasNext();
  }

  /**
   * Creates a {@link MiddlemanType#ERROR_PROPAGATING_MIDDLEMAN error-propagating} middleman.
   *
   * @param owner the owner of the action that will be created. May not be null.
   * @param middlemanName a unique file name for the middleman artifact in the {@code middlemanDir};
   *     in practice this is usually the owning rule's label (so it gets escaped as such)
   * @param purpose the purpose for which this middleman is created. This should be a string which
   *     is suitable for use as a filename. A single rule may have many middlemen with distinct
   *     purposes.
   * @param inputs the set of artifacts for which the created artifact is to be the middleman; must
   *     not be null or empty
   * @param middlemanDir the directory in which to place the middleman.
   * @return a middleman that enforces scheduling order (just like a scheduling middleman) and
   *     propagates errors, but is ignored by the dependency checker
   * @throws IllegalArgumentException if {@code inputs} is null or empty
   */
  public Artifact createErrorPropagatingMiddleman(
      ActionOwner owner,
      String middlemanName,
      String purpose,
      Iterable<Artifact> inputs,
      ArtifactRoot middlemanDir) {
    Preconditions.checkArgument(inputs != null);
    Preconditions.checkArgument(!Iterables.isEmpty(inputs));
    // We must always create this middleman even if there is only one input.
    return createMiddleman(owner, middlemanName, purpose, inputs, middlemanDir,
        MiddlemanType.ERROR_PROPAGATING_MIDDLEMAN).getFirst();
  }

  /**
   * Returns the same artifact as {@code createErrorPropagatingMiddleman} would return, but doesn't
   * create any action.
   */
  public Artifact getErrorPropagatingMiddlemanArtifact(
      String middlemanName, String purpose, ArtifactRoot middlemanDir) {
    return getStampFileArtifact(middlemanName, purpose, middlemanDir);
  }

  /**
   * Creates both normal and scheduling middlemen.
   *
   * <p>Note: there's no need to synchronize this method; the only use of a field is via a call to
   * another synchronized method (getArtifact()).
   *
   * @return null iff {@code inputs} is null or empty; the middleman file and the middleman action
   *     otherwise
   */
  private Pair<Artifact, Action> createMiddleman(
      ActionOwner owner,
      String middlemanName,
      String purpose,
      Iterable<Artifact> inputs,
      ArtifactRoot middlemanDir,
      MiddlemanType middlemanType) {
    if (inputs == null || CollectionUtils.isEmpty(inputs)) {
      return null;
    }

    Artifact stampFile = getStampFileArtifact(middlemanName, purpose, middlemanDir);
    Action action = new MiddlemanAction(owner, inputs, stampFile, purpose, middlemanType);
    actionRegistry.registerAction(action);
    return Pair.of(stampFile, action);
  }

  /**
   * Creates a normal middleman.
   *
   * <p>If called multiple times, it always returns the same object depending on the {@code
   * purpose}. It does not check that the list of inputs is identical. In contrast to other
   * middleman methods, this one also returns an object if the list of inputs is empty.
   *
   * <p>Note: there's no need to synchronize this method; the only use of a field is via a call to
   * another synchronized method (getArtifact()).
   */
  public Artifact createMiddlemanAllowMultiple(
      ActionRegistry registry,
      ActionOwner owner,
      PathFragment packageDirectory,
      String purpose,
      Iterable<Artifact> inputs,
      ArtifactRoot middlemanDir) {
    String escapedPackageDirectory = Actions.escapedPath(packageDirectory.getPathString());
    PathFragment stampName =
        PathFragment.create("_middlemen/" + (purpose.startsWith(escapedPackageDirectory)
                             ? purpose : (escapedPackageDirectory + purpose)));
    Artifact stampFile = artifactFactory.getDerivedArtifact(stampName, middlemanDir,
        actionRegistry.getOwner());
    MiddlemanAction.create(
        registry, owner, inputs, stampFile, purpose, MiddlemanType.AGGREGATING_MIDDLEMAN);
    return stampFile;
  }

  private Artifact getStampFileArtifact(
      String middlemanName, String purpose, ArtifactRoot middlemanDir) {
    String escapedFilename = Actions.escapedPath(middlemanName);
    PathFragment stampName = PathFragment.create("_middlemen/" + escapedFilename + "-" + purpose);
    Artifact stampFile = artifactFactory.getDerivedArtifact(stampName, middlemanDir,
        actionRegistry.getOwner());
    return stampFile;
  }
}