diff options
Diffstat (limited to 'src/main/java')
7 files changed, 272 insertions, 5 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java b/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java index 44623f7ef6..4a1c283a3a 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java @@ -17,6 +17,7 @@ import static com.google.devtools.build.lib.packages.BuildType.DISTRIBUTIONS; import static com.google.devtools.build.lib.packages.BuildType.FILESET_ENTRY_LIST; import static com.google.devtools.build.lib.packages.BuildType.LABEL; import static com.google.devtools.build.lib.packages.BuildType.LABEL_DICT_UNARY; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_KEYED_STRING_DICT; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.packages.BuildType.LICENSE; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; @@ -45,6 +46,7 @@ import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Sele import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.SelectorEntry.Builder; import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Tristate; import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelDictUnaryEntry; +import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelKeyedStringDictEntry; import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelListDictEntry; import com.google.devtools.build.lib.query2.proto.proto2api.Build.StringDictEntry; import com.google.devtools.build.lib.query2.proto.proto2api.Build.StringDictUnaryEntry; @@ -61,7 +63,15 @@ public class AttributeFormatter { private static final ImmutableSet<Type<?>> depTypes = ImmutableSet.<Type<?>>of( - STRING, LABEL, OUTPUT, STRING_LIST, LABEL_LIST, OUTPUT_LIST, DISTRIBUTIONS); + STRING, + LABEL, + OUTPUT, + STRING_LIST, + LABEL_LIST, + LABEL_DICT_UNARY, + LABEL_KEYED_STRING_DICT, + OUTPUT_LIST, + DISTRIBUTIONS); private static final ImmutableSet<Type<?>> noDepTypes = ImmutableSet.<Type<?>>of(NODEP_LABEL_LIST, NODEP_LABEL); @@ -230,6 +240,15 @@ public class AttributeFormatter { .setValue(dictEntry.getValue().toString()); builder.addLabelDictUnaryValue(entry); } + } else if (type == LABEL_KEYED_STRING_DICT) { + Map<Label, String> dict = (Map<Label, String>) value; + for (Map.Entry<Label, String> dictEntry : dict.entrySet()) { + LabelKeyedStringDictEntry.Builder entry = + LabelKeyedStringDictEntry.newBuilder() + .setKey(dictEntry.getKey().toString()) + .setValue(dictEntry.getValue()); + builder.addLabelKeyedStringDictValue(entry); + } } else if (type == FILESET_ENTRY_LIST) { List<FilesetEntry> filesetEntries = (List<FilesetEntry>) value; for (FilesetEntry filesetEntry : filesetEntries) { @@ -302,6 +321,8 @@ public class AttributeFormatter { void addLabelDictUnaryValue(LabelDictUnaryEntry.Builder builder); + void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder); + void addLabelListDictValue(LabelListDictEntry.Builder builder); void addIntListValue(int i); @@ -362,6 +383,11 @@ public class AttributeFormatter { } @Override + public void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder) { + attributeBuilder.addLabelKeyedStringDictValue(builder); + } + + @Override public void addLabelListDictValue(LabelListDictEntry.Builder builder) { attributeBuilder.addLabelListDictValue(builder); } @@ -488,6 +514,11 @@ public class AttributeFormatter { } @Override + public void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder) { + selectorEntryBuilder.addLabelKeyedStringDictValue(builder); + } + + @Override public void addLabelListDictValue(LabelListDictEntry.Builder builder) { selectorEntryBuilder.addLabelListDictValue(builder); } diff --git a/src/main/java/com/google/devtools/build/lib/packages/BuildType.java b/src/main/java/com/google/devtools/build/lib/packages/BuildType.java index 051b7e033f..263fe63e79 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/BuildType.java +++ b/src/main/java/com/google/devtools/build/lib/packages/BuildType.java @@ -15,6 +15,7 @@ package com.google.devtools.build.lib.packages; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -22,6 +23,7 @@ import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.packages.License.LicenseParsingException; +import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SelectorValue; import com.google.devtools.build.lib.syntax.Type; @@ -29,6 +31,7 @@ import com.google.devtools.build.lib.syntax.Type.ConversionException; import com.google.devtools.build.lib.syntax.Type.DictType; import com.google.devtools.build.lib.syntax.Type.LabelClass; import com.google.devtools.build.lib.syntax.Type.ListType; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -55,6 +58,11 @@ public final class BuildType { public static final DictType<String, Label> LABEL_DICT_UNARY = DictType.create( Type.STRING, LABEL); /** + * The type of a dictionary keyed by {@linkplain #LABEL labels} with string values. + */ + public static final DictType<Label, String> LABEL_KEYED_STRING_DICT = + LabelKeyedDictType.create(Type.STRING); + /** * The type of a list of {@linkplain #LABEL labels}. */ public static final ListType<Label> LABEL_LIST = ListType.create(LABEL); @@ -247,6 +255,70 @@ public final class BuildType { } /** + * Dictionary type specialized for label keys, which is able to detect collisions caused by the + * fact that labels have multiple equivalent representations in Skylark code. + */ + private static class LabelKeyedDictType<ValueT> extends DictType<Label, ValueT> { + private LabelKeyedDictType(Type<ValueT> valueType) { + super(LABEL, valueType, LabelClass.DEPENDENCY); + } + + public static <ValueT> LabelKeyedDictType<ValueT> create(Type<ValueT> valueType) { + Preconditions.checkArgument( + valueType.getLabelClass() == LabelClass.NONE + || valueType.getLabelClass() == LabelClass.DEPENDENCY, + "Values associated with label keys must not be labels themselves."); + return new LabelKeyedDictType<>(valueType); + } + + @Override + public Map<Label, ValueT> convert(Object x, Object what, Object context) + throws ConversionException { + Map<Label, ValueT> result = super.convert(x, what, context); + // The input is known to be a map because super.convert succeded; otherwise, a + // ConversionException would have been thrown. + Map<?, ?> input = (Map<?, ?>) x; + + if (input.size() == result.size()) { + // No collisions found. Exit early. + return result; + } + // Look for collisions in order to produce a nicer error message. + Map<Label, List<Object>> convertedFrom = new LinkedHashMap<>(); + for (Object original : input.keySet()) { + Label label = LABEL.convert(original, what, context); + if (!convertedFrom.containsKey(label)) { + convertedFrom.put(label, new ArrayList<Object>()); + } + convertedFrom.get(label).add(original); + } + StringBuilder errorMessage = new StringBuilder(); + errorMessage.append("duplicate labels"); + if (what != null) { + errorMessage.append(" in ").append(what); + } + errorMessage.append(':'); + boolean isFirstEntry = true; + for (Map.Entry<Label, List<Object>> entry : convertedFrom.entrySet()) { + if (entry.getValue().size() == 1) { + continue; + } + if (isFirstEntry) { + isFirstEntry = false; + } else { + errorMessage.append(','); + } + errorMessage.append(' '); + errorMessage.append(entry.getKey()); + errorMessage.append(" (as "); + Printer.write(errorMessage, entry.getValue()); + errorMessage.append(')'); + } + throw new ConversionException(errorMessage.toString()); + } + } + + /** * Like Label, LicenseType is a derived type, which is declared specially * in order to allow syntax validation. It represents the licenses, as * described in {@ref License}. diff --git a/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java b/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java index c34ad2e224..7c0335d406 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java +++ b/src/main/java/com/google/devtools/build/lib/packages/ProtoUtils.java @@ -18,6 +18,7 @@ import static com.google.devtools.build.lib.packages.BuildType.DISTRIBUTIONS; import static com.google.devtools.build.lib.packages.BuildType.FILESET_ENTRY_LIST; import static com.google.devtools.build.lib.packages.BuildType.LABEL; import static com.google.devtools.build.lib.packages.BuildType.LABEL_DICT_UNARY; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_KEYED_STRING_DICT; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.packages.BuildType.LICENSE; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; @@ -68,6 +69,7 @@ public class ProtoUtils { .put(TRISTATE, Discriminator.TRISTATE) .put(INTEGER_LIST, Discriminator.INTEGER_LIST) .put(STRING_DICT_UNARY, Discriminator.STRING_DICT_UNARY) + .put(LABEL_KEYED_STRING_DICT, Discriminator.LABEL_KEYED_STRING_DICT) .build(); /** Returns the {@link Discriminator} value corresponding to the provided {@link Type}. */ diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java index 317888d073..66e1182c74 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java @@ -432,7 +432,8 @@ public class ProtoOutputFormatter extends AbstractUnorderedFormatter { if (attrType == Type.STRING_DICT || attrType == Type.STRING_DICT_UNARY || attrType == Type.STRING_LIST_DICT - || attrType == BuildType.LABEL_DICT_UNARY) { + || attrType == BuildType.LABEL_DICT_UNARY + || attrType == BuildType.LABEL_KEYED_STRING_DICT) { Map<Object, Object> mergedDict = new HashMap<>(); for (Object possibleValue : possibleValues) { Map<Object, Object> stringDict = (Map<Object, Object>) possibleValue; diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java index 08113b6f20..b28b08e07c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java @@ -922,6 +922,158 @@ public final class SkylarkAttr { }; @SkylarkSignature( + name = "label_keyed_string_dict", + doc = + "Creates an attribute which is a <a href=\"dict.html\">dict</a>. Its keys are type " + + "<a href=\"Target.html\">Target</a> and are specified by the label keys of the " + + "input dict. Its values are <a href=\"string.html\">strings</a>. See " + + "<a href=\"attr.html#label\">label</a> for more information.", + objectType = SkylarkAttr.class, + returnType = Descriptor.class, + parameters = { + @Param( + name = DEFAULT_ARG, + type = SkylarkDict.class, + callbackEnabled = true, + defaultValue = "{}", + named = true, + positional = false, + doc = + DEFAULT_DOC + + " Use the <a href=\"globals.html#Label\"><code>Label</code></a> function to " + + "specify default values ex:</p>" + + "<code>attr.label_keyed_string_dict(default = " + + "{ Label(\"//a:b\"): \"value\", Label(\"//a:c\"): \"string\" })</code>" + ), + @Param( + name = ALLOW_FILES_ARG, // bool or FileType filter + defaultValue = "None", + named = true, + positional = false, + doc = ALLOW_FILES_DOC + ), + @Param( + name = ALLOW_RULES_ARG, + type = SkylarkList.class, + generic1 = String.class, + noneable = true, + defaultValue = "None", + named = true, + positional = false, + doc = ALLOW_RULES_DOC + ), + @Param( + name = PROVIDERS_ARG, + type = SkylarkList.class, + defaultValue = "[]", + named = true, + positional = false, + doc = PROVIDERS_DOC + ), + @Param( + name = FLAGS_ARG, + type = SkylarkList.class, + generic1 = String.class, + defaultValue = "[]", + named = true, + positional = false, + doc = FLAGS_DOC + ), + @Param( + name = MANDATORY_ARG, + type = Boolean.class, + defaultValue = "False", + named = true, + positional = false, + doc = MANDATORY_DOC + ), + @Param( + name = NON_EMPTY_ARG, + type = Boolean.class, + defaultValue = "False", + named = true, + positional = false, + doc = NON_EMPTY_DOC + ), + @Param( + name = ALLOW_EMPTY_ARG, + type = Boolean.class, + defaultValue = "True", + doc = ALLOW_EMPTY_DOC + ), + @Param( + name = CONFIGURATION_ARG, + type = Object.class, + noneable = true, + defaultValue = "None", + named = true, + positional = false, + doc = CONFIGURATION_DOC + ), + @Param( + name = ASPECTS_ARG, + type = SkylarkList.class, + generic1 = SkylarkAspect.class, + defaultValue = "[]", + named = true, + positional = false, + doc = ASPECTS_ARG_DOC + ) + }, + useAst = true, + useEnvironment = true + ) + private static BuiltinFunction labelKeyedStringDict = + new BuiltinFunction("label_keyed_string_dict") { + public Descriptor invoke( + Object defaultList, + Object allowFiles, + Object allowRules, + SkylarkList<?> providers, + SkylarkList<?> flags, + Boolean mandatory, + Boolean nonEmpty, + Boolean allowEmpty, + Object cfg, + SkylarkList<?> aspects, + FuncallExpression ast, + Environment env) + throws EvalException { + env.checkLoadingOrWorkspacePhase("attr.label_keyed_string_dict", ast.getLocation()); + SkylarkDict<String, Object> kwargs = + EvalUtils.<String, Object>optionMap( + env, + DEFAULT_ARG, + defaultList, + ALLOW_FILES_ARG, + allowFiles, + ALLOW_RULES_ARG, + allowRules, + PROVIDERS_ARG, + providers, + FLAGS_ARG, + flags, + MANDATORY_ARG, + mandatory, + NON_EMPTY_ARG, + nonEmpty, + ALLOW_EMPTY_ARG, + allowEmpty, + CONFIGURATION_ARG, + cfg); + try { + Attribute.Builder<?> attribute = + createAttribute(BuildType.LABEL_KEYED_STRING_DICT, kwargs, ast, env); + ImmutableList<SkylarkAspect> skylarkAspects = + ImmutableList.copyOf(aspects.getContents(SkylarkAspect.class, "aspects")); + return new Descriptor(attribute, skylarkAspects); + } catch (EvalException e) { + throw new EvalException(ast.getLocation(), e.getMessage(), e); + } + } + }; + + @SkylarkSignature( name = "bool", doc = "Creates an attribute of type bool.", objectType = SkylarkAttr.class, diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java index 5414bd993c..def045239b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java @@ -344,6 +344,16 @@ public final class SkylarkRuleContext { || (type == BuildType.LABEL && a.hasSplitConfigurationTransition())) { List<?> allPrereq = ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK); attrBuilder.put(skyname, SkylarkList.createImmutable(allPrereq)); + } else if (type == BuildType.LABEL_KEYED_STRING_DICT) { + ImmutableMap.Builder<TransitiveInfoCollection, String> builder = + new ImmutableMap.Builder<>(); + Map<Label, String> original = BuildType.LABEL_KEYED_STRING_DICT.cast(val); + List<? extends TransitiveInfoCollection> allPrereq = + ruleContext.getPrerequisites(a.getName(), Mode.DONT_CHECK); + for (TransitiveInfoCollection prereq : allPrereq) { + builder.put(prereq, original.get(prereq.getLabel())); + } + attrBuilder.put(skyname, SkylarkType.convertToSkylark(builder.build(), null)); } else if (type == BuildType.LABEL_DICT_UNARY) { Map<Label, TransitiveInfoCollection> prereqsByLabel = new LinkedHashMap<>(); for (TransitiveInfoCollection target diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Type.java b/src/main/java/com/google/devtools/build/lib/syntax/Type.java index 387794b40f..fc89927c42 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Type.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Type.java @@ -475,7 +475,7 @@ public abstract class Type<T> { return new DictType<>(keyType, valueType, labelClass); } - private DictType(Type<KeyT> keyType, Type<ValueT> valueType, LabelClass labelClass) { + protected DictType(Type<KeyT> keyType, Type<ValueT> valueType, LabelClass labelClass) { this.keyType = keyType; this.valueType = valueType; this.labelClass = labelClass; @@ -509,8 +509,7 @@ public abstract class Type<T> { public Map<KeyT, ValueT> convert(Object x, Object what, Object context) throws ConversionException { if (!(x instanceof Map<?, ?>)) { - throw new ConversionException(String.format( - "Expected a map for dictionary but got a %s", x.getClass().getName())); + throw new ConversionException(this, x, what); } // Order the keys so the return value will be independent of insertion order. Map<KeyT, ValueT> result = new TreeMap<>(); |