blob: af56eb112f7638c6252b1e0b34eccc36393b07e2 [file] [log] [blame]
/*
* Copyright 2012-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.util;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.eventbus.Subscribe;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.concurrent.ExecutionException;
public class DefaultFileHashCache implements FileHashCache {
private static final Logger LOG = Logger.get(DefaultFileHashCache.class);
private final ProjectFilesystem projectFilesystem;
@VisibleForTesting
final LoadingCache<Path, HashCode> loadingCache;
public DefaultFileHashCache(ProjectFilesystem projectFilesystem) {
this.projectFilesystem = projectFilesystem;
this.loadingCache = CacheBuilder.newBuilder()
.build(new CacheLoader<Path, HashCode>() {
@Override
public HashCode load(Path path) throws Exception {
return getHashCode(path);
}
});
}
private HashCode getHashCode(Path path) throws IOException {
// TODO(simons): Should be this.projectFilesystem.computeSha1(path);
File file = this.projectFilesystem.resolve(path).toFile();
ByteSource source = Files.asByteSource(file);
return source.hash(Hashing.sha1());
}
@Override
public boolean contains(Path path) {
return loadingCache.getIfPresent(path) != null;
}
/**
* @return The {@link com.google.common.hash.HashCode} of the contents of path.
*/
@Override
public HashCode get(Path path) {
HashCode sha1;
try {
// Ignored paths will not generate file change events and so are not invalidated correctly.
// Where ignored paths are output files, they are generated by each build and will not
// generate cache hits so not caching them is likely a performance win in any case.
if (projectFilesystem.isIgnored(path)) {
sha1 = getHashCode(path);
} else {
sha1 = loadingCache.get(path.normalize());
}
} catch (ExecutionException | IOException e) {
throw new RuntimeException(e);
}
return Preconditions.checkNotNull(sha1, "Failed to find a HashCode for %s.", path);
}
/**
* Called when file change events are posted to the file change EventBus to invalidate cached
* build rules if required. {@link Path}s contained within events must all be relative to the
* {@link ProjectFilesystem} root.
*/
@Subscribe
public synchronized void onFileSystemChange(WatchEvent<?> event) throws IOException {
if (projectFilesystem.isPathChangeEvent(event)) {
// Path event, remove the path from the cache as it has been changed, added or deleted.
Path path = ((Path) event.context()).normalize();
LOG.verbose("Invalidating %s", path);
loadingCache.invalidate(path);
} else {
// Non-path change event, likely an overflow due to many change events: invalidate everything.
LOG.debug("Invalidating all");
loadingCache.invalidateAll();
}
}
}