Convert DexProducedFromJavaLibraryThatContainsClassFiles into an AbstractCachingBuildRule that is an AbiRule.
Summary:
There are three possible scenarios when building `DexProducedFromJavaLibraryThatContainsClassFiles`:
(1) The are are no `.class` files to dex.
(2) There are `.class` files that need to be dexed to update/create the `.dex.jar` file in `buck-out`.
(3) The existing `.dex.jar` file in `buck-out` already represents the `.class` files to dex.
In order to be able to look at what is in `buck-out` before building (case 3), we either
need to get a rule key match (which works by default in Buck), or leverage the `AbiRule`
logic we already have in place for Java rules. (If we wait until we start building
`DexProducedFromJavaLibraryThatContainsClassFiles`, the `buck-out` directory containing
the old `.dex.jar` may already be deleted.)
Recall that an `AbiRule` can avoid rebuilding if the following conditions hold:
(1) The hash of the current rule definition and its input files matches the
hash on disk (written in `.metadata/METADATA_KEY_FOR_RULE_KEY_WITHOUT_DEPS`).
(2) All relevant deps that have an ABI have the same ABI as the last time the
rule was built (written in `.metadata/ABI_KEY_FOR_DEPS_ON_DISK_METADATA`).
Therefore, for an `AccumulateClassNames`, we assign it an ABI based on the
contents of the `classes.txt` file that it writes.
Because a `DexProducedFromJavaLibraryThatContainsClassFiles` has a
`AccumulateClassNames` as its only dependency, the ABI of `AccumulateClassNames`
is the ABI-key-for-deps of the `DexProducedFromJavaLibraryThatContainsClassFiles`.
Ultimately, this ensures that if a `java_library` rule is recompiled such that its
generated `.class` files are the same (this happens when a `java_library` rule is
recompiled in response to one of its deps changing in a way that does not affect
that `java_library` that depends on it, such as adding a new public method),
then the output of the `classes.txt` file for the `java_library` should be the same.
In turn, because the ABI of the `AccumulateClassNames` that generated the
`classes.txt` is unchanged, the `DexProducedFromJavaLibraryThatContainsClassFiles`
should rightfully avoid a rebuild. This optimization can have a significant impact on
incremental build times.
Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/android/AndroidBinaryRule.java b/src/com/facebook/buck/android/AndroidBinaryRule.java
index 401dc5c..d99c4dd 100644
--- a/src/com/facebook/buck/android/AndroidBinaryRule.java
+++ b/src/com/facebook/buck/android/AndroidBinaryRule.java
@@ -1352,13 +1352,13 @@
(AccumulateClassNames) accumulateClassNamesRule.getBuildable();
// Create the PreDex and add it to both the ruleResolver and preDexDeps.
- DexProducedFromJavaLibraryThatContainsClassFiles.Builder preDexBuilder =
- DexProducedFromJavaLibraryThatContainsClassFiles.newPreDexBuilder(
+ IntermediateDexRule.Builder preDexBuilder =
+ IntermediateDexRule.newPreDexBuilder(
new DefaultBuildRuleBuilderParams(
pathRelativizer,
ruleKeyBuilderFactory));
preDexBuilder.setBuildTarget(preDexTarget);
- preDexBuilder.setPathToClassNamesList(accumulateClassNames);
+ preDexBuilder.setAccumulateClassNamesDep(accumulateClassNames);
preDexBuilder.addDep(accumulateClassNamesBuildTarget);
preDexBuilder.addVisibilityPattern(BuildTargetPattern.MATCH_ALL);
BuildRule preDex = ruleResolver.buildAndAddToIndex(preDexBuilder);
diff --git a/src/com/facebook/buck/android/BUCK b/src/com/facebook/buck/android/BUCK
index 9fb3aca..7270ae2 100644
--- a/src/com/facebook/buck/android/BUCK
+++ b/src/com/facebook/buck/android/BUCK
@@ -59,6 +59,7 @@
'GenAidl.java',
'GenAidlBuildRuleFactory.java',
'HasAndroidPlatformTarget.java',
+ 'IntermediateDexRule.java',
'NativeLibraryBuildable.java',
'NdkLibraryBuildRuleFactory.java',
'NdkLibrary.java',
@@ -82,6 +83,7 @@
'//src/com/facebook/buck/graph:graph',
'//src/com/facebook/buck/java:rules',
'//src/com/facebook/buck/java:support',
+ '//src/com/facebook/buck/java/abi:protocol',
'//src/com/facebook/buck/model:model',
'//src/com/facebook/buck/parser:rule_pattern',
'//src/com/facebook/buck/rules:build_rule',
diff --git a/src/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFiles.java b/src/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFiles.java
index d917476..1728704 100644
--- a/src/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFiles.java
+++ b/src/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFiles.java
@@ -19,14 +19,12 @@
import com.facebook.buck.java.AccumulateClassNames;
import com.facebook.buck.java.JavaLibraryRule;
import com.facebook.buck.model.BuildTarget;
-import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
+import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.AbstractBuildable;
import com.facebook.buck.rules.BuildContext;
-import com.facebook.buck.rules.BuildRuleParams;
-import com.facebook.buck.rules.BuildRuleResolver;
-import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.Buildable;
import com.facebook.buck.rules.BuildableContext;
+import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
@@ -36,6 +34,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.hash.HashCode;
import java.io.IOException;
import java.nio.file.Path;
@@ -87,7 +87,9 @@
// Make sure that the buck-out/gen/ directory exists for this.buildTarget.
steps.add(new MkdirStep(getPathToDex().getParent()));
- if (!javaLibraryWithClassesList.getClassNames().isEmpty()) {
+ // If there are classes, run dx.
+ final boolean hasClassesToDx = !javaLibraryWithClassesList.getClassNames().isEmpty();
+ if (hasClassesToDx) {
// To be conservative, use --force-jumbo for these intermediate .dex files so that they can be
// merged into a final classes.dex that uses jumbo instructions.
JavaLibraryRule javaLibraryRuleToDex = javaLibraryWithClassesList.getJavaLibraryRule();
@@ -95,17 +97,30 @@
Collections.singleton(Paths.get(javaLibraryRuleToDex.getPathToOutputFile())),
EnumSet.of(DxStep.Option.NO_OPTIMIZE, DxStep.Option.FORCE_JUMBO));
steps.add(dx);
-
- AbstractExecutionStep recordArtifactStep = new AbstractExecutionStep("record_dx_success") {
- @Override
- public int execute(ExecutionContext context) {
- buildableContext.recordArtifact(getPathToDex().getFileName());
- return 0;
- }
- };
- steps.add(recordArtifactStep);
}
+ // Run a step to record artifacts and metadata. The values recorded depend upon whether dx was
+ // run.
+ String stepName = hasClassesToDx ? "record_dx_success" : "record_empty_dx";
+ AbstractExecutionStep recordArtifactAndMetadataStep = new AbstractExecutionStep(stepName) {
+ @Override
+ public int execute(ExecutionContext context) {
+ if (hasClassesToDx) {
+ buildableContext.recordArtifact(getPathToDex().getFileName());
+ }
+
+ // The ABI key for the deps is also the ABI key for this Buildable. A dx-merge step can keep
+ // track of the ABIs of the DexProducedFromJavaLibraryThatContainsClassFiles that it has
+ // dexed before so it knows whether it needs to re-dex them. This way, adding a comment to a
+ // Java file that triggers a recompile will not trigger a dx or a dx-merge.
+ String abiKeyHash = getAbiKeyForDeps().getHash();
+ buildableContext.addMetadata(AbiRule.ABI_KEY_FOR_DEPS_ON_DISK_METADATA, abiKeyHash);
+ buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, abiKeyHash);
+ return 0;
+ }
+ };
+ steps.add(recordArtifactAndMetadataStep);
+
return steps.build();
}
@@ -124,38 +139,17 @@
}
public boolean hasOutput() {
+ return !getClassNames().isEmpty();
+ }
+
+ private ImmutableSortedMap<String, HashCode> getClassNames() {
// TODO(mbolin): Assert that this Buildable has been built. Currently, there is no way to do
// that from a Buildable (but there is from an AbstractCachingBuildRule).
- return !javaLibraryWithClassesList.getClassNames().isEmpty();
+ return javaLibraryWithClassesList.getClassNames();
}
- public static Builder newPreDexBuilder(AbstractBuildRuleBuilderParams params) {
- return new Builder(params);
+ Sha1HashCode getAbiKeyForDeps() {
+ return javaLibraryWithClassesList.getAbiKey();
}
- public static class Builder extends AbstractBuildable.Builder {
-
- private AccumulateClassNames javaLibraryWithClassesList;
-
- private Builder(AbstractBuildRuleBuilderParams params) {
- super(params);
- }
-
- @Override
- protected BuildRuleType getType() {
- return BuildRuleType._PRE_DEX;
- }
-
- @Override
- protected DexProducedFromJavaLibraryThatContainsClassFiles newBuildable(
- BuildRuleParams params, BuildRuleResolver resolver) {
- return new DexProducedFromJavaLibraryThatContainsClassFiles(params.getBuildTarget(),
- javaLibraryWithClassesList);
- }
-
- public Builder setPathToClassNamesList(AccumulateClassNames javaLibraryWithClassesList) {
- this.javaLibraryWithClassesList = javaLibraryWithClassesList;
- return this;
- }
- }
}
diff --git a/src/com/facebook/buck/android/IntermediateDexRule.java b/src/com/facebook/buck/android/IntermediateDexRule.java
new file mode 100644
index 0000000..8d5b301
--- /dev/null
+++ b/src/com/facebook/buck/android/IntermediateDexRule.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.android;
+
+import com.facebook.buck.java.AccumulateClassNames;
+import com.facebook.buck.rules.AbiRule;
+import com.facebook.buck.rules.AbstractBuildRuleBuilder;
+import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
+import com.facebook.buck.rules.AbstractCachingBuildRule;
+import com.facebook.buck.rules.BuildRuleParams;
+import com.facebook.buck.rules.BuildRuleResolver;
+import com.facebook.buck.rules.BuildRuleType;
+import com.facebook.buck.rules.Buildable;
+import com.facebook.buck.rules.Sha1HashCode;
+import com.google.common.base.Preconditions;
+
+public class IntermediateDexRule extends AbstractCachingBuildRule implements AbiRule {
+
+ private final DexProducedFromJavaLibraryThatContainsClassFiles buildable;
+
+ IntermediateDexRule(DexProducedFromJavaLibraryThatContainsClassFiles buildable,
+ BuildRuleParams params) {
+ super(buildable, params);
+ this.buildable = Preconditions.checkNotNull(buildable);
+ }
+
+ @Override
+ public Buildable getBuildable() {
+ return buildable;
+ }
+
+ @Override
+ public BuildRuleType getType() {
+ return BuildRuleType._PRE_DEX;
+ }
+
+ /**
+ * The ABI key for the deps of this rule should be a hash of the classes.txt file produced by
+ * {@link AccumulateClassNames}.
+ */
+ @Override
+ public Sha1HashCode getAbiKeyForDeps() {
+ return buildable.getAbiKeyForDeps();
+ }
+
+ public static Builder newPreDexBuilder(AbstractBuildRuleBuilderParams params) {
+ return new Builder(params);
+ }
+
+ public static class Builder extends AbstractBuildRuleBuilder<IntermediateDexRule> {
+
+ private AccumulateClassNames javaLibraryWithClassesList;
+
+ protected Builder(AbstractBuildRuleBuilderParams params) {
+ super(params);
+ }
+
+ @Override
+ public IntermediateDexRule build(BuildRuleResolver ruleResolver) {
+ DexProducedFromJavaLibraryThatContainsClassFiles buildable =
+ new DexProducedFromJavaLibraryThatContainsClassFiles(getBuildTarget(),
+ javaLibraryWithClassesList);
+ return new IntermediateDexRule(buildable,
+ createBuildRuleParams(ruleResolver));
+ }
+
+ public Builder setAccumulateClassNamesDep(AccumulateClassNames javaLibraryWithClassesList) {
+ this.javaLibraryWithClassesList = javaLibraryWithClassesList;
+ return this;
+ }
+ }
+}
diff --git a/src/com/facebook/buck/java/AccumulateClassNames.java b/src/com/facebook/buck/java/AccumulateClassNames.java
index 142bfad..a0069cc 100644
--- a/src/com/facebook/buck/java/AccumulateClassNames.java
+++ b/src/com/facebook/buck/java/AccumulateClassNames.java
@@ -17,6 +17,7 @@
package com.facebook.buck.java;
import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
import com.facebook.buck.rules.AbstractBuildable;
import com.facebook.buck.rules.BuildContext;
@@ -26,6 +27,9 @@
import com.facebook.buck.rules.Buildable;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.OnDiskBuildInfo;
+import com.facebook.buck.rules.Sha1HashCode;
+import com.facebook.buck.step.AbstractExecutionStep;
+import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.RmStep;
@@ -39,11 +43,14 @@
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.hash.HashCode;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Map;
import javax.annotation.Nullable;
@@ -60,6 +67,12 @@
private final Path pathToOutputFile;
/**
+ * This is not set until this {@link Buildable} is built.
+ */
+ @Nullable
+ private Sha1HashCode abiKey;
+
+ /**
* This will contain the classes info discovered in the course of building this {@link Buildable}.
* This {@link Supplier} will be defined and determined after this buildable is built.
*/
@@ -93,7 +106,7 @@
}
@Override
- public List<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext)
+ public List<Step> getBuildSteps(BuildContext context, final BuildableContext buildableContext)
throws IOException {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
@@ -108,20 +121,24 @@
classNames = accumulateClassNamesStep;
steps.add(accumulateClassNamesStep);
+ AbstractExecutionStep recordAbiStep = new AbstractExecutionStep("record_abi") {
+ @Override
+ public int execute(ExecutionContext context) {
+ AccumulateClassNames.this.abiKey = computeAbiKey(classNames);
+ buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, abiKey.toString());
+ return 0;
+ }
+ };
+ steps.add(recordAbiStep);
+
return steps.build();
}
- public ImmutableSortedMap<String, HashCode> getClassNames() {
- // TODO(mbolin): Assert that this Buildable has been built. Currently, there is no way to do
- // that from a Buildable (but there is from an AbstractCachingBuildRule).
- Preconditions.checkNotNull(classNames);
- return classNames.get();
- }
-
+ /**
+ * Sets both {@link #classNames} and {@link #abiKey}.
+ */
@Override
protected void initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
- // Assign this.classNames when instantiating this Buildable when pulled from cache.
-
// Read the output file, which should now be in place because this rule was downloaded from
// cache.
List<String> lines;
@@ -141,6 +158,31 @@
classNamesBuilder.put(key, value);
}
this.classNames = Suppliers.ofInstance(classNamesBuilder.build());
+ this.abiKey = onDiskBuildInfo.getHash(AbiRule.ABI_KEY_ON_DISK_METADATA).get();
+ }
+
+ public ImmutableSortedMap<String, HashCode> getClassNames() {
+ // TODO(mbolin): Assert that this Buildable has been built. Currently, there is no way to do
+ // that from a Buildable (but there is from an AbstractCachingBuildRule).
+ Preconditions.checkNotNull(classNames);
+ return classNames.get();
+ }
+
+ public Sha1HashCode getAbiKey() {
+ Preconditions.checkNotNull(abiKey);
+ return abiKey;
+ }
+
+ @VisibleForTesting
+ static Sha1HashCode computeAbiKey(Supplier<ImmutableSortedMap<String, HashCode>> classNames) {
+ Hasher hasher = Hashing.sha1().newHasher();
+ for (Map.Entry<String, HashCode> entry : classNames.get().entrySet()) {
+ hasher.putUnencodedChars(entry.getKey());
+ hasher.putByte((byte)0);
+ hasher.putUnencodedChars(entry.getValue().toString());
+ hasher.putByte((byte)0);
+ }
+ return new Sha1HashCode(hasher.hash().toString());
}
public static Builder newAccumulateClassNamesBuilder(AbstractBuildRuleBuilderParams params) {
diff --git a/src/com/facebook/buck/java/AccumulateClassNamesStep.java b/src/com/facebook/buck/java/AccumulateClassNamesStep.java
index e2e0cb5..8add939 100644
--- a/src/com/facebook/buck/java/AccumulateClassNamesStep.java
+++ b/src/com/facebook/buck/java/AccumulateClassNamesStep.java
@@ -23,6 +23,7 @@
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
@@ -142,4 +143,8 @@
return classNames;
}
+ @VisibleForTesting
+ void setClassNamesForTesting(ImmutableSortedMap<String, HashCode> classNames) {
+ this.classNames = classNames;
+ }
}
diff --git a/test/com/facebook/buck/android/BUCK b/test/com/facebook/buck/android/BUCK
index 4d5fd00..4677812 100644
--- a/test/com/facebook/buck/android/BUCK
+++ b/test/com/facebook/buck/android/BUCK
@@ -18,6 +18,7 @@
'//src/com/facebook/buck/graph:graph',
'//src/com/facebook/buck/java:rules',
'//src/com/facebook/buck/java:support',
+ '//src/com/facebook/buck/java/abi:protocol',
'//src/com/facebook/buck/model:model',
'//src/com/facebook/buck/parser:rule_pattern',
'//src/com/facebook/buck/rules:build_rule',
diff --git a/test/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFilesTest.java b/test/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFilesTest.java
index c18b4a5..4d2f5bf 100644
--- a/test/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFilesTest.java
+++ b/test/com/facebook/buck/android/DexProducedFromJavaLibraryThatContainsClassFilesTest.java
@@ -23,9 +23,12 @@
import com.facebook.buck.java.AccumulateClassNames;
import com.facebook.buck.java.JavaLibraryRule;
+import com.facebook.buck.java.abi.AbiWriterProtocol;
import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildableContext;
+import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.TestExecutionContext;
@@ -102,6 +105,20 @@
executionContext);
verifyAll();
+ resetAll();
+
+ buildableContext.recordArtifact(Paths.get("bar.dex.jar"));
+ Sha1HashCode abiKey = new Sha1HashCode("f7f34ed13b881c6c6f663533cde4a436ea84435e");
+ expect(accumulateClassNames.getAbiKey()).andReturn(abiKey);
+ buildableContext.addMetadata(AbiRule.ABI_KEY_FOR_DEPS_ON_DISK_METADATA, abiKey.getHash());
+ buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, abiKey.getHash());
+ replayAll();
+
+ Step recordArtifactAndMetadataStep = steps.get(3);
+ int exitCode = recordArtifactAndMetadataStep.execute(executionContext);
+ assertEquals(0, exitCode);
+
+ verifyAll();
}
@Test
@@ -138,11 +155,25 @@
MoreAsserts.assertSteps("Do not generate a .dex.jar file.",
ImmutableList.of(
"rm -f /home/user/buck-out/gen/foo/bar.dex.jar",
- "mkdir -p /home/user/buck-out/gen/foo"),
+ "mkdir -p /home/user/buck-out/gen/foo",
+ "record_empty_dx"),
steps,
executionContext);
verifyAll();
+ resetAll();
+
+ Sha1HashCode abiKey = new Sha1HashCode(AbiWriterProtocol.EMPTY_ABI_KEY);
+ expect(accumulateClassNames.getAbiKey()).andReturn(abiKey);
+ buildableContext.addMetadata(AbiRule.ABI_KEY_FOR_DEPS_ON_DISK_METADATA, abiKey.getHash());
+ buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, abiKey.getHash());
+ replayAll();
+
+ Step recordArtifactAndMetadataStep = steps.get(2);
+ int exitCode = recordArtifactAndMetadataStep.execute(executionContext);
+ assertEquals(0, exitCode);
+
+ verifyAll();
}
@Test
diff --git a/test/com/facebook/buck/java/AccumulateClassNamesTest.java b/test/com/facebook/buck/java/AccumulateClassNamesTest.java
index d9987a6..9c1dbcf 100644
--- a/test/com/facebook/buck/java/AccumulateClassNamesTest.java
+++ b/test/com/facebook/buck/java/AccumulateClassNamesTest.java
@@ -20,7 +20,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import com.facebook.buck.java.abi.AbiWriterProtocol;
import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
@@ -29,15 +31,19 @@
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.FakeAbstractBuildRuleBuilderParams;
import com.facebook.buck.rules.OnDiskBuildInfo;
+import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.testutil.MoreAsserts;
import com.facebook.buck.util.ProjectFilesystem;
+import com.google.common.base.Optional;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
import org.easymock.EasyMockSupport;
import org.junit.Test;
@@ -65,7 +71,6 @@
params);
assertEquals(BuildRuleType._CLASS_NAMES, builder.getType());
-
// Construct the Buildable.
BuildTarget buildTarget = new BuildTarget("//foo/bar", "baz", "class_names");
builder.setBuildTarget(buildTarget);
@@ -104,9 +109,30 @@
ImmutableList.of(
"rm -f " + absolutePathToOutput,
"mkdir -p " + absolutePathToOutput.getParent(),
- "get_class_names foo/bar/example.jar > " + pathToOutput),
+ "get_class_names foo/bar/example.jar > " + pathToOutput,
+ "record_abi"),
steps,
context);
+
+ // Prepare to invoke the recordAbiStep by populating the accumulateClassNamesStep first.
+ AccumulateClassNamesStep accumulateClassNamesStep = (AccumulateClassNamesStep) steps.get(2);
+ ImmutableSortedMap<String, HashCode> classNames = ImmutableSortedMap.of();
+ accumulateClassNamesStep.setClassNamesForTesting(classNames);
+
+ resetAll();
+ String expectedAbiKey = AbiWriterProtocol.EMPTY_ABI_KEY;
+ buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, expectedAbiKey);
+ replayAll();
+
+ // Invoke the recordAbiStep and ensure the correct data is recorded.
+ Step recordAbiStep = steps.get(3);
+ int exitCode = recordAbiStep.execute(context);
+ assertEquals(0, exitCode);
+ assertEquals("Should be the empty ABI key because the classNames map is empty.",
+ new Sha1HashCode(expectedAbiKey),
+ accumulateClassNames.getAbiKey());
+
+ verifyAll();
}
@Test
@@ -125,6 +151,8 @@
"com/example/Baz 62b1c2510840c0de55c13f66065a98a719be0f19",
"com/example/Foo e4fccb7520b7795e632651323c63217c9f59f72a");
expect(onDiskBuildInfo.getOutputFileContentsByLine(accumulateClassNames)).andReturn(lines);
+ expect(onDiskBuildInfo.getHash(AbiRule.ABI_KEY_ON_DISK_METADATA))
+ .andReturn(Optional.of(new Sha1HashCode("f7d6d1efa11c8ceef36cc56b0ec6c3a20ddbf19f")));
replayAll();
accumulateClassNames.initializeFromDisk(onDiskBuildInfo);
@@ -139,5 +167,36 @@
"com/example/Foo", HashCode.fromString("e4fccb7520b7795e632651323c63217c9f59f72a")
),
observedClasses);
+ assertEquals(new Sha1HashCode("f7d6d1efa11c8ceef36cc56b0ec6c3a20ddbf19f"),
+ accumulateClassNames.getAbiKey());
+ }
+
+ @Test
+ public void testComputeAbiKey() {
+ ImmutableSortedMap<String, HashCode> classNamesAndHashes = ImmutableSortedMap.of(
+ "com/example/Foo", HashCode.fromString("e4fccb7520b7795e632651323c63217c9f59f72a"),
+ "com/example/Bar", HashCode.fromString("087b7707a5f8e0a2adf5652e3cd2072d89a197dc"),
+ "com/example/Baz", HashCode.fromString("62b1c2510840c0de55c13f66065a98a719be0f19")
+ );
+ String observedSha1 = AccumulateClassNames
+ .computeAbiKey(Suppliers.ofInstance(classNamesAndHashes))
+ .getHash();
+
+ String expectedSha1 = Hashing.sha1().newHasher()
+ .putUnencodedChars("com/example/Bar")
+ .putByte((byte)0)
+ .putUnencodedChars("087b7707a5f8e0a2adf5652e3cd2072d89a197dc")
+ .putByte((byte)0)
+ .putUnencodedChars("com/example/Baz")
+ .putByte((byte)0)
+ .putUnencodedChars("62b1c2510840c0de55c13f66065a98a719be0f19")
+ .putByte((byte)0)
+ .putUnencodedChars("com/example/Foo")
+ .putByte((byte)0)
+ .putUnencodedChars("e4fccb7520b7795e632651323c63217c9f59f72a")
+ .putByte((byte)0)
+ .hash()
+ .toString();
+ assertEquals(expectedSha1, observedSha1);
}
}