// Copyright 2014 Google Inc. 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.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.devtools.build.docgen.DocgenConsts.RuleType; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.RuleDefinition; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.RuleClass; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Class that parses the documentation fragments of rule-classes and * generates the html format documentation. */ class BuildDocCollector { private ConfiguredRuleClassProvider ruleClassProvider; private boolean printMessages; public BuildDocCollector(ConfiguredRuleClassProvider ruleClassProvider, boolean printMessages) { this.ruleClassProvider = ruleClassProvider; this.printMessages = printMessages; } /** * Collects all the rule and attribute documentation present in inputDirs, integrates the * attribute documentation in the rule documentation and returns the rule documentation. */ public Map collect(String[] inputDirs) throws BuildEncyclopediaDocException, IOException { // RuleDocumentations are generated in order (based on rule type then alphabetically). // The ordering is also used to determine in which rule doc the common attribute docs are // generated (they are generated at the first appearance). Map ruleDocEntries = new TreeMap<>(); // RuleDocumentationAttribute objects equal based on attributeName so they have to be // collected in a List instead of a Set. ListMultimap attributeDocEntries = LinkedListMultimap.create(); // Map of rule class name to file that defined it. Map ruleClassFiles = new HashMap<>(); // Set of files already processed. The same file may be encountered multiple times because // directories are processed recursively, and an input directory may be a subdirectory of // another one. Set processedFiles = new HashSet<>(); for (String inputDir : inputDirs) { if (printMessages) { System.out.println(" Processing input directory: " + inputDir); } int ruleNum = ruleDocEntries.size(); collectDocs(processedFiles, ruleClassFiles, ruleDocEntries, attributeDocEntries, new File(inputDir)); if (printMessages) { System.out.println(" " + (ruleDocEntries.size() - ruleNum) + " rule documentations found."); } } processAttributeDocs(ruleDocEntries.values(), attributeDocEntries); return ruleDocEntries; } /** * Go through all attributes of all documented rules and search the best attribute documentation * if exists. The best documentation is the closest documentation in the ancestor graph. E.g. if * java_library.deps documented in $rule and $java_rule then the one in $java_rule is going to * apply since it's a closer ancestor of java_library. */ private void processAttributeDocs(Iterable ruleDocEntries, ListMultimap attributeDocEntries) throws BuildEncyclopediaDocException { for (RuleDocumentation ruleDoc : ruleDocEntries) { RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(ruleDoc.getRuleName()); if (ruleClass != null) { if (ruleClass.isDocumented()) { Class ruleDefinition = ruleClassProvider.getRuleClassDefinition(ruleDoc.getRuleName()); for (Attribute attribute : ruleClass.getAttributes()) { String attrName = attribute.getName(); List attributeDocList = attributeDocEntries.get(attrName); if (attributeDocList != null) { // There are attribute docs for this attribute. // Search the closest one in the ancestor graph. // Note that there can be only one 'closest' attribute since we forbid multiple // inheritance of the same attribute in RuleClass. int minLevel = Integer.MAX_VALUE; RuleDocumentationAttribute bestAttributeDoc = null; for (RuleDocumentationAttribute attributeDoc : attributeDocList) { int level = attributeDoc.getDefinitionClassAncestryLevel(ruleDefinition); if (level >= 0 && level < minLevel) { bestAttributeDoc = attributeDoc; minLevel = level; } } if (bestAttributeDoc != null) { // Add reference to the Attribute that the attribute doc is associated with // in order to generate documentation for the Attribute. bestAttributeDoc.setAttribute(attribute); ruleDoc.addAttribute(bestAttributeDoc); // If there is no matching attribute doc try to add the common. } else if (ruleDoc.getRuleType().equals(RuleType.BINARY) && PredefinedAttributes.BINARY_ATTRIBUTES.containsKey(attrName)) { ruleDoc.addAttribute(PredefinedAttributes.BINARY_ATTRIBUTES.get(attrName)); } else if (ruleDoc.getRuleType().equals(RuleType.TEST) && PredefinedAttributes.TEST_ATTRIBUTES.containsKey(attrName)) { ruleDoc.addAttribute(PredefinedAttributes.TEST_ATTRIBUTES.get(attrName)); } else if (PredefinedAttributes.COMMON_ATTRIBUTES.containsKey(attrName)) { ruleDoc.addAttribute(PredefinedAttributes.COMMON_ATTRIBUTES.get(attrName)); } } } } } else { throw ruleDoc.createException("Can't find RuleClass for " + ruleDoc.getRuleName()); } } } /** * Goes through all the html files and subdirs under inputPath and collects the rule * and attribute documentations using the ruleDocEntries and attributeDocEntries variable. */ public void collectDocs( Set processedFiles, Map ruleClassFiles, Map ruleDocEntries, ListMultimap attributeDocEntries, File inputPath) throws BuildEncyclopediaDocException, IOException { if (processedFiles.contains(inputPath)) { return; } if (inputPath.isFile()) { if (DocgenConsts.JAVA_SOURCE_FILE_SUFFIX.apply(inputPath.getName())) { SourceFileReader sfr = new SourceFileReader( ruleClassProvider, inputPath.getAbsolutePath()); sfr.readDocsFromComments(); for (RuleDocumentation d : sfr.getRuleDocEntries()) { String ruleName = d.getRuleName(); if (ruleDocEntries.containsKey(ruleName) && !ruleClassFiles.get(ruleName).equals(inputPath)) { System.err.printf("WARNING: '%s' from '%s' overrides value already in map from '%s'\n", d.getRuleName(), inputPath, ruleClassFiles.get(ruleName)); } ruleClassFiles.put(ruleName, inputPath); ruleDocEntries.put(ruleName, d); } if (attributeDocEntries != null) { // Collect all attribute documentations from this file. attributeDocEntries.putAll(sfr.getAttributeDocEntries()); } } } else if (inputPath.isDirectory()) { for (File childPath : inputPath.listFiles()) { collectDocs(processedFiles, ruleClassFiles, ruleDocEntries, attributeDocEntries, childPath); } } processedFiles.add(inputPath); } }