aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google')
-rw-r--r--src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaGenerator.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java74
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java204
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java175
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionsParser.java136
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionsParserImpl.java5
-rw-r--r--src/main/java/com/google/devtools/common/options/OptionsUsage.java90
7 files changed, 588 insertions, 98 deletions
diff --git a/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaGenerator.java b/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaGenerator.java
index 52f34478eb..6c6b4f35bd 100644
--- a/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaGenerator.java
+++ b/src/main/java/com/google/devtools/build/docgen/BuildEncyclopediaGenerator.java
@@ -32,7 +32,7 @@ public class BuildEncyclopediaGenerator {
+ "The product name (-n), rule class provider (-p) and at least one input_dir\n"
+ "(-i) must be specified.\n");
System.err.println(
- parser.describeOptions(
+ parser.describeOptionsWithDeprecatedCategories(
Collections.<String, String>emptyMap(), OptionsParser.HelpVerbosity.LONG));
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
index 5f57b735bd..07573fa3a8 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandUtils.java
@@ -21,7 +21,6 @@ import com.google.devtools.build.lib.syntax.SkylarkSemanticsOptions;
import com.google.devtools.build.lib.util.ResourceFileLoader;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
-
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
@@ -114,21 +113,42 @@ public class BlazeCommandUtils {
* Returns the expansion of the specified help topic.
*
* @param topic the name of the help topic; used in %{command} expansion.
- * @param help the text template of the help message. Certain %{x} variables
- * will be expanded. A prefix of "resource:" means use the .jar
- * resource of that name.
- * @param categoryDescriptions a mapping from option category names to
- * descriptions, passed to {@link OptionsParser#describeOptions}.
- * @param helpVerbosity a tri-state verbosity option selecting between just
- * names, names and syntax, and full description.
+ * @param help the text template of the help message. Certain %{x} variables will be expanded. A
+ * prefix of "resource:" means use the .jar resource of that name.
+ * @param categoryDescriptions a mapping from option category names to descriptions, passed to
+ * {@link OptionsParser#describeOptionsWithDeprecatedCategories}.
+ * @param helpVerbosity a tri-state verbosity option selecting between just names, names and
+ * syntax, and full description.
* @param productName the product name
*/
- public static String expandHelpTopic(String topic, String help,
+ public static final String expandHelpTopic(
+ String topic,
+ String help,
Class<? extends BlazeCommand> commandClass,
Collection<Class<? extends OptionsBase>> options,
Map<String, String> categoryDescriptions,
OptionsParser.HelpVerbosity helpVerbosity,
String productName) {
+ return expandHelpTopic(
+ topic,
+ help,
+ commandClass,
+ options,
+ categoryDescriptions,
+ helpVerbosity,
+ productName,
+ false);
+ }
+
+ public static final String expandHelpTopic(
+ String topic,
+ String help,
+ Class<? extends BlazeCommand> commandClass,
+ Collection<Class<? extends OptionsBase>> options,
+ Map<String, String> categoryDescriptions,
+ OptionsParser.HelpVerbosity helpVerbosity,
+ String productName,
+ boolean useNewCategoryEnum) {
OptionsParser parser = OptionsParser.newOptionsParser(options);
String template;
@@ -137,8 +157,12 @@ public class BlazeCommandUtils {
try {
template = ResourceFileLoader.loadResource(commandClass, resourceName);
} catch (IOException e) {
- throw new IllegalStateException("failed to load help resource '" + resourceName
- + "' due to I/O error: " + e.getMessage(), e);
+ throw new IllegalStateException(
+ "failed to load help resource '"
+ + resourceName
+ + "' due to I/O error: "
+ + e.getMessage(),
+ e);
}
} else {
template = help;
@@ -148,10 +172,16 @@ public class BlazeCommandUtils {
throw new IllegalStateException("Help template for '" + topic + "' omits %{options}!");
}
- String optionStr =
- parser
- .describeOptions(categoryDescriptions, helpVerbosity)
- .replace("%{product}", productName);
+ String optionStr;
+ if (useNewCategoryEnum) {
+ optionStr =
+ parser.describeOptions(productName, helpVerbosity).replace("%{product}", productName);
+ } else {
+ optionStr =
+ parser
+ .describeOptionsWithDeprecatedCategories(categoryDescriptions, helpVerbosity)
+ .replace("%{product}", productName);
+ }
return template
.replace("%{product}", productName)
.replace("%{command}", topic)
@@ -166,10 +196,10 @@ public class BlazeCommandUtils {
/**
* The help page for this command.
*
- * @param categoryDescriptions a mapping from option category names to
- * descriptions, passed to {@link OptionsParser#describeOptions}.
- * @param verbosity a tri-state verbosity option selecting between just names,
- * names and syntax, and full description.
+ * @param categoryDescriptions a mapping from option category names to descriptions, passed to
+ * {@link OptionsParser#describeOptionsWithDeprecatedCategories}.
+ * @param verbosity a tri-state verbosity option selecting between just names, names and syntax,
+ * and full description.
*/
public static String getUsage(
Class<? extends BlazeCommand> commandClass,
@@ -177,7 +207,8 @@ public class BlazeCommandUtils {
OptionsParser.HelpVerbosity verbosity,
Iterable<BlazeModule> blazeModules,
ConfiguredRuleClassProvider ruleClassProvider,
- String productName) {
+ String productName,
+ boolean useNewCategoryEnum) {
Command commandAnnotation = commandClass.getAnnotation(Command.class);
return BlazeCommandUtils.expandHelpTopic(
commandAnnotation.name(),
@@ -186,6 +217,7 @@ public class BlazeCommandUtils {
BlazeCommandUtils.getOptions(commandClass, blazeModules, ruleClassProvider),
categoryDescriptions,
verbosity,
- productName);
+ productName,
+ useNewCategoryEnum);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
index 8c3821bff6..8b1d9a8f03 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/HelpCommand.java
@@ -43,6 +43,8 @@ import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDefinition;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionFilterDescriptions;
+import com.google.devtools.common.options.OptionMetadataTag;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsProvider;
@@ -113,15 +115,25 @@ public final class HelpCommand implements BlazeCommand {
help = "Show only the names of the options, not their types or meanings."
)
public Void showShortFormOptions;
+
+ @Option(
+ name = "use_new_category_enum",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.LOGGING,
+ effectTags = {OptionEffectTag.AFFECTS_OUTPUTS, OptionEffectTag.TERMINAL_OUTPUT},
+ metadataTags = {OptionMetadataTag.EXPERIMENTAL}
+ )
+ public boolean useNewCategoryEnum;
}
/**
- * Returns a map that maps option categories to descriptive help strings for categories that
- * are not part of the Bazel core.
+ * Returns a map that maps option categories to descriptive help strings for categories that are
+ * not part of the Bazel core.
*/
- private static ImmutableMap<String, String> getOptionCategories(BlazeRuntime runtime) {
+ @Deprecated
+ private static ImmutableMap<String, String> getDeprecatedOptionCategoriesDescriptions(
+ String name) {
ImmutableMap.Builder<String, String> optionCategoriesBuilder = ImmutableMap.builder();
- String name = runtime.getProductName();
optionCategoriesBuilder
.put("checking", String.format(
"Checking options, which control %s's error checking and/or warnings", name))
@@ -180,27 +192,40 @@ public final class HelpCommand implements BlazeCommand {
return ExitCode.COMMAND_LINE_ERROR;
}
String helpSubject = options.getResidue().get(0);
- if (helpSubject.equals("startup_options")) {
- emitBlazeVersionInfo(outErr, runtime.getProductName());
- emitStartupOptions(
- outErr, helpOptions.helpVerbosity, runtime, getOptionCategories(runtime));
- return ExitCode.SUCCESS;
- } else if (helpSubject.equals("target-syntax")) {
- emitBlazeVersionInfo(outErr, runtime.getProductName());
- emitTargetSyntaxHelp(outErr, getOptionCategories(runtime), runtime.getProductName());
- return ExitCode.SUCCESS;
- } else if (helpSubject.equals("info-keys")) {
- emitInfoKeysHelp(env, outErr);
- return ExitCode.SUCCESS;
- } else if (helpSubject.equals("completion")) {
- emitCompletionHelp(runtime, outErr);
- return ExitCode.SUCCESS;
- } else if (helpSubject.equals("flags-as-proto")) {
- emitFlagsAsProtoHelp(runtime, outErr);
- return ExitCode.SUCCESS;
- } else if (helpSubject.equals("everything-as-html")) {
- new HtmlEmitter(runtime).emit(outErr);
- return ExitCode.SUCCESS;
+ String productName = runtime.getProductName();
+ // Go through the custom subjects before going through Bazel commands.
+ switch (helpSubject) {
+ case "startup_options":
+ emitBlazeVersionInfo(outErr, runtime.getProductName());
+ emitStartupOptions(
+ outErr,
+ helpOptions.helpVerbosity,
+ runtime,
+ getDeprecatedOptionCategoriesDescriptions(productName),
+ helpOptions.useNewCategoryEnum);
+ return ExitCode.SUCCESS;
+ case "target-syntax":
+ emitBlazeVersionInfo(outErr, runtime.getProductName());
+ emitTargetSyntaxHelp(
+ outErr,
+ getDeprecatedOptionCategoriesDescriptions(productName),
+ productName,
+ helpOptions.useNewCategoryEnum);
+
+ return ExitCode.SUCCESS;
+ case "info-keys":
+ emitInfoKeysHelp(env, outErr);
+ return ExitCode.SUCCESS;
+ case "completion":
+ emitCompletionHelp(runtime, outErr);
+ return ExitCode.SUCCESS;
+ case "flags-as-proto":
+ emitFlagsAsProtoHelp(runtime, outErr);
+ return ExitCode.SUCCESS;
+ case "everything-as-html":
+ new HtmlEmitter(runtime, helpOptions.useNewCategoryEnum).emit(outErr);
+ return ExitCode.SUCCESS;
+ default: // fall out
}
BlazeCommand command = runtime.getCommandMap().get(helpSubject);
@@ -218,14 +243,17 @@ public final class HelpCommand implements BlazeCommand {
return ExitCode.COMMAND_LINE_ERROR;
}
}
- emitBlazeVersionInfo(outErr, runtime.getProductName());
- outErr.printOut(BlazeCommandUtils.getUsage(
- command.getClass(),
- getOptionCategories(runtime),
- helpOptions.helpVerbosity,
- runtime.getBlazeModules(),
- runtime.getRuleClassProvider(),
- runtime.getProductName()));
+ emitBlazeVersionInfo(outErr, productName);
+ outErr.printOut(
+ BlazeCommandUtils.getUsage(
+ command.getClass(),
+ getDeprecatedOptionCategoriesDescriptions(productName),
+ helpOptions.helpVerbosity,
+ runtime.getBlazeModules(),
+ runtime.getRuleClassProvider(),
+ productName,
+ helpOptions.useNewCategoryEnum));
+
return ExitCode.SUCCESS;
}
@@ -235,16 +263,22 @@ public final class HelpCommand implements BlazeCommand {
outErr.printOut(String.format("%80s\n", line));
}
- private void emitStartupOptions(OutErr outErr, OptionsParser.HelpVerbosity helpVerbosity,
- BlazeRuntime runtime, ImmutableMap<String, String> optionCategories) {
+ private void emitStartupOptions(
+ OutErr outErr,
+ OptionsParser.HelpVerbosity helpVerbosity,
+ BlazeRuntime runtime,
+ ImmutableMap<String, String> optionCategories,
+ boolean useNewCategoryEnum) {
outErr.printOut(
- BlazeCommandUtils.expandHelpTopic("startup_options",
+ BlazeCommandUtils.expandHelpTopic(
+ "startup_options",
"resource:startup_options.txt",
getClass(),
BlazeCommandUtils.getStartupOptions(runtime.getBlazeModules()),
optionCategories,
helpVerbosity,
- runtime.getProductName()));
+ runtime.getProductName(),
+ useNewCategoryEnum));
}
private void emitCompletionHelp(BlazeRuntime runtime, OutErr outErr) {
@@ -345,15 +379,21 @@ public final class HelpCommand implements BlazeCommand {
return ImmutableSortedMap.copyOf(runtime.getCommandMap());
}
- private void emitTargetSyntaxHelp(OutErr outErr, ImmutableMap<String, String> optionCategories,
- String productName) {
- outErr.printOut(BlazeCommandUtils.expandHelpTopic("target-syntax",
- "resource:target-syntax.txt",
- getClass(),
- ImmutableList.<Class<? extends OptionsBase>>of(),
- optionCategories,
- OptionsParser.HelpVerbosity.MEDIUM,
- productName));
+ private void emitTargetSyntaxHelp(
+ OutErr outErr,
+ ImmutableMap<String, String> optionCategories,
+ String productName,
+ boolean useNewCategoryEnum) {
+ outErr.printOut(
+ BlazeCommandUtils.expandHelpTopic(
+ "target-syntax",
+ "resource:target-syntax.txt",
+ getClass(),
+ ImmutableList.<Class<? extends OptionsBase>>of(),
+ optionCategories,
+ OptionsParser.HelpVerbosity.MEDIUM,
+ productName,
+ useNewCategoryEnum));
}
private void emitInfoKeysHelp(CommandEnvironment env, OutErr outErr) {
@@ -400,11 +440,19 @@ public final class HelpCommand implements BlazeCommand {
private static final class HtmlEmitter {
private final BlazeRuntime runtime;
- private final ImmutableMap<String, String> optionCategories;
+ private final ImmutableMap<String, String> deprecatedOptionCategoryDescriptions;
+ private final boolean useNewCategoriesEnum;
- private HtmlEmitter(BlazeRuntime runtime) {
+ private HtmlEmitter(BlazeRuntime runtime, boolean useNewCategoriesEnum) {
this.runtime = runtime;
- this.optionCategories = getOptionCategories(runtime);
+ this.useNewCategoriesEnum = useNewCategoriesEnum;
+ String productName = runtime.getProductName();
+ if (useNewCategoriesEnum) {
+ this.deprecatedOptionCategoryDescriptions = null;
+ } else {
+ this.deprecatedOptionCategoryDescriptions =
+ getDeprecatedOptionCategoriesDescriptions(productName);
+ }
}
private void emit(OutErr outErr) {
@@ -478,14 +526,67 @@ public final class HelpCommand implements BlazeCommand {
result.append("\n");
}
}
+
+ // Describe the tags once, any mentions above should link to these descriptions.
+ if (useNewCategoriesEnum) {
+ String productName = runtime.getProductName();
+ ImmutableMap<OptionEffectTag, String> effectTagDescriptions =
+ OptionFilterDescriptions.getOptionEffectTagDescription(productName);
+ result.append("<h3>Option Effect Tags</h3>\n");
+ result.append("<table>\n");
+ for (OptionEffectTag tag : OptionEffectTag.values()) {
+ String tagDescription = effectTagDescriptions.get(tag);
+
+ result.append("<tr>\n");
+ result.append(
+ String.format(
+ "<td id=\"effect_tag_%s\"><code>%s</code></td>\n",
+ tag, tag.name().toLowerCase()));
+ result.append(String.format("<td>%s</td>\n", HTML_ESCAPER.escape(tagDescription)));
+ result.append("</tr>\n");
+ }
+ result.append("</table>\n");
+
+ ImmutableMap<OptionMetadataTag, String> metadataTagDescriptions =
+ OptionFilterDescriptions.getOptionMetadataTagDescription(productName);
+ result.append("<h3>Option Metadata Tags</h3>\n");
+ result.append("<table>\n");
+ for (OptionMetadataTag tag : OptionMetadataTag.values()) {
+ // skip the tags that are reserved for undocumented flags.
+ if (!tag.equals(OptionMetadataTag.HIDDEN) && !tag.equals(OptionMetadataTag.INTERNAL)) {
+ String tagDescription = metadataTagDescriptions.get(tag);
+
+ result.append("<tr>\n");
+ result.append(
+ String.format(
+ "<td id=\"metadata_tag_%s\"><code>%s</code></td>\n",
+ tag, tag.name().toLowerCase()));
+ result.append(String.format("<td>%s</td>\n", HTML_ESCAPER.escape(tagDescription)));
+ result.append("</tr>\n");
+ }
+ }
+ result.append("</table>\n");
+ }
+
outErr.printOut(result.toString());
}
private void appendOptionsHtml(
StringBuilder result, Iterable<Class<? extends OptionsBase>> optionsClasses) {
OptionsParser parser = OptionsParser.newOptionsParser(optionsClasses);
- result.append(parser.describeOptionsHtml(optionCategories, HTML_ESCAPER)
- .replace("%{product}", runtime.getProductName()));
+ String productName = runtime.getProductName();
+ if (useNewCategoriesEnum) {
+ result.append(
+ parser
+ .describeOptionsHtml(HTML_ESCAPER, productName)
+ .replace("%{product}", productName));
+ } else {
+ result.append(
+ parser
+ .describeOptionsHtmlWithDeprecatedCategories(
+ deprecatedOptionCategoryDescriptions, HTML_ESCAPER)
+ .replace("%{product}", productName));
+ }
}
private static String capitalize(String s) {
@@ -510,3 +611,4 @@ public final class HelpCommand implements BlazeCommand {
void visit(String commandName, Command commandAnnotation, OptionsParser parser);
}
}
+
diff --git a/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java b/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
new file mode 100644
index 0000000000..4c6efefe1d
--- /dev/null
+++ b/src/main/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/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index 28c2206fbc..2b4bd2a3e4 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/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/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
index 221dcf030f..38248bf940 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/src/main/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/src/main/java/com/google/devtools/common/options/OptionsUsage.java b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
index 68a460e8e2..ecc7a6b61b 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsUsage.java
+++ b/src/main/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");