summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorccalvarin <ccalvarin@google.com>2017-09-26 18:11:53 -0400
committerColin Cross <ccross@android.com>2017-10-24 12:20:25 -0700
commit5846f33fd5435036751ecf36331386cf6fe655b5 (patch)
treedf51cadd88a4f5d93f74f20bbc90ec25347cf1a4
parenta0d433e79adbd70bc30ee613654fdf3fbb9de991 (diff)
downloaddesugar-5846f33fd5435036751ecf36331386cf6fe655b5.tar.gz
Add new option categorization and tagging information to HelpCommand's output.
If setting flag --use_new_category_enum, group the options by the new categories in both the command line output and the "everything-as-html" output used for the generated docs at https://bazel.build/versions/master/docs/command-line-reference.html. In the html output, the effect and metadata tags are listed for each option, with links to their descriptions at the bottom of the page. The tags only appear in the terminal output in -l/--long/--help_verbosity=long, and only the names appear. This is still experimental as the majority of options do not yet use the new categorization system. The new output can be seen in command-line-reference.html format by adding the new flag to the "help everything-as-html" line in //src/main/java/com/google/devtools/build/lib:gen_command-line-reference. The html output is in the same order as before (by blaze rule, with inherited options not repeated), which means it still has duplicate options, that should ideally be fixed separately. I propose either dropping the high-level grouping and just grouping the options by documentation category, or potentially grouping them by optionsbase in some non-class-naming way, and listing the commands that they apply to, as more and more optionsbases are used by multiple commands without being inherited (for example, all BuildEventServiceOptions are listed 20 times). People probably use ctrl-f to navigate this page anyway. Once we know that we only list each option once, we can actually have links to the options, which will make it possible to have links in the expansion lists. Issue #3758 RELNOTES: added experimental --use_new_category_enum to the help command to output options grouped by the new type of category. PiperOrigin-RevId: 170116553 GitOrigin-RevId: 77e3a5ca955a3834406a837dbd4607b0b432b2d8 Change-Id: Id3f1fea6833f0cc05d127eb1454f21c92b22e03a
-rw-r--r--java/com/google/devtools/common/options/OptionFilterDescriptions.java175
-rw-r--r--java/com/google/devtools/common/options/OptionsParser.java136
-rw-r--r--java/com/google/devtools/common/options/OptionsParserImpl.java5
-rw-r--r--java/com/google/devtools/common/options/OptionsUsage.java90
4 files changed, 381 insertions, 25 deletions
diff --git a/java/com/google/devtools/common/options/OptionFilterDescriptions.java b/java/com/google/devtools/common/options/OptionFilterDescriptions.java
new file mode 100644
index 0000000..4c6efef
--- /dev/null
+++ b/java/com/google/devtools/common/options/OptionFilterDescriptions.java
@@ -0,0 +1,175 @@
+// Copyright 2017 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.common.options;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Provides descriptions of the options filters, for use in generated documentation and usage text.
+ */
+public class OptionFilterDescriptions {
+
+ /** The order that the categories should be listed in. */
+ static OptionDocumentationCategory[] documentationOrder = {
+ OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
+ OptionDocumentationCategory.EXECUTION_STRATEGY,
+ OptionDocumentationCategory.TOOLCHAIN,
+ OptionDocumentationCategory.OUTPUT_SELECTION,
+ OptionDocumentationCategory.OUTPUT_PARAMETERS,
+ OptionDocumentationCategory.SIGNING,
+ OptionDocumentationCategory.TESTING,
+ OptionDocumentationCategory.QUERY,
+ OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
+ OptionDocumentationCategory.LOGGING,
+ OptionDocumentationCategory.GENERIC_INPUTS,
+ OptionDocumentationCategory.UNCATEGORIZED
+ };
+
+ static ImmutableMap<OptionDocumentationCategory, String> getOptionCategoriesEnumDescription(
+ String productName) {
+ ImmutableMap.Builder<OptionDocumentationCategory, String> optionCategoriesBuilder =
+ ImmutableMap.builder();
+ optionCategoriesBuilder
+ .put(
+ OptionDocumentationCategory.UNCATEGORIZED,
+ "Miscellaneous options, not otherwise categorized.")
+ .put( // Here for completeness, the help output should not include this option.
+ OptionDocumentationCategory.UNDOCUMENTED,
+ "This feature should not be documented, as it is not meant for general use")
+ .put(
+ OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
+ "Options that appear before the command and are parsed by the client")
+ .put(
+ OptionDocumentationCategory.LOGGING,
+ "Options that affect the verbosity, format or location of logging")
+ .put(OptionDocumentationCategory.EXECUTION_STRATEGY, "Options that control build execution")
+ .put(
+ OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
+ "Options that trigger optimizations of the build time")
+ .put(
+ OptionDocumentationCategory.OUTPUT_SELECTION,
+ "Options that control the output of the command")
+ .put(
+ OptionDocumentationCategory.OUTPUT_PARAMETERS,
+ "Options that let the user configure the intended output, affecting its value, as "
+ + "opposed to its existence")
+ .put(
+ OptionDocumentationCategory.SIGNING,
+ "Options that affect the signing outputs of a build")
+ .put(
+ OptionDocumentationCategory.TESTING,
+ "Options that govern the behavior of the test environment or test runner")
+ .put(
+ OptionDocumentationCategory.TOOLCHAIN,
+ "Options that configure the toolchain used for action execution")
+ .put(OptionDocumentationCategory.QUERY, "Options relating to query output and semantics")
+ .put(
+ OptionDocumentationCategory.GENERIC_INPUTS,
+ "Options specifying or altering a generic input to a Bazel command that does not fall "
+ + "into other categories.");
+ return optionCategoriesBuilder.build();
+ }
+
+ public static ImmutableMap<OptionEffectTag, String> getOptionEffectTagDescription(
+ String productName) {
+ ImmutableMap.Builder<OptionEffectTag, String> effectTagDescriptionBuilder =
+ ImmutableMap.builder();
+ effectTagDescriptionBuilder
+ .put(OptionEffectTag.UNKNOWN, "This option has unknown, or undocumented, effect.")
+ .put(OptionEffectTag.NO_OP, "This option has literally no effect.")
+ .put(
+ OptionEffectTag.LOSES_INCREMENTAL_STATE,
+ "Changing the value of this option can cause significant loss of incremental "
+ + "state, which slows builds. State could be lost due to a server restart or to "
+ + "invalidation of a large part of the dependency graph.")
+ .put(
+ OptionEffectTag.CHANGES_INPUTS,
+ "This option actively changes the inputs that "
+ + productName
+ + " considers for the build, such as filesystem restrictions, repository versions, "
+ + "or other options.")
+ .put(
+ OptionEffectTag.AFFECTS_OUTPUTS,
+ "This option affects "
+ + productName
+ + "'s outputs. This tag is intentionally broad, can include transitive affects, "
+ + "and does not specify the type of output it affects.")
+ .put(
+ OptionEffectTag.BUILD_FILE_SEMANTICS,
+ "This option affects the semantics of BUILD or .bzl files.")
+ .put(
+ OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION,
+ "This option affects settings of "
+ + productName
+ + "-internal machinery. This tag does not, on its own, mean that build artifacts "
+ + "are affected.")
+ .put(
+ OptionEffectTag.LOADING_AND_ANALYSIS,
+ "This option affects the loading and analysis of dependencies, and the building "
+ + "of the dependency graph.")
+ .put(
+ OptionEffectTag.EXECUTION,
+ "This option affects the execution phase, such as sandboxing or remote execution "
+ + "related options.")
+ .put(
+ OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS,
+ "This option triggers an optimization that may be machine specific and is not "
+ + "guaranteed to work on all machines. The optimization could include a tradeoff "
+ + "with other aspects of performance, such as memory or cpu cost.")
+ .put(
+ OptionEffectTag.EAGERNESS_TO_EXIT,
+ "This option changes how eagerly "
+ + productName
+ + " will exit from a failure, where a choice between continuing despite the "
+ + "failure and ending the invocation exists.")
+ .put(
+ OptionEffectTag.BAZEL_MONITORING,
+ "This option is used to monitor " + productName + "'s behavior and performance.")
+ .put(
+ OptionEffectTag.TERMINAL_OUTPUT,
+ "This option affects " + productName + "'s terminal output.")
+ .put(
+ OptionEffectTag.ACTION_OPTIONS,
+ "This option changes the command line arguments of one or more build actions.")
+ .put(
+ OptionEffectTag.TEST_RUNNER,
+ "This option changes the testrunner environment of the build.");
+ return effectTagDescriptionBuilder.build();
+ }
+
+ public static ImmutableMap<OptionMetadataTag, String> getOptionMetadataTagDescription(
+ String productName) {
+ ImmutableMap.Builder<OptionMetadataTag, String> effectTagDescriptionBuilder =
+ ImmutableMap.builder();
+ effectTagDescriptionBuilder
+ .put(
+ OptionMetadataTag.EXPERIMENTAL,
+ "This option triggers an experimental feature with no guarantees of functionality.")
+ .put(
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ "This option triggers a breaking change. Use this option to test your migration "
+ + "readiness or get early access to the new feature")
+ .put(
+ OptionMetadataTag.DEPRECATED,
+ "This option is deprecated. It might be that the feature it affects is deprecated, "
+ + "or that another method of supplying the information is preferred.")
+ .put(
+ OptionMetadataTag.HIDDEN, // Here for completeness, these options are UNDOCUMENTED.
+ "This option should not be used by a user, and should not be logged.")
+ .put(
+ OptionMetadataTag.INTERNAL, // Here for completeness, these options are UNDOCUMENTED.
+ "This option isn't even a option, and should not be logged.");
+ return effectTagDescriptionBuilder.build();
+ }
+}
diff --git a/java/com/google/devtools/common/options/OptionsParser.java b/java/com/google/devtools/common/options/OptionsParser.java
index 28c2206..2b4bd2a 100644
--- a/java/com/google/devtools/common/options/OptionsParser.java
+++ b/java/com/google/devtools/common/options/OptionsParser.java
@@ -17,8 +17,10 @@ package com.google.devtools.common.options;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
import com.google.common.escape.Escaper;
import com.google.devtools.common.options.OptionDefinition.NotAnOptionException;
import java.lang.reflect.Constructor;
@@ -32,6 +34,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -224,7 +227,9 @@ public class OptionsParser implements OptionsProvider {
public void parseAndExitUponError(OptionPriority priority, String source, String[] args) {
for (String arg : args) {
if (arg.equals("--help")) {
- System.out.println(describeOptions(ImmutableMap.of(), HelpVerbosity.LONG));
+ System.out.println(
+ describeOptionsWithDeprecatedCategories(ImmutableMap.of(), HelpVerbosity.LONG));
+
System.exit(0);
}
}
@@ -283,6 +288,78 @@ public class OptionsParser implements OptionsProvider {
* Returns a description of all the options this parser can digest. In addition to {@link Option}
* annotations, this method also interprets {@link OptionsUsage} annotations which give an
* intuitive short description for the options. Options of the same category (see {@link
+ * OptionDocumentationCategory}) will be grouped together.
+ *
+ * @param productName the name of this product (blaze, bazel)
+ * @param helpVerbosity if {@code long}, the options will be described verbosely, including their
+ * types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
+ * {@code short}, the options are just enumerated.
+ */
+ public String describeOptions(String productName, HelpVerbosity helpVerbosity) {
+ StringBuilder desc = new StringBuilder();
+ LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
+ getOptionsSortedByCategory();
+ ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
+ OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
+ for (Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
+ optionsByCategory.entrySet()) {
+ String categoryDescription = optionCategoryDescriptions.get(e.getKey());
+ List<OptionDefinition> categorizedOptionList = e.getValue();
+
+ // Describe the category if we're going to end up using it at all.
+ if (!categorizedOptionList.isEmpty()) {
+ desc.append("\n").append(categoryDescription).append(":\n");
+ }
+ // Describe the options in this category.
+ for (OptionDefinition optionDef : categorizedOptionList) {
+ OptionsUsage.getUsage(optionDef, desc, helpVerbosity, impl.getOptionsData(), true);
+ }
+ }
+
+ return desc.toString().trim();
+ }
+
+ /**
+ * @return all documented options loaded in this parser, grouped by categories in display order.
+ */
+ private LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>>
+ getOptionsSortedByCategory() {
+ OptionsData data = impl.getOptionsData();
+ if (data.getOptionsClasses().isEmpty()) {
+ return new LinkedHashMap<>();
+ }
+
+ // Get the documented options grouped by category.
+ ListMultimap<OptionDocumentationCategory, OptionDefinition> optionsByCategories =
+ ArrayListMultimap.create();
+ for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
+ for (OptionDefinition optionDefinition :
+ OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
+ // Only track documented options.
+ if (optionDefinition.getDocumentationCategory()
+ != OptionDocumentationCategory.UNDOCUMENTED) {
+ optionsByCategories.put(optionDefinition.getDocumentationCategory(), optionDefinition);
+ }
+ }
+ }
+
+ // Put the categories into display order and sort the options in each category.
+ LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> sortedCategoriesToOptions =
+ new LinkedHashMap<>(OptionFilterDescriptions.documentationOrder.length, 1);
+ for (OptionDocumentationCategory category : OptionFilterDescriptions.documentationOrder) {
+ List<OptionDefinition> optionList = optionsByCategories.get(category);
+ if (optionList != null) {
+ optionList.sort(OptionDefinition.BY_OPTION_NAME);
+ sortedCategoriesToOptions.put(category, optionList);
+ }
+ }
+ return sortedCategoriesToOptions;
+ }
+
+ /**
+ * Returns a description of all the options this parser can digest. In addition to {@link Option}
+ * annotations, this method also interprets {@link OptionsUsage} annotations which give an
+ * intuitive short description for the options. Options of the same category (see {@link
* Option#category}) will be grouped together.
*
* @param categoryDescriptions a mapping from category names to category descriptions.
@@ -291,7 +368,8 @@ public class OptionsParser implements OptionsProvider {
* types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
* {@code short}, the options are just enumerated.
*/
- public String describeOptions(
+ @Deprecated
+ public String describeOptionsWithDeprecatedCategories(
Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) {
OptionsData data = impl.getOptionsData();
StringBuilder desc = new StringBuilder();
@@ -318,7 +396,8 @@ public class OptionsParser implements OptionsProvider {
if (optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
- OptionsUsage.getUsage(optionDefinition, desc, helpVerbosity, impl.getOptionsData());
+ OptionsUsage.getUsage(
+ optionDefinition, desc, helpVerbosity, impl.getOptionsData(), false);
}
}
}
@@ -326,17 +405,17 @@ public class OptionsParser implements OptionsProvider {
}
/**
- * Returns a description of all the options this parser can digest.
- * In addition to {@link Option} annotations, this method also
- * interprets {@link OptionsUsage} annotations which give an intuitive short
- * description for the options.
+ * Returns a description of all the options this parser can digest. In addition to {@link Option}
+ * annotations, this method also interprets {@link OptionsUsage} annotations which give an
+ * intuitive short description for the options.
*
- * @param categoryDescriptions a mapping from category names to category
- * descriptions. Options of the same category (see {@link
- * Option#category}) will be grouped together, preceded by the description
- * of the category.
+ * @param categoryDescriptions a mapping from category names to category descriptions. Options of
+ * the same category (see {@link Option#category}) will be grouped together, preceded by the
+ * description of the category.
*/
- public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) {
+ @Deprecated
+ public String describeOptionsHtmlWithDeprecatedCategories(
+ Map<String, String> categoryDescriptions, Escaper escaper) {
OptionsData data = impl.getOptionsData();
StringBuilder desc = new StringBuilder();
if (!data.getOptionsClasses().isEmpty()) {
@@ -366,7 +445,7 @@ public class OptionsParser implements OptionsProvider {
if (optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
- OptionsUsage.getUsageHtml(optionDefinition, desc, escaper, impl.getOptionsData());
+ OptionsUsage.getUsageHtml(optionDefinition, desc, escaper, impl.getOptionsData(), false);
}
}
desc.append("</dl>\n");
@@ -375,6 +454,37 @@ public class OptionsParser implements OptionsProvider {
}
/**
+ * Returns a description of all the options this parser can digest. In addition to {@link Option}
+ * annotations, this method also interprets {@link OptionsUsage} annotations which give an
+ * intuitive short description for the options.
+ */
+ public String describeOptionsHtml(Escaper escaper, String productName) {
+ StringBuilder desc = new StringBuilder();
+ LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
+ getOptionsSortedByCategory();
+ ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
+ OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
+
+ for (Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
+ optionsByCategory.entrySet()) {
+ desc.append("<dl>");
+ String categoryDescription = optionCategoryDescriptions.get(e.getKey());
+ List<OptionDefinition> categorizedOptionsList = e.getValue();
+
+ // Describe the category if we're going to end up using it at all.
+ if (!categorizedOptionsList.isEmpty()) {
+ desc.append(escaper.escape(categoryDescription)).append(":\n");
+ }
+ // Describe the options in this category.
+ for (OptionDefinition optionDef : categorizedOptionsList) {
+ OptionsUsage.getUsageHtml(optionDef, desc, escaper, impl.getOptionsData(), true);
+ }
+ desc.append("</dl>\n");
+ }
+ return desc.toString();
+ }
+
+ /**
* Returns a string listing the possible flag completion for this command along with the command
* completion if any. See {@link OptionsUsage#getCompletion(OptionDefinition, StringBuilder)} for
* more details on the format for the flag completion.
diff --git a/java/com/google/devtools/common/options/OptionsParserImpl.java b/java/com/google/devtools/common/options/OptionsParserImpl.java
index 221dcf0..38248bf 100644
--- a/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -90,10 +90,9 @@ class OptionsParserImpl {
}
};
- /**
- * Create a new parser object
- */
+ /** Create a new parser object. Do not accept a null OptionsData object. */
OptionsParserImpl(OptionsData optionsData) {
+ Preconditions.checkNotNull(optionsData);
this.optionsData = optionsData;
}
diff --git a/java/com/google/devtools/common/options/OptionsUsage.java b/java/com/google/devtools/common/options/OptionsUsage.java
index 68a460e..ecc7a6b 100644
--- a/java/com/google/devtools/common/options/OptionsUsage.java
+++ b/java/com/google/devtools/common/options/OptionsUsage.java
@@ -21,7 +21,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.escape.Escaper;
import java.text.BreakIterator;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
/** A renderer for usage messages for any combination of options classes. */
@@ -42,7 +46,7 @@ class OptionsUsage {
new ArrayList<>(OptionsData.getAllOptionDefinitionsForClass(optionsClass));
optionDefinitions.sort(OptionDefinition.BY_OPTION_NAME);
for (OptionDefinition optionDefinition : optionDefinitions) {
- getUsage(optionDefinition, usage, OptionsParser.HelpVerbosity.LONG, data);
+ getUsage(optionDefinition, usage, OptionsParser.HelpVerbosity.LONG, data, false);
}
}
@@ -95,19 +99,33 @@ class OptionsUsage {
}
+ // Placeholder tag "UNKNOWN" is ignored.
+ private static boolean shouldEffectTagBeListed(OptionEffectTag effectTag) {
+ return !effectTag.equals(OptionEffectTag.UNKNOWN);
+ }
+
+ // Tags that only apply to undocumented options are excluded.
+ private static boolean shouldMetadataTagBeListed(OptionMetadataTag metadataTag) {
+ return !metadataTag.equals(OptionMetadataTag.HIDDEN)
+ && !metadataTag.equals(OptionMetadataTag.INTERNAL);
+ }
+
/** Appends the usage message for a single option-field message to 'usage'. */
static void getUsage(
OptionDefinition optionDefinition,
StringBuilder usage,
OptionsParser.HelpVerbosity helpVerbosity,
- OptionsData optionsData) {
+ OptionsData optionsData,
+ boolean includeTags) {
String flagName = getFlagName(optionDefinition);
String typeDescription = getTypeDescription(optionDefinition);
usage.append(" --").append(flagName);
- if (helpVerbosity == OptionsParser.HelpVerbosity.SHORT) { // just the name
+ if (helpVerbosity == OptionsParser.HelpVerbosity.SHORT) {
usage.append('\n');
return;
}
+
+ // Add the option's type and default information. Stop there for "medium" verbosity.
if (optionDefinition.getAbbreviation() != '\0') {
usage.append(" [-").append(optionDefinition.getAbbreviation()).append(']');
}
@@ -127,9 +145,12 @@ class OptionsUsage {
usage.append(")");
}
usage.append("\n");
- if (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM) { // just the name and type.
+ if (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM) {
return;
}
+
+ // For verbosity "long," add the full description and expansion, along with the tag
+ // information if requested.
if (!optionDefinition.getHelpText().isEmpty()) {
usage.append(paragraphFill(optionDefinition.getHelpText(), /*indent=*/ 4, /*width=*/ 80));
usage.append('\n');
@@ -151,9 +172,28 @@ class OptionsUsage {
for (String req : optionDefinition.getImplicitRequirements()) {
requiredMsg.append(req).append(" ");
}
- usage.append(paragraphFill(requiredMsg.toString(), /*indent=*/ 6, /*width=*/ 80));
+ usage.append(paragraphFill(requiredMsg.toString(), 6, 80)); // (indent, width)
usage.append('\n');
}
+ if (!includeTags) {
+ return;
+ }
+
+ // If we are expected to include the tags, add them for high verbosity.
+ Stream<OptionEffectTag> effectTagStream =
+ Arrays.stream(optionDefinition.getOptionEffectTags())
+ .filter(OptionsUsage::shouldEffectTagBeListed);
+ Stream<OptionMetadataTag> metadataTagStream =
+ Arrays.stream(optionDefinition.getOptionMetadataTags())
+ .filter(OptionsUsage::shouldMetadataTagBeListed);
+ String tagList =
+ Stream.concat(effectTagStream, metadataTagStream)
+ .map(tag -> tag.toString().toLowerCase())
+ .collect(Collectors.joining(", "));
+ if (!tagList.isEmpty()) {
+ usage.append(paragraphFill("Tags: " + tagList, 6, 80)); // (indent, width)
+ usage.append("\n");
+ }
}
/** Append the usage message for a single option-field message to 'usage'. */
@@ -161,7 +201,8 @@ class OptionsUsage {
OptionDefinition optionDefinition,
StringBuilder usage,
Escaper escaper,
- OptionsData optionsData) {
+ OptionsData optionsData,
+ boolean includeTags) {
String plainFlagName = optionDefinition.getOptionName();
String flagName = getFlagName(optionDefinition);
String valueDescription = optionDefinition.getValueTypeHelpText();
@@ -215,7 +256,10 @@ class OptionsUsage {
Preconditions.checkArgument(!expansion.isEmpty());
expandsMsg = new StringBuilder("Expands to:<br/>\n");
for (String exp : expansion) {
- // TODO(ulfjack): Can we link to the expanded flags here?
+ // TODO(ulfjack): We should link to the expanded flags, but unfortunately we don't
+ // currently guarantee that all flags are only printed once. A flag in an OptionBase that
+ // is included by 2 different commands, but not inherited through a parent command, will
+ // be printed multiple times.
expandsMsg
.append("&nbsp;&nbsp;<code>")
.append(escaper.escape(exp))
@@ -225,6 +269,32 @@ class OptionsUsage {
usage.append(expandsMsg.toString());
}
+ // Add effect tags, if not UNKNOWN, and metadata tags, if not empty.
+ if (includeTags) {
+ Stream<OptionEffectTag> effectTagStream =
+ Arrays.stream(optionDefinition.getOptionEffectTags())
+ .filter(OptionsUsage::shouldEffectTagBeListed);
+ Stream<OptionMetadataTag> metadataTagStream =
+ Arrays.stream(optionDefinition.getOptionMetadataTags())
+ .filter(OptionsUsage::shouldMetadataTagBeListed);
+ String tagList =
+ Stream.concat(
+ effectTagStream.map(
+ tag ->
+ String.format(
+ "<a href=\"#effect_tag_%s\"><code>%s</code></a>",
+ tag, tag.name().toLowerCase())),
+ metadataTagStream.map(
+ tag ->
+ String.format(
+ "<a href=\"#metadata_tag_%s\"><code>%s</code></a>",
+ tag, tag.name().toLowerCase())))
+ .collect(Collectors.joining(", "));
+ if (!tagList.isEmpty()) {
+ usage.append("<br>Tags: \n").append(tagList);
+ }
+ }
+
usage.append("</dd>\n");
}
@@ -263,8 +333,10 @@ class OptionsUsage {
builder.append("={auto,yes,no}\n");
builder.append("--no").append(flagName).append("\n");
} else if (fieldType.isEnum()) {
- builder.append("={")
- .append(COMMA_JOINER.join(fieldType.getEnumConstants()).toLowerCase()).append("}\n");
+ builder
+ .append("={")
+ .append(COMMA_JOINER.join(fieldType.getEnumConstants()).toLowerCase(Locale.ENGLISH))
+ .append("}\n");
} else if (fieldType.getSimpleName().equals("Label")) {
// String comparison so we don't introduce a dependency to com.google.devtools.build.lib.
builder.append("=label\n");