// 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.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; 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> 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> COMMON_COMMAND_OPTIONS = ImmutableList.of( BlazeCommandEventHandler.Options.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. SkylarkSemanticsOptions.class); private BlazeCommandUtils() {} public static ImmutableList> getStartupOptions( Iterable modules) { Set> options = new HashSet<>(); options.addAll(DEFAULT_STARTUP_OPTIONS); for (BlazeModule blazeModule : modules) { Iterables.addAll(options, blazeModule.getStartupOptions()); } return ImmutableList.copyOf(options); } public static ImmutableSet> getCommonOptions( Iterable modules) { ImmutableSet.Builder> 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. * *

Why does metaprogramming always seem like such a bright idea in the * beginning? */ public static ImmutableList> getOptions( Class clazz, Iterable modules, ConfiguredRuleClassProvider ruleClassProvider) { Command commandAnnotation = clazz.getAnnotation(Command.class); if (commandAnnotation == null) { throw new IllegalStateException("@Command missing for " + clazz.getName()); } Set> 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 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 commandClass, Collection> options, OptionsParser.HelpVerbosity helpVerbosity, String productName) { OptionsParser parser = OptionsParser.newOptionsParser(options); 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 commandClass, OptionsParser.HelpVerbosity verbosity, Iterable 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); } }