| // 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 static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Splitter; | |
| 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.BufferedReader; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| 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. | |
| */ | |
| @VisibleForTesting | |
| public class BuildDocCollector { | |
| private static final Splitter SHARP_SPLITTER = Splitter.on('#').limit(2).trimResults(); | |
| private ConfiguredRuleClassProvider ruleClassProvider; | |
| private boolean printMessages; | |
| public BuildDocCollector(ConfiguredRuleClassProvider ruleClassProvider, | |
| boolean printMessages) { | |
| this.ruleClassProvider = ruleClassProvider; | |
| this.printMessages = printMessages; | |
| } | |
| /** | |
| * Parse the file containing black-listed rules for documentation. The list is simply a list of | |
| * rules separated by new lines. Line comments can be added to the file by starting them with #. | |
| * | |
| * @param blackList The name of the file containing the black list. | |
| * @return The set of black listed rules. | |
| * @throws IOException | |
| */ | |
| @VisibleForTesting | |
| public static Set<String> readBlackList(String blackList) throws IOException { | |
| Set<String> result = new HashSet<String>(); | |
| if (blackList != null && !blackList.isEmpty()) { | |
| File file = new File(blackList); | |
| try (BufferedReader reader = Files.newBufferedReader(file.toPath(), UTF_8)) { | |
| for (String line = reader.readLine(); line != null; line = reader.readLine()) { | |
| String rule = SHARP_SPLITTER.split(line).iterator().next(); | |
| if (!rule.isEmpty()) { | |
| result.add(rule); | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Collects all the rule and attribute documentation present in inputDirs, integrates the | |
| * attribute documentation in the rule documentation and returns the rule documentation. | |
| * | |
| * @param inputDirs list of directories to scan for documentation | |
| * @param blackList specify an optional black list file that list some rules that should | |
| * not be listed in the output. | |
| */ | |
| public Map<String, RuleDocumentation> collect(String[] inputDirs, String blackList) | |
| throws BuildEncyclopediaDocException, IOException { | |
| // Read the blackList file | |
| Set<String> blacklistedRules = readBlackList(blackList); | |
| // 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<String, RuleDocumentation> ruleDocEntries = new TreeMap<>(); | |
| // RuleDocumentationAttribute objects equal based on attributeName so they have to be | |
| // collected in a List instead of a Set. | |
| ListMultimap<String, RuleDocumentationAttribute> attributeDocEntries = | |
| LinkedListMultimap.create(); | |
| // Map of rule class name to file that defined it. | |
| Map<String, File> 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<File> processedFiles = new HashSet<>(); | |
| for (String inputDir : inputDirs) { | |
| if (printMessages) { | |
| System.out.println(" Processing input directory: " + inputDir); | |
| } | |
| int ruleNum = ruleDocEntries.size(); | |
| collectDocs(processedFiles, ruleClassFiles, ruleDocEntries, blacklistedRules, | |
| attributeDocEntries, new File(inputDir)); | |
| if (printMessages) { | |
| System.out.println(" " + (ruleDocEntries.size() - ruleNum) | |
| + " rule documentations found."); | |
| } | |
| } | |
| processAttributeDocs(ruleDocEntries.values(), attributeDocEntries); | |
| RuleLinkExpander expander = buildRuleLinkExpander(ruleDocEntries.values()); | |
| for (RuleDocumentation rule : ruleDocEntries.values()) { | |
| rule.setRuleLinkExpander(expander); | |
| } | |
| return ruleDocEntries; | |
| } | |
| /** | |
| * Generates an index mapping rule name to its normalized rule family name. | |
| */ | |
| private RuleLinkExpander buildRuleLinkExpander(Iterable<RuleDocumentation> rules) { | |
| Map<String, String> index = new HashMap<>(); | |
| for (RuleDocumentation rule : rules) { | |
| index.put(rule.getRuleName(), RuleFamily.normalize(rule.getRuleFamily())); | |
| } | |
| return new RuleLinkExpander(index); | |
| } | |
| /** | |
| * 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<RuleDocumentation> ruleDocEntries, | |
| ListMultimap<String, RuleDocumentationAttribute> attributeDocEntries) | |
| throws BuildEncyclopediaDocException { | |
| for (RuleDocumentation ruleDoc : ruleDocEntries) { | |
| RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(ruleDoc.getRuleName()); | |
| if (ruleClass != null) { | |
| if (ruleClass.isDocumented()) { | |
| Class<? extends RuleDefinition> ruleDefinition = | |
| ruleClassProvider.getRuleClassDefinition(ruleDoc.getRuleName()).getClass(); | |
| for (Attribute attribute : ruleClass.getAttributes()) { | |
| String attrName = attribute.getName(); | |
| List<RuleDocumentationAttribute> 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, | |
| ruleClassProvider); | |
| 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<File> processedFiles, | |
| Map<String, File> ruleClassFiles, | |
| Map<String, RuleDocumentation> ruleDocEntries, | |
| Set<String> blackList, | |
| ListMultimap<String, RuleDocumentationAttribute> 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 (!blackList.contains(ruleName)) { | |
| 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, blackList, | |
| attributeDocEntries, childPath); | |
| } | |
| } | |
| processedFiles.add(inputPath); | |
| } | |
| } |