// 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 inputs = NestedSetBuilder.naiveLinkOrder(); private final ImmutableList.Builder 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. * *

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 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. * *

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 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. * *

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 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. * *

These artifacts will not be mentioned on the command line - use {@link * #addTransitiveFlag(String, NestedSet, AndroidDataConverter)} for that. */ public BusyBoxActionBuilder addTransitiveInputValues(NestedSet values) { inputs.addTransitive(values); return this; } /** * Adds an efficient flag based on transitive values. * *

The flag will only be specified once, followed by the joined values specified by the * converter, for example: --flag value1,value2 * *

The values will only be collapsed and turned into a flag at execution time. * *

The values will not be added as inputs - use {@link #addTransitiveInputValues(NestedSet)} * for that. */ public BusyBoxActionBuilder addTransitiveFlag( @CompileTimeConstant String arg, NestedSet transitiveValues, AndroidDataConverter converter) { commandLine.addAll(arg, converter.getVectorArg(transitiveValues)); return this; } /** * Adds an efficient flag based on transitive values. * *

Each transitive value, as created using the converter, will be proceeded by the flag, for * example: --flag value1 --flag value2 * *

The values will only be collapsed and turned into a flag at execution time. * *

The values will not be added as inputs - use {@link #addTransitiveInputValues(NestedSet)} * for that. */ public BusyBoxActionBuilder addTransitiveFlagForEach( @CompileTimeConstant String arg, NestedSet transitiveValues, AndroidDataConverter converter) { commandLine.addAll(converter.getVectorArgForEach(arg, transitiveValues)); return this; } /** * Adds an efficient flag and inputs based on transitive values. * *

Each value will be separated on the command line by the host-specific path separator. * *

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 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. * *

The values will be joined in execution and separated by commas. */ public BusyBoxActionBuilder addVectoredFlag( @CompileTimeConstant String arg, List 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. * *

The values will be joined in execution and separated by commas. */ public BusyBoxActionBuilder maybeAddVectoredFlag( @CompileTimeConstant String arg, @Nullable List 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)); } }