From c4b49f276fc4a410f8f249e32f30ac4602724a95 Mon Sep 17 00:00:00 2001 From: juliexxia Date: Fri, 23 Feb 2018 08:11:48 -0800 Subject: Add a new BuildOptions diff method that holds and pretty prints the different option values between two BuildOptions PiperOrigin-RevId: 186769975 --- .../build/lib/analysis/config/BuildOptions.java | 111 +++++++++++++++++++++ 1 file changed, 111 insertions(+) (limited to 'src/main') diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java index cd1db7c261..df99dc6aa0 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy; import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext; @@ -28,6 +29,7 @@ import com.google.devtools.build.lib.skyframe.serialization.SerializationContext import com.google.devtools.build.lib.skyframe.serialization.SerializationException; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.common.options.InvocationPolicyEnforcer; +import com.google.devtools.common.options.OptionDefinition; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsClassProvider; import com.google.devtools.common.options.OptionsParser; @@ -36,12 +38,14 @@ import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -332,6 +336,113 @@ public final class BuildOptions implements Cloneable, Serializable { } } + /** Returns the difference between two BuildOptions in a {@link BuildOptions.OptionsDiff}. */ + public static OptionsDiff diff(BuildOptions first, BuildOptions second) { + OptionsDiff diff = new OptionsDiff(); + if (first.equals(second)) { + return diff; + } + // Check if either class has been trimmed of an options class that exists in the other. + ImmutableSet> firstOptionClasses = + first.getOptions() + .stream() + .map(FragmentOptions::getClass) + .collect(ImmutableSet.toImmutableSet()); + ImmutableSet> secondOptionClasses = + second.getOptions() + .stream() + .map(FragmentOptions::getClass) + .collect(ImmutableSet.toImmutableSet()); + Sets.difference(firstOptionClasses, secondOptionClasses).forEach(diff::addExtraFirstFragments); + Sets.difference(secondOptionClasses, firstOptionClasses).forEach(diff::addExtraSecondFragments); + // For fragments in common, report differences. + for (Class clazz : + Sets.intersection(firstOptionClasses, secondOptionClasses)) { + if (!first.get(clazz).equals(second.get(clazz))) { + ImmutableList definitions = OptionsParser.getOptionDefinitions(clazz); + Map firstClazzOptions = first.get(clazz).asMap(); + Map secondClazzOptions = second.get(clazz).asMap(); + for (OptionDefinition definition : definitions) { + String name = definition.getOptionName(); + Object firstValue = firstClazzOptions.get(name); + Object secondValue = secondClazzOptions.get(name); + if (!Objects.equals(firstValue, secondValue)) { + diff.addDiff(definition, firstValue, secondValue); + } + } + } + } + return diff; + } + + /** + * A diff class for BuildOptions. Fields are meant to be populated and returned by + * {@link BuildOptions#diff} + */ + public static class OptionsDiff{ + private final Map first = new HashMap<>(); + private final Map second = new HashMap<>(); + private final List> extraFirstFragments = new ArrayList<>(); + private final List> extraSecondFragments = new ArrayList<>(); + + private void addExtraFirstFragments(Class clazz) { + extraFirstFragments.add(clazz); + } + + private void addExtraSecondFragments(Class clazz) { + extraSecondFragments.add(clazz); + } + + /** Return the extra fragments from the first configuration. */ + public List> getExtraFirstFragments() { + return extraFirstFragments; + } + + /** Return the extra fragments from the second configuration. */ + public List> getExtraSecondFragments() { + return extraSecondFragments; + } + + public Map getFirst() { + return first; + } + + public Map getSecond() { + return second; + } + + + private void addDiff(OptionDefinition option, Object firstValue, Object secondValue) { + first.put(option, firstValue); + second.put(option, secondValue); + } + + /** + * Note: it's not enough for first and second to be empty, with trimming, they must also contain + * the same options classes. + */ + boolean areSame() { + return first.isEmpty() + && second.isEmpty() + && extraSecondFragments.isEmpty() + && extraFirstFragments.isEmpty(); + } + + public String prettyPrint() { + StringBuilder toReturn = new StringBuilder(); + for (Map.Entry firstOption : first.entrySet()) { + toReturn + .append(firstOption.getKey().getOptionName()) + .append(":") + .append(firstOption.getValue()) + .append(" -> ") + .append(second.get(firstOption.getKey())) + .append(System.lineSeparator()); + } + return toReturn.toString(); + } + } + private static class BuildOptionsCodec implements ObjectCodec { @Override public Class getEncodedClass() { -- cgit v1.2.3