diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java | 903 |
1 files changed, 500 insertions, 403 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java index b4ac7aa7d2..a2a7c9bd65 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java @@ -17,21 +17,22 @@ package com.google.devtools.build.lib.analysis.actions; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; +import com.google.common.collect.Interner; +import com.google.common.collect.Lists; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; -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.BlazeInterners; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -44,10 +45,269 @@ import javax.annotation.Nullable; @Immutable public final class CustomCommandLine extends CommandLine { - private abstract static class ArgvFragment { + private interface ArgvFragment { + /** + * Expands this fragment into the passed command line vector. + * + * @param arguments The command line's argument vector. + * @param argi The index of the next available argument. + * @param builder The command line builder to which we should add arguments. + * @return The index of the next argument, after the ArgvFragment has consumed its args. + * If the ArgvFragment doesn't have any args, it should return {@code argi} unmodified. + */ + int eval(List<Object> arguments, int argi, ImmutableList.Builder<String> builder); + } + + /** + * Helper base class for an ArgvFragment that doesn't use the input argument vector. + * + * <p>This can be used for any ArgvFragments that self-contain all the necessary state. + */ + private abstract static class StandardArgvFragment implements ArgvFragment { + @Override + public final int eval(List<Object> arguments, int argi, ImmutableList.Builder<String> builder) { + eval(builder); + return argi; // Doesn't consume any arguments, so return argi unmodified + } + abstract void eval(ImmutableList.Builder<String> builder); } + // TODO(bazel-team): CustomArgv and CustomMultiArgv is going to be difficult to expose + // in Skylark. Maybe we can get rid of them by refactoring JavaCompileAction. It also + // raises immutability / serialization issues. + /** Custom Java code producing a String argument. Usage of this class is discouraged. */ + public abstract static class CustomArgv extends StandardArgvFragment { + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.add(argv()); + } + + public abstract String argv(); + } + + /** + * Custom Java code producing a List of String arguments. + * + * <p>Usage of this class is discouraged. Please see {@link CustomArgv}. + */ + public abstract static class CustomMultiArgv extends StandardArgvFragment { + + @Override + void eval(ImmutableList.Builder<String> builder) { + builder.addAll(argv()); + } + + public abstract Iterable<String> argv(); + } + + /** + * An ArgvFragment that expands a collection of objects in a user-specified way. + * + * <p>To construct one, use {@link VectorArg#of} with your collection of objects, + * and configure the returned object depending on how you want the expansion to + * take place. Finally, pass it to {@link CustomCommandLine.Builder#add(VectorArg.Builder}. + */ + public static final class VectorArg implements ArgvFragment { + private static Interner<VectorArg> interner = BlazeInterners.newStrongInterner(); + + private final boolean hasMapEach; + private final boolean hasFormatEach; + private final boolean hasBeforeEach; + private final boolean hasJoinWith; + + private VectorArg( + boolean hasMapEach, boolean hasFormatEach, boolean hasBeforeEach, boolean hasJoinWith) { + this.hasMapEach = hasMapEach; + this.hasFormatEach = hasFormatEach; + this.hasBeforeEach = hasBeforeEach; + this.hasJoinWith = hasJoinWith; + } + + private static void push(List<Object> arguments, Builder argv) { + VectorArg vectorArg = + new VectorArg( + argv.mapFn != null, + argv.formatEach != null, + argv.beforeEach != null, + argv.joinWith != null); + vectorArg = interner.intern(vectorArg); + arguments.add(vectorArg); + arguments.add(argv.values); + if (vectorArg.hasMapEach) { + arguments.add(argv.mapFn); + } + if (vectorArg.hasFormatEach) { + arguments.add(argv.formatEach); + } + if (vectorArg.hasBeforeEach) { + arguments.add(argv.beforeEach); + } + if (vectorArg.hasJoinWith) { + arguments.add(argv.joinWith); + } + } + + @SuppressWarnings("unchecked") + @Override + public int eval(List<Object> arguments, int argi, ImmutableList.Builder<String> builder) { + Iterable<Object> values = (Iterable<Object>) arguments.get(argi++); + List<Object> mutatedValues = Lists.newArrayList(values); + int count = mutatedValues.size(); + if (hasMapEach) { + Function<Object, Object> mapFn = (Function<Object, Object>) arguments.get(argi++); + for (int i = 0; i < count; ++i) { + mutatedValues.set(i, mapFn.apply(mutatedValues.get(i))); + } + } + for (int i = 0; i < count; ++i) { + mutatedValues.set(i, valueToString(mutatedValues.get(i))); + } + if (hasFormatEach) { + String formatStr = (String) arguments.get(argi++); + for (int i = 0; i < count; ++i) { + mutatedValues.set(i, String.format(formatStr, mutatedValues.get(i))); + } + } + if (hasBeforeEach) { + String beforeEach = (String) arguments.get(argi++); + for (int i = 0; i < count; ++i) { + builder.add(beforeEach); + builder.add((String) mutatedValues.get(i)); + } + } else if (hasJoinWith) { + String joinWith = (String) arguments.get(argi++); + builder.add(Joiner.on(joinWith).join(mutatedValues)); + } else { + for (int i = 0; i < count; ++i) { + builder.add((String) mutatedValues.get(i)); + } + } + return argi; + } + + public static Builder of(@Nullable ImmutableCollection<?> values) { + return new Builder(values); + } + + public static Builder of(@Nullable NestedSet<?> values) { + return new Builder(values); + } + + /** Builder for a VectorArg */ + public static class Builder { + @Nullable private final Iterable<?> values; + private final boolean isEmpty; + private String formatEach; + private String beforeEach; + private Function<?, String> mapFn; + private String joinWith; + + private Builder(@Nullable ImmutableCollection<?> values) { + this(values, values == null || values.isEmpty()); + } + + private Builder(@Nullable NestedSet<?> values) { + this(values, values == null || values.isEmpty()); + } + + private Builder(@Nullable Iterable<?> values, boolean isEmpty) { + this.values = values; + this.isEmpty = isEmpty; + } + + /** Each argument is formatted via {@link String#format}. */ + public Builder formatEach(String formatEach) { + Preconditions.checkNotNull(formatEach); + this.formatEach = formatEach; + return this; + } + + /** Each argument is prepended by the beforeEach param. */ + public Builder beforeEach(String beforeEach) { + Preconditions.checkNotNull(beforeEach); + this.beforeEach = beforeEach; + return this; + } + + /** Each argument is mapped using the supplied map function */ + public <T> Builder mapEach(Function<T, String> mapFn) { + Preconditions.checkNotNull(mapFn); + this.mapFn = mapFn; + return this; + } + + /** Once all arguments have been evaluated, they are joined with this delimiter */ + public Builder joinWith(String delimiter) { + Preconditions.checkNotNull(delimiter); + this.joinWith = delimiter; + return this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VectorArg vectorArg = (VectorArg) o; + return hasMapEach == vectorArg.hasMapEach + && hasFormatEach == vectorArg.hasFormatEach + && hasBeforeEach == vectorArg.hasBeforeEach + && hasJoinWith == vectorArg.hasJoinWith; + } + + @Override + public int hashCode() { + return Objects.hashCode(hasMapEach, hasFormatEach, hasBeforeEach, hasJoinWith); + } + } + + private static class FormatArg implements ArgvFragment { + private static final FormatArg INSTANCE = new FormatArg(); + + private static void push(List<Object> arguments, String formatStr, Object... args) { + arguments.add(INSTANCE); + arguments.add(args.length); + arguments.add(formatStr); + Collections.addAll(arguments, args); + } + + @Override + public int eval(List<Object> arguments, int argi, ImmutableList.Builder<String> builder) { + int argCount = (Integer) arguments.get(argi++); + String formatStr = (String) arguments.get(argi++); + Object[] args = new Object[argCount]; + for (int i = 0; i < argCount; ++i) { + args[i] = valueToString(arguments.get(argi++)); + } + builder.add(String.format(formatStr, args)); + return argi; + } + } + + private static class PrefixArg implements ArgvFragment { + private static final PrefixArg INSTANCE = new PrefixArg(); + + private static void push(List<Object> arguments, String before, Object arg) { + arguments.add(INSTANCE); + arguments.add(before); + arguments.add(arg); + } + + @Override + public int eval(List<Object> arguments, int argi, ImmutableList.Builder<String> builder) { + String before = (String) arguments.get(argi++); + Object arg = arguments.get(argi++); + builder.add(before + valueToString(arg)); + return argi; + } + } + /** * A command line argument for {@link TreeFileArtifact}. * @@ -68,16 +328,15 @@ public final class CustomCommandLine extends CommandLine { } /** - * A command line argument that can expand enclosed TreeArtifacts into a list of child - * {@link TreeFileArtifact}s at execution time before argument evaluation. + * A command line argument that can expand enclosed TreeArtifacts into a list of child {@link + * TreeFileArtifact}s at execution time before argument evaluation. * * <p>The main difference between this class and {@link TreeFileArtifactArgvFragment} is that * {@link TreeFileArtifactArgvFragment} is used in {@link SpawnActionTemplate} to substitutes a * TreeArtifact with *one* of its child TreeFileArtifacts, while this class expands a TreeArtifact * into *all* of its child TreeFileArtifacts. - * */ - private abstract static class TreeArtifactExpansionArgvFragment extends ArgvFragment { + private abstract static class TreeArtifactExpansionArgvFragment extends StandardArgvFragment { /** * Evaluates this argument fragment into an argument string and adds it into {@code builder}. * The enclosed TreeArtifact will be expanded using {@code artifactExpander}. @@ -104,30 +363,6 @@ public final class CustomCommandLine extends CommandLine { } } - // It's better to avoid anonymous classes if we want to serialize command lines - private static final class JoinExecPathsArg extends ArgvFragment { - - private final String arg; - private final String delimiter; - private final Iterable<Artifact> artifacts; - - private JoinExecPathsArg(String arg, String delimiter, Iterable<Artifact> artifacts) { - this.arg = arg; - this.delimiter = delimiter; - this.artifacts = CollectionUtils.makeImmutable(artifacts); - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - Iterator<Artifact> it = artifacts.iterator(); - if (!it.hasNext()) { - return; - } - builder.add(arg); - builder.add(Artifact.joinExecPaths(delimiter, artifacts)); - } - } - private static final class JoinExpandedTreeArtifactExecPathsArg extends TreeArtifactExpansionArgvFragment { @@ -188,38 +423,6 @@ public final class CustomCommandLine extends CommandLine { } } - private static final class PathWithTemplateArg extends ArgvFragment { - - private final String template; - private final PathFragment[] paths; - - private PathWithTemplateArg(String template, PathFragment... paths) { - this.template = template; - this.paths = paths; - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - // PathFragment.toString() uses getPathString() - builder.add(String.format(template, (Object[]) paths)); - } - } - - private static final class ParamFileArgument extends ArgvFragment { - private final String paramFilePrefix; - private final PathFragment path; - - private ParamFileArgument(String paramFilePrefix, PathFragment path) { - this.paramFilePrefix = paramFilePrefix; - this.path = path; - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - builder.add(paramFilePrefix + path); - } - } - /** * An argument object that evaluates to a formatted string for {@link TreeFileArtifact} exec * paths, enclosing the associated string format template and {@link TreeFileArtifact}s. @@ -238,202 +441,14 @@ public final class CustomCommandLine extends CommandLine { } @Override - ArgvFragment substituteTreeArtifact(Map<Artifact, TreeFileArtifact> substitutionMap) { + Object substituteTreeArtifact(Map<Artifact, TreeFileArtifact> substitutionMap) { Artifact treeFileArtifact = substitutionMap.get(placeHolderTreeArtifact); Preconditions.checkNotNull(treeFileArtifact, "Artifact to substitute: %s", placeHolderTreeArtifact); - - return new PathWithTemplateArg(template, treeFileArtifact.getExecPath()); + return String.format(template, treeFileArtifact.getExecPath()); } } - // TODO(bazel-team): CustomArgv and CustomMultiArgv is going to be difficult to expose - // in Skylark. Maybe we can get rid of them by refactoring JavaCompileAction. It also - // raises immutability / serialization issues. - /** - * Custom Java code producing a String argument. Usage of this class is discouraged. - */ - public abstract static class CustomArgv extends ArgvFragment { - - @Override - void eval(ImmutableList.Builder<String> builder) { - builder.add(argv()); - } - - public abstract String argv(); - } - - /** - * Custom Java code producing a List of String arguments. Usage of this class is discouraged. - */ - public abstract static class CustomMultiArgv extends ArgvFragment { - - @Override - void eval(ImmutableList.Builder<String> builder) { - builder.addAll(argv()); - } - - public abstract Iterable<String> argv(); - } - - private static final class JoinPathsArg extends ArgvFragment { - - private final String delimiter; - private final Iterable<PathFragment> paths; - - private JoinPathsArg(String delimiter, Iterable<PathFragment> paths) { - this.delimiter = delimiter; - this.paths = CollectionUtils.makeImmutable(paths); - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - builder.add(Joiner.on(delimiter).join(paths)); - } - } - - private static final class JoinStringsArg extends ArgvFragment { - - private final String arg; - private final String delimiter; - private final Iterable<String> strings; - - private JoinStringsArg(String arg, String delimiter, Iterable<String> strings) { - this.arg = arg; - this.delimiter = delimiter; - this.strings = CollectionUtils.makeImmutable(strings); - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - Iterator<String> parts = strings.iterator(); - if (!parts.hasNext()) { - return; - } - builder.add(this.arg); - StringBuilder arg = new StringBuilder(); - arg.append(parts.next()); - while (parts.hasNext()) { - arg.append(delimiter); - arg.append(parts.next()); - } - builder.add(arg.toString()); - } - } - - private static final class JoinValuesTransformed<T> extends ArgvFragment { - - private final String arg; - private final String delimiter; - private final Iterable<T> values; - private final Function<T, String> toString; - - private JoinValuesTransformed( - String arg, String delimiter, Iterable<T> values, Function<T, String> toString) { - this.arg = arg; - this.delimiter = delimiter; - this.values = CollectionUtils.makeImmutable(values); - this.toString = toString; - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - Iterator<T> parts = values.iterator(); - if (!parts.hasNext()) { - return; - } - builder.add(this.arg); - StringBuilder arg = new StringBuilder(); - arg.append(toString.apply(parts.next())); - while (parts.hasNext()) { - arg.append(delimiter); - arg.append(toString.apply(parts.next())); - } - builder.add(arg.toString()); - } - } - - /** - * Arguments that intersperse strings between the items in a sequence. There are two forms of - * interspersing, and either may be used by this implementation: - * <ul> - * <li>before each - a string is added before each item in a sequence. e.g. - * {@code -f foo -f bar -f baz} - * <li>format each - a format string is used to format each item in a sequence. e.g. - * {@code -I/foo -I/bar -I/baz} for the format {@code "-I%s"} - * </ul> - * - * <p>This class could be used both with both the "before" and "format" features at the same - * time, but this is probably more confusion than it is worth. If you need this functionality, - * consider using "before" only but storing the strings pre-formatted in a {@link NestedSet}. - */ - private static final class InterspersingArgs extends ArgvFragment { - @Nullable private final String arg; - private final Iterable<?> sequence; - @Nullable private final String beforeEach; - @Nullable private final String formatEach; - - /** - * Do not call from outside this class because this does not guarantee that {@code sequence} is - * immutable. - */ - private InterspersingArgs( - @Nullable String arg, - Iterable<?> sequence, - @Nullable String beforeEach, - @Nullable String formatEach) { - this.arg = arg; - this.sequence = sequence; - this.beforeEach = beforeEach; - this.formatEach = formatEach; - } - - static InterspersingArgs fromStrings( - @Nullable String arg, - Iterable<?> sequence, - @Nullable String beforeEach, - @Nullable String formatEach) { - return new InterspersingArgs( - arg, CollectionUtils.makeImmutable(sequence), beforeEach, formatEach); - } - - static InterspersingArgs fromExecPaths( - @Nullable String arg, - Iterable<Artifact> sequence, - @Nullable String beforeEach, - @Nullable String formatEach) { - return new InterspersingArgs( - arg, - Artifact.toExecPaths(CollectionUtils.makeImmutable(sequence)), - beforeEach, - formatEach); - } - - @Override - void eval(ImmutableList.Builder<String> builder) { - Iterator<?> it = sequence.iterator(); - if (arg != null && it.hasNext()) { - builder.add(arg); - } - while (it.hasNext()) { - Object item = it.next(); - if (item == null) { - continue; - } - - if (beforeEach != null) { - builder.add(beforeEach); - } - String arg = item.toString(); - if (formatEach != null) { - arg = String.format(formatEach, arg); - } - builder.add(arg); - } - } - } - - /** * An argument object that evaluates to the exec path of a {@link TreeFileArtifact}, enclosing * the associated {@link TreeFileArtifact}. @@ -476,166 +491,160 @@ public final class CustomCommandLine extends CommandLine { // toString() results. private final List<Object> arguments = new ArrayList<>(); - public Builder add(@Nullable CharSequence arg) { - if (arg != null) { - arguments.add(arg); + /** + * Adds a value to the argument list. + * + * <p>At expansion time, the object is turned into a string by calling {@link Object#toString}, + * unless it is an {@link Artifact}, in which case the exec path is used. + */ + public Builder add(@Nullable Object value) { + if (value != null) { + arguments.add(value); } return this; } - public Builder add(@Nullable Label arg) { - if (arg != null) { + /** Adds the arg and the passed value if the value is non-null. */ + public Builder add(String arg, @Nullable Object value) { + Preconditions.checkNotNull(arg); + if (value != null) { arguments.add(arg); + add(value); } return this; } - public Builder add(String arg, @Nullable Iterable<String> args) { - Preconditions.checkNotNull(arg); - if (args != null) { - arguments.add( - InterspersingArgs.fromStrings(arg, args, /*beforeEach=*/ null, /*formatEach=*/ null)); + /** Adds the string representation of the passed values. */ + public Builder add(@Nullable ImmutableCollection<?> values) { + if (values != null) { + arguments.add(values); } return this; } - public Builder add(@Nullable Iterable<String> args) { - if (args != null) { - arguments.add( - InterspersingArgs.fromStrings(null, args, /*beforeEach=*/ null, /*formatEach=*/ null)); + /** Adds the string representation of the passed values. */ + public Builder add(@Nullable NestedSet<?> values) { + if (values != null) { + arguments.add(values); } return this; } - public Builder addExecPath(String arg, @Nullable Artifact artifact) { + /** + * Adds the arg followed by the string representation of the passed values. + * + * <p>If values is empty, the arg isn't added. + */ + public Builder add(String arg, @Nullable ImmutableCollection<?> values) { Preconditions.checkNotNull(arg); - if (artifact != null) { + if (values != null && !values.isEmpty()) { arguments.add(arg); - arguments.add(artifact.getExecPath()); + add(values); } return this; } - public Builder addExecPaths(String arg, @Nullable Iterable<Artifact> artifacts) { + /** + * Adds the arg followed by the string representation of the passed values. + * + * <p>If values is empty, the arg isn't added. + */ + public Builder add(String arg, @Nullable NestedSet<?> values) { Preconditions.checkNotNull(arg); - if (artifacts != null) { - arguments.add( - InterspersingArgs.fromExecPaths( - arg, artifacts, /*beforeEach=*/ null, /*formatEach=*/ null)); - } - return this; - } - - public Builder addExecPaths(@Nullable Iterable<Artifact> artifacts) { - if (artifacts != null) { - arguments.add( - InterspersingArgs.fromExecPaths( - null, artifacts, /*beforeEach=*/ null, /*formatEach=*/ null)); + if (values != null && !values.isEmpty()) { + arguments.add(arg); + add(values); } return this; } /** - * Adds a flag with the exec path of a placeholder TreeArtifact. When the command line is used - * in an action template, the placeholder will be replaced by the exec path of a {@link - * TreeFileArtifact} inside the TreeArtifact at execution time for each expanded action. + * Adds the expansion of the passed vector arg. * - * @param arg the name of the argument - * @param treeArtifact the TreeArtifact that will be evaluated to one of its child {@link - * TreeFileArtifact} at execution time + * <p>Please see {@link VectorArg} for more information. */ - public Builder addPlaceholderTreeArtifactExecPath(String arg, @Nullable Artifact treeArtifact) { - Preconditions.checkNotNull(arg); - if (treeArtifact != null) { - arguments.add(arg); - arguments.add(new TreeFileArtifactExecPathArg(treeArtifact)); + public Builder add(VectorArg.Builder vectorArg) { + if (!vectorArg.isEmpty) { + VectorArg.push(arguments, vectorArg); } return this; } /** - * Adds a placeholder TreeArtifact exec path. When the command line is used in an action - * template, the placeholder will be replaced by the exec path of a {@link TreeFileArtifact} - * inside the TreeArtifact at execution time for each expanded action. + * Adds the arg followed by the expansion of the vector arg. * - * @param treeArtifact the TreeArtifact that will be evaluated to one of its child {@link - * TreeFileArtifact} at execution time + * <p>Please see {@link VectorArg} for more information. + * <p>If values is empty, the arg isn't added. */ - public Builder addPlaceholderTreeArtifactExecPath(@Nullable Artifact treeArtifact) { - if (treeArtifact != null) { - arguments.add(new TreeFileArtifactExecPathArg(treeArtifact)); + public Builder add(String arg, VectorArg.Builder vectorArg) { + Preconditions.checkNotNull(arg); + if (!vectorArg.isEmpty) { + arguments.add(arg); + add(vectorArg); } return this; } - public Builder addJoinStrings( - String arg, String delimiter, @Nullable Iterable<String> strings) { - Preconditions.checkNotNull(arg); - Preconditions.checkNotNull(delimiter); - if (strings != null) { - arguments.add(new JoinStringsArg(arg, delimiter, strings)); + public Builder add(@Nullable CustomArgv arg) { + if (arg != null) { + arguments.add(arg); } return this; } - /** - * Adds a list of values transformed by a function and delimited by a string. - * - * <p>Prefer this to transforming nested sets yourself as it is more memory-efficient. By using - * this class, expansion of the nested set is deferred until action execution instead of - * retained on the heap. - * - * @param arg The argument - * @param delimiter A delimiter string placed in between each transformed value - * @param values The values to expand into a list - * @param toString A function that transforms a value into a string - */ - public <T> Builder addJoinValues( - String arg, String delimiter, @Nullable Iterable<T> values, Function<T, String> toString) { - Preconditions.checkNotNull(arg); - Preconditions.checkNotNull(delimiter); - Preconditions.checkNotNull(toString); - if (values != null) { - arguments.add(new JoinValuesTransformed<>(arg, delimiter, values, toString)); + public Builder add(@Nullable CustomMultiArgv arg) { + if (arg != null) { + arguments.add(arg); } return this; } - public Builder addJoinExecPaths( - String arg, String delimiter, @Nullable Iterable<Artifact> artifacts) { - Preconditions.checkNotNull(arg); - Preconditions.checkNotNull(delimiter); - if (artifacts != null) { - arguments.add(new JoinExecPathsArg(arg, delimiter, artifacts)); - } + /** Calls {@link String#format} at command line expansion time. */ + public Builder addFormat(String formatStr, Object... args) { + Preconditions.checkNotNull(formatStr); + FormatArg.push(arguments, formatStr, args); return this; } - public Builder addPath(@Nullable PathFragment path) { - if (path != null) { - arguments.add(path); + /** Cancatenates the passed prefix string and the object's string representation. */ + public Builder addWithPrefix(String prefix, @Nullable Object arg) { + Preconditions.checkNotNull(prefix); + if (arg != null) { + PrefixArg.push(arguments, prefix, arg); } return this; } - public Builder addPaths(String template, @Nullable PathFragment... path) { - Preconditions.checkNotNull(template); - if (path != null) { - arguments.add(new PathWithTemplateArg(template, path)); + /** + * Adds a flag with the exec path of a placeholder TreeArtifact. When the command line is used + * in an action template, the placeholder will be replaced by the exec path of a {@link + * TreeFileArtifact} inside the TreeArtifact at execution time for each expanded action. + * + * @param arg the name of the argument + * @param treeArtifact the TreeArtifact that will be evaluated to one of its child {@link + * TreeFileArtifact} at execution time + */ + public Builder addPlaceholderTreeArtifactExecPath(String arg, @Nullable Artifact treeArtifact) { + Preconditions.checkNotNull(arg); + if (treeArtifact != null) { + arguments.add(arg); + arguments.add(new TreeFileArtifactExecPathArg(treeArtifact)); } return this; } /** - * Adds a param file as an argument. + * Adds a placeholder TreeArtifact exec path. When the command line is used in an action + * template, the placeholder will be replaced by the exec path of a {@link TreeFileArtifact} + * inside the TreeArtifact at execution time for each expanded action. * - * @param paramFilePrefix The character that denotes a param file, commonly '@' - * @param paramFile The param file artifact + * @param treeArtifact the TreeArtifact that will be evaluated to one of its child {@link + * TreeFileArtifact} at execution time */ - public Builder addParamFile(String paramFilePrefix, @Nullable Artifact paramFile) { - Preconditions.checkNotNull(paramFilePrefix); - Preconditions.checkNotNull(paramFile); - arguments.add(new ParamFileArgument(paramFilePrefix, paramFile.getExecPath())); + public Builder addPlaceholderTreeArtifactExecPath(@Nullable Artifact treeArtifact) { + if (treeArtifact != null) { + arguments.add(new TreeFileArtifactExecPathArg(treeArtifact)); + } return this; } @@ -659,14 +668,6 @@ public final class CustomCommandLine extends CommandLine { return this; } - public Builder addJoinPaths(String delimiter, @Nullable Iterable<PathFragment> paths) { - Preconditions.checkNotNull(delimiter); - if (paths != null && !Iterables.isEmpty(paths)) { - arguments.add(new JoinPathsArg(delimiter, paths)); - } - return this; - } - /** * Adds a string joined together by the exec paths of all {@link TreeFileArtifact}s under * {@code treeArtifact}. @@ -693,51 +694,132 @@ public final class CustomCommandLine extends CommandLine { return this; } - public Builder addBeforeEachPath(String repeated, @Nullable Iterable<PathFragment> paths) { - Preconditions.checkNotNull(repeated); - if (paths != null) { - arguments.add(InterspersingArgs.fromStrings(null, paths, repeated, /*formatEach=*/ null)); - } - return this; + @Deprecated + public Builder addPath(PathFragment pathFragment) { + return add(pathFragment); } - public Builder addBeforeEach(String repeated, @Nullable Iterable<String> strings) { - Preconditions.checkNotNull(repeated); - if (strings != null) { - arguments.add(InterspersingArgs.fromStrings(null, strings, repeated, /*formatEach=*/ null)); - } - return this; + @Deprecated + public Builder addExecPath(String arg, @Nullable Artifact artifact) { + return add(arg, artifact); } - public Builder addBeforeEachExecPath(String repeated, @Nullable Iterable<Artifact> artifacts) { - Preconditions.checkNotNull(repeated); - if (artifacts != null) { - arguments.add( - InterspersingArgs.fromExecPaths(null, artifacts, repeated, /*formatEach=*/ null)); - } - return this; + @Deprecated + public Builder addExecPaths(@Nullable ImmutableCollection<Artifact> artifacts) { + return add(artifacts); } - public Builder addFormatEach(String format, @Nullable Iterable<String> strings) { - Preconditions.checkNotNull(format); - if (strings != null) { - arguments.add(InterspersingArgs.fromStrings(null, strings, /*beforeEach=*/ null, format)); - } - return this; + @Deprecated + public Builder addExecPaths(@Nullable NestedSet<Artifact> artifacts) { + return add(artifacts); } - public Builder add(@Nullable CustomArgv arg) { - if (arg != null) { - arguments.add(arg); - } - return this; + @Deprecated + public Builder addExecPaths(String arg, @Nullable ImmutableCollection<Artifact> artifacts) { + return add(arg, artifacts); } - public Builder add(@Nullable CustomMultiArgv arg) { - if (arg != null) { - arguments.add(arg); - } - return this; + @Deprecated + public Builder addExecPaths(String arg, @Nullable NestedSet<Artifact> artifacts) { + return add(arg, artifacts); + } + + @Deprecated + public Builder addJoinExecPaths( + String arg, String delimiter, @Nullable ImmutableCollection<?> values) { + return add(arg, VectorArg.of(values).joinWith(delimiter)); + } + + @Deprecated + public Builder addJoinExecPaths(String arg, String delimiter, @Nullable NestedSet<?> values) { + return add(arg, VectorArg.of(values).joinWith(delimiter)); + } + + @Deprecated + public Builder addPaths(String formatStr, PathFragment path) { + Preconditions.checkNotNull(path); + return addFormat(formatStr, path); + } + + @Deprecated + public Builder addPaths(String formatStr, PathFragment path0, PathFragment path1) { + Preconditions.checkNotNull(path0); + Preconditions.checkNotNull(path1); + return addFormat(formatStr, path0, path1); + } + + @Deprecated + public Builder addBeforeEachExecPath( + String beforeEach, @Nullable ImmutableCollection<Artifact> values) { + return add(VectorArg.of(values).beforeEach(beforeEach)); + } + + @Deprecated + public Builder addBeforeEachExecPath(String beforeEach, @Nullable NestedSet<Artifact> values) { + return add(VectorArg.of(values).beforeEach(beforeEach)); + } + + @Deprecated + public Builder addJoinPaths( + String delimiter, @Nullable ImmutableCollection<PathFragment> pathFragments) { + return add(VectorArg.of(pathFragments).joinWith(delimiter)); + } + + public Builder addBeforeEach(String beforeEach, @Nullable ImmutableCollection<?> values) { + return add(VectorArg.of(values).beforeEach(beforeEach)); + } + + @Deprecated + public Builder addBeforeEachPath( + String beforeEach, @Nullable ImmutableCollection<PathFragment> values) { + return add(VectorArg.of(values).beforeEach(beforeEach)); + } + + @Deprecated + public Builder addBeforeEachPath(String beforeEach, @Nullable NestedSet<PathFragment> values) { + return add(VectorArg.of(values).beforeEach(beforeEach)); + } + + @Deprecated + public <T> Builder addJoinValues( + String arg, + String join, + @Nullable ImmutableCollection<?> values, + Function<T, String> mapFn) { + return add(arg, VectorArg.of(values).joinWith(join).mapEach(mapFn)); + } + + @Deprecated + public <T> Builder addJoinValues( + String arg, String join, @Nullable NestedSet<?> values, Function<T, String> mapFn) { + return add(arg, VectorArg.of(values).joinWith(join).mapEach(mapFn)); + } + + @Deprecated + public Builder addFormatEach(String formatStr, @Nullable ImmutableCollection<?> values) { + return add(VectorArg.of(values).formatEach(formatStr)); + } + + @Deprecated + public Builder addFormatEach(String formatStr, @Nullable NestedSet<?> values) { + return add(VectorArg.of(values).formatEach(formatStr)); + } + + @Deprecated + public Builder addJoinStrings( + String arg, String delimiter, @Nullable ImmutableCollection<?> values) { + return add(arg, VectorArg.of(values).joinWith(delimiter)); + } + + /** + * Adds a param file as an argument. + * + * @param paramFilePrefix The character that denotes a param file, commonly '@' + * @param paramFile The param file artifact + */ + @Deprecated + public Builder addParamFile(String paramFilePrefix, @Nullable Artifact paramFile) { + return addWithPrefix(paramFilePrefix, paramFile); } public CustomCommandLine build() { @@ -751,13 +833,12 @@ public final class CustomCommandLine extends CommandLine { private final ImmutableList<Object> arguments; - /** - * A map between enclosed TreeArtifacts and their associated {@link TreeFileArtifacts} for + * A map between enclosed TreeArtifacts and their associated {@link TreeFileArtifact}s for * substitution. * - * <p> This map is used to support TreeArtifact substitutions in - * {@link TreeFileArtifactArgvFragment}s. + * <p>This map is used to support TreeArtifact substitutions in {@link + * TreeFileArtifactArgvFragment}s. */ private final Map<Artifact, TreeFileArtifact> substitutionMap; @@ -799,24 +880,34 @@ public final class CustomCommandLine extends CommandLine { private Iterable<String> argumentsInternal(@Nullable ArtifactExpander artifactExpander) { ImmutableList.Builder<String> builder = ImmutableList.builder(); - for (Object arg : arguments) { + int count = arguments.size(); + for (int i = 0; i < count; ) { + Object arg = arguments.get(i++); Object substitutedArg = substituteTreeFileArtifactArgvFragment(arg); - if (substitutedArg instanceof ArgvFragment) { + if (substitutedArg instanceof Iterable) { + evalSimpleVectorArg((Iterable<?>) substitutedArg, builder); + } else if (substitutedArg instanceof ArgvFragment) { if (artifactExpander != null && substitutedArg instanceof TreeArtifactExpansionArgvFragment) { TreeArtifactExpansionArgvFragment expansionArg = (TreeArtifactExpansionArgvFragment) substitutedArg; expansionArg.eval(builder, artifactExpander); } else { - ((ArgvFragment) substitutedArg).eval(builder); + i = ((ArgvFragment) substitutedArg).eval(arguments, i, builder); } } else { - builder.add(substitutedArg.toString()); + builder.add(valueToString(substitutedArg)); } } return builder.build(); } + private void evalSimpleVectorArg(Iterable<?> arg, ImmutableList.Builder<String> builder) { + for (Object value : arg) { + builder.add(valueToString(value)); + } + } + /** * If the given arg is a {@link TreeFileArtifactArgvFragment} and we have its associated * TreeArtifact substitution map, returns another argument object that has its enclosing @@ -832,4 +923,10 @@ public final class CustomCommandLine extends CommandLine { return arg; } } + + private static String valueToString(Object value) { + return value instanceof Artifact + ? ((Artifact) value).getExecPath().getPathString() + : value.toString(); + } } |