/* * Copyright 2015-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.hashing; import com.facebook.buck.io.ArchiveMemberPath; import com.facebook.buck.io.MorePaths; import com.google.common.collect.ImmutableSet; 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.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; /** * A {@link FileHashLoader} that only hashes the files' paths without reading their contents. * * <p>If file's hash needs to be changed, for example to reflect changes to the file's contents, the * file's path can be specified in a set of modified files. Files specified in this set will get new * unique hashes based on their paths distinct from the hashes they would get if they were omitted * from the set. */ public class FilePathHashLoader implements FileHashLoader { private final Path defaultCellRoot; private final ImmutableSet<Path> assumeModifiedFiles; public FilePathHashLoader(final Path defaultCellRoot, ImmutableSet<Path> assumeModifiedFiles) throws IOException { this.defaultCellRoot = defaultCellRoot; ImmutableSet.Builder<Path> modifiedFilesBuilder = ImmutableSet.builder(); for (Path path : assumeModifiedFiles) { modifiedFilesBuilder.add(defaultCellRoot.resolve(path).toRealPath()); } this.assumeModifiedFiles = modifiedFilesBuilder.build(); } @Override public HashCode get(Path root) throws IOException { // In case the root path is a directory, collect all files contained in it and sort them before // hashing to avoid non-deterministic directory traversal order from influencing the hash. final ImmutableSortedSet.Builder<Path> files = ImmutableSortedSet.naturalOrder(); Files.walkFileTree( defaultCellRoot.resolve(root), ImmutableSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { files.add(file); return FileVisitResult.CONTINUE; } }); Hasher hasher = Hashing.sha1().newHasher(); for (Path file : files.build()) { file = defaultCellRoot.resolve(file).toRealPath(); boolean assumeModified = assumeModifiedFiles.contains(file); Path relativePath = MorePaths.relativize(defaultCellRoot, file); // For each file add its path to the hasher suffixed by whether we assume the file to be // modified or not. This way files with different paths always result in different hashes and // files that are assumed to be modified get different hashes than all unmodified files. StringHashing.hashStringAndLength(hasher, relativePath.toString()); hasher.putBoolean(assumeModified); } return hasher.hash(); } @Override public long getSize(Path path) throws IOException { return Files.size(path); } @Override public HashCode get(ArchiveMemberPath archiveMemberPath) throws IOException { throw new UnsupportedOperationException("Not implemented"); } }