com.google.devtools.build.lib.runtime.BlazeCommandUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.runtime.BlazeCommandUtils.java

Source

// 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.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.Map;
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
            .<Class<? extends OptionsBase>>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(CommonCommandOptions.class, BlazeCommandEventHandler.Options.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 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 productName the product name
     */
    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) {
        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 = parser.describeOptions(categoryDescriptions, 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 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.
     */
    public static String getUsage(Class<? extends BlazeCommand> commandClass,
            Map<String, String> categoryDescriptions, 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), categoryDescriptions,
                verbosity, productName);
    }
}