|  | // 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.docgen; | 
|  |  | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.devtools.build.docgen.StarlarkDocumentationProcessor.Category; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.ApiContext; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.Builtins; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.Callable; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.Param; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.Type; | 
|  | import com.google.devtools.build.docgen.builtin.BuiltinProtos.Value; | 
|  | import com.google.devtools.build.docgen.starlark.StarlarkConstructorMethodDoc; | 
|  | import com.google.devtools.build.docgen.starlark.StarlarkDocExpander; | 
|  | import com.google.devtools.build.docgen.starlark.StarlarkDocPage; | 
|  | import com.google.devtools.build.docgen.starlark.StarlarkMethodDoc; | 
|  | import com.google.devtools.build.docgen.starlark.StarlarkParamDoc; | 
|  | import com.google.devtools.common.options.OptionsParser; | 
|  | import java.io.BufferedOutputStream; | 
|  | import java.io.FileOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.lang.reflect.Method; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.HashMap; | 
|  | import java.util.Iterator; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Map.Entry; | 
|  | import java.util.function.Function; | 
|  | import net.starlark.java.annot.StarlarkAnnotations; | 
|  | import net.starlark.java.annot.StarlarkBuiltin; | 
|  | import net.starlark.java.annot.StarlarkMethod; | 
|  | import net.starlark.java.eval.BuiltinFunction; | 
|  | import net.starlark.java.eval.GuardedValue; | 
|  | import net.starlark.java.eval.Starlark; | 
|  | import net.starlark.java.eval.StarlarkCallable; | 
|  | import net.starlark.java.eval.StarlarkFunction; | 
|  | import net.starlark.java.eval.StarlarkSemantics; | 
|  |  | 
|  | /** The main class for the Starlark documentation generator. */ | 
|  | public class ApiExporter { | 
|  |  | 
|  | private static void appendTypes( | 
|  | Builtins.Builder builtins, StarlarkDocPage docPage, List<RuleDocumentation> nativeRules) | 
|  | throws BuildEncyclopediaDocException { | 
|  | Type.Builder type = Type.newBuilder(); | 
|  | type.setName(docPage.getName()); | 
|  | type.setDoc(docPage.getDocumentation()); | 
|  | for (StarlarkMethodDoc meth : docPage.getJavaMethods()) { | 
|  | // Constructors are exported as global symbols. | 
|  | if (!(meth instanceof StarlarkConstructorMethodDoc)) { | 
|  | Value.Builder value = collectMethodInfo(meth); | 
|  | if (type.getName().equals("native")) { | 
|  | // Methods from the native package are available as top level functions in BUILD files. | 
|  | value.setApiContext(ApiContext.BUILD); | 
|  | builtins.addGlobal(value); | 
|  |  | 
|  | value.setApiContext(ApiContext.BZL); | 
|  | type.addField(value); | 
|  | } else { | 
|  | value.setApiContext(ApiContext.ALL); | 
|  | type.addField(value); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (type.getName().equals("native")) { | 
|  | for (RuleDocumentation rule : nativeRules) { | 
|  | Value.Builder field = collectRuleInfo(rule); | 
|  | field.setApiContext(ApiContext.BZL); | 
|  | type.addField(field); | 
|  | } | 
|  | } | 
|  | builtins.addType(type); | 
|  | } | 
|  |  | 
|  | private static void appendGlobals( | 
|  | Builtins.Builder builtins, | 
|  | Map<String, Object> globals, | 
|  | Map<String, StarlarkMethodDoc> globalToDoc, | 
|  | Map<String, StarlarkConstructorMethodDoc> typeNameToConstructor, | 
|  | ApiContext context) { | 
|  | for (Entry<String, Object> entry : globals.entrySet()) { | 
|  | String name = entry.getKey(); | 
|  | Object obj = entry.getValue(); | 
|  | if (obj instanceof GuardedValue) { | 
|  | obj = ((GuardedValue) obj).getObject(); | 
|  | } | 
|  |  | 
|  | Value.Builder value = Value.newBuilder(); | 
|  | if (obj instanceof StarlarkCallable) { | 
|  | StarlarkMethodDoc meth = globalToDoc.get(name); | 
|  | if (meth != null) { | 
|  | value = collectMethodInfo(meth); | 
|  | } else { | 
|  | value = valueFromCallable((StarlarkCallable) obj); | 
|  | } | 
|  | } else { | 
|  | StarlarkBuiltin typeModule = StarlarkAnnotations.getStarlarkBuiltin(obj.getClass()); | 
|  | if (typeModule != null) { | 
|  | Method selfCallMethod = | 
|  | Starlark.getSelfCallMethod(StarlarkSemantics.DEFAULT, obj.getClass()); | 
|  | if (selfCallMethod != null) { | 
|  | // selfCallMethod may be from a subclass of the annotated method. | 
|  | StarlarkMethod annotation = StarlarkAnnotations.getStarlarkMethod(selfCallMethod); | 
|  | value = valueFromAnnotation(annotation); | 
|  | // For constructors, we can also set the return type. | 
|  | StarlarkConstructorMethodDoc constructor = typeNameToConstructor.get(entry.getKey()); | 
|  | if (constructor != null && value.hasCallable()) { | 
|  | value.getCallableBuilder().setReturnType(constructor.getReturnType()); | 
|  | } | 
|  | } else { | 
|  | value.setName(name); | 
|  | // TODO(b/255647089): We should use the type module's type here, since it will more | 
|  | // accurately represent Providers, but has some issues with builtins. For now, just | 
|  | // special case None which has type NoneType. | 
|  | if (!name.equals("None")) { | 
|  | value.setType(name); | 
|  | value.setDoc(typeModule.doc()); | 
|  | } else { | 
|  | value.setType("NoneType"); | 
|  | } | 
|  | } | 
|  | } else if (!name.equals("_builtins_dummy")) { // Ignore the test only dummy global. | 
|  | // Special case bool since we can't infer the type module for it. | 
|  | if (name.equals("True") || name.equals("False")) { | 
|  | value.setType("bool"); | 
|  | } | 
|  | value.setName(name); | 
|  | } | 
|  | } | 
|  | value.setApiContext(context); | 
|  | builtins.addGlobal(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Native rules are available as top level functions in BUILD files. | 
|  | private static void appendNativeRules( | 
|  | Builtins.Builder builtins, List<RuleDocumentation> nativeRules) | 
|  | throws BuildEncyclopediaDocException { | 
|  | for (RuleDocumentation rule : nativeRules) { | 
|  | Value.Builder global = collectRuleInfo(rule); | 
|  | global.setApiContext(ApiContext.BUILD); | 
|  | builtins.addGlobal(global); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static Value.Builder valueFromCallable(StarlarkCallable x) { | 
|  | // Starlark def statement? | 
|  | if (x instanceof StarlarkFunction) { | 
|  | StarlarkFunction fn = (StarlarkFunction) x; | 
|  | Signature sig = new Signature(); | 
|  | sig.name = fn.getName(); | 
|  | sig.doc = fn.getDocumentation(); | 
|  | sig.parameterNames = fn.getParameterNames(); | 
|  | sig.hasVarargs = fn.hasVarargs(); | 
|  | sig.hasKwargs = fn.hasKwargs(); | 
|  | sig.getDefaultValue = | 
|  | (i) -> { | 
|  | Object v = fn.getDefaultValue(i); | 
|  | return v == null ? null : Starlark.repr(v); | 
|  | }; | 
|  | return signatureToValue(sig); | 
|  | } | 
|  |  | 
|  | // annotated Java method? | 
|  | if (x instanceof BuiltinFunction) { | 
|  | return valueFromAnnotation(((BuiltinFunction) x).getAnnotation()); | 
|  | } | 
|  |  | 
|  | // application-defined callable?  Treat as def f(**kwargs). | 
|  | Signature sig = new Signature(); | 
|  | sig.name = x.getName(); | 
|  | sig.parameterNames = ImmutableList.of("kwargs"); | 
|  | sig.hasKwargs = true; | 
|  | return signatureToValue(sig); | 
|  | } | 
|  |  | 
|  | private static Value.Builder valueFromAnnotation(StarlarkMethod annot) { | 
|  | return signatureToValue(getSignature(annot)); | 
|  | } | 
|  |  | 
|  | private static class Signature { | 
|  | String name; | 
|  | List<String> parameterNames; | 
|  | boolean hasVarargs; | 
|  | boolean hasKwargs; | 
|  | String doc; | 
|  |  | 
|  | // Returns the string form of the ith default value, using the | 
|  | // index, ordering, and null Conventions of StarlarkFunction.getDefaultValue. | 
|  | Function<Integer, String> getDefaultValue = (i) -> null; | 
|  | } | 
|  |  | 
|  | private static Value.Builder signatureToValue(Signature sig) { | 
|  | Value.Builder value = Value.newBuilder(); | 
|  | value.setName(sig.name); | 
|  | value.setDoc(sig.doc); | 
|  |  | 
|  | int nparams = sig.parameterNames.size(); | 
|  | int kwargsIndex = sig.hasKwargs ? --nparams : -1; | 
|  | int varargsIndex = sig.hasVarargs ? --nparams : -1; | 
|  | // Inv: nparams is number of regular parameters. | 
|  |  | 
|  | Callable.Builder callable = Callable.newBuilder(); | 
|  | for (int i = 0; i < sig.parameterNames.size(); i++) { | 
|  | String name = sig.parameterNames.get(i); | 
|  | Param.Builder param = Param.newBuilder(); | 
|  | if (i == varargsIndex) { | 
|  | // *args | 
|  | param.setName("*" + name); // * seems redundant | 
|  | param.setIsStarArg(true); | 
|  | } else if (i == kwargsIndex) { | 
|  | // **kwargs | 
|  | param.setName("**" + name); // ** seems redundant | 
|  | param.setIsStarStarArg(true); | 
|  | } else { | 
|  | // regular parameter | 
|  | param.setName(name); | 
|  | String v = sig.getDefaultValue.apply(i); | 
|  | if (v != null) { | 
|  | param.setDefaultValue(v); | 
|  | } else { | 
|  | param.setIsMandatory(true); // bool seems redundant | 
|  | } | 
|  | } | 
|  | callable.addParam(param); | 
|  | } | 
|  | value.setCallable(callable); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | private static Value.Builder collectMethodInfo(StarlarkMethodDoc meth) { | 
|  | Value.Builder field = Value.newBuilder(); | 
|  | field.setName(meth.getShortName()); | 
|  | field.setDoc(meth.getDocumentation()); | 
|  | if (meth.isCallable()) { | 
|  | Callable.Builder callable = Callable.newBuilder(); | 
|  | for (StarlarkParamDoc par : meth.getParams()) { | 
|  | Param.Builder param = newParam(par.getName(), par.getDefaultValue().isEmpty()); | 
|  | param.setType(par.getType()); | 
|  | param.setDoc(par.getDocumentation()); | 
|  | param.setDefaultValue(par.getDefaultValue()); | 
|  | switch (par.getKind()) { | 
|  | case NORMAL: | 
|  | break; | 
|  | case EXTRA_POSITIONALS: | 
|  | param.setName("*" + par.getName()); | 
|  | param.setIsStarArg(true); | 
|  | break; | 
|  | case EXTRA_KEYWORDS: | 
|  | param.setName("**" + par.getName()); | 
|  | param.setIsStarStarArg(true); | 
|  | break; | 
|  | } | 
|  | callable.addParam(param); | 
|  | } | 
|  | callable.setReturnType(meth.getReturnType()); | 
|  | field.setCallable(callable); | 
|  | } else { | 
|  | field.setType(meth.getReturnType()); | 
|  | } | 
|  | return field; | 
|  | } | 
|  |  | 
|  | private static Param.Builder newParam(String name, Boolean isMandatory) { | 
|  | Param.Builder param = Param.newBuilder(); | 
|  | param.setName(name); | 
|  | param.setIsMandatory(isMandatory); | 
|  | return param; | 
|  | } | 
|  |  | 
|  | private static Value.Builder collectRuleInfo(RuleDocumentation rule) | 
|  | throws BuildEncyclopediaDocException { | 
|  | Value.Builder value = Value.newBuilder(); | 
|  | value.setName(rule.getRuleName()); | 
|  | value.setDoc(rule.getHtmlDocumentation()); | 
|  | Callable.Builder callable = Callable.newBuilder(); | 
|  | // All native rules have attribute "name". It is not included in the attributes list and needs | 
|  | // to be added separately. | 
|  | callable.addParam(newParam("name", true)); | 
|  | for (RuleDocumentationAttribute attr : rule.getAttributes()) { | 
|  | callable.addParam(newParam(attr.getAttributeName(), attr.isMandatory())); | 
|  | } | 
|  | value.setCallable(callable); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | private static void writeBuiltins(String filename, Builtins.Builder builtins) throws IOException { | 
|  | try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filename))) { | 
|  | Builtins build = builtins.build(); | 
|  | build.writeTo(out); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void printUsage(OptionsParser parser) { | 
|  | System.err.println( | 
|  | "Usage: api_exporter_bin -m link_map_path -p rule_class_provider (-i input_dir)+\n" | 
|  | + "   -f outputFile [-b denylist] [-h]\n\n" | 
|  | + "Exports all Starlark builtins to a file including the embedded native rules.\n" | 
|  | + "The link map path (-m), rule class provider (-p), output file (-f) and at least \n" | 
|  | + " one input_dir (-i) must be specified.\n"); | 
|  | System.err.println( | 
|  | parser.describeOptionsWithDeprecatedCategories( | 
|  | Collections.<String, String>emptyMap(), OptionsParser.HelpVerbosity.LONG)); | 
|  | } | 
|  |  | 
|  | public static void main(String[] args) { | 
|  | OptionsParser parser = | 
|  | OptionsParser.builder().optionsClasses(BuildEncyclopediaOptions.class).build(); | 
|  | parser.parseAndExitUponError(args); | 
|  | BuildEncyclopediaOptions options = parser.getOptions(BuildEncyclopediaOptions.class); | 
|  |  | 
|  | if (options.help) { | 
|  | printUsage(parser); | 
|  | Runtime.getRuntime().exit(0); | 
|  | } | 
|  |  | 
|  | if (options.linkMapPath.isEmpty() | 
|  | || options.inputDirs.isEmpty() | 
|  | || options.provider.isEmpty() | 
|  | || options.outputFile.isEmpty()) { | 
|  | printUsage(parser); | 
|  | Runtime.getRuntime().exit(1); | 
|  | } | 
|  |  | 
|  | try { | 
|  | DocLinkMap linkMap = DocLinkMap.createFromFile(options.linkMapPath); | 
|  | RuleLinkExpander ruleExpander = new RuleLinkExpander(true, linkMap); | 
|  | SymbolFamilies symbols = | 
|  | new SymbolFamilies( | 
|  | new StarlarkDocExpander(ruleExpander), | 
|  | options.provider, | 
|  | options.inputDirs, | 
|  | options.denylist); | 
|  | ImmutableMap<Category, ImmutableList<StarlarkDocPage>> allDocPages = symbols.getAllDocPages(); | 
|  | Builtins.Builder builtins = Builtins.newBuilder(); | 
|  |  | 
|  | ImmutableList<StarlarkDocPage> globalPages = allDocPages.get(Category.GLOBAL_FUNCTION); | 
|  | Map<String, StarlarkMethodDoc> globalToDoc = new HashMap<>(); | 
|  | for (StarlarkDocPage globalPage : globalPages) { | 
|  | for (StarlarkMethodDoc meth : globalPage.getJavaMethods()) { | 
|  | globalToDoc.put(meth.getShortName(), meth); | 
|  | } | 
|  | } | 
|  |  | 
|  | Iterator<StarlarkDocPage> typesIterator = | 
|  | allDocPages.entrySet().stream() | 
|  | .filter(e -> !e.getKey().equals(Category.GLOBAL_FUNCTION)) | 
|  | .flatMap(e -> e.getValue().stream()) | 
|  | .iterator(); | 
|  | Map<String, StarlarkConstructorMethodDoc> typeNameToConstructor = new HashMap<>(); | 
|  | while (typesIterator.hasNext()) { | 
|  | StarlarkDocPage typeDocPage = typesIterator.next(); | 
|  | appendTypes(builtins, typeDocPage, symbols.getNativeRules()); | 
|  | typeNameToConstructor.put(typeDocPage.getName(), typeDocPage.getConstructor()); | 
|  | } | 
|  | appendGlobals( | 
|  | builtins, symbols.getGlobals(), globalToDoc, typeNameToConstructor, ApiContext.ALL); | 
|  | appendGlobals( | 
|  | builtins, symbols.getBzlGlobals(), globalToDoc, typeNameToConstructor, ApiContext.BZL); | 
|  | appendNativeRules(builtins, symbols.getNativeRules()); | 
|  | writeBuiltins(options.outputFile, builtins); | 
|  |  | 
|  | } catch (Throwable e) { | 
|  | System.err.println("ERROR: " + e.getMessage()); | 
|  | e.printStackTrace(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Extracts signature and parameter default value expressions from a StarlarkMethod annotation. | 
|  | private static Signature getSignature(StarlarkMethod annot) { | 
|  | // Build-time annotation processing ensures mandatory parameters do not follow optional ones. | 
|  | boolean hasStar = false; | 
|  | String star = null; | 
|  | String starStar = null; | 
|  | ArrayList<String> params = new ArrayList<>(); | 
|  | ArrayList<String> defaults = new ArrayList<>(); | 
|  |  | 
|  | for (net.starlark.java.annot.Param param : annot.parameters()) { | 
|  | // Ignore undocumented parameters | 
|  | if (!param.documented()) { | 
|  | continue; | 
|  | } | 
|  | // Implicit * or *args parameter separates transition from positional to named. | 
|  | // f (..., *, ... )  or  f(..., *args, ...) | 
|  | // TODO(adonovan): this logic looks fishy. Clean it up. | 
|  | if (param.named() && !param.positional() && !hasStar) { | 
|  | hasStar = true; | 
|  | if (!annot.extraPositionals().name().isEmpty()) { | 
|  | star = annot.extraPositionals().name(); | 
|  | } | 
|  | } | 
|  | params.add(param.name()); | 
|  | defaults.add(param.defaultValue().isEmpty() ? null : param.defaultValue()); | 
|  | } | 
|  |  | 
|  | // f(..., *args, ...) | 
|  | if (!annot.extraPositionals().name().isEmpty() && !hasStar) { | 
|  | star = annot.extraPositionals().name(); | 
|  | } | 
|  | if (star != null) { | 
|  | params.add(star); | 
|  | } | 
|  |  | 
|  | // f(..., **kwargs) | 
|  | if (!annot.extraKeywords().name().isEmpty()) { | 
|  | starStar = annot.extraKeywords().name(); | 
|  | params.add(starStar); | 
|  | } | 
|  |  | 
|  | Signature sig = new Signature(); | 
|  | sig.name = annot.name(); | 
|  | sig.doc = annot.doc(); | 
|  | sig.parameterNames = params; | 
|  | sig.hasVarargs = star != null; | 
|  | sig.hasKwargs = starStar != null; | 
|  | sig.getDefaultValue = defaults::get; | 
|  | return sig; | 
|  | } | 
|  | } |