Document provider() calls in Stardoc
RELNOTES: None.
PiperOrigin-RevId: 217544224
diff --git a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
index f011afe..2c17a20 100644
--- a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
+++ b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
@@ -160,7 +160,7 @@
new FakeSkylarkAttrApi(),
new FakeSkylarkCommandLineApi(),
new FakeSkylarkNativeModuleApi(),
- new FakeSkylarkRuleFunctionsApi(Lists.newArrayList()),
+ new FakeSkylarkRuleFunctionsApi(Lists.newArrayList(), Lists.newArrayList()),
new FakeStructProviderApi(),
new FakeOutputGroupInfoProvider(),
new FakeActionsInfoProvider(),
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
index 1ef470f..ebb19db 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -69,6 +69,7 @@
import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisTestResultInfoProvider;
import com.google.devtools.build.skydoc.fakebuildapi.test.FakeTestingModule;
import com.google.devtools.build.skydoc.rendering.MarkdownRenderer;
+import com.google.devtools.build.skydoc.rendering.ProviderInfo;
import com.google.devtools.build.skydoc.rendering.RuleInfo;
import java.io.IOException;
import java.io.PrintWriter;
@@ -128,24 +129,31 @@
ImmutableSet<String> symbolNames = getSymbolNames(args);
ImmutableMap.Builder<String, RuleInfo> ruleInfoMap = ImmutableMap.builder();
+ ImmutableMap.Builder<String, ProviderInfo> providerInfoMap = ImmutableMap.builder();
ImmutableList.Builder<RuleInfo> unknownNamedRules = ImmutableList.builder();
new SkydocMain(new FilesystemFileAccessor())
- .eval(targetFileLabel, ruleInfoMap, unknownNamedRules);
+ .eval(targetFileLabel, ruleInfoMap, unknownNamedRules, providerInfoMap);
MarkdownRenderer renderer = new MarkdownRenderer();
if (symbolNames.isEmpty()) {
try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
printRuleInfos(printWriter, renderer, ruleInfoMap.build(), unknownNamedRules.build());
+ printProviderInfos(printWriter, renderer, providerInfoMap.build());
}
} else {
- Map<String, RuleInfo> filteredRuleInfos = ImmutableMap.copyOf(
+ Map<String, RuleInfo> filteredRuleInfos =
ruleInfoMap.build().entrySet().stream()
.filter(entry -> symbolNames.contains(entry.getKey()))
- .collect(Collectors.toList()));
+ .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue));
+ Map<String, ProviderInfo> filteredProviderInfos =
+ providerInfoMap.build().entrySet().stream()
+ .filter(entry -> symbolNames.contains(entry.getKey()))
+ .collect(ImmutableMap.toImmutableMap(Entry::getKey, Entry::getValue));
try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
printRuleInfos(printWriter, renderer, filteredRuleInfos, ImmutableList.of());
+ printProviderInfos(printWriter, renderer, filteredProviderInfos);
}
}
}
@@ -173,38 +181,64 @@
}
}
+ private static void printProviderInfos(
+ PrintWriter printWriter,
+ MarkdownRenderer renderer,
+ Map<String, ProviderInfo> providerInfos) throws IOException {
+ for (Entry<String, ProviderInfo> entry : providerInfos.entrySet()) {
+ printProviderInfo(printWriter, renderer, entry.getKey(), entry.getValue());
+ printWriter.println();
+ }
+ }
+
private static void printRuleInfo(
PrintWriter printWriter, MarkdownRenderer renderer,
String exportedName, RuleInfo ruleInfo) throws IOException {
printWriter.println(renderer.render(exportedName, ruleInfo));
}
+ private static void printProviderInfo(
+ PrintWriter printWriter, MarkdownRenderer renderer,
+ String exportedName, ProviderInfo providerInfo) throws IOException {
+ printWriter.println(renderer.render(exportedName, providerInfo));
+ }
+
/**
* Evaluates/interprets the skylark file at a given path and its transitive skylark dependencies
- * using a fake build API and collects information about all rule definitions made in the
- * root skylark file.
+ * using a fake build API and collects information about all rule definitions made in the root
+ * skylark file.
*
* @param label the label of the skylark file to evaluate
- * @param ruleInfoMap a map builder to be populated with rule definition information for
- * named rules. Keys are exported names of rules, and values are their {@link RuleInfo}
- * rule descriptions. For example, 'my_rule = rule(...)' has key 'my_rule'
- * @param unknownNamedRules a list builder to be populated with rule definition information
- * for rules which were not exported as top level symbols
+ * @param ruleInfoMap a map builder to be populated with rule definition information for named
+ * rules. Keys are exported names of rules, and values are their {@link RuleInfo} rule
+ * descriptions. For example, 'my_rule = rule(...)' has key 'my_rule'
+ * @param unknownNamedRules a list builder to be populated with rule definition information for
+ * rules which were not exported as top level symbols
+ * @param providerInfoMap a map builder to be populated with provider definition information for
+ * named providers. Keys are exported names of providers, and values are their
+ * {@link ProviderInfo} descriptions. For example, 'my_provider = provider(...)' has key
+ * 'my_provider'
* @throws InterruptedException if evaluation is interrupted
*/
public Environment eval(
Label label,
ImmutableMap.Builder<String, RuleInfo> ruleInfoMap,
- ImmutableList.Builder<RuleInfo> unknownNamedRules)
+ ImmutableList.Builder<RuleInfo> unknownNamedRules,
+ ImmutableMap.Builder<String, ProviderInfo> providerInfoMap)
throws InterruptedException, IOException, LabelSyntaxException {
List<RuleInfo> ruleInfoList = new ArrayList<>();
- Environment env = recursiveEval(label, ruleInfoList);
+ List<ProviderInfo> providerInfoList = new ArrayList<>();
+ Environment env = recursiveEval(label, ruleInfoList, providerInfoList);
Map<BaseFunction, RuleInfo> ruleFunctions = ruleInfoList.stream()
.collect(Collectors.toMap(
RuleInfo::getIdentifierFunction,
Functions.identity()));
+ Map<BaseFunction, ProviderInfo> providerInfos = providerInfoList.stream()
+ .collect(Collectors.toMap(
+ ProviderInfo::getIdentifier,
+ Functions.identity()));
ImmutableSet.Builder<RuleInfo> handledRuleDefinitions = ImmutableSet.builder();
for (Entry<String, Object> envEntry : env.getGlobals().getBindings().entrySet()) {
@@ -213,6 +247,10 @@
ruleInfoMap.put(envEntry.getKey(), ruleInfo);
handledRuleDefinitions.add(ruleInfo);
}
+ if (providerInfos.containsKey(envEntry.getValue())) {
+ ProviderInfo providerInfo = providerInfos.get(envEntry.getValue());
+ providerInfoMap.put(envEntry.getKey(), providerInfo);
+ }
}
unknownNamedRules.addAll(ruleFunctions.values().stream()
@@ -232,7 +270,7 @@
* @throws InterruptedException if evaluation is interrupted
*/
private Environment recursiveEval(
- Label label, List<RuleInfo> ruleInfoList)
+ Label label, List<RuleInfo> ruleInfoList, List<ProviderInfo> providerInfoList)
throws InterruptedException, IOException, LabelSyntaxException {
Path path = pathOfLabel(label);
@@ -251,7 +289,7 @@
Label relativeLabel = label.getRelative(anImport.getImportString());
try {
- Environment importEnv = recursiveEval(relativeLabel, ruleInfoList);
+ Environment importEnv = recursiveEval(relativeLabel, ruleInfoList, providerInfoList);
imports.put(anImport.getImportString(), new Extension(importEnv));
} catch (NoSuchFileException noSuchFileException) {
throw new IllegalStateException(
@@ -260,7 +298,7 @@
}
}
- Environment env = evalSkylarkBody(buildFileAST, imports, ruleInfoList);
+ Environment env = evalSkylarkBody(buildFileAST, imports, ruleInfoList, providerInfoList);
pending.remove(path);
env.mutability().freeze();
@@ -282,11 +320,12 @@
private Environment evalSkylarkBody(
BuildFileAST buildFileAST,
Map<String, Extension> imports,
- List<RuleInfo> ruleInfoList) throws InterruptedException {
+ List<RuleInfo> ruleInfoList,
+ List<ProviderInfo> providerInfoList) throws InterruptedException {
Environment env = createEnvironment(
eventHandler,
- globalFrame(ruleInfoList),
+ globalFrame(ruleInfoList, providerInfoList),
imports);
if (!buildFileAST.exec(env, eventHandler)) {
@@ -303,14 +342,17 @@
*
* @param ruleInfoList the list of {@link RuleInfo} objects, to which rule() invocation
* information will be added
+ * @param providerInfoList the list of {@link ProviderInfo} objects, to which provider()
+ * invocation information will be added
*/
- private static GlobalFrame globalFrame(List<RuleInfo> ruleInfoList) {
+ private static GlobalFrame globalFrame(List<RuleInfo> ruleInfoList,
+ List<ProviderInfo> providerInfoList) {
TopLevelBootstrap topLevelBootstrap =
new TopLevelBootstrap(new FakeBuildApiGlobals(),
new FakeSkylarkAttrApi(),
new FakeSkylarkCommandLineApi(),
new FakeSkylarkNativeModuleApi(),
- new FakeSkylarkRuleFunctionsApi(ruleInfoList),
+ new FakeSkylarkRuleFunctionsApi(ruleInfoList, providerInfoList),
new FakeStructProviderApi(),
new FakeOutputGroupInfoProvider(),
new FakeActionsInfoProvider(),
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeProviderApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeProviderApi.java
index 01b64c1..645f96e 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeProviderApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeProviderApi.java
@@ -29,8 +29,14 @@
*/
public class FakeProviderApi extends BaseFunction implements ProviderApi {
+ /**
+ * Each fake is constructed with a unique name, controlled by this counter being the name suffix.
+ */
+ private static int idCounter = 0;
+
public FakeProviderApi() {
- super("ProviderFunction", FunctionSignature.WithValues.create(FunctionSignature.KWARGS));
+ super("ProviderIdentifier" + idCounter++,
+ FunctionSignature.WithValues.create(FunctionSignature.KWARGS));
}
@Override
@@ -41,4 +47,10 @@
@Override
public void repr(SkylarkPrinter printer) {}
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ // Use exact object matching.
+ return this == other;
+ }
}
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 8cd428e..1c3d82b 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
@@ -14,6 +14,7 @@
package com.google.devtools.build.skydoc.fakebuildapi;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -30,11 +31,15 @@
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.skydoc.fakebuildapi.FakeDescriptor.Type;
import com.google.devtools.build.skydoc.rendering.AttributeInfo;
+import com.google.devtools.build.skydoc.rendering.ProviderFieldInfo;
+import com.google.devtools.build.skydoc.rendering.ProviderInfo;
import com.google.devtools.build.skydoc.rendering.RuleInfo;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -49,20 +54,50 @@
private static final FakeDescriptor IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR =
new FakeDescriptor(Type.STRING, "A unique name for this target.", true);
private final List<RuleInfo> ruleInfoList;
+ private final List<ProviderInfo> providerInfoList;
/**
* Constructor.
*
* @param ruleInfoList the list of {@link RuleInfo} objects to which rule() invocation information
* will be added
+ * @param providerInfoList the list of {@link ProviderInfo} objects to which provider()
+ * invocation information will be added
*/
- public FakeSkylarkRuleFunctionsApi(List<RuleInfo> ruleInfoList) {
+ public FakeSkylarkRuleFunctionsApi(List<RuleInfo> ruleInfoList,
+ List<ProviderInfo> providerInfoList) {
this.ruleInfoList = ruleInfoList;
+ this.providerInfoList = providerInfoList;
}
@Override
public ProviderApi provider(String doc, Object fields, Location location) throws EvalException {
- return new FakeProviderApi();
+ FakeProviderApi fakeProvider = new FakeProviderApi();
+ // Field documentation will be output preserving the order in which the fields are listed.
+ ImmutableList.Builder<ProviderFieldInfo> providerFieldInfos = ImmutableList.builder();
+ if (fields instanceof SkylarkList) {
+ @SuppressWarnings("unchecked")
+ SkylarkList<String> fieldNames = (SkylarkList<String>)
+ SkylarkType.cast(
+ fields,
+ SkylarkList.class, String.class, location,
+ "Expected list of strings or dictionary of string -> string for 'fields'");
+ for (String fieldName : fieldNames) {
+ providerFieldInfos.add(new ProviderFieldInfo(fieldName));
+ }
+ } else if (fields instanceof SkylarkDict) {
+ Map<String, String> dict = SkylarkType.castMap(
+ fields,
+ String.class, String.class,
+ "Expected list of strings or dictionary of string -> string for 'fields'");
+ for (Map.Entry<String, String> fieldEntry : dict.entrySet()) {
+ providerFieldInfos.add(new ProviderFieldInfo(fieldEntry.getKey(), fieldEntry.getValue()));
+ }
+ } else {
+ // fields is NONE, so there is no field information to add.
+ }
+ providerInfoList.add(new ProviderInfo(fakeProvider, doc, providerFieldInfos.build()));
+ return fakeProvider;
}
@Override
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java
index 9632d53..9c82cde 100644
--- a/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/MarkdownRenderer.java
@@ -32,8 +32,10 @@
*/
public class MarkdownRenderer {
- private static final String TEMPLATE_FILENAME =
+ private static final String RULE_TEMPLATE_FILENAME =
"com/google/devtools/build/skydoc/rendering/templates/rule.vm";
+ private static final String PROVIDER_TEMPLATE_FILENAME =
+ "com/google/devtools/build/skydoc/rendering/templates/provider.vm";
private final VelocityEngine velocityEngine;
@@ -61,7 +63,25 @@
StringWriter stringWriter = new StringWriter();
try {
- velocityEngine.mergeTemplate(TEMPLATE_FILENAME, "UTF-8", context, stringWriter);
+ velocityEngine.mergeTemplate(RULE_TEMPLATE_FILENAME, "UTF-8", context, stringWriter);
+ } catch (ResourceNotFoundException | ParseErrorException | MethodInvocationException e) {
+ throw new IOException(e);
+ }
+ return stringWriter.toString();
+ }
+
+ /**
+ * Returns a markdown rendering of provider documentation for the given provider information
+ * object with the given name.
+ */
+ public String render(String providerName, ProviderInfo providerInfo) throws IOException {
+ VelocityContext context = new VelocityContext();
+ context.put("providerName", providerName);
+ context.put("providerInfo", providerInfo);
+
+ StringWriter stringWriter = new StringWriter();
+ try {
+ velocityEngine.mergeTemplate(PROVIDER_TEMPLATE_FILENAME, "UTF-8", context, stringWriter);
} catch (ResourceNotFoundException | ParseErrorException | MethodInvocationException e) {
throw new IOException(e);
}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderFieldInfo.java b/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderFieldInfo.java
new file mode 100644
index 0000000..4697622
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderFieldInfo.java
@@ -0,0 +1,42 @@
+// Copyright 2018 The Bazel Authors. 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.skydoc.rendering;
+
+/**
+ * Stores information about a provider field definition.
+ */
+public class ProviderFieldInfo {
+ private final String name;
+ private final String docString;
+
+ public ProviderFieldInfo(String name) {
+ this(name, "(Undocumented)");
+ }
+
+ public ProviderFieldInfo(String name, String docString) {
+ this.name = name;
+ this.docString = docString.trim();
+ }
+
+ @SuppressWarnings("unused") // Used by markdown template.
+ public String getName() {
+ return name;
+ }
+
+ @SuppressWarnings("unused") // Used by markdown template.
+ public String getDocString() {
+ return docString;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderInfo.java b/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderInfo.java
new file mode 100644
index 0000000..3c39a11
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/ProviderInfo.java
@@ -0,0 +1,52 @@
+// Copyright 2018 The Bazel Authors. 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.skydoc.rendering;
+
+import com.google.devtools.build.lib.syntax.BaseFunction;
+import java.util.Collection;
+
+/**
+ * Stores information about a starlark provider definition.
+ *
+ * For example, in <pre>FooInfo = provider(doc = 'My provider', fields = {'bar' : 'a bar'})</pre>,
+ * this contains all information about the definition of FooInfo for purposes of generating its
+ * documentation.
+ */
+public class ProviderInfo {
+
+ private final BaseFunction identifier;
+ private final String docString;
+ private final Collection<ProviderFieldInfo> fieldInfos;
+
+ public ProviderInfo(BaseFunction identifier,
+ String docString,
+ Collection<ProviderFieldInfo> fieldInfos) {
+ this.identifier = identifier;
+ this.docString = docString;
+ this.fieldInfos = fieldInfos;
+ }
+
+ public BaseFunction getIdentifier() {
+ return identifier;
+ }
+
+ public String getDocString() {
+ return docString;
+ }
+
+ public Collection<ProviderFieldInfo> getFields() {
+ return fieldInfos;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/skydoc/rendering/templates/provider.vm b/src/main/java/com/google/devtools/build/skydoc/rendering/templates/provider.vm
new file mode 100644
index 0000000..f708daa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/rendering/templates/provider.vm
@@ -0,0 +1,25 @@
+<a name="#${providerName}"></a>
+#[[##]]# ${providerName}
+
+${providerInfo.docString}
+
+#if (!$providerInfo.fields.isEmpty())
+#[[###]]# Fields
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+#foreach ($field in $providerInfo.fields)
+ <tr id="#${providerName}_${field.name}">
+ <td><code>${field.name}</code></td>
+ <td>
+ <p>${field.docString}</p>
+ </td>
+ </tr>
+#end
+ </tbody>
+</table>
+#end