aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java b/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java
new file mode 100644
index 0000000000..f278507ddf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java
@@ -0,0 +1,142 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.google.devtools.build.lib.actions.cache;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.VarInt;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * A value class for capturing and comparing MD5-based digests.
+ *
+ * <p>Note that this class is responsible for digesting file metadata in an
+ * order-independent manner. Care must be taken to do this properly. The
+ * digest must be a function of the set of (path, metadata) tuples. While the
+ * order of these pairs must not matter, it would <b>not</b> be safe to make
+ * the digest be a function of the set of paths and the set of metadata.
+ *
+ * <p>Note that the (path, metadata) tuples must be unique, otherwise the
+ * XOR-based approach will fail.
+ */
+public class Digest {
+
+ static final int MD5_SIZE = 16;
+
+ private final byte[] digest;
+
+ /**
+ * Construct the digest from the given bytes.
+ * @param digest an MD5 digest. Must be sized properly.
+ */
+ @VisibleForTesting
+ Digest(byte[] digest) {
+ Preconditions.checkState(digest.length == MD5_SIZE);
+ this.digest = Arrays.copyOf(digest, digest.length);
+ }
+
+ /**
+ * @param source the byte buffer source.
+ * @return the digest from the given buffer.
+ * @throws IOException if the byte buffer is incorrectly formatted.
+ */
+ public static Digest read(ByteBuffer source) throws IOException {
+ int size = VarInt.getVarInt(source);
+ if (size != MD5_SIZE) {
+ throw new IOException("Unexpected digest length: " + size);
+ }
+ byte[] bytes = new byte[size];
+ source.get(bytes);
+ return new Digest(bytes);
+ }
+
+ /**
+ * Write the digest to the output stream.
+ */
+ public void write(OutputStream sink) throws IOException {
+ VarInt.putVarInt(digest.length, sink);
+ sink.write(digest);
+ }
+
+ /**
+ * @param mdMap A collection of (execPath, Metadata) pairs.
+ * Values may be null.
+ * @return an <b>order-independent</b> digest from the given "set" of
+ * (path, metadata) pairs.
+ */
+ public static Digest fromMetadata(Map<String, Metadata> mdMap) {
+ byte[] result = new byte[MD5_SIZE];
+ // Profiling showed that MD5 engine instantiation was a hotspot, so create one instance for
+ // this computation to amortize its cost.
+ Fingerprint fp = new Fingerprint();
+ for (Map.Entry<String, Metadata> entry : mdMap.entrySet()) {
+ xorWith(result, getDigest(fp, entry.getKey(), entry.getValue()));
+ fp.reset();
+ }
+ return new Digest(result);
+ }
+
+ /**
+ * @return this Digest as a Metadata with no mtime.
+ */
+ public Metadata asMetadata() {
+ return new Metadata(digest);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(digest);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof Digest) && Arrays.equals(digest, ((Digest) obj).digest);
+ }
+
+ @Override
+ public String toString() {
+ return Fingerprint.hexDigest(digest);
+ }
+
+ private static byte[] getDigest(Fingerprint fp, String execPath, Metadata md) {
+ fp.addString(execPath);
+
+ if (md == null) {
+ // Move along, nothing to see here.
+ } else if (md.digest == null) {
+ // Use the timestamp if the digest is not present, but not both.
+ // Modifying a timestamp while keeping the contents of a file the
+ // same should not cause rebuilds.
+ fp.addLong(md.mtime);
+ } else {
+ fp.addBytes(md.digest);
+ }
+ return fp.digestAndReset();
+ }
+
+ /**
+ * Compute lhs ^= rhs bitwise operation of the arrays.
+ */
+ private static void xorWith(byte[] lhs, byte[] rhs) {
+ for (int i = 0; i < lhs.length; i++) {
+ lhs[i] ^= rhs[i];
+ }
+ }
+}