Skip to content

Commit 9d250ed

Browse files
fmeumcopybara-github
authored andcommitted
Add uniquify parameter to TemplateDict.add_joined
The behavior is analogous to that of Args.add_joined. Closes #16213. PiperOrigin-RevId: 485571903 Change-Id: Id69de92d703d5202bfc7b50abfbbb4326441f242
1 parent cdce638 commit 9d250ed

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

src/main/java/com/google/devtools/build/lib/analysis/starlark/TemplateDict.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
import com.google.common.collect.ImmutableList;
1818
import com.google.common.collect.ImmutableMap;
1919
import com.google.common.collect.Lists;
20+
import com.google.common.collect.Sets;
2021
import com.google.devtools.build.lib.analysis.actions.Substitution;
2122
import com.google.devtools.build.lib.analysis.actions.Substitution.ComputedSubstitution;
2223
import com.google.devtools.build.lib.collect.nestedset.Depset;
2324
import com.google.devtools.build.lib.starlarkbuildapi.TemplateDictApi;
2425
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2526
import java.util.ArrayList;
27+
import java.util.HashSet;
2628
import java.util.List;
2729
import net.starlark.java.eval.EvalException;
2830
import net.starlark.java.eval.Mutability;
@@ -58,6 +60,7 @@ public TemplateDictApi addJoined(
5860
Depset valuesSet,
5961
String joinWith,
6062
StarlarkCallable mapEach,
63+
Boolean uniquify,
6164
StarlarkThread thread)
6265
throws EvalException {
6366
if (mapEach instanceof StarlarkFunction) {
@@ -71,7 +74,7 @@ public TemplateDictApi addJoined(
7174
}
7275
}
7376
substitutions.add(
74-
new LazySubstitution(key, thread.getSemantics(), valuesSet, mapEach, joinWith));
77+
new LazySubstitution(key, thread.getSemantics(), valuesSet, mapEach, uniquify, joinWith));
7578
return this;
7679
}
7780

@@ -84,18 +87,21 @@ private static class LazySubstitution extends ComputedSubstitution {
8487
private final StarlarkSemantics semantics;
8588
private final Depset valuesSet;
8689
private final StarlarkCallable mapEach;
90+
private final boolean uniquify;
8791
private final String joinWith;
8892

8993
public LazySubstitution(
9094
String key,
9195
StarlarkSemantics semantics,
9296
Depset valuesSet,
9397
StarlarkCallable mapEach,
98+
boolean uniquify,
9499
String joinWith) {
95100
super(key);
96101
this.semantics = semantics;
97102
this.valuesSet = valuesSet;
98103
this.mapEach = mapEach;
104+
this.uniquify = uniquify;
99105
this.joinWith = joinWith;
100106
}
101107

@@ -127,6 +133,19 @@ public String getValue() throws EvalException {
127133
"Could not evaluate substitution for %s: %s", val, e.getMessage());
128134
}
129135
}
136+
if (uniquify) {
137+
// Stably deduplicate parts in-place.
138+
int count = parts.size();
139+
HashSet<String> seen = Sets.newHashSetWithExpectedSize(count);
140+
int addIndex = 0;
141+
for (int i = 0; i < count; ++i) {
142+
String val = parts.get(i);
143+
if (seen.add(val)) {
144+
parts.set(addIndex++, val);
145+
}
146+
}
147+
parts = parts.subList(0, addIndex);
148+
}
130149
return Joiner.on(joinWith).join(parts);
131150
}
132151
}

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/TemplateDictApi.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,24 @@ public interface TemplateDictApi extends StarlarkValue {
7373
"A Starlark function accepting a single argument and returning a String. This"
7474
+ " function is applied to each item of the depset specified in the"
7575
+ " <code>values</code> parameter"),
76+
@Param(
77+
name = "uniquify",
78+
named = true,
79+
positional = false,
80+
defaultValue = "False",
81+
doc =
82+
"If true, duplicate strings derived from <code>values</code> will be omitted. Only "
83+
+ "the first occurrence of each string will remain. Usually this feature is "
84+
+ "not needed because depsets already omit duplicates, but it can be useful "
85+
+ "if <code>map_each</code> emits the same string for multiple items."),
7686
},
7787
useStarlarkThread = true)
7888
TemplateDictApi addJoined(
79-
String key, Depset values, String joinWith, StarlarkCallable mapEach, StarlarkThread thread)
89+
String key,
90+
Depset values,
91+
String joinWith,
92+
StarlarkCallable mapEach,
93+
Boolean uniquify,
94+
StarlarkThread thread)
8095
throws EvalException;
8196
}

src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java

+57
Original file line numberDiff line numberDiff line change
@@ -3683,6 +3683,63 @@ public void testTemplateExpansionComputedSubstitution() throws Exception {
36833683
"td_files_key", "foo.txt%%bar.txt%%baz.txt"));
36843684
}
36853685

3686+
@Test
3687+
public void testTemplateExpansionComputedSubstitutionWithUniquify() throws Exception {
3688+
setBuildLanguageOptions("--experimental_lazy_template_expansion");
3689+
scratch.file(
3690+
"test/rules.bzl",
3691+
"def _artifact_to_extension(file):",
3692+
" return file.extension",
3693+
"",
3694+
"def _undertest_impl(ctx):",
3695+
" template_dict = ctx.actions.template_dict()",
3696+
" template_dict.add_joined('exts', depset(ctx.files.srcs),",
3697+
" map_each = _artifact_to_extension,",
3698+
" uniquify = True,",
3699+
" join_with = '%%',",
3700+
" )",
3701+
" ctx.actions.expand_template(output=ctx.outputs.out,",
3702+
" template=ctx.file.template,",
3703+
" computed_substitutions=template_dict,",
3704+
" )",
3705+
"undertest_rule = rule(",
3706+
" implementation = _undertest_impl,",
3707+
" outputs = {'out': '%{name}.txt'},",
3708+
" attrs = {'template': attr.label(allow_single_file=True),",
3709+
" 'srcs':attr.label_list(allow_files=True)",
3710+
" },",
3711+
" _skylark_testable = True,",
3712+
")",
3713+
testingRuleDefinition);
3714+
scratch.file("test/template.txt", "exts", "exts");
3715+
scratch.file(
3716+
"test/BUILD",
3717+
"load(':rules.bzl', 'undertest_rule', 'testing_rule')",
3718+
"undertest_rule(",
3719+
" name = 'undertest',",
3720+
" template = ':template.txt',",
3721+
" srcs = ['foo.txt', 'bar.log', 'baz.txt', 'bak.exe', 'far.sh', 'boo.sh'],",
3722+
")",
3723+
"testing_rule(",
3724+
" name = 'testing',",
3725+
" dep = ':undertest',",
3726+
")");
3727+
StarlarkRuleContext ruleContext = createRuleContext("//test:testing");
3728+
setRuleContext(ruleContext);
3729+
ev.update("file", ev.eval("ruleContext.attr.dep.files.to_list()[0]"));
3730+
ev.update("action", ev.eval("ruleContext.attr.dep[Actions].by_file[file]"));
3731+
3732+
assertThat(ev.eval("type(action)")).isEqualTo("Action");
3733+
3734+
Object contentUnchecked = ev.eval("action.content");
3735+
assertThat(contentUnchecked).isInstanceOf(String.class);
3736+
assertThat(contentUnchecked).isEqualTo("txt%%log%%exe%%sh\ntxt%%log%%exe%%sh\n");
3737+
3738+
Object substitutionsUnchecked = ev.eval("action.substitutions");
3739+
assertThat(substitutionsUnchecked).isInstanceOf(Dict.class);
3740+
assertThat(substitutionsUnchecked).isEqualTo(ImmutableMap.of("exts", "txt%%log%%exe%%sh"));
3741+
}
3742+
36863743
@Test
36873744
public void testTemplateExpansionComputedSubstitutionDuplicateKeys() throws Exception {
36883745
setBuildLanguageOptions("--experimental_lazy_template_expansion");

0 commit comments

Comments
 (0)