blob: 76c42a2c39ea77398bbd60fedffecaefb760217e [file] [log] [blame]
// Copyright 2019 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.commands;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
import static java.util.Map.Entry.comparingByKey;
import com.google.common.base.Functions;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.runtime.BlazeCommand;
import com.google.devtools.build.lib.runtime.BlazeCommandResult;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.commands.ConfigCommand.ConfigOptions;
import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
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.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingResult;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Handles the 'config' command on the Blaze command line. */
@Command(
name = "config",
builds = true,
inherits = {BuildCommand.class},
options = {ConfigOptions.class},
usesConfigurationOptions = true,
shortDescription = "Displays details of configurations.",
allowResidue = true,
completion = "string",
hidden = true,
help = "resource:config.txt")
public class ConfigCommand implements BlazeCommand {
/** Options for the "config" command. */
public static class ConfigOptions extends OptionsBase {
@Option(
name = "dump_all",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
help = "If set, dump all known configurations instead of just the ids.")
public boolean dumpAll;
}
@Override
public void editOptions(OptionsParser optionsParser) {}
@Override
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
ImmutableMap<String, BuildConfiguration> configurations = findConfigurations(env);
try (PrintWriter writer =
new PrintWriter(
new OutputStreamWriter(env.getReporter().getOutErr().getOutputStream(), UTF_8))) {
if (options.getResidue().isEmpty()) {
if (options.getOptions(ConfigOptions.class).dumpAll) {
return reportAllConfigurations(writer, env);
} else {
return reportConfigurationIds(writer, configurations.keySet());
}
}
if (options.getResidue().size() == 1) {
String configHash = options.getResidue().get(0);
env.getReporter()
.handle(Event.info(String.format("Displaying config with id %s", configHash)));
BuildConfiguration config = configurations.get(configHash);
if (config == null) {
env.getReporter()
.handle(Event.error(String.format("No configuration found with id: %s", configHash)));
return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
}
StringBuilder sb = new StringBuilder();
config.describe(sb);
writer.print(sb.toString());
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
} else if (options.getResidue().size() == 2) {
String configHash1 = options.getResidue().get(0);
String configHash2 = options.getResidue().get(1);
env.getReporter()
.handle(
Event.info(
String.format(
"Displaying diff between configs" + " %s and" + " %s",
configHash1, configHash2)));
BuildConfiguration config1 = configurations.get(configHash1);
if (config1 == null) {
env.getReporter()
.handle(
Event.error(String.format("No configuration found with id: %s", configHash1)));
return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
}
BuildConfiguration config2 = configurations.get(configHash2);
if (config2 == null) {
env.getReporter()
.handle(
Event.error(String.format("No configuration found with id: %s", configHash2)));
return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
}
writer.printf(
"Displaying diff between configs" + " %s and" + " %s\n", configHash1, configHash2);
Table<Class<? extends FragmentOptions>, String, Pair<Object, Object>> diffs =
diffConfigurations(config1, config2);
writer.print(describeConfigDiff(diffs));
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
} else {
env.getReporter().handle(Event.error("Too many config ids."));
return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
}
}
}
private ImmutableMap<String, BuildConfiguration> findConfigurations(CommandEnvironment env) {
InMemoryMemoizingEvaluator evaluator =
(InMemoryMemoizingEvaluator)
env.getRuntime().getWorkspace().getSkyframeExecutor().getEvaluatorForTesting();
return evaluator.getDoneValues().entrySet().stream()
.filter(e -> SkyFunctions.BUILD_CONFIGURATION.equals(e.getKey().functionName()))
.map(Map.Entry::getValue)
.map(v -> (BuildConfigurationValue) v)
.map(BuildConfigurationValue::getConfiguration)
.collect(
toImmutableMap(
BuildConfiguration::checksum, Functions.identity(), (config1, config2) -> config1));
}
private BlazeCommandResult reportAllConfigurations(PrintWriter writer, CommandEnvironment env) {
InMemoryMemoizingEvaluator evaluator =
(InMemoryMemoizingEvaluator)
env.getRuntime().getWorkspace().getSkyframeExecutor().getEvaluatorForTesting();
ImmutableMap<BuildConfigurationValue.Key, BuildConfigurationValue> configs =
evaluator.getDoneValues().entrySet().stream()
.filter(e -> SkyFunctions.BUILD_CONFIGURATION.equals(e.getKey().functionName()))
.collect(
toImmutableMap(
e -> (BuildConfigurationValue.Key) e.getKey(),
e -> (BuildConfigurationValue) e.getValue()));
for (Map.Entry<BuildConfigurationValue.Key, BuildConfigurationValue> entry :
configs.entrySet()) {
writer.print("BuildConfigurationValue.Key: ");
writer.println(entry.getKey().toString());
writer.print("BuildConfigurationValue:\n");
StringBuilder sb = new StringBuilder();
entry.getValue().getConfiguration().describe(sb);
writer.print(sb.toString());
}
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
}
private BlazeCommandResult reportConfigurationIds(
PrintWriter writer, ImmutableSet<String> configurationIds) {
writer.println("Available configurations:");
writer.println(configurationIds.stream().collect(Collectors.joining("\n")));
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
}
private Table<Class<? extends FragmentOptions>, String, Pair<Object, Object>> diffConfigurations(
BuildConfiguration config1, BuildConfiguration config2) {
Table<Class<? extends FragmentOptions>, String, Pair<Object, Object>> diffs =
HashBasedTable.create();
for (Class<? extends FragmentOptions> fragment :
Sets.union(
config1.getOptions().getFragmentClasses(), config2.getOptions().getFragmentClasses())) {
FragmentOptions options1 = config1.getOptions().get(fragment);
FragmentOptions options2 = config2.getOptions().get(fragment);
diffs.row(fragment).putAll(diffOptions(fragment, options1, options2));
}
return diffs;
}
private Map<String, Pair<Object, Object>> diffOptions(
Class<? extends FragmentOptions> fragment,
@Nullable FragmentOptions options1,
@Nullable FragmentOptions options2) {
Map<String, Pair<Object, Object>> diffs = new HashMap<>();
for (OptionDefinition option : OptionsParser.getOptionDefinitions(fragment)) {
Object value1 = options1 == null ? null : options1.getValueFromDefinition(option);
Object value2 = options2 == null ? null : options2.getValueFromDefinition(option);
if (!Objects.equals(value1, value2)) {
diffs.put(option.getOptionName(), Pair.of(value1, value2));
}
}
return diffs;
}
private String describeConfigDiff(
Table<Class<? extends FragmentOptions>, String, Pair<Object, Object>> diff) {
StringBuilder sb = new StringBuilder();
diff.rowKeySet().stream()
.sorted(comparing(Class::getName))
.forEach(fragmentClass -> displayFragmentDiff(fragmentClass, diff.row(fragmentClass), sb));
return sb.toString();
}
private void displayFragmentDiff(
Class<? extends FragmentOptions> fragmentClass,
Map<String, Pair<Object, Object>> diff,
StringBuilder sb) {
sb.append("Fragment ").append(fragmentClass.getName()).append(" {\n");
diff.entrySet().stream()
.sorted(comparingByKey())
.forEach(
e ->
sb.append(" ")
.append(e.getKey())
.append(": ")
.append(e.getValue().getFirst())
.append(", ")
.append(e.getValue().getSecond())
.append("\n"));
sb.append("}\n");
}
}