// 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.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Ordering; import com.google.devtools.build.docgen.DocgenConsts.RuleType; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.packages.RuleClass; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * A class to assemble documentation for the Build Encyclopedia. This class uses * {@link BuildDocCollector} to extract documentation fragments from rule classes. */ public abstract class BuildEncyclopediaProcessor { protected static final Predicate<String> RULE_WORTH_DOCUMENTING = new Predicate<String>() { @Override public boolean apply(String name) { return !name.contains("$"); } }; protected ConfiguredRuleClassProvider ruleClassProvider; /** * Creates the BuildEncyclopediaProcessor instance. The ruleClassProvider parameter * is used for rule class hierarchy and attribute checking. * */ public BuildEncyclopediaProcessor(ConfiguredRuleClassProvider ruleClassProvider) { this.ruleClassProvider = Preconditions.checkNotNull(ruleClassProvider); } /** * Collects and processes all the rule and attribute documentation in inputDirs and * generates the Build Encyclopedia into the outputDir. * * @param inputDirs list of directory to scan for document in the source code * @param outputRootDir output directory where to write the build encyclopedia * @param blackList optional path to a file listing rules to not document */ public abstract void generateDocumentation(List<String> inputDirs, String outputDir, String blackList) throws BuildEncyclopediaDocException, IOException; /** * POD class for containing lists of rule families separated into language-specific and generic as * returned by {@link #assembleRuleFamilies(Iterable<RuleDocumentation>) assembleRuleFamilies}. */ protected static class RuleFamilies { public List<RuleFamily> langSpecific; public List<RuleFamily> generic; public List<RuleFamily> all; public RuleFamilies(List<RuleFamily> langSpecific, List<RuleFamily> generic, List<RuleFamily> all) { this.langSpecific = langSpecific; this.generic = generic; this.all = all; } } protected RuleFamilies assembleRuleFamilies(Iterable<RuleDocumentation> docEntries) throws BuildEncyclopediaDocException, IOException { // Separate rule families into language-specific and generic ones. Set<String> langSpecificRuleFamilyNames = new TreeSet<>(); Set<String> genericRuleFamilyNames = new TreeSet<>(); separateRuleFamilies(docEntries, langSpecificRuleFamilyNames, genericRuleFamilyNames); // Create a mapping of rules based on rule type and family. Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping = new HashMap<>(); createRuleMapping(docEntries, ruleMapping); // Create lists of RuleFamily objects that will be used to generate the documentation. // The separate language-specific and general rule families will be used to generate // the Overview page while the list containing all rule families will be used to // generate all other documentation. List<RuleFamily> langSpecificRuleFamilies = filterRuleFamilies(ruleMapping, langSpecificRuleFamilyNames); List<RuleFamily> genericRuleFamilies = filterRuleFamilies(ruleMapping, genericRuleFamilyNames); List<RuleFamily> allRuleFamilies = new ArrayList<>(langSpecificRuleFamilies); allRuleFamilies.addAll(genericRuleFamilies); return new RuleFamilies(langSpecificRuleFamilies, genericRuleFamilies, allRuleFamilies); } private List<RuleFamily> filterRuleFamilies( Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping, Set<String> ruleFamilyNames) { List<RuleFamily> ruleFamilies = new ArrayList<>(ruleFamilyNames.size()); for (String name : ruleFamilyNames) { ListMultimap<RuleType, RuleDocumentation> ruleTypeMap = ruleMapping.get(name); ruleFamilies.add(new RuleFamily(ruleTypeMap, name)); } return ruleFamilies; } /** * Create a mapping of rules based on rule type and family. */ private void createRuleMapping(Iterable<RuleDocumentation> docEntries, Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping) throws BuildEncyclopediaDocException { for (RuleDocumentation ruleDoc : docEntries) { RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(ruleDoc.getRuleName()); if (ruleClass != null) { String ruleFamily = ruleDoc.getRuleFamily(); if (!ruleMapping.containsKey(ruleFamily)) { ruleMapping.put(ruleFamily, LinkedListMultimap.<RuleType, RuleDocumentation>create()); } if (ruleClass.isDocumented()) { ruleMapping.get(ruleFamily).put(ruleDoc.getRuleType(), ruleDoc); } } else { throw ruleDoc.createException("Can't find RuleClass for " + ruleDoc.getRuleName()); } } } /** * Separates all rule families in docEntries into language-specific rules and generic rules. */ private void separateRuleFamilies(Iterable<RuleDocumentation> docEntries, Set<String> langSpecific, Set<String> generic) throws BuildEncyclopediaDocException { for (RuleDocumentation ruleDoc : docEntries) { if (ruleDoc.isLanguageSpecific()) { if (generic.contains(ruleDoc.getRuleFamily())) { throw ruleDoc.createException("The rule is marked as being language-specific, but other " + "rules of the same family have already been marked as being not."); } langSpecific.add(ruleDoc.getRuleFamily()); } else { if (langSpecific.contains(ruleDoc.getRuleFamily())) { throw ruleDoc.createException("The rule is marked as being generic, but other rules of " + "the same family have already been marked as being language-specific."); } generic.add(ruleDoc.getRuleFamily()); } } } /** * Helper method for displaying an warning message about undocumented rules. * * @param rulesWithoutDocumentation Undocumented rules to list in the warning message. */ protected static void warnAboutUndocumentedRules(Iterable<String> rulesWithoutDocumentation) { Iterable<String> undocumentedRules = Iterables.filter(rulesWithoutDocumentation, RULE_WORTH_DOCUMENTING); System.err.printf("WARNING: The following rules are undocumented: [%s]\n", Joiner.on(", ").join(Ordering.<String>natural().immutableSortedCopy(undocumentedRules))); } /** * Sets the {@link RuleLinkExpander} for the provided {@link RuleDocumentationAttributes}. * * <p>This method is used to set the {@link RuleLinkExpander} for common attributes, such as * those defined in {@link PredefinedAttributes}, so that rule references in the docs for those * attributes can be expanded. * * @param attributes The map containing the RuleDocumentationAttributes, keyed by attribute name. * @param expander The RuleLinkExpander to set in each of the RuleDocumentationAttributes. * @return A map of name to RuleDocumentationAttribute with the RuleLinkExpander set for each * attribute. */ protected static Map<String, RuleDocumentationAttribute> expandCommonAttributes( Map<String, RuleDocumentationAttribute> attributes, RuleLinkExpander expander) { Map<String, RuleDocumentationAttribute> expanded = new HashMap<>(attributes.size()); for (Map.Entry<String, RuleDocumentationAttribute> entry : attributes.entrySet()) { RuleDocumentationAttribute attribute = entry.getValue(); attribute.setRuleLinkExpander(expander); expanded.put(entry.getKey(), attribute); } return expanded; } /** * Writes the {@link Page} using the provided file name in the specified output directory. * * @param page The page to write. * @param outputDir The output directory to write the file. * @param fileName The name of the file to write the page to. * @throws IOException */ protected static void writePage(Page page, String outputDir, String fileName) throws IOException { page.write(new File(outputDir + "/" + fileName)); } }