// Copyright 2014 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.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.docgen.skylark.SkylarkBuiltinMethodDoc; import com.google.devtools.build.docgen.skylark.SkylarkJavaMethodDoc; import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.rules.SkylarkModules; import com.google.devtools.build.lib.rules.SkylarkRuleContext; import com.google.devtools.build.lib.rules.android.AndroidSkylarkApiProvider; import com.google.devtools.build.lib.rules.apple.AppleConfiguration; import com.google.devtools.build.lib.rules.cpp.CppConfiguration; import com.google.devtools.build.lib.rules.java.JavaConfiguration; import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider; import com.google.devtools.build.lib.rules.java.JavaSkylarkApiProvider; import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.MethodLibrary; import com.google.devtools.build.lib.syntax.Runtime; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; /** * A helper class that collects Skylark module documentation. */ final class SkylarkDocumentationCollector { @SkylarkModule(name = "globals", doc = "Objects, functions and modules registered in the global environment.") private static final class TopLevelModule {} private SkylarkDocumentationCollector() {} /** * Returns the SkylarkModule annotation for the top-level Skylark module. */ public static SkylarkModule getTopLevelModule() { return TopLevelModule.class.getAnnotation(SkylarkModule.class); } /** * Collects the documentation for all Skylark modules and returns a map that maps Skylark * module name to the module documentation. */ public static Map collectModules(String... clazz) { Map modules = new TreeMap<>(); Map builtinModules = collectBuiltinModules(clazz); Map> builtinJavaObjects = collectBuiltinJavaObjects(clazz); modules.putAll(builtinModules); for (SkylarkModuleDoc builtinObject : builtinModules.values()) { // Check the return type for built-in functions, it can be a module previously not added. for (SkylarkBuiltinMethodDoc builtinMethod : builtinObject.getBuiltinMethods().values()) { Class type = builtinMethod.getAnnotation().returnType(); if (type.isAnnotationPresent(SkylarkModule.class)) { collectJavaObjects(type.getAnnotation(SkylarkModule.class), type, modules); } } collectJavaObjects(builtinObject.getAnnotation(), builtinObject.getClassObject(), modules); } for (Entry> builtinModule : builtinJavaObjects.entrySet()) { collectJavaObjects(builtinModule.getKey(), builtinModule.getValue(), modules); } return modules; } /** * Collects and returns all the Java objects reachable in Skylark from (and including) * firstClass with the corresponding SkylarkModule annotation. * *

Note that the {@link SkylarkModule} annotation for firstClass - firstModule - * is also an input parameter, because some top level Skylark built-in objects and methods * are not annotated on the class, but on a field referencing them. */ @VisibleForTesting static void collectJavaObjects(SkylarkModule firstModule, Class firstClass, Map modules) { Set> done = new HashSet<>(); Deque> toProcess = new LinkedList<>(); Map, SkylarkModule> annotations = new HashMap<>(); toProcess.addLast(firstClass); annotations.put(firstClass, firstModule); while (!toProcess.isEmpty()) { Class c = toProcess.removeFirst(); SkylarkModule annotation = annotations.get(c); done.add(c); if (!modules.containsKey(annotation.name())) { modules.put(annotation.name(), new SkylarkModuleDoc(annotation, c)); } SkylarkModuleDoc module = modules.get(annotation.name()); if (module.javaMethodsNotCollected()) { ImmutableMap methods = FuncallExpression.collectSkylarkMethodsWithAnnotation(c); for (Map.Entry entry : methods.entrySet()) { module.addMethod(new SkylarkJavaMethodDoc(module, entry.getKey(), entry.getValue())); } for (Map.Entry method : methods.entrySet()) { Class returnClass = method.getKey().getReturnType(); if (returnClass.isAnnotationPresent(SkylarkModule.class) && !done.contains(returnClass)) { toProcess.addLast(returnClass); annotations.put(returnClass, returnClass.getAnnotation(SkylarkModule.class)); } } } } } private static Map collectBuiltinModules(String... clazz) { Map modules = new HashMap<>(); collectBuiltinDoc(modules, Runtime.class.getDeclaredFields()); collectBuiltinDoc(modules, MethodLibrary.class.getDeclaredFields()); for (Class moduleClass : SkylarkModules.MODULES) { collectBuiltinDoc(modules, moduleClass.getDeclaredFields()); } for (String c : clazz) { try { collectBuiltinDoc(modules, SkylarkDocumentationCollector.class.getClassLoader().loadClass(c).getDeclaredFields()); } catch (ClassNotFoundException e) { System.err.println("SkylarkModule class " + c + " could not be found, ignoring..."); } } return modules; } private static void collectBuiltinDoc(Map modules, Field[] fields) { for (Field field : fields) { if (field.isAnnotationPresent(SkylarkSignature.class)) { SkylarkSignature skylarkSignature = field.getAnnotation(SkylarkSignature.class); Class moduleClass = skylarkSignature.objectType(); SkylarkModule skylarkModule = moduleClass.equals(Object.class) ? getTopLevelModule() : Runtime.getCanonicalRepresentation(moduleClass).getAnnotation(SkylarkModule.class); if (skylarkModule == null) { // TODO(bazel-team): we currently have undocumented methods on undocumented data // structures, namely java.util.List. Remove this case when we are done. Preconditions.checkState(!skylarkSignature.documented()); Preconditions.checkState(moduleClass == List.class); } else { if (!modules.containsKey(skylarkModule.name())) { modules.put(skylarkModule.name(), new SkylarkModuleDoc(skylarkModule, moduleClass)); } SkylarkModuleDoc module = modules.get(skylarkModule.name()); module.addMethod(new SkylarkBuiltinMethodDoc(module, skylarkSignature, field.getType())); } } } } private static Map> collectBuiltinJavaObjects(String ...clazz) { Map> modules = new HashMap<>(); collectBuiltinModule(modules, SkylarkRuleContext.class); collectBuiltinModule(modules, TransitiveInfoCollection.class); collectBuiltinModule(modules, AppleConfiguration.class); collectBuiltinModule(modules, CppConfiguration.class); collectBuiltinModule(modules, JavaConfiguration.class); collectBuiltinModule(modules, Jvm.class); collectBuiltinModule(modules, JavaSkylarkApiProvider.class); collectBuiltinModule(modules, JavaRuleOutputJarsProvider.OutputJar.class); collectBuiltinModule(modules, AndroidSkylarkApiProvider.class); for (String c : clazz) { try { collectBuiltinModule(modules, SkylarkDocumentationCollector.class.getClassLoader().loadClass(c)); } catch (ClassNotFoundException e) { System.err.println("SkylarkModule class " + c + " could not be found, ignoring..."); } } return modules; } private static void collectBuiltinModule( Map> modules, Class moduleClass) { if (moduleClass.isAnnotationPresent(SkylarkModule.class)) { SkylarkModule skylarkModule = moduleClass.getAnnotation(SkylarkModule.class); modules.put(skylarkModule, moduleClass); } } /** * Returns the top level modules and functions with their documentation in a command-line * printable format. */ public static Map collectTopLevelModules() { Map modules = new TreeMap<>(); for (SkylarkModuleDoc doc : collectBuiltinModules().values()) { if (doc.getAnnotation() == getTopLevelModule()) { for (Map.Entry entry : doc.getBuiltinMethods().entrySet()) { if (entry.getValue().documented()) { modules.put(entry.getKey(), DocgenConsts.toCommandLineFormat(entry.getValue().getDocumentation())); } } } else { modules.put(doc.getAnnotation().name(), DocgenConsts.toCommandLineFormat(doc.getAnnotation().doc())); } } return modules; } }