Allows Stardoc to document the providers a rule attribute requires
PiperOrigin-RevId: 262222091
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java
index 1793dad..30668a4 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeDescriptor.java
@@ -18,6 +18,8 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeType;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderNameGroup;
+import java.util.List;
/**
* Fake implementation of {@link Descriptor}.
@@ -26,34 +28,38 @@
private final AttributeType type;
private final String docString;
private final boolean mandatory;
+ private final List<List<String>> providerNameGroups;
- public FakeDescriptor(AttributeType type, String docString, boolean mandatory) {
+ public FakeDescriptor(
+ AttributeType type,
+ String docString,
+ boolean mandatory,
+ List<List<String>> providerNameGroups) {
this.type = type;
this.docString = docString;
this.mandatory = mandatory;
- }
-
- public AttributeType getType() {
- return type;
- }
-
- public String getDocString() {
- return docString;
- }
-
- public boolean isMandatory() {
- return mandatory;
+ this.providerNameGroups = providerNameGroups;
}
@Override
public void repr(SkylarkPrinter printer) {}
public AttributeInfo asAttributeInfo(String attributeName) {
- return AttributeInfo.newBuilder()
- .setName(attributeName)
- .setDocString(getDocString())
- .setType(getType())
- .setMandatory(isMandatory())
- .build();
+ AttributeInfo.Builder attrInfo =
+ AttributeInfo.newBuilder()
+ .setName(attributeName)
+ .setDocString(docString)
+ .setType(type)
+ .setMandatory(mandatory);
+
+ if (!providerNameGroups.isEmpty()) {
+ for (List<String> providerNameGroup : providerNameGroups) {
+ ProviderNameGroup.Builder providerNameListBuild = ProviderNameGroup.newBuilder();
+ ProviderNameGroup providerNameList =
+ providerNameListBuild.addAllProviderName(providerNameGroup).build();
+ attrInfo.addProviderNameGroup(providerNameList);
+ }
+ }
+ return attrInfo.build();
}
}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java
index a67fff7..5935b0e 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkAttrApi.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.skydoc.fakebuildapi;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.skylarkbuildapi.ProviderApi;
import com.google.devtools.build.lib.skylarkbuildapi.SkylarkAttrApi;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.StarlarkContext;
@@ -23,6 +25,10 @@
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* Fake implementation of {@link SkylarkAttrApi}.
@@ -39,7 +45,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.INT, doc, mandatory);
+ return new FakeDescriptor(AttributeType.INT, doc, mandatory, ImmutableList.of());
}
@Override
@@ -52,7 +58,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.STRING, doc, mandatory);
+ return new FakeDescriptor(AttributeType.STRING, doc, mandatory, ImmutableList.of());
}
@Override
@@ -72,7 +78,11 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.LABEL, doc, mandatory);
+ List<List<String>> allNameGroups = new ArrayList<>();
+ if (providers != null) {
+ allNameGroups = allProviderNameGroups(providers, env);
+ }
+ return new FakeDescriptor(AttributeType.LABEL, doc, mandatory, allNameGroups);
}
@Override
@@ -86,7 +96,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.STRING_LIST, doc, mandatory);
+ return new FakeDescriptor(AttributeType.STRING_LIST, doc, mandatory, ImmutableList.of());
}
@Override
@@ -100,7 +110,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.INT_LIST, doc, mandatory);
+ return new FakeDescriptor(AttributeType.INT_LIST, doc, mandatory, ImmutableList.of());
}
@Override
@@ -120,7 +130,11 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.LABEL_LIST, doc, mandatory);
+ List<List<String>> allNameGroups = new ArrayList<>();
+ if (providers != null) {
+ allNameGroups = allProviderNameGroups(providers, env);
+ }
+ return new FakeDescriptor(AttributeType.LABEL_LIST, doc, mandatory, allNameGroups);
}
@Override
@@ -140,7 +154,11 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.LABEL_STRING_DICT, doc, mandatory);
+ List<List<String>> allNameGroups = new ArrayList<>();
+ if (providers != null) {
+ allNameGroups = allProviderNameGroups(providers, env);
+ }
+ return new FakeDescriptor(AttributeType.LABEL_STRING_DICT, doc, mandatory, allNameGroups);
}
@Override
@@ -152,7 +170,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.BOOLEAN, doc, mandatory);
+ return new FakeDescriptor(AttributeType.BOOLEAN, doc, mandatory, ImmutableList.of());
}
@Override
@@ -164,7 +182,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.OUTPUT, doc, mandatory);
+ return new FakeDescriptor(AttributeType.OUTPUT, doc, mandatory, ImmutableList.of());
}
@Override
@@ -178,7 +196,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.OUTPUT_LIST, doc, mandatory);
+ return new FakeDescriptor(AttributeType.OUTPUT_LIST, doc, mandatory, ImmutableList.of());
}
@Override
@@ -192,7 +210,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.STRING_DICT, doc, mandatory);
+ return new FakeDescriptor(AttributeType.STRING_DICT, doc, mandatory, ImmutableList.of());
}
@Override
@@ -206,7 +224,7 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.STRING_LIST_DICT, doc, mandatory);
+ return new FakeDescriptor(AttributeType.STRING_LIST_DICT, doc, mandatory, ImmutableList.of());
}
@Override
@@ -218,9 +236,72 @@
Environment env,
StarlarkContext context)
throws EvalException {
- return new FakeDescriptor(AttributeType.STRING_LIST, doc, mandatory);
+ return new FakeDescriptor(AttributeType.STRING_LIST, doc, mandatory, ImmutableList.of());
}
@Override
public void repr(SkylarkPrinter printer) {}
+
+ /**
+ * Returns a list of provider name groups, given the value of a Starlark attribute's "providers"
+ * argument.
+ *
+ * <p>{@code providers} can either be a list of providers, or a list of lists of providers. If it
+ * is the first case, the entire list is considered a single group. In the second case, each of
+ * the inner lists is a group.
+ */
+ private static List<List<String>> allProviderNameGroups(SkylarkList<?> providers, Environment env)
+ throws EvalException {
+
+ List<List<String>> allNameGroups = new ArrayList<>();
+ List<List<ProviderApi>> allProviderGroups = new ArrayList<>();
+ for (Object object : providers) {
+ if (object instanceof ProviderApi) {
+ allProviderGroups.add(providers.getContents(ProviderApi.class, "providers"));
+ break;
+ } else if (object instanceof SkylarkList) {
+ allProviderGroups.add(
+ ((SkylarkList<?>) object).getContents(ProviderApi.class, "provider groups"));
+ }
+ }
+
+ for (List<ProviderApi> providerGroup : allProviderGroups) {
+ List<String> nameGroup = providerNameGroup(providerGroup, env);
+ allNameGroups.add(nameGroup);
+ }
+
+ return allNameGroups;
+ }
+
+ /** Returns the names of the providers in the given group. */
+ private static List<String> providerNameGroup(List<ProviderApi> providerGroup, Environment env) {
+ List<String> providerNameGroup = new ArrayList<>();
+ for (ProviderApi provider : providerGroup) {
+ String providerName = providerName(provider, env);
+ providerNameGroup.add(providerName);
+ }
+ return providerNameGroup;
+ }
+
+ /**
+ * Returns the name of {@code provider}.
+ *
+ * <p>{@code env} contains a {@code Map<String, Object>} where the values are built-in objects or
+ * objects defined in the file and the keys are the names of these objects. If a {@code provider}
+ * is in the map, the name of the provider is set as the key of this object in {@code bindings}.
+ * If it is not in the map, the provider may be part of a module in the map and the name will be
+ * set to "Unknown Provider".
+ */
+ private static String providerName(ProviderApi provider, Environment env) {
+ Map<String, Object> bindings = env.getGlobals().getTransitiveBindings();
+ if (bindings.containsValue(provider)) {
+ for (Entry<String, Object> envEntry : bindings.entrySet()) {
+ if (provider.equals(envEntry.getValue())) {
+ return envEntry.getKey();
+ }
+ }
+ }
+ return "Unknown Provider";
+ }
}
+
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index c30ab1e..4eb7f01 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -58,7 +58,8 @@
public class FakeSkylarkRuleFunctionsApi implements SkylarkRuleFunctionsApi<FileApi> {
private static final FakeDescriptor IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR =
- new FakeDescriptor(AttributeType.NAME, "A unique name for this target.", true);
+ new FakeDescriptor(
+ AttributeType.NAME, "A unique name for this target.", true, ImmutableList.of());
private final List<RuleInfoWrapper> ruleInfoList;
private final List<ProviderInfoWrapper> providerInfoList;
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
index 7c1ab1c..f21a7aa 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.skydoc.fakebuildapi.repository;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryModuleApi;
import com.google.devtools.build.lib.syntax.BaseFunction;
@@ -38,7 +39,8 @@
*/
public class FakeRepositoryModule implements RepositoryModuleApi {
private static final FakeDescriptor IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR =
- new FakeDescriptor(AttributeType.NAME, "A unique name for this repository.", true);
+ new FakeDescriptor(
+ AttributeType.NAME, "A unique name for this repository.", true, ImmutableList.of());
private final List<RuleInfoWrapper> ruleInfoList;
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownUtil.java b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownUtil.java
index 44e6e45..3392e70 100644
--- a/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownUtil.java
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownUtil.java
@@ -20,8 +20,10 @@
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeType;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionParamInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
+import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderNameGroup;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.UserDefinedFunctionInfo;
+import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -150,6 +152,19 @@
return paramInfo.getMandatory() ? "required" : "optional";
}
+ /**
+ * Return a string explaining what providers an attribute requires. Adds hyperlinks to providers.
+ */
+ public String attributeProviders(AttributeInfo attributeInfo) {
+ List<ProviderNameGroup> providerNames = attributeInfo.getProviderNameGroupList();
+ List<String> finalProviderNames = new ArrayList<>();
+ for (ProviderNameGroup providerNameList : providerNames) {
+ List<String> providers = providerNameList.getProviderNameList();
+ finalProviderNames.add(String.format(Joiner.on(", ").join(providers)));
+ }
+ return String.format(Joiner.on("; or ").join(finalProviderNames));
+ }
+
private String attributeTypeDescription(AttributeType attributeType) {
switch (attributeType) {
case NAME:
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto b/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
index d8850bf..38c1d8d 100644
--- a/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/proto/stardoc_output.proto
@@ -88,6 +88,22 @@
// If true, all targets of the rule must specify a value for this attribute.
bool mandatory = 4;
+
+ // The target(s) in this attribute must define all the providers of at least
+ // one of the ProviderNameGroups in this list. If the Attribute Type is not a
+ // label, a label list, or a label-keyed string dictionary, the field will be
+ // left empty.
+ repeated ProviderNameGroup provider_name_group = 5;
+}
+
+// Representation of a set of providers that a rule attribute may be required to
+// have.
+message ProviderNameGroup {
+ // The names of the providers that must be given by any dependency appearing
+ // in this attribute. The name will be "Unknown Provider" if the name is
+ // unidentifiable, for example, if the provider is part of a namespace.
+ // TODO(kendalllane): Fix documentation of providers from namespaces.
+ repeated string provider_name = 1;
}
// Representation of Starlark function definition.
diff --git a/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java b/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
index 29ca936..92eec57 100644
--- a/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
+++ b/src/test/java/com/google/devtools/build/skydoc/SkydocTest.java
@@ -547,4 +547,53 @@
.inOrder();
assertThat(aspectInfo.getAspectAttributeList()).containsExactly("deps");
}
+
+ @Test
+ public void testProvidersForAttributes() throws Exception {
+ scratch.file(
+ "/test/test.bzl",
+ "\n"
+ + "def my_rule_impl(ctx):\n"
+ + " return struct()\n"
+ + "MyInfo = provider()\n"
+ + "my_rule = rule(\n"
+ + " implementation = my_rule_impl,\n"
+ + " attrs = {\n"
+ + " \"deps\": attr.label_list(\n"
+ + " doc = \"\"\"\n"
+ + " A list of dependencies.\n"
+ + " \"\"\",\n"
+ + " providers = [MyInfo],\n"
+ + " allow_files = False,"
+ + " )"
+ + " },"
+ + ")\n");
+
+ ImmutableMap.Builder<String, RuleInfo> ruleInfoMap = ImmutableMap.builder();
+ ImmutableMap.Builder<String, ProviderInfo> providerInfoMap = ImmutableMap.builder();
+
+ skydocMain.eval(
+ StarlarkSemantics.DEFAULT_SEMANTICS,
+ Label.parseAbsoluteUnchecked("//test:test.bzl"),
+ ruleInfoMap,
+ providerInfoMap,
+ ImmutableMap.builder(),
+ ImmutableMap.builder());
+
+ Map<String, RuleInfo> rules = ruleInfoMap.build();
+ Map<String, ProviderInfo> providers = providerInfoMap.build();
+
+ ModuleInfo moduleInfo =
+ new ProtoRenderer()
+ .appendRuleInfos(rules.values())
+ .appendProviderInfos(providers.values())
+ .getModuleInfo()
+ .build();
+ RuleInfo ruleInfo = moduleInfo.getRuleInfo(0);
+ ProviderInfo providerInfo = moduleInfo.getProviderInfo(0);
+
+ assertThat(getAttrNames(ruleInfo)).containsExactly("name", "deps").inOrder();
+ assertThat(ruleInfo.getAttribute(1).getProviderNameGroupList()).hasSize(1);
+ assertThat(providerInfo.getProviderName()).isEqualTo("MyInfo");
+ }
}