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