| // 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.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.RuleDefinition; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.TriState; |
| import com.google.devtools.build.lib.syntax.Type; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A class storing a rule attribute documentation along with some meta information. The class |
| * provides functionality to compute the ancestry level of this attribute's generator rule |
| * definition class compared to other rule definition classes. |
| * |
| * <p>Warning, two RuleDocumentationAttribute objects are equal based on only the attributeName. |
| */ |
| public class RuleDocumentationAttribute |
| implements Comparable<RuleDocumentationAttribute>, Cloneable { |
| |
| private static final ImmutableMap<Type<?>, String> TYPE_DESC = |
| ImmutableMap.<Type<?>, String>builder() |
| .put(Type.BOOLEAN, "Boolean") |
| .put(Type.INTEGER, "Integer") |
| .put(Type.INTEGER_LIST, "List of integers") |
| .put(Type.STRING, "String") |
| .put(Type.STRING_DICT, "Dictionary: String -> String") |
| .put(Type.STRING_LIST, "List of strings") |
| .put(BuildType.TRISTATE, "Integer") |
| .put(BuildType.LABEL, "<a href=\"../build-ref.html#labels\">Label</a>") |
| .put(BuildType.LABEL_LIST, "List of <a href=\"../build-ref.html#labels\">labels</a>") |
| .put( |
| BuildType.LABEL_DICT_UNARY, |
| "Dictionary mapping strings to <a href=\"../build-ref.html#labels\">labels</a>") |
| .put(BuildType.LICENSE, "Licence type") |
| .put(BuildType.NODEP_LABEL, "<a href=\"../build-ref.html#name\">Name</a>") |
| .put(BuildType.NODEP_LABEL_LIST, "List of <a href=\"../build-ref.html#name\">names</a>") |
| .put(BuildType.OUTPUT, "<a href=\"../build-ref.html#filename\">Filename</a>") |
| .put( |
| BuildType.OUTPUT_LIST, "List of <a href=\"../build-ref.html#filename\">filenames</a>") |
| .build(); |
| |
| private final Class<? extends RuleDefinition> definitionClass; |
| private final String attributeName; |
| private final String htmlDocumentation; |
| private final String commonType; |
| // Used to expand rule link references in the attribute documentation. |
| private RuleLinkExpander linkExpander; |
| private int startLineCnt; |
| private String fileName; |
| private Set<String> flags; |
| private Attribute attribute; |
| |
| |
| /** |
| * Creates common RuleDocumentationAttribute such as deps or data. |
| * These attribute docs have no definitionClass or htmlDocumentation (it's in the BE header). |
| */ |
| static RuleDocumentationAttribute create( |
| String attributeName, String commonType, String htmlDocumentation) { |
| RuleDocumentationAttribute docAttribute = new RuleDocumentationAttribute( |
| null, attributeName, htmlDocumentation, 0, "", ImmutableSet.<String>of(), commonType); |
| return docAttribute; |
| } |
| |
| /** |
| * Creates a RuleDocumentationAttribute with all the necessary fields for explicitly |
| * defined rule attributes. |
| */ |
| static RuleDocumentationAttribute create(Class<? extends RuleDefinition> definitionClass, |
| String attributeName, String htmlDocumentation, int startLineCnt, String fileName, |
| Set<String> flags) { |
| return new RuleDocumentationAttribute(definitionClass, attributeName, htmlDocumentation, |
| startLineCnt, fileName, flags, null); |
| } |
| |
| private RuleDocumentationAttribute(Class<? extends RuleDefinition> definitionClass, |
| String attributeName, String htmlDocumentation, int startLineCnt, String fileName, |
| Set<String> flags, String commonType) { |
| Preconditions.checkNotNull(attributeName, "AttributeName must not be null."); |
| this.definitionClass = definitionClass; |
| this.attributeName = attributeName; |
| this.htmlDocumentation = htmlDocumentation; |
| this.startLineCnt = startLineCnt; |
| this.flags = flags; |
| this.commonType = commonType; |
| this.fileName = fileName; |
| } |
| |
| @Override |
| protected Object clone() throws CloneNotSupportedException { |
| return super.clone(); |
| } |
| |
| /** |
| * Sets the Attribute object that this documents. |
| */ |
| void setAttribute(Attribute attribute) { |
| this.attribute = attribute; |
| } |
| |
| /** |
| * Returns the name of the rule attribute. |
| */ |
| public String getAttributeName() { |
| return attributeName; |
| } |
| |
| /** Returns the file name where the rule attribute is defined. */ |
| public String getFileName() { |
| return fileName; |
| } |
| |
| /** |
| * Returns whether this attribute is marked as deprecated. |
| */ |
| public boolean isDeprecated() { |
| return hasFlag(DocgenConsts.FLAG_DEPRECATED); |
| } |
| |
| /** |
| * Sets the {@link RuleLinkExpander} to be used to expand links in the HTML documentation. |
| */ |
| public void setRuleLinkExpander(RuleLinkExpander linkExpander) { |
| this.linkExpander = linkExpander; |
| } |
| |
| /** |
| * Returns the html documentation of the rule attribute. |
| */ |
| public String getHtmlDocumentation() throws BuildEncyclopediaDocException { |
| String expandedHtmlDoc = htmlDocumentation; |
| if (linkExpander != null) { |
| try { |
| expandedHtmlDoc = linkExpander.expand(expandedHtmlDoc); |
| } catch (IllegalArgumentException e) { |
| throw new BuildEncyclopediaDocException(fileName, startLineCnt, e.getMessage()); |
| } |
| } |
| return expandedHtmlDoc; |
| } |
| |
| /** Returns whether the param is required or optional. */ |
| public boolean isMandatory() { |
| if (attribute == null) { |
| return false; |
| } |
| return attribute.isMandatory(); |
| } |
| |
| private String getDefaultValue() { |
| if (attribute == null) { |
| return ""; |
| } |
| String prefix = "; default is "; |
| Object value = attribute.getDefaultValueUnchecked(); |
| if (value instanceof Boolean) { |
| return prefix + ((Boolean) value ? "True" : "False"); |
| } else if (value instanceof Integer) { |
| return prefix + String.valueOf(value); |
| } else if (value instanceof String && !((String) value).isEmpty()) { |
| return prefix + "\"" + value + "\""; |
| } else if (value instanceof TriState) { |
| switch((TriState) value) { |
| case AUTO: |
| return prefix + "-1"; |
| case NO: |
| return prefix + "0"; |
| case YES: |
| return prefix + "1"; |
| } |
| } else if (value instanceof Label) { |
| return prefix + "<code>" + value + "</code>"; |
| } |
| return ""; |
| } |
| |
| /** |
| * Returns a string containing the synopsis for this attribute. |
| */ |
| public String getSynopsis() { |
| if (attribute == null) { |
| return ""; |
| } |
| StringBuilder sb = new StringBuilder() |
| .append(TYPE_DESC.get(attribute.getType())) |
| .append("; " + (attribute.isMandatory() ? "required" : "optional")) |
| .append(!attribute.isConfigurable() |
| ? String.format("; <a href=\"%s#configurable-attributes\">nonconfigurable</a>", |
| RuleDocumentation.COMMON_DEFINITIONS_PAGE) |
| : "") |
| .append(getDefaultValue()); |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns the number of first line of the attribute documentation in its declaration file. |
| */ |
| int getStartLineCnt() { |
| return startLineCnt; |
| } |
| |
| /** |
| * Returns true if the attribute doc is of a common attribute type. |
| */ |
| public boolean isCommonType() { |
| return commonType != null; |
| } |
| |
| /** |
| * Returns the common attribute type if this attribute doc is of a common type |
| * otherwise actualRule. |
| */ |
| String getGeneratedInRule(String actualRule) { |
| return isCommonType() ? commonType : actualRule; |
| } |
| |
| /** |
| * Returns true if this attribute documentation has the parameter flag. |
| */ |
| boolean hasFlag(String flag) { |
| return flags.contains(flag); |
| } |
| |
| /** |
| * Returns the length of a shortest path from usingClass to the definitionClass of this |
| * RuleDocumentationAttribute in the rule definition ancestry graph. Returns -1 |
| * if definitionClass is not the ancestor (transitively) of usingClass. |
| */ |
| int getDefinitionClassAncestryLevel(Class<? extends RuleDefinition> usingClass, |
| ConfiguredRuleClassProvider ruleClassProvider) { |
| if (usingClass.equals(definitionClass)) { |
| return 0; |
| } |
| // Storing nodes (rule class definitions) with the length of the shortest path from usingClass |
| Map<Class<? extends RuleDefinition>, Integer> visited = new HashMap<>(); |
| LinkedList<Class<? extends RuleDefinition>> toVisit = new LinkedList<>(); |
| visited.put(usingClass, 0); |
| toVisit.add(usingClass); |
| // Searching the shortest path from usingClass to this.definitionClass using BFS |
| do { |
| Class<? extends RuleDefinition> ancestor = toVisit.removeFirst(); |
| visitAncestor(ancestor, visited, toVisit, ruleClassProvider); |
| if (ancestor.equals(definitionClass)) { |
| return visited.get(ancestor); |
| } |
| } while (!toVisit.isEmpty()); |
| return -1; |
| } |
| |
| private void visitAncestor( |
| Class<? extends RuleDefinition> usingClass, |
| Map<Class<? extends RuleDefinition>, Integer> visited, |
| LinkedList<Class<? extends RuleDefinition>> toVisit, |
| ConfiguredRuleClassProvider ruleClassProvider) { |
| RuleDefinition instance = getRuleDefinition(usingClass, ruleClassProvider); |
| for (Class<? extends RuleDefinition> ancestor : instance.getMetadata().ancestors()) { |
| if (!visited.containsKey(ancestor)) { |
| toVisit.addLast(ancestor); |
| visited.put(ancestor, visited.get(usingClass) + 1); |
| } |
| } |
| } |
| |
| private RuleDefinition getRuleDefinition(Class<? extends RuleDefinition> usingClass, |
| ConfiguredRuleClassProvider ruleClassProvider) { |
| if (ruleClassProvider == null) { |
| try { |
| return usingClass.getConstructor().newInstance(); |
| } catch (ReflectiveOperationException | IllegalArgumentException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| return ruleClassProvider.getRuleClassDefinition(usingClass.getName()); |
| } |
| |
| private int getAttributeOrderingPriority(RuleDocumentationAttribute attribute) { |
| if (DocgenConsts.ATTRIBUTE_ORDERING.containsKey(attribute.attributeName)) { |
| return DocgenConsts.ATTRIBUTE_ORDERING.get(attribute.attributeName); |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public int compareTo(RuleDocumentationAttribute o) { |
| int thisPriority = getAttributeOrderingPriority(this); |
| int otherPriority = getAttributeOrderingPriority(o); |
| if (thisPriority > otherPriority) { |
| return 1; |
| } else if (thisPriority < otherPriority) { |
| return -1; |
| } else { |
| return this.attributeName.compareTo(o.attributeName); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof RuleDocumentationAttribute)) { |
| return false; |
| } |
| return attributeName.equals(((RuleDocumentationAttribute) obj).attributeName); |
| } |
| |
| @Override |
| public int hashCode() { |
| return attributeName.hashCode(); |
| } |
| } |