AccumulateClassNamesStep now includes the hashes of the .class files in classes.txt. Summary: This is the first step in making it so that `DexProducedFromJavaLibraryThatContainsClassFiles` does not re-dex if the `.class` files from its dependent `JavaLibraryRule` are unchanged. Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/java/AccumulateClassNamesStep.java b/src/com/facebook/buck/java/AccumulateClassNamesStep.java index 0eb7c2f..fd1ccbc 100644 --- a/src/com/facebook/buck/java/AccumulateClassNamesStep.java +++ b/src/com/facebook/buck/java/AccumulateClassNamesStep.java
@@ -23,12 +23,21 @@ import com.facebook.buck.step.AbstractExecutionStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; +import com.google.common.base.Function; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Iterables; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; +import com.google.common.io.InputSupplier; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; /** * {@link Step} that takes a directory or zip of {@code .class} files and traverses it to get the @@ -37,6 +46,12 @@ public class AccumulateClassNamesStep extends AbstractExecutionStep { /** + * In the generated {@code classes.txt} file, each line will contain the path to a {@code .class} + * file (without its suffix) and the SHA-1 hash of its contents, separated by this separator. + */ + static final String CLASS_NAME_HASH_CODE_SEPARATOR = " "; + + /** * Entries in the {@link #pathToJarOrClassesDirectory} that end with this suffix will be written * to {@link #whereClassNamesShouldBeWritten}. Since they all share the same suffix, the suffix * will be stripped when written to {@link #whereClassNamesShouldBeWritten}. @@ -55,17 +70,26 @@ @Override public int execute(ExecutionContext context) { - final ImmutableSortedSet.Builder<String> classNamesBuilder = ImmutableSortedSet.naturalOrder(); + final ImmutableSortedMap.Builder<String, HashCode> classNamesBuilder = + ImmutableSortedMap.naturalOrder(); Path path = context.getProjectFilesystem().resolve(pathToJarOrClassesDirectory); ClasspathTraversal traversal = new ClasspathTraversal(Collections.singleton(path)) { @Override - public void visit(FileLike fileLike) throws IOException { + public void visit(final FileLike fileLike) throws IOException { String name = fileLike.getRelativePath(); // When traversing a JAR file, it may have resources or directory entries that do not end // in .class, which should be ignored. if (name.endsWith(CLASS_NAME_SUFFIX)) { - classNamesBuilder.add(name.substring(0, name.length() - CLASS_NAME_SUFFIX.length())); + String key = name.substring(0, name.length() - CLASS_NAME_SUFFIX.length()); + InputSupplier<InputStream> input = new InputSupplier<InputStream>() { + @Override + public InputStream getInput() throws IOException { + return fileLike.getInput(); + } + }; + HashCode value = ByteStreams.hash(input, Hashing.sha1()); + classNamesBuilder.put(key, value); } } }; @@ -77,9 +101,17 @@ return 1; } - ImmutableSortedSet<String> classNames = classNamesBuilder.build(); + ImmutableSortedMap<String, HashCode> classNames = classNamesBuilder.build(); try { - context.getProjectFilesystem().writeLinesToPath(classNames, whereClassNamesShouldBeWritten); + context.getProjectFilesystem().writeLinesToPath( + Iterables.transform(classNames.entrySet(), + new Function<Map.Entry<String, HashCode>, String>() { + @Override + public String apply(Entry<String, HashCode> entry) { + return entry.getKey() + CLASS_NAME_HASH_CODE_SEPARATOR + entry.getValue(); + } + }), + whereClassNamesShouldBeWritten); } catch (IOException e) { context.getBuckEventBus().post(ThrowableLogEvent.create(e, "There was an error writing the list of .class files to %s.",
diff --git a/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java b/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java index 85e46b8..60de63a 100644 --- a/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java +++ b/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java
@@ -39,6 +39,8 @@ public class AccumulateClassNamesStepTest { + private static final String SHA1_FOR_EMPTY_STRING = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; + @Rule public TemporaryFolder tmp = new TemporaryFolder(); @@ -70,12 +72,13 @@ accumulateClassNamesStep.execute(context); String contents = Files.toString(new File(tmp.getRoot(), "output.txt"), Charsets.UTF_8); + String separator = AccumulateClassNamesStep.CLASS_NAME_HASH_CODE_SEPARATOR; assertEquals( "Verify that the contents are sorted alphabetically and ignore non-.class files.", Joiner.on('\n').join( - "com/example/Bar", - "com/example/Foo", - "com/example/subpackage/Baz") + '\n', + "com/example/Bar" + separator + SHA1_FOR_EMPTY_STRING, + "com/example/Foo" + separator + SHA1_FOR_EMPTY_STRING, + "com/example/subpackage/Baz" + separator + SHA1_FOR_EMPTY_STRING) + '\n', contents); } @@ -104,12 +107,13 @@ accumulateClassNamesStep.execute(context); String contents = Files.toString(new File(tmp.getRoot(), "output.txt"), Charsets.UTF_8); + String separator = AccumulateClassNamesStep.CLASS_NAME_HASH_CODE_SEPARATOR; assertEquals( "Verify that the contents are sorted alphabetically and ignore non-.class files.", Joiner.on('\n').join( - "com/example/Bar", - "com/example/Foo", - "com/example/subpackage/Baz") + '\n', + "com/example/Bar" + separator + SHA1_FOR_EMPTY_STRING, + "com/example/Foo" + separator + SHA1_FOR_EMPTY_STRING, + "com/example/subpackage/Baz" + separator + SHA1_FOR_EMPTY_STRING) + '\n', contents); } }