aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/BusyBoxActionBuilder.java
blob: 16e55f8993d44e045e1c9af5b86e27b1ea4d933d (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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// Copyright 2018 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.rules.android;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion;
import com.google.devtools.build.lib.util.OS;
import com.google.errorprone.annotations.CompileTimeConstant;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;

/** Builder for actions that invoke the Android BusyBox. */
public final class BusyBoxActionBuilder {

  // Some flags (e.g. --mainData) may specify lists (or lists of lists) separated by special
  // characters (colon, semicolon, hashmark, ampersand) that don't work on Windows, and quoting
  // semantics are very complicated (more so than in Bash), so let's just always use a parameter
  // file.
  // TODO(laszlocsomor), TODO(corysmith): restructure the Android BusyBux's flags by deprecating
  // list-type and list-of-list-type flags that use such problematic separators in favor of
  // multi-value flags (to remove one level of listing) and by changing all list separators to a
  // platform-safe character (= comma).
  private static final ParamFileInfo FORCED_PARAM_FILE_INFO =
      ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED)
          .setUseAlways(OS.getCurrent() == OS.WINDOWS)
          .build();

  private final AndroidDataContext dataContext;
  private final NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder();
  private final ImmutableList.Builder<Artifact> outputs = ImmutableList.builder();
  private final SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder();
  private final CustomCommandLine.Builder commandLine = CustomCommandLine.builder();

  public static BusyBoxActionBuilder create(
      AndroidDataContext dataContext, @CompileTimeConstant String toolName) {
    BusyBoxActionBuilder builder = new BusyBoxActionBuilder(dataContext);
    builder.commandLine.add("--tool").add(toolName).add("--");
    return builder;
  }

  private BusyBoxActionBuilder(AndroidDataContext dataContext) {
    this.dataContext = dataContext;
  }

  /** Adds a direct input artifact. */
  public BusyBoxActionBuilder addInput(@CompileTimeConstant String arg, Artifact value) {
    Preconditions.checkNotNull(value);
    commandLine.addExecPath(arg, value);
    inputs.add(value);
    return this;
  }

  /**
   * Adds a series of direct input artifacts.
   *
   * <p>For efficiency, when adding a NestedSet of artifacts, use one of the transitive methods,
   * such as {@link #addTransitiveFlag(String, NestedSet, AndroidDataConverter)} and {@link
   * #addTransitiveInputValues(NestedSet)}, instead.
   *
   * @param value a string representation of the value artifacts
   */
  public BusyBoxActionBuilder addInput(
      @CompileTimeConstant String arg, String value, Iterable<Artifact> valueArtifacts) {
    Preconditions.checkState(
        !(valueArtifacts instanceof NestedSet),
        "NestedSet values should not be added here, since they will be inefficiently collapsed in"
            + " analysis time. Use one of the transitive input methods instead.");
    commandLine.add(arg, value);
    inputs.addAll(valueArtifacts);
    return this;
  }

  /** Adds an input artifact if it is non-null */
  public BusyBoxActionBuilder maybeAddInput(
      @CompileTimeConstant String arg, @Nullable Artifact value) {
    if (value != null) {
      addInput(arg, value);
    }
    return this;
  }

  /**
   * Adds a series of direct input artifacts if the list containing them is not null or empty.
   *
   * <p>For efficiency, when adding a NestedSet of artifacts, use one of the transitive methods,
   * such as {@link #addTransitiveFlag(String, NestedSet, AndroidDataConverter)} and {@link
   * #addTransitiveInputValues(NestedSet)}, instead.
   */
  public BusyBoxActionBuilder maybeAddInput(
      @CompileTimeConstant String arg, @Nullable Collection<Artifact> values) {
    if (values != null && !values.isEmpty()) {
      commandLine.addExecPaths(arg, values);
      inputs.addAll(values);
    }
    return this;
  }

  /**
   * Adds a series of direct input artifacts if the list containing them is not null or empty.
   *
   * <p>For efficiency, when adding a NestedSet of artifacts, use one of the transitive methods,
   * such as {@link #addTransitiveFlag(String, NestedSet, AndroidDataConverter)} and {@link
   * #addTransitiveInputValues(NestedSet)}, instead.
   *
   * @param value a string representation of the value artifacts
   */
  public BusyBoxActionBuilder maybeAddInput(
      @CompileTimeConstant String arg,
      String value,
      @Nullable Collection<Artifact> valueArtifacts) {
    if (valueArtifacts != null && !valueArtifacts.isEmpty()) {
      addInput(arg, value, valueArtifacts);
    }
    return this;
  }

  /** Adds an output artifact */
  public BusyBoxActionBuilder addOutput(@CompileTimeConstant String arg, Artifact value) {
    Preconditions.checkNotNull(value);
    commandLine.addExecPath(arg, value);
    outputs.add(value);
    return this;
  }

  /** Adds an output artifact if it is non-null */
  public BusyBoxActionBuilder maybeAddOutput(
      @CompileTimeConstant String arg, @Nullable Artifact value) {
    if (value != null) {
      return addOutput(arg, value);
    }
    return this;
  }

  /**
   * Adds a series of transitive input artifacts.
   *
   * <p>These artifacts will not be mentioned on the command line - use {@link
   * #addTransitiveFlag(String, NestedSet, AndroidDataConverter)} for that.
   */
  public BusyBoxActionBuilder addTransitiveInputValues(NestedSet<Artifact> values) {
    inputs.addTransitive(values);
    return this;
  }

  /**
   * Adds an efficient flag based on transitive values.
   *
   * <p>The flag will only be specified once, followed by the joined values specified by the
   * converter, for example: --flag value1,value2
   *
   * <p>The values will only be collapsed and turned into a flag at execution time.
   *
   * <p>The values will not be added as inputs - use {@link #addTransitiveInputValues(NestedSet)}
   * for that.
   */
  public <T> BusyBoxActionBuilder addTransitiveFlag(
      @CompileTimeConstant String arg,
      NestedSet<? extends T> transitiveValues,
      AndroidDataConverter<T> converter) {
    commandLine.addAll(arg, converter.getVectorArg(transitiveValues));
    return this;
  }

  /**
   * Adds an efficient flag based on transitive values.
   *
   * <p>Each transitive value, as created using the converter, will be proceeded by the flag, for
   * example: --flag value1 --flag value2
   *
   * <p>The values will only be collapsed and turned into a flag at execution time.
   *
   * <p>The values will not be added as inputs - use {@link #addTransitiveInputValues(NestedSet)}
   * for that.
   */
  public <T> BusyBoxActionBuilder addTransitiveFlagForEach(
      @CompileTimeConstant String arg,
      NestedSet<? extends T> transitiveValues,
      AndroidDataConverter<T> converter) {
    commandLine.addAll(converter.getVectorArgForEach(arg, transitiveValues));
    return this;
  }

  /**
   * Adds an efficient flag and inputs based on transitive values.
   *
   * <p>Each value will be separated on the command line by the host-specific path separator.
   *
   * <p>Unlike other transitive input methods in this class, this method adds the values to both the
   * command line and the list of inputs.
   */
  public BusyBoxActionBuilder addTransitiveVectoredInput(
      @CompileTimeConstant String arg, NestedSet<Artifact> values) {
    commandLine.addExecPaths(
        arg,
        VectorArg.join(
                dataContext
                    .getActionConstructionContext()
                    .getConfiguration()
                    .getHostPathSeparator())
            .each(values));
    inputs.addTransitive(values);
    return this;
  }

  /** Adds a flag with a value set to the current target's label */
  public BusyBoxActionBuilder addLabelFlag(@CompileTimeConstant String arg) {
    commandLine.addLabel(arg, dataContext.getLabel());
    return this;
  }

  /** Adds a flag with no arguments to the command line. */
  public BusyBoxActionBuilder addFlag(@CompileTimeConstant String value) {
    commandLine.add(value);
    return this;
  }

  /** Adds a flag with a String value to the command line. */
  public BusyBoxActionBuilder addFlag(@CompileTimeConstant String arg, String value) {
    Preconditions.checkNotNull(value);
    commandLine.add(arg, value);
    return this;
  }

  /** If the condition is true, adds a flag with no arguments to the command line. */
  public BusyBoxActionBuilder maybeAddFlag(@CompileTimeConstant String arg, boolean condition) {
    if (condition) {
      commandLine.add(arg);
    }
    return this;
  }

  /** If the flag is a non-null, non-empty String, adds the flag and value to the command line. */
  public BusyBoxActionBuilder maybeAddFlag(
      @CompileTimeConstant String arg, @Nullable String value) {
    if (value != null && !value.isEmpty()) {
      addFlag(arg, value);
    }
    return this;
  }

  /**
   * Efficiently adds a flag and a list of values to the command line.
   *
   * <p>The values will be joined in execution and separated by commas.
   */
  public BusyBoxActionBuilder addVectoredFlag(
      @CompileTimeConstant String arg, List<String> values) {
    Preconditions.checkNotNull(values);
    commandLine.addAll(arg, VectorArg.join(",").each(values));

    return this;
  }

  /**
   * If the values are not null or empty, efficiently adds a flag with them to the command line.
   *
   * <p>The values will be joined in execution and separated by commas.
   */
  public BusyBoxActionBuilder maybeAddVectoredFlag(
      @CompileTimeConstant String arg, @Nullable List<String> values) {
    if (values != null && !values.isEmpty()) {
      addVectoredFlag(arg, values);
    }
    return this;
  }

  /** Adds aapt to the command line and inputs. */
  public BusyBoxActionBuilder addAapt(AndroidAaptVersion aaptVersion) {
    FilesToRunProvider aapt;
    if (aaptVersion == AndroidAaptVersion.AAPT2) {
      aapt = dataContext.getSdk().getAapt2();
      commandLine.addExecPath("--aapt2", aapt.getExecutable());
    } else {
      aapt = dataContext.getSdk().getAapt();
      commandLine.addExecPath("--aapt", aapt.getExecutable());
    }

    spawnActionBuilder.addTool(aapt);

    return this;
  }

  /** Adds the Android JAR from the SDK to the command line and inputs */
  public BusyBoxActionBuilder addAndroidJar() {
    return addInput("--androidJar", dataContext.getSdk().getAndroidJar());
  }

  /**
   * Builds and registers this action.
   *
   * @param message a progress message (visible in Bazel output), for example "Running tool". The
   *     current label will be appended to this message.
   * @param mnemonic a mnemonic used to indicate the tool being run, for example, "BusyBoxTool".
   */
  public void buildAndRegister(String message, String mnemonic) {
    dataContext.registerAction(
        spawnActionBuilder
            .useDefaultShellEnvironment()
            .addTransitiveInputs(inputs.build())
            .addOutputs(outputs.build())
            .addCommandLine(commandLine.build(), FORCED_PARAM_FILE_INFO)
            .setExecutable(dataContext.getBusybox())
            .setProgressMessage("%s for %s", message, dataContext.getLabel())
            .setMnemonic(mnemonic));
  }
}