diff options
9 files changed, 283 insertions, 66 deletions
diff --git a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java index 0d6f5d691a..c68dae2dbc 100644 --- a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java +++ b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java @@ -45,6 +45,8 @@ class RuleDocumentationAttribute implements Comparable<RuleDocumentationAttribut .put(Type.TRISTATE, "Integer") .put(Type.LABEL, "<a href=\"build-ref.html#labels\">Label</a>") .put(Type.LABEL_LIST, "List of <a href=\"build-ref.html#labels\">labels</a>") + .put(Type.LABEL_DICT_UNARY, + "Dictionary mapping strings to <a href=\"build-ref.html#labels\">labels</a>") .put(Type.LABEL_LIST_DICT, "Dictionary mapping strings to lists of <a href=\"build-ref.html#labels\">labels</a>") .put(Type.NODEP_LABEL, "<a href=\"build-ref.html#name\">Name</a>") diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageSerializer.java b/src/main/java/com/google/devtools/build/lib/packages/PackageSerializer.java index 75067b726d..16b318cb62 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageSerializer.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageSerializer.java @@ -19,6 +19,7 @@ import static com.google.devtools.build.lib.packages.Type.FILESET_ENTRY_LIST; import static com.google.devtools.build.lib.packages.Type.INTEGER; import static com.google.devtools.build.lib.packages.Type.INTEGER_LIST; import static com.google.devtools.build.lib.packages.Type.LABEL; +import static com.google.devtools.build.lib.packages.Type.LABEL_DICT_UNARY; import static com.google.devtools.build.lib.packages.Type.LABEL_LIST; import static com.google.devtools.build.lib.packages.Type.LABEL_LIST_DICT; import static com.google.devtools.build.lib.packages.Type.LICENSE; @@ -252,6 +253,17 @@ public class PackageSerializer { attrPb.addStringListDictValue(entry); } } + } else if (type == LABEL_DICT_UNARY) { + for (Object value : values) { + Map<String, Label> dict = (Map<String, Label>) value; + for (Map.Entry<String, Label> dictEntry : dict.entrySet()) { + Build.LabelDictUnaryEntry entry = Build.LabelDictUnaryEntry.newBuilder() + .setKey(dictEntry.getKey()) + .setValue(dictEntry.getValue().toString()) + .build(); + attrPb.addLabelDictUnaryValue(entry); + } + } } else if (type == LABEL_LIST_DICT) { for (Object value : values) { Map<String, List<Label>> dict = (Map<String, List<Label>>) value; 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 7b6eaf1efa..653e283e84 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 @@ -20,6 +20,7 @@ import static com.google.devtools.build.lib.packages.Type.FILESET_ENTRY_LIST; import static com.google.devtools.build.lib.packages.Type.INTEGER; import static com.google.devtools.build.lib.packages.Type.INTEGER_LIST; import static com.google.devtools.build.lib.packages.Type.LABEL; +import static com.google.devtools.build.lib.packages.Type.LABEL_DICT_UNARY; import static com.google.devtools.build.lib.packages.Type.LABEL_LIST; import static com.google.devtools.build.lib.packages.Type.LABEL_LIST_DICT; import static com.google.devtools.build.lib.packages.Type.LICENSE; @@ -65,6 +66,7 @@ public class ProtoUtils { .put(LICENSE, Discriminator.LICENSE) .put(STRING_DICT, Discriminator.STRING_DICT) .put(FILESET_ENTRY_LIST, Discriminator.FILESET_ENTRY_LIST) + .put(LABEL_DICT_UNARY, Discriminator.LABEL_DICT_UNARY) .put(LABEL_LIST_DICT, Discriminator.LABEL_LIST_DICT) .put(STRING_LIST_DICT, Discriminator.STRING_LIST_DICT) .put(BOOLEAN, Discriminator.BOOLEAN) diff --git a/src/main/java/com/google/devtools/build/lib/packages/Type.java b/src/main/java/com/google/devtools/build/lib/packages/Type.java index b1ca03e8a0..96191626dc 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/Type.java +++ b/src/main/java/com/google/devtools/build/lib/packages/Type.java @@ -336,6 +336,11 @@ public abstract class Type<T> { DictType.create(STRING, LABEL_LIST); /** + * The type of a dictionary of {@linkplain #LABEL labels}. + */ + public static final DictType<String, Label> LABEL_DICT_UNARY = DictType.create(STRING, LABEL); + + /** * The type of a list of {@linkplain #FILESET_ENTRY FilesetEntries}. */ public static final ListType<FilesetEntry> FILESET_ENTRY_LIST = ListType.create(FILESET_ENTRY); @@ -1003,7 +1008,7 @@ public abstract class Type<T> { * Returns whether the specified type is a label type or not. */ public static boolean isLabelType(Type<?> type) { - return type == LABEL || type == LABEL_LIST + return type == LABEL || type == LABEL_LIST || type == LABEL_DICT_UNARY || type == NODEP_LABEL || type == NODEP_LABEL_LIST || type == LABEL_LIST_DICT || type == FILESET_ENTRY_LIST; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuite.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuite.java new file mode 100644 index 0000000000..17d14feb37 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuite.java @@ -0,0 +1,39 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.rules.cpp; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; + +/** + * Implementation of the {@code cc_toolchain_suite} rule. + * + * <p>This is currently a no-op because the logic that transforms this rule into something that can + * be understood by the {@code cc_*} rules is in + * {@link com.google.devtools.build.lib.rules.cpp.CppConfigurationLoader}. + */ +public class CcToolchainSuite implements RuleConfiguredTargetFactory { + + @Override + public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException { + return new RuleConfiguredTargetBuilder(ruleContext) + .setFilesToBuild(NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)) + .build(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuiteRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuiteRule.java new file mode 100644 index 0000000000..9bc59c9d24 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainSuiteRule.java @@ -0,0 +1,50 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.rules.cpp; + +import static com.google.devtools.build.lib.packages.Attribute.attr; + +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleClass.Builder; +import com.google.devtools.build.lib.packages.Type; + +/** + * Definition of the {@code cc_toolchain_sute} rule. + */ +public final class CcToolchainSuiteRule implements RuleDefinition { + + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) { + return builder + .setUndocumented() + .add(attr("toolchains", Type.LABEL_DICT_UNARY) + .mandatory() + .nonconfigurable("Used during configuration creation")) + .add(attr("proto", Type.STRING) + .nonconfigurable("Used during configuration creation")) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("cc_toolchain_suite") + .ancestors(BaseRuleClasses.BaseRule.class) + .factoryClass(CcToolchainSuite.class) + .build(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java index bf26f630f8..9d4818ef24 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationLoader.java @@ -28,8 +28,10 @@ import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.NoSuchThingException; +import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.Label.SyntaxException; import com.google.devtools.build.lib.vfs.Path; @@ -108,13 +110,14 @@ public class CppConfigurationLoader implements ConfigurationFragmentFactory { if (directories == null) { return null; } - Label crosstoolTop = RedirectChaser.followRedirects(env, + Label crosstoolTopLabel = RedirectChaser.followRedirects(env, options.get(CppOptions.class).crosstoolTop, "crosstool_top"); - if (crosstoolTop == null) { + if (crosstoolTopLabel == null) { return null; } + CrosstoolConfigurationLoader.CrosstoolFile file = - CrosstoolConfigurationLoader.readCrosstool(env, crosstoolTop); + CrosstoolConfigurationLoader.readCrosstool(env, crosstoolTopLabel); if (file == null) { return null; } @@ -150,12 +153,33 @@ public class CppConfigurationLoader implements ConfigurationFragmentFactory { } Label ccToolchainLabel; + Target crosstoolTop; + try { - ccToolchainLabel = crosstoolTop.getRelative("cc-compiler-" + toolchain.getTargetCpu()); - } catch (Label.SyntaxException e) { - throw new InvalidConfigurationException(String.format( - "'%s' is not a valid CPU. It should only consist of characters valid in labels", - toolchain.getTargetCpu())); + crosstoolTop = env.getTarget(crosstoolTopLabel); + } catch (NoSuchThingException e) { + throw new IllegalStateException(e); // Should have been found out during redirect chasing + } + + if (crosstoolTop instanceof Rule + && ((Rule) crosstoolTop).getRuleClass().equals("cc_toolchain_suite")) { + Rule ccToolchainSuite = (Rule) crosstoolTop; + ccToolchainLabel = NonconfigurableAttributeMapper.of(ccToolchainSuite) + .get("toolchains", Type.LABEL_DICT_UNARY) + .get(toolchain.getTargetCpu()); + if (ccToolchainLabel == null) { + throw new InvalidConfigurationException(String.format( + "cc_toolchain_suite '%s' does not contain a toolchain for CPU '%s'", + crosstoolTopLabel, toolchain.getTargetCpu())); + } + } else { + try { + ccToolchainLabel = crosstoolTopLabel.getRelative("cc-compiler-" + toolchain.getTargetCpu()); + } catch (Label.SyntaxException e) { + throw new InvalidConfigurationException(String.format( + "'%s' is not a valid CPU. It should only consist of characters valid in labels", + toolchain.getTargetCpu())); + } } Target ccToolchain; @@ -176,6 +200,6 @@ public class CppConfigurationLoader implements ConfigurationFragmentFactory { } return new CppConfigurationParameters(toolchain, file.getMd5(), options, - fdoZip, directories.getExecRoot(), crosstoolTop, ccToolchainLabel); + fdoZip, directories.getExecRoot(), crosstoolTopLabel, ccToolchainLabel); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java index a113f5f126..e5957581ae 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoader.java @@ -14,24 +14,30 @@ package com.google.devtools.build.lib.rules.cpp; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; +import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.io.BaseEncoding; import com.google.devtools.build.lib.analysis.RedirectChaser; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.packages.NoSuchThingException; +import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.Label.SyntaxException; -import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig; +import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CrosstoolRelease; import com.google.protobuf.TextFormat; import com.google.protobuf.TextFormat.ParseException; import com.google.protobuf.UninitializedMessageException; @@ -39,6 +45,7 @@ import com.google.protobuf.UninitializedMessageException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; @@ -46,6 +53,10 @@ import javax.annotation.Nullable; /** * A loader that reads Crosstool configuration files and creates CToolchain * instances from them. + * + * <p>Note that this class contains a cache for the text format -> proto objects mapping of + * Crosstool protos that is completely independent from Skyframe or anything else. This should be + * done in a saner way. */ public class CrosstoolConfigurationLoader { private static final String CROSSTOOL_CONFIGURATION_FILENAME = "CROSSTOOL"; @@ -54,53 +65,27 @@ public class CrosstoolConfigurationLoader { * Cache for storing result of toReleaseConfiguration function based on path and md5 sum of * input file. We can use md5 because result of this function depends only on the file content. */ - private static final LoadingCache<Pair<Path, String>, CrosstoolConfig.CrosstoolRelease> - crosstoolReleaseCache = CacheBuilder.newBuilder().concurrencyLevel(4).maximumSize(100).build( - new CacheLoader<Pair<Path, String>, CrosstoolConfig.CrosstoolRelease>() { - @Override - public CrosstoolConfig.CrosstoolRelease load(Pair<Path, String> key) throws IOException { - char[] data = FileSystemUtils.readContentAsLatin1(key.first); - return toReleaseConfiguration(key.first.getPathString(), new String(data)); - } - }); - + private static final Cache<String, CrosstoolRelease> crosstoolReleaseCache = + CacheBuilder.newBuilder().concurrencyLevel(4).maximumSize(100).build(); /** * A class that holds the results of reading a CROSSTOOL file. */ public static class CrosstoolFile { - private final Label crosstoolTop; - private Path crosstoolPath; - private CrosstoolConfig.CrosstoolRelease crosstool; - private String md5; - - CrosstoolFile(Label crosstoolTop) { - this.crosstoolTop = crosstoolTop; - } - - void setCrosstoolPath(Path crosstoolPath) { - this.crosstoolPath = crosstoolPath; - } + private final String location; + private final CrosstoolConfig.CrosstoolRelease crosstool; + private final String md5; - void setCrosstool(CrosstoolConfig.CrosstoolRelease crosstool) { + CrosstoolFile(String location, CrosstoolConfig.CrosstoolRelease crosstool, String md5) { + this.location = location; this.crosstool = crosstool; - } - - void setMd5(String md5) { this.md5 = md5; } /** - * Returns the crosstool top as resolved. - */ - public Label getCrosstoolTop() { - return crosstoolTop; - } - - /** - * Returns the absolute path from which the CROSSTOOL file was read. + * Returns a user-friendly location of the CROSSTOOL proto for error messages. */ - public Path getCrosstoolPath() { - return crosstoolPath; + public String getLocation() { + return location; } /** @@ -145,27 +130,115 @@ public class CrosstoolConfigurationLoader { } } - private static boolean findCrosstoolConfiguration( - ConfigurationEnvironment env, - CrosstoolConfigurationLoader.CrosstoolFile file) + /** + * This class is the in-memory representation of a text-formatted Crosstool proto file. + * + * <p>This layer of abstraction is here so that we can load these protos either from BUILD files + * or from CROSSTOOL files. + * + * <p>An implementation of this class should override {@link #getContents()} and call + * the constructor with the MD5 checksum of what that method will return and a human-readable name + * used in error messages. + */ + private abstract static class CrosstoolProto { + private final byte[] md5; + private final String name; + + private CrosstoolProto(byte[] md5, String name) { + this.md5 = md5; + this.name = name; + } + + /** + * The binary MD5 checksum of the proto. + */ + public byte[] getMd5() { + return md5; + } + + /** + * A user-friendly string describing the location of the proto. + */ + public String getName() { + return name; + } + + /** + * The proto itself. + */ + public abstract String getContents() throws IOException; + } + + private static CrosstoolProto getCrosstoolProtofromBuildFile( + ConfigurationEnvironment env, Label crosstoolTop) { + Target target; + try { + target = env.getTarget(crosstoolTop); + } catch (NoSuchThingException e) { + throw new IllegalStateException(e); // Should have beeen evaluated by RedirectChaser + } + + if (!(target instanceof Rule)) { + return null; + } + + Rule rule = (Rule) target; + if (!(rule.getRuleClass().equals("cc_toolchain_suite")) + || !rule.isAttributeValueExplicitlySpecified("proto")) { + return null; + } + + final String contents = NonconfigurableAttributeMapper.of(rule).get("proto", Type.STRING); + byte[] md5 = new Fingerprint().addBytes(contents.getBytes(UTF_8)).digestAndReset(); + return new CrosstoolProto(md5, "cc_toolchain_suite rule " + crosstoolTop.toString()) { + + @Override + public String getContents() throws IOException { + return contents; + } + }; + } + + private static CrosstoolProto getCrosstoolProtoFromCrosstoolFile( + ConfigurationEnvironment env, Label crosstoolTop) throws IOException, InvalidConfigurationException { - Label crosstoolTop = file.getCrosstoolTop(); - Path path = null; + final Path path; try { Package containingPackage = env.getTarget(crosstoolTop.getLocalTargetLabel("BUILD")) .getPackage(); if (containingPackage == null) { - return false; + return null; } path = env.getPath(containingPackage, CROSSTOOL_CONFIGURATION_FILENAME); } catch (SyntaxException e) { throw new InvalidConfigurationException(e); } catch (NoSuchThingException e) { // Handled later + return null; } - // If we can't find a file, fall back to the provided alternative. if (path == null || !path.exists()) { + return null; + } + + return new CrosstoolProto(path.getMD5Digest(), "CROSSTOOL file " + path.getPathString()) { + @Override + public String getContents() throws IOException { + return new String(FileSystemUtils.readContentAsLatin1(path.getInputStream())); + } + }; + } + + private static CrosstoolFile findCrosstoolConfiguration( + ConfigurationEnvironment env, Label crosstoolTop) + throws IOException, InvalidConfigurationException { + + CrosstoolProto crosstoolProto = getCrosstoolProtofromBuildFile(env, crosstoolTop); + if (crosstoolProto == null) { + crosstoolProto = getCrosstoolProtoFromCrosstoolFile(env, crosstoolTop); + } + + if (crosstoolProto == null) { throw new InvalidConfigurationException("The crosstool_top you specified was resolved to '" + crosstoolTop + "', which does not contain a CROSSTOOL file. " + "You can use a crosstool from the depot by specifying its label."); @@ -173,18 +246,22 @@ public class CrosstoolConfigurationLoader { // Do this before we read the data, so if it changes, we get a different MD5 the next time. // Alternatively, we could calculate the MD5 of the contents, which we also read, but this // is faster if the file comes from a file system with md5 support. - file.setCrosstoolPath(path); - String md5 = BaseEncoding.base16().lowerCase().encode(path.getMD5Digest()); + final CrosstoolProto finalProto = crosstoolProto; + String md5 = BaseEncoding.base16().lowerCase().encode(finalProto.getMd5()); CrosstoolConfig.CrosstoolRelease release; try { - release = crosstoolReleaseCache.get(new Pair<Path, String>(path, md5)); - file.setCrosstool(release); - file.setMd5(md5); + release = crosstoolReleaseCache.get(md5, new Callable<CrosstoolRelease>() { + @Override + public CrosstoolRelease call() throws Exception { + return toReleaseConfiguration(finalProto.getName(), finalProto.getContents()); + } + }); } catch (ExecutionException e) { throw new InvalidConfigurationException(e); } + + return new CrosstoolFile(finalProto.getName(), release, md5); } - return true; } /** @@ -197,11 +274,8 @@ public class CrosstoolConfigurationLoader { if (crosstoolTop == null) { return null; } - CrosstoolConfigurationLoader.CrosstoolFile file = - new CrosstoolConfigurationLoader.CrosstoolFile(crosstoolTop); try { - boolean allDependenciesPresent = findCrosstoolConfiguration(env, file); - return allDependenciesPresent ? file : null; + return findCrosstoolConfiguration(env, crosstoolTop); } catch (IOException e) { throw new InvalidConfigurationException(e); } diff --git a/src/main/protobuf/build.proto b/src/main/protobuf/build.proto index a84df42b43..6780d4d844 100644 --- a/src/main/protobuf/build.proto +++ b/src/main/protobuf/build.proto @@ -38,6 +38,11 @@ message StringDictUnaryEntry { required string value = 2; } +message LabelDictUnaryEntry { + required string key = 1; + required string value = 2; +} + message LabelListDictEntry { required string key = 1; repeated string value = 2; @@ -112,6 +117,7 @@ message Attribute { INTEGER_LIST = 16; // int_list_value STRING_DICT_UNARY = 17; // string_dict_unary_value UNKNOWN = 18; // unknown type, use only for build extensions + LABEL_DICT_UNARY = 19; // label_dict_unary_value } // Values for the TriState field type. @@ -186,6 +192,9 @@ message Attribute { // If this is a string dict unary, each entry will be stored here. repeated StringDictUnaryEntry string_dict_unary_value = 18; + + // If this is a label dict unary, each entry will be stored here. + repeated LabelDictUnaryEntry label_dict_unary_value = 19; } // A rule from a BUILD file (e.g., cc_library, java_binary). The rule class |