Support directory expansion in Skylark Args.
This is controlled by a flag "expand_directories", whose default value is controlled by --incompatible_expand_directories.
Example:
directory = ctx.actions.declare_directory('dir')
...
args = ctx.actions.args()
args.add_all([directory])
The resulting command line will now contain the directory contents instead of the path to the directory.
RELNOTES[INC]: Adds --incompatible_expand_directories to automatically expand directories in skylark command lines. Design doc: https://docs.google.com/document/d/11agWFiOUiz2htBLj6swPTob5z78TrCxm8DQE4uJLOwM
PiperOrigin-RevId: 210155043
diff --git a/site/docs/skylark/backward-compatibility.md b/site/docs/skylark/backward-compatibility.md
index 1a20b05..3e2d509 100644
--- a/site/docs/skylark/backward-compatibility.md
+++ b/site/docs/skylark/backward-compatibility.md
@@ -45,6 +45,7 @@
* [Remove native http archive](#remove-native-http-archive)
* [New-style JavaInfo constructor](#new-style-java_info)
* [Disallow tools in action inputs](#disallow-tools-in-action-inputs)
+* [Expand directories in Args](#expand-directories-in-args)
### Dictionary concatenation
@@ -387,4 +388,32 @@
* Flag: `--incompatible_no_support_tools_in_action_inputs`
* Default: `false`
-<!-- Add new options here -->
+
+### Expand directories in Args
+
+Previously, directories created by
+[`ctx.actions.declare_directory`](lib/actions.html#declare_directory) expanded
+to the path of the directory when added to an [`Args`](lib/Args.html) object.
+
+With this flag enabled, directories are instead replaced by the full file
+contents of that directory when passed to `args.add_all()` or
+`args.add_joined()`. (Directories may not be passed to `args.add()`.)
+
+If you want the old behavior on a case-by-case basis (perhaps your tool can
+handle directories on the command line), you can pass `expand_directories=False`
+to the `args.add_all()` or `args.add_joined()` call.
+
+```
+d = ctx.action.declare_directory(“dir”)
+# ... Some action runs and produces [“dir/file1”, “dir/file2”] ...
+f = ctx.action.declare_file(“file”)
+args = ctx.action.args()
+args.add_all([d, f])
+ -> Used to expand to ["dir", "file"]
+ Now expands to [“dir/file1”, “dir/file2”, “file”]
+```
+
+* Flag: `--incompatible_expand_directories`
+* Default: `false`
+
+!-- Add new options here -->
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 755a909..19ca017 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -19,6 +19,7 @@
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Artifact;
@@ -68,8 +69,10 @@
import com.google.protobuf.GeneratedMessage;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -167,6 +170,9 @@
action =
new ParameterFileWriteAction(
ruleContext.getActionOwner(),
+ skylarkSemantics.incompatibleExpandDirectories()
+ ? args.getTreeArtifacts()
+ : ImmutableList.of(),
(Artifact) output,
args.build(),
args.parameterFileType,
@@ -343,6 +349,11 @@
.setFlagFormatString(args.flagFormatString)
.setUseAlways(args.useAlways)
.setCharset(StandardCharsets.UTF_8)
+ .setInputs(
+ skylarkSemantics.incompatibleExpandDirectories()
+ && !context.getConfiguration().deferParamFiles()
+ ? args.getTreeArtifacts()
+ : ImmutableList.of())
.build();
}
builder.addCommandLine(args.commandLine.build(), paramFileInfo);
@@ -539,6 +550,8 @@
private final Mutability mutability;
private final SkylarkSemantics skylarkSemantics;
private final SkylarkCustomCommandLine.Builder commandLine;
+ private List<NestedSet<Object>> potentialTreeArtifacts = new ArrayList<>();
+ private final Set<Artifact> treeArtifacts = new HashSet<>();
private ParameterFileType parameterFileType = ParameterFileType.SHELL_QUOTED;
private String flagFormatString;
private boolean useAlways;
@@ -586,6 +599,7 @@
/* formatJoined= */ null,
/* omitIfEmpty= */ false,
/* uniquify= */ false,
+ skylarkSemantics.incompatibleExpandDirectories(),
/* terminateWith= */ null,
loc);
@@ -621,6 +635,7 @@
Object beforeEach,
Boolean omitIfEmpty,
Boolean uniquify,
+ Object expandDirectories,
Object terminateWith,
Location loc)
throws EvalException {
@@ -647,6 +662,9 @@
/* formatJoined= */ null,
omitIfEmpty,
uniquify,
+ expandDirectories == Runtime.UNBOUND
+ ? skylarkSemantics.incompatibleExpandDirectories()
+ : (Boolean) expandDirectories,
terminateWith != Runtime.NONE ? (String) terminateWith : null,
loc);
return Runtime.NONE;
@@ -662,6 +680,7 @@
Object formatJoined,
Boolean omitIfEmpty,
Boolean uniquify,
+ Object expandDirectories,
Location loc)
throws EvalException {
if (isImmutable()) {
@@ -687,6 +706,9 @@
formatJoined != Runtime.NONE ? (String) formatJoined : null,
omitIfEmpty,
uniquify,
+ expandDirectories == Runtime.UNBOUND
+ ? skylarkSemantics.incompatibleExpandDirectories()
+ : (Boolean) expandDirectories,
/* terminateWith= */ null,
loc);
return Runtime.NONE;
@@ -703,23 +725,33 @@
String formatJoined,
boolean omitIfEmpty,
boolean uniquify,
+ boolean expandDirectories,
String terminateWith,
Location loc)
throws EvalException {
SkylarkCustomCommandLine.VectorArg.Builder vectorArg;
if (value instanceof SkylarkNestedSet) {
- NestedSet<?> nestedSet = ((SkylarkNestedSet) value).getSet(Object.class);
+ SkylarkNestedSet skylarkNestedSet = ((SkylarkNestedSet) value);
+ NestedSet<Object> nestedSet = skylarkNestedSet.getSet(Object.class);
+ if (expandDirectories) {
+ potentialTreeArtifacts.add(nestedSet);
+ }
vectorArg = new SkylarkCustomCommandLine.VectorArg.Builder(nestedSet);
} else {
- SkylarkList skylarkList = (SkylarkList) value;
+ @SuppressWarnings("unchecked")
+ SkylarkList<Object> skylarkList = (SkylarkList<Object>) value;
+ if (expandDirectories) {
+ scanForTreeArtifacts(skylarkList);
+ }
vectorArg = new SkylarkCustomCommandLine.VectorArg.Builder(skylarkList);
}
validateMapEach(mapEach, loc);
- validateFormatString("format_each", formatEach);
- validateFormatString("format_joined", formatJoined);
+ validateFormatString("format_each", formatEach, loc);
+ validateFormatString("format_joined", formatJoined, loc);
vectorArg
.setLocation(loc)
.setArgName(argName)
+ .setExpandDirectories(expandDirectories)
.setMapAll(mapAll)
.setFormatEach(formatEach)
.setBeforeEach(beforeEach)
@@ -769,13 +801,13 @@
}
}
- private void validateFormatString(String argumentName, @Nullable String formatStr)
+ private void validateFormatString(String argumentName, @Nullable String formatStr, Location loc)
throws EvalException {
if (formatStr != null
&& skylarkSemantics.incompatibleDisallowOldStyleArgsAdd()
&& !SingleStringArgFormatter.isValid(formatStr)) {
throw new EvalException(
- null,
+ loc,
String.format(
"Invalid value for parameter \"%s\": Expected string with a single \"%%s\"",
argumentName));
@@ -784,7 +816,8 @@
private void addScalarArg(Object value, String format, BaseFunction mapFn, Location loc)
throws EvalException {
- validateFormatString("format", format);
+ validateNoTreeArtifact(value, loc);
+ validateFormatString("format", format, loc);
if (format == null && mapFn == null) {
commandLine.add(value);
} else {
@@ -794,6 +827,18 @@
}
}
+ private void validateNoTreeArtifact(Object value, Location loc) throws EvalException {
+ if (skylarkSemantics.incompatibleExpandDirectories()
+ && (value instanceof Artifact)
+ && ((Artifact) value).isTreeArtifact()) {
+ throw new EvalException(
+ loc,
+ "Cannot add directories to Args#add since they may expand to multiple values. "
+ + "Either use Args#add_all (if you want expansion) "
+ + "or args.add(directory.path) (if you do not).");
+ }
+ }
+
@Override
public void useParamsFile(String paramFileArg, Boolean useAlways) throws EvalException {
if (isImmutable()) {
@@ -848,6 +893,22 @@
public void repr(SkylarkPrinter printer) {
printer.append("context.args() object");
}
+
+ ImmutableSet<Artifact> getTreeArtifacts() {
+ for (Iterable<Object> collection : potentialTreeArtifacts) {
+ scanForTreeArtifacts(collection);
+ }
+ potentialTreeArtifacts.clear();
+ return ImmutableSet.copyOf(treeArtifacts);
+ }
+
+ private void scanForTreeArtifacts(Iterable<?> objects) {
+ for (Object object : objects) {
+ if ((object instanceof Artifact) && ((Artifact) object).isTreeArtifact()) {
+ treeArtifacts.add((Artifact) object);
+ }
+ }
+ }
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
index 3e44975..eb404bb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
@@ -20,6 +20,8 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.CommandLineItem;
@@ -64,15 +66,18 @@
private static final int HAS_MAP_ALL = 1 << 1;
private static final int HAS_MAP_EACH = 1 << 2;
private static final int IS_NESTED_SET = 1 << 3;
- private static final int UNIQUIFY = 1 << 4;
- private static final int OMIT_IF_EMPTY = 1 << 5;
- private static final int HAS_ARG_NAME = 1 << 6;
- private static final int HAS_FORMAT_EACH = 1 << 7;
- private static final int HAS_BEFORE_EACH = 1 << 8;
- private static final int HAS_JOIN_WITH = 1 << 9;
- private static final int HAS_FORMAT_JOINED = 1 << 10;
- private static final int HAS_TERMINATE_WITH = 1 << 11;
+ private static final int EXPAND_DIRECTORIES = 1 << 4;
+ private static final int UNIQUIFY = 1 << 5;
+ private static final int OMIT_IF_EMPTY = 1 << 6;
+ private static final int HAS_ARG_NAME = 1 << 7;
+ private static final int HAS_FORMAT_EACH = 1 << 8;
+ private static final int HAS_BEFORE_EACH = 1 << 9;
+ private static final int HAS_JOIN_WITH = 1 << 10;
+ private static final int HAS_FORMAT_JOINED = 1 << 11;
+ private static final int HAS_TERMINATE_WITH = 1 << 12;
+ private static final UUID EXPAND_DIRECTORIES_UUID =
+ UUID.fromString("9d7520d2-a187-11e8-98d0-529269fb1459");
private static final UUID UNIQUIFY_UUID =
UUID.fromString("7f494c3e-faea-4498-a521-5d3bc6ee19eb");
private static final UUID OMIT_IF_EMPTY_UUID =
@@ -107,6 +112,7 @@
features |= arg.mapAll != null ? HAS_MAP_ALL : 0;
features |= arg.mapEach != null ? HAS_MAP_EACH : 0;
features |= arg.nestedSet != null ? IS_NESTED_SET : 0;
+ features |= arg.expandDirectories ? EXPAND_DIRECTORIES : 0;
features |= arg.uniquify ? UNIQUIFY : 0;
features |= arg.omitIfEmpty ? OMIT_IF_EMPTY : 0;
features |= arg.argName != null ? HAS_ARG_NAME : 0;
@@ -168,6 +174,7 @@
List<Object> arguments,
int argi,
ImmutableList.Builder<String> builder,
+ @Nullable ArtifactExpander artifactExpander,
SkylarkSemantics skylarkSemantics)
throws CommandLineExpansionException {
final Location location =
@@ -185,12 +192,18 @@
originalValues = arguments.subList(argi, argi + count);
argi += count;
}
+ List<Object> expandedValues = originalValues;
+ if (artifactExpander != null && (features & EXPAND_DIRECTORIES) != 0) {
+ if (hasTreeArtifact(originalValues)) {
+ expandedValues = expandTreeArtifacts(artifactExpander, originalValues);
+ }
+ }
List<String> stringValues;
if (mapEach != null) {
- stringValues = new ArrayList<>(originalValues.size());
- applyMapEach(mapEach, originalValues, stringValues::add, location, skylarkSemantics);
+ stringValues = new ArrayList<>(expandedValues.size());
+ applyMapEach(mapEach, expandedValues, stringValues::add, location, skylarkSemantics);
} else if (mapAll != null) {
- Object result = applyMapFn(mapAll, originalValues, location, skylarkSemantics);
+ Object result = applyMapFn(mapAll, expandedValues, location, skylarkSemantics);
if (!(result instanceof List)) {
throw new CommandLineExpansionException(
errorMessage(
@@ -199,13 +212,13 @@
null));
}
List resultAsList = (List) result;
- if (resultAsList.size() != originalValues.size()) {
+ if (resultAsList.size() != expandedValues.size()) {
throw new CommandLineExpansionException(
errorMessage(
String.format(
"map_fn must return a list of the same length as the input. "
+ "Found list of length %d, expected %d.",
- resultAsList.size(), originalValues.size()),
+ resultAsList.size(), expandedValues.size()),
location,
null));
}
@@ -217,10 +230,10 @@
stringValues.add(CommandLineItem.expandToCommandLine(resultAsList.get(i)));
}
} else {
- int count = originalValues.size();
- stringValues = new ArrayList<>(originalValues.size());
+ int count = expandedValues.size();
+ stringValues = new ArrayList<>(expandedValues.size());
for (int i = 0; i < count; ++i) {
- stringValues.add(CommandLineItem.expandToCommandLine(originalValues.get(i)));
+ stringValues.add(CommandLineItem.expandToCommandLine(expandedValues.get(i)));
}
}
// It's safe to uniquify at this stage, any transformations after this
@@ -291,6 +304,33 @@
return argi;
}
+ private static boolean hasTreeArtifact(List<Object> originalValues) {
+ int n = originalValues.size();
+ for (int i = 0; i < n; ++i) {
+ Object object = originalValues.get(i);
+ if ((object instanceof Artifact) && ((Artifact) object).isTreeArtifact()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static List<Object> expandTreeArtifacts(
+ Artifact.ArtifactExpander artifactExpander, List<Object> originalValues) {
+ List<Object> expandedValues;
+ int n = originalValues.size();
+ expandedValues = new ArrayList<>(n);
+ for (int i = 0; i < n; ++i) {
+ Object object = originalValues.get(i);
+ if (object instanceof Artifact && ((Artifact) object).isTreeArtifact()) {
+ artifactExpander.expand((Artifact) object, expandedValues);
+ } else {
+ expandedValues.add(object);
+ }
+ }
+ return expandedValues;
+ }
+
private int addToFingerprint(
List<Object> arguments,
int argi,
@@ -335,6 +375,9 @@
}
}
}
+ if ((features & EXPAND_DIRECTORIES) != 0) {
+ fingerprint.addUUID(EXPAND_DIRECTORIES_UUID);
+ }
if ((features & UNIQUIFY) != 0) {
fingerprint.addUUID(UNIQUIFY_UUID);
}
@@ -380,7 +423,7 @@
SkylarkSemantics skylarkSemantics)
throws CommandLineExpansionException {
ImmutableList.Builder<String> builder = ImmutableList.builder();
- argi = eval(arguments, argi, builder, skylarkSemantics);
+ argi = eval(arguments, argi, builder, null, skylarkSemantics);
for (String s : builder.build()) {
fingerprint.addString(s);
}
@@ -392,6 +435,7 @@
@Nullable private final NestedSet<?> nestedSet;
private Location location;
public String argName;
+ private boolean expandDirectories;
private BaseFunction mapAll;
private BaseFunction mapEach;
private String formatEach;
@@ -422,6 +466,11 @@
return this;
}
+ Builder setExpandDirectories(boolean expandDirectories) {
+ this.expandDirectories = expandDirectories;
+ return this;
+ }
+
Builder setMapAll(BaseFunction mapAll) {
this.mapAll = mapAll;
return this;
@@ -666,11 +715,17 @@
@Override
public Iterable<String> arguments() throws CommandLineExpansionException {
+ return arguments(null);
+ }
+
+ @Override
+ public Iterable<String> arguments(@Nullable ArtifactExpander artifactExpander)
+ throws CommandLineExpansionException {
ImmutableList.Builder<String> result = ImmutableList.builder();
for (int argi = 0; argi < arguments.size(); ) {
Object arg = arguments.get(argi++);
if (arg instanceof VectorArg) {
- argi = ((VectorArg) arg).eval(arguments, argi, result, skylarkSemantics);
+ argi = ((VectorArg) arg).eval(arguments, argi, result, artifactExpander, skylarkSemantics);
} else if (arg instanceof ScalarArg) {
argi = ((ScalarArg) arg).eval(arguments, argi, result, skylarkSemantics);
} else {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
index 8a2b397..076f156 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
@@ -269,6 +269,21 @@
public boolean incompatibleDisallowOldStyleArgsAdd;
@Option(
+ name = "incompatible_expand_directories",
+ defaultValue = "false",
+ category = "incompatible changes",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+ },
+ help =
+ "Controls whether directories are expanded to the list of files under that directory "
+ + "when added to Args, instead of replaced by the path of the directory.")
+ public boolean incompatibleExpandDirectories;
+
+ @Option(
name = "incompatible_new_actions_api",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.SKYLARK_SEMANTICS,
@@ -400,6 +415,7 @@
.incompatibleDisallowLegacyJavaInfo(incompatibleDisallowLegacyJavaInfo)
.incompatibleDisallowOldStyleArgsAdd(incompatibleDisallowOldStyleArgsAdd)
.incompatibleDisallowSlashOperator(incompatibleDisallowSlashOperator)
+ .incompatibleExpandDirectories(incompatibleExpandDirectories)
.incompatibleGenerateJavaCommonSourceJar(incompatibleGenerateJavaCommonSourceJar)
.incompatibleNewActionsApi(incompatibleNewActionsApi)
.incompatibleNoSupportToolsInActionInputs(incompatibleNoSupportToolsInActionInputs)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/CommandLineArgsApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/CommandLineArgsApi.java
index 2062772..0a34af6 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/CommandLineArgsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/CommandLineArgsApi.java
@@ -140,8 +140,11 @@
"The object to append. It will be converted to a string using the standard "
+ "conversion mentioned above. Since there is no <code>map_each</code> "
+ "parameter for this function, <code>value</code> should be either a "
- + "string or a <code>File</code>."
- + ""
+ + "string or a <code>File</code>. A directory <code>File</code> must be "
+ + "passed to <a href='#add_all'><code>add_all()</code> or "
+ + "<a href='#add_joined'><code>add_joined()</code></a> instead of this method."
+ + "<p><i>Deprecated behavior:</i> <code>value</code> may be a directory "
+ + "unless --noincompatible_expand_directories is passed.</p>"
+ "<p><i>Deprecated behavior:</i> <code>value</code> may also be a "
+ "list, tuple, or depset of multiple items to append."),
@Param(
@@ -213,7 +216,11 @@
+ "<p>Most of the processing occurs over a list of arguments to be appended, as per "
+ "the following steps:"
+ "<ol>"
- + "<li>If <code>map_each</code> is given, it is applied to each input item, and the "
+ + "<li>Each directory <code>File</code> item is replaced by all <code>File</code>s "
+ + "recursively contained in that directory, unless "
+ + "<code>expand_directories=False</code> is specified."
+ + "</li>"
+ + "<li>If <code>map_each</code> is given, it is applied to each item, and the "
+ " resulting lists of strings are concatenated to form the initial argument "
+ " list. Otherwise, the initial argument list is the result of applying the "
+ " standard conversion to each item."
@@ -325,6 +332,17 @@
+ "but it can be useful if <code>map_each</code> emits the same string for "
+ "multiple items."),
@Param(
+ name = "expand_directories",
+ type = Boolean.class,
+ named = true,
+ positional = false,
+ defaultValue = "unbound",
+ doc =
+ "If true, any directories in <code>values</code> will be expanded to a flat list "
+ + "of files. This happens before <code>map_each</code> is applied. "
+ + "The default value of this flag is controlled by "
+ + "--incompatible_expand_directories."),
+ @Param(
name = "terminate_with",
type = String.class,
named = true,
@@ -346,6 +364,7 @@
Object beforeEach,
Boolean omitIfEmpty,
Boolean uniquify,
+ Object expandDirectories,
Object terminateWith,
Location loc)
throws EvalException;
@@ -443,7 +462,14 @@
named = true,
positional = false,
defaultValue = "False",
- doc = "Same as for <a href='#add_all.uniquify'><code>add_all</code></a>.")
+ doc = "Same as for <a href='#add_all.uniquify'><code>add_all</code></a>."),
+ @Param(
+ name = "expand_directories",
+ type = Boolean.class,
+ named = true,
+ positional = false,
+ defaultValue = "unbound",
+ doc = "Same as for <a href='#add_all.expand_directories'><code>add_all</code></a>.")
},
useLocation = true)
public Runtime.NoneType addJoined(
@@ -455,6 +481,7 @@
Object formatJoined,
Boolean omitIfEmpty,
Boolean uniquify,
+ Object expandDirectories,
Location loc)
throws EvalException;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
index ac13aad..187bf0f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
@@ -71,6 +71,8 @@
public abstract boolean incompatibleDisallowSlashOperator();
+ public abstract boolean incompatibleExpandDirectories();
+
public abstract boolean incompatibleGenerateJavaCommonSourceJar();
public abstract boolean incompatibleNewActionsApi();
@@ -119,6 +121,7 @@
.incompatibleDisallowLegacyJavaInfo(false)
.incompatibleDisallowOldStyleArgsAdd(false)
.incompatibleDisallowSlashOperator(false)
+ .incompatibleExpandDirectories(false)
.incompatibleGenerateJavaCommonSourceJar(false)
.incompatibleNewActionsApi(false)
.incompatibleNoSupportToolsInActionInputs(false)
@@ -165,6 +168,8 @@
public abstract Builder incompatibleDisallowSlashOperator(boolean value);
+ public abstract Builder incompatibleExpandDirectories(boolean value);
+
public abstract Builder incompatibleGenerateJavaCommonSourceJar(boolean value);
public abstract Builder incompatibleNewActionsApi(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 3dd878c..eff7ba3 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -136,6 +136,7 @@
"--incompatible_disallow_legacy_javainfo=" + rand.nextBoolean(),
"--incompatible_disallow_old_style_args_add=" + rand.nextBoolean(),
"--incompatible_disallow_slash_operator=" + rand.nextBoolean(),
+ "--incompatible_expand_directories=" + rand.nextBoolean(),
"--incompatible_generate_javacommon_source_jar=" + rand.nextBoolean(),
"--incompatible_new_actions_api=" + rand.nextBoolean(),
"--incompatible_no_support_tools_in_action_inputs=" + rand.nextBoolean(),
@@ -170,6 +171,7 @@
.incompatibleDisallowLegacyJavaInfo(rand.nextBoolean())
.incompatibleDisallowOldStyleArgsAdd(rand.nextBoolean())
.incompatibleDisallowSlashOperator(rand.nextBoolean())
+ .incompatibleExpandDirectories(rand.nextBoolean())
.incompatibleGenerateJavaCommonSourceJar(rand.nextBoolean())
.incompatibleNewActionsApi(rand.nextBoolean())
.incompatibleNoSupportToolsInActionInputs(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
index b8dc016..742da5a 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
@@ -26,6 +26,8 @@
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpanderImpl;
+import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.CompositeRunfilesSupplier;
import com.google.devtools.build.lib.actions.RunfilesSupplier;
@@ -41,6 +43,7 @@
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.skylark.SkylarkActionFactory;
+import com.google.devtools.build.lib.analysis.skylark.SkylarkActionFactory.Args;
import com.google.devtools.build.lib.analysis.skylark.SkylarkCustomCommandLine;
import com.google.devtools.build.lib.analysis.skylark.SkylarkRuleContext;
import com.google.devtools.build.lib.cmdline.Label;
@@ -56,6 +59,7 @@
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.testutil.MoreAsserts;
@@ -2873,6 +2877,128 @@
return ((SkylarkActionFactory.Args) evalRuleContextCode(lines)).build();
}
+ @Test
+ public void testDirectoryInArgs() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_expand_directories");
+ SkylarkList<?> result =
+ (SkylarkList<?>)
+ evalRuleContextCode(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _short_path(f): return f.short_path", // For easier assertions
+ "args.add_all([directory], map_each=_short_path)",
+ "args, directory");
+ Args args = (Args) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ CommandLine commandLine = args.build();
+
+ // When asking for arguments without an artifact expander we just return the directory
+ assertThat(commandLine.arguments()).containsExactly("foo/dir");
+
+ // Now ask for one with an expanded directory
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ assertThat(commandLine.arguments(artifactExpander))
+ .containsExactly("foo/dir/file1", "foo/dir/file2");
+ }
+
+ @Test
+ public void testDirectoryInArgsIncompatibleFlagOff() throws Exception {
+ setSkylarkSemanticsOptions("--noincompatible_expand_directories");
+ SkylarkList<?> result =
+ (SkylarkList<?>)
+ evalRuleContextCode(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _short_path(f): return f.short_path", // For easier assertions
+ "args.add_all([directory], map_each=_short_path)",
+ "args, directory");
+ Args args = (Args) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ CommandLine commandLine = args.build();
+
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ // Do not expand the directory, incompatible flag is off
+ assertThat(commandLine.arguments(artifactExpander)).containsExactly("foo/dir");
+ }
+
+ @Test
+ public void testDirectoryInArgsExpandDirectories() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_expand_directories");
+ SkylarkList<?> result =
+ (SkylarkList<?>)
+ evalRuleContextCode(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "def _short_path(f): return f.short_path", // For easier assertions
+ "args.add_all([directory], map_each=_short_path, expand_directories=True)",
+ "args.add_all([directory], map_each=_short_path, expand_directories=False)",
+ "args, directory");
+ Args args = (Args) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ CommandLine commandLine = args.build();
+
+ Artifact file1 = getBinArtifactWithNoOwner("foo/dir/file1");
+ Artifact file2 = getBinArtifactWithNoOwner("foo/dir/file2");
+ ArtifactExpanderImpl artifactExpander =
+ new ArtifactExpanderImpl(
+ ImmutableMap.of(directory, ImmutableList.of(file1, file2)), ImmutableMap.of());
+ // First expanded, then not expanded (two separate calls)
+ assertThat(commandLine.arguments(artifactExpander))
+ .containsExactly("foo/dir/file1", "foo/dir/file2", "foo/dir");
+ }
+
+ @Test
+ public void testDirectoryInScalarArgsFails() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_expand_directories");
+ checkErrorContains(
+ "Cannot add directories to Args#add",
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add(directory)");
+ }
+
+ @Test
+ public void testDirectoryInScalarArgsIsOkWithoutIncompatibleFlag() throws Exception {
+ setSkylarkSemanticsOptions("--noincompatible_expand_directories");
+ Args args =
+ (Args)
+ evalRuleContextCode(
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add(directory)",
+ "args");
+ assertThat(Iterables.getOnlyElement(args.build().arguments())).endsWith("foo/dir");
+ }
+
+ @Test
+ public void testParamFileHasDirectoryAsInput() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_expand_directories");
+ SkylarkRuleContext ctx = dummyRuleContext();
+ SkylarkList<?> result =
+ (SkylarkList<?>)
+ evalRuleContextCode(
+ ctx,
+ "args = ruleContext.actions.args()",
+ "directory = ruleContext.actions.declare_directory('dir')",
+ "args.add_all([directory])",
+ "params = ruleContext.actions.declare_file('params')",
+ "ruleContext.actions.write(params, args)",
+ "params, directory");
+ Artifact params = (Artifact) result.get(0);
+ Artifact directory = (Artifact) result.get(1);
+ ActionAnalysisMetadata action =
+ ctx.getRuleContext().getAnalysisEnvironment().getLocalGeneratingAction(params);
+ assertThat(action.getInputs()).contains(directory);
+ }
+
private void setupThrowFunction(BuiltinFunction func) throws Exception {
throwFunction = func;
throwFunction.configure(