Make BuildOptions$OptionsDiffForReconstruction deterministic in its construction. Also restrict visibility of some test-only OptionsDiff methods, and remove a server-specific part of the ConfiguredTargetKey #toString representation.
PiperOrigin-RevId: 197830577
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 a91649b..33cd98af 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
@@ -426,10 +426,19 @@
}
LinkedHashMap<Class<? extends FragmentOptions>, Map<String, Object>> differingOptions =
new LinkedHashMap<>(diff.differingOptions.keySet().size());
- for (Class<? extends FragmentOptions> clazz : diff.differingOptions.keySet()) {
+ for (Class<? extends FragmentOptions> clazz :
+ diff.differingOptions
+ .keySet()
+ .stream()
+ .sorted(lexicalFragmentOptionsComparator)
+ .collect(Collectors.toList())) {
Collection<OptionDefinition> fields = diff.differingOptions.get(clazz);
LinkedHashMap<String, Object> valueMap = new LinkedHashMap<>(fields.size());
- for (OptionDefinition optionDefinition : fields) {
+ for (OptionDefinition optionDefinition :
+ fields
+ .stream()
+ .sorted(Comparator.comparing(o -> o.getField().getName()))
+ .collect(Collectors.toList())) {
Object secondValue;
try {
secondValue = Iterables.getOnlyElement(diff.second.get(optionDefinition));
@@ -448,8 +457,14 @@
first.maybeInitializeFingerprintAndHashCode();
return new OptionsDiffForReconstruction(
differingOptions,
- ImmutableSet.copyOf(diff.extraFirstFragments),
- ImmutableList.copyOf(diff.extraSecondFragments),
+ diff.extraFirstFragments
+ .stream()
+ .sorted(lexicalFragmentOptionsComparator)
+ .collect(ImmutableSet.toImmutableSet()),
+ diff.extraSecondFragments
+ .stream()
+ .sorted(Comparator.comparing(o -> o.getClass().getName()))
+ .collect(ImmutableList.toImmutableList()),
first.fingerprint,
second.computeChecksum());
}
@@ -480,14 +495,14 @@
extraSecondFragments.add(options);
}
- /** Return the extra fragments classes from the first configuration. */
- public Set<Class<? extends FragmentOptions>> getExtraFirstFragmentClasses() {
+ @VisibleForTesting
+ Set<Class<? extends FragmentOptions>> getExtraFirstFragmentClassesForTesting() {
return extraFirstFragments;
}
- /** Return the extra fragments classes from the second configuration. */
- public Set<Class<?>> getExtraSecondFragmentClasses() {
- return extraSecondFragments.stream().map(Object::getClass).collect(Collectors.toSet());
+ @VisibleForTesting
+ Set<FragmentOptions> getExtraSecondFragmentsForTesting() {
+ return extraSecondFragments;
}
public Map<OptionDefinition, Object> getFirst() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetKey.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetKey.java
index f9ac4b5..fa9bbd7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetKey.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetKey.java
@@ -170,12 +170,7 @@
@Override
public String toString() {
- return String.format(
- "%s %s %s (%s)",
- label,
- configurationKey,
- isHostConfiguration(),
- System.identityHashCode(this));
+ return String.format("%s %s %s", label, configurationKey, isHostConfiguration());
}
static class HostConfiguredTargetKey extends ConfiguredTargetKey {
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 8876204..e8d568d 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -699,6 +699,7 @@
":guava_junit_truth",
":test_runner",
":testutil",
+ "//src/main/java/com/google/devtools/build/lib:android-rules",
"//src/main/java/com/google/devtools/build/lib:bazel-main",
"//src/main/java/com/google/devtools/build/lib:bazel-rules",
"//src/main/java/com/google/devtools/build/lib:build-base",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
index a8a120a..3f0f93a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
@@ -17,10 +17,17 @@
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.config.BuildOptions.OptionsDiff;
import com.google.devtools.build.lib.analysis.config.BuildOptions.OptionsDiffForReconstruction;
+import com.google.devtools.build.lib.rules.android.AndroidConfiguration;
import com.google.devtools.build.lib.rules.cpp.CppOptions;
+import com.google.devtools.build.lib.rules.java.JavaOptions;
+import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
+import com.google.devtools.build.lib.rules.python.PythonOptions;
+import com.google.devtools.build.lib.skyframe.serialization.testutils.TestUtils;
import com.google.devtools.common.options.OptionsParser;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -100,8 +107,13 @@
OptionsDiff diff = BuildOptions.diff(one, two);
assertThat(diff.areSame()).isFalse();
- assertThat(diff.getExtraFirstFragmentClasses()).containsExactly(CppOptions.class);
- assertThat(diff.getExtraSecondFragmentClasses()).containsExactlyElementsIn(TEST_OPTIONS);
+ assertThat(diff.getExtraFirstFragmentClassesForTesting()).containsExactly(CppOptions.class);
+ assertThat(
+ diff.getExtraSecondFragmentsForTesting()
+ .stream()
+ .map(Object::getClass)
+ .collect(Collectors.toSet()))
+ .containsExactlyElementsIn(TEST_OPTIONS);
}
@Test
@@ -165,4 +177,40 @@
assertThat(otherFragment.applyDiff(BuildOptions.diffForReconstruction(otherFragment, one)))
.isEqualTo(one);
}
+
+ private static ImmutableList.Builder<Class<? extends FragmentOptions>> makeOptionsClassBuilder() {
+ return ImmutableList.<Class<? extends FragmentOptions>>builder()
+ .addAll(TEST_OPTIONS)
+ .add(CppOptions.class);
+ }
+
+ /**
+ * Tests that an {@link OptionsDiffForReconstruction} serializes stably. Unfortunately, still
+ * passes without fixes! (Perhaps more classes and diffs are needed?)
+ */
+ @Test
+ public void codecStability() throws Exception {
+ BuildOptions one =
+ BuildOptions.of(
+ makeOptionsClassBuilder()
+ .add(AndroidConfiguration.Options.class)
+ .add(ProtoConfiguration.Options.class)
+ .build());
+ BuildOptions two =
+ BuildOptions.of(
+ makeOptionsClassBuilder().add(JavaOptions.class).add(PythonOptions.class).build(),
+ "--cpu=k8",
+ "--compilation_mode=opt",
+ "--compiler=gcc",
+ "--copt=-Dfoo",
+ "--javacopt=--javacoption",
+ "--experimental_fix_deps_tool=fake",
+ "--build_python_zip=no",
+ "--force_python=py2");
+ OptionsDiffForReconstruction diff1 = BuildOptions.diffForReconstruction(one, two);
+ OptionsDiffForReconstruction diff2 = BuildOptions.diffForReconstruction(one, two);
+ assertThat(diff2).isEqualTo(diff1);
+ assertThat(TestUtils.toBytes(diff2, ImmutableMap.of()))
+ .isEqualTo(TestUtils.toBytes(diff1, ImmutableMap.of()));
+ }
}