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);
}
}