blob: eb5630b1cbf56932643df9c3412c607516fe8270 [file] [log] [blame]
// 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.lib.runtime;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
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;
import java.util.HashSet;
import java.util.Set;
/**
* Utility class for functionality related to Blaze commands.
*/
public class BlazeCommandUtils {
/**
* Options classes used as startup options in Blaze core.
*/
private static final ImmutableList<Class<? extends OptionsBase>> DEFAULT_STARTUP_OPTIONS =
ImmutableList.of(
BlazeServerStartupOptions.class,
HostJvmStartupOptions.class);
/** The set of option-classes that are common to all Blaze commands. */
private static final ImmutableList<Class<? extends OptionsBase>> COMMON_COMMAND_OPTIONS =
ImmutableList.of(
UiOptions.class,
CommonCommandOptions.class,
ClientOptions.class,
// Skylark options aren't applicable to all commands, but making them a common option
// allows users to put them in the common section of the bazelrc. See issue #3538.
StarlarkSemanticsOptions.class);
private BlazeCommandUtils() {}
public static ImmutableList<Class<? extends OptionsBase>> getStartupOptions(
Iterable<BlazeModule> modules) {
Set<Class<? extends OptionsBase>> options = new HashSet<>();
options.addAll(DEFAULT_STARTUP_OPTIONS);
for (BlazeModule blazeModule : modules) {
Iterables.addAll(options, blazeModule.getStartupOptions());
}
return ImmutableList.copyOf(options);
}
public static ImmutableSet<Class<? extends OptionsBase>> getCommonOptions(
Iterable<BlazeModule> modules) {
ImmutableSet.Builder<Class<? extends OptionsBase>> builder = ImmutableSet.builder();
builder.addAll(COMMON_COMMAND_OPTIONS);
for (BlazeModule blazeModule : modules) {
builder.addAll(blazeModule.getCommonCommandOptions());
}
return builder.build();
}
/**
* Returns the set of all options (including those inherited directly and
* transitively) for this AbstractCommand's @Command annotation.
*
* <p>Why does metaprogramming always seem like such a bright idea in the
* beginning?
*/
public static ImmutableList<Class<? extends OptionsBase>> getOptions(
Class<? extends BlazeCommand> clazz,
Iterable<BlazeModule> modules,
ConfiguredRuleClassProvider ruleClassProvider) {
Command commandAnnotation = clazz.getAnnotation(Command.class);
if (commandAnnotation == null) {
throw new IllegalStateException("@Command missing for " + clazz.getName());
}
Set<Class<? extends OptionsBase>> options = new HashSet<>();
options.addAll(getCommonOptions(modules));
Collections.addAll(options, commandAnnotation.options());
if (commandAnnotation.usesConfigurationOptions()) {
options.addAll(ruleClassProvider.getConfigurationOptions());
}
for (BlazeModule blazeModule : modules) {
Iterables.addAll(options, blazeModule.getCommandOptions(commandAnnotation));
}
for (Class<? extends BlazeCommand> base : commandAnnotation.inherits()) {
options.addAll(getOptions(base, modules, ruleClassProvider));
}
return ImmutableList.copyOf(options);
}
/**
* 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 helpVerbosity a tri-state verbosity option selecting between just names, names and
* syntax, and full description.
* @param productName the product name
*/
public static final String expandHelpTopic(
String topic,
String help,
Class<? extends BlazeCommand> commandClass,
Collection<Class<? extends OptionsBase>> options,
OptionsParser.HelpVerbosity helpVerbosity,
String productName) {
OptionsParser parser = OptionsParser.builder().optionsClasses(options).build();
String template;
if (help.startsWith("resource:")) {
String resourceName = help.substring("resource:".length());
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);
}
} else {
template = help;
}
if (!template.contains("%{options}")) {
throw new IllegalStateException("Help template for '" + topic + "' omits %{options}!");
}
String optionStr;
optionStr =
parser.describeOptions(productName, helpVerbosity).replace("%{product}", productName);
return template
.replace("%{product}", productName)
.replace("%{command}", topic)
.replace("%{options}", optionStr)
.trim()
+ "\n\n"
+ (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM
? "(Use 'help --long' for full details or --short to just enumerate options.)\n"
: "");
}
/**
* The help page for this command.
*
* @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,
OptionsParser.HelpVerbosity verbosity,
Iterable<BlazeModule> blazeModules,
ConfiguredRuleClassProvider ruleClassProvider,
String productName) {
Command commandAnnotation = commandClass.getAnnotation(Command.class);
return BlazeCommandUtils.expandHelpTopic(
commandAnnotation.name(),
commandAnnotation.help(),
commandClass,
BlazeCommandUtils.getOptions(commandClass, blazeModules, ruleClassProvider),
verbosity,
productName);
}
}