// 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.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.util.Classpath; import com.google.devtools.build.lib.util.Classpath.ClassPathException; 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.Set; import java.util.TreeMap; /** * A helper class that collects Skylark module documentation. */ final class SkylarkDocumentationCollector { @SkylarkModule( name = "globals", title = "Globals", category = SkylarkModuleCategory.TOP_LEVEL_TYPE, doc = "Objects, functions and modules registered in the global environment." ) private static final class TopLevelModule {} // Common prefix of packages that may contain Skylark modules. private static final String MODULES_PACKAGE_PREFIX = "com/google/devtools/build"; 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. * * <p>WARNING: This method no longer supports the specification of additional module classes via * parameters. Instead, all module classes are being picked up automatically. * * @param clazz DEPRECATED. */ public static Map<String, SkylarkModuleDoc> collectModules(@Deprecated String... clazz) throws ClassPathException { Map<String, SkylarkModuleDoc> modules = new TreeMap<>(); Map<SkylarkModule, Class<?>> builtinModules = new HashMap<>(); for (Class<?> candidateClass : Classpath.findClasses(MODULES_PACKAGE_PREFIX)) { SkylarkModule annotation = candidateClass.getAnnotation(SkylarkModule.class); if (annotation != null) { collectBuiltinModule(builtinModules, candidateClass); collectJavaObjects(annotation, candidateClass, modules); } collectBuiltinDoc(modules, candidateClass.getDeclaredFields()); } return modules; } /** * Collects and returns all the Java objects reachable in Skylark from (and including) * firstClass with the corresponding SkylarkModule annotation. * * <p>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<String, SkylarkModuleDoc> modules) { Set<Class<?>> done = new HashSet<>(); Deque<Class<?>> toProcess = new LinkedList<>(); Map<Class<?>, 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<Method, SkylarkCallable> methods = FuncallExpression.collectSkylarkMethodsWithAnnotation(c); for (Map.Entry<Method, SkylarkCallable> entry : methods.entrySet()) { module.addMethod(new SkylarkJavaMethodDoc(module, entry.getKey(), entry.getValue())); } for (Map.Entry<Method, SkylarkCallable> 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 void collectBuiltinDoc(Map<String, SkylarkModuleDoc> 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 void collectBuiltinModule( Map<SkylarkModule, Class<?>> modules, Class<?> moduleClass) { if (moduleClass.isAnnotationPresent(SkylarkModule.class)) { SkylarkModule skylarkModule = moduleClass.getAnnotation(SkylarkModule.class); modules.put(skylarkModule, moduleClass); } } }