/*
* Copyright 2016-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.autosparse;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.io.DefaultProjectFilesystemDelegate;
import com.facebook.buck.io.ProjectFilesystemDelegate;
import com.facebook.buck.log.Logger;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.sha1.Sha1HashCode;
import com.facebook.buck.util.versioncontrol.SparseSummary;
import java.io.IOException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
/**
* Virtual project filesystem that answers questions about files via the source control manifest.
* This removes the need to have all files checked out while Buck parses (a so-called
* <em>sparse</em> profile.
*
* <p>The source control system state is tracked by in an {@link AutoSparseState} instance. Any
* files queries about files outside the source control system manifest are forwarded to {@link
* DefaultProjectFilesystemDelegate}.
*/
public final class AutoSparseProjectFilesystemDelegate implements ProjectFilesystemDelegate {
private static final Logger LOG = Logger.get(AutoSparseProjectFilesystemDelegate.class);
private final AutoSparseState autoSparseState;
private final Path scRoot;
/** Delegate to forward requests to for files that are outside of the hg root. */
private final ProjectFilesystemDelegate delegate;
public AutoSparseProjectFilesystemDelegate(AutoSparseState autoSparseState, Path projectRoot)
throws InterruptedException {
this.autoSparseState = autoSparseState;
this.scRoot = autoSparseState.getSCRoot();
this.delegate = new DefaultProjectFilesystemDelegate(projectRoot);
}
@Override
public void ensureConcreteFilesExist(BuckEventBus eventBus) {
LOG.debug("Materialising the sparse profile");
AutoSparseStateEvents.SparseRefreshStarted started =
new AutoSparseStateEvents.SparseRefreshStarted();
eventBus.post(started);
SparseSummary sparseSummary = SparseSummary.of();
try {
sparseSummary = autoSparseState.materialiseSparseProfile();
} catch (IOException | InterruptedException e) {
Throwable cause = e.getCause();
String details = cause == null ? e.getMessage() : cause.getMessage();
AutoSparseStateEvents.SparseRefreshFailed failed =
new AutoSparseStateEvents.SparseRefreshFailed(started, details);
eventBus.post(failed);
throw new HumanReadableException(
e,
"Sparse profile could not be materialised. "
+ "Try again or disable the project.enable_autosparse option.");
} finally {
eventBus.post(new AutoSparseStateEvents.SparseRefreshFinished(started, sparseSummary));
}
}
@Override
public Sha1HashCode computeSha1(Path pathRelativeToProjectRootOrJustAbsolute) throws IOException {
// compute absolute path to ensure it is listed in the sparse profile
Path path = getPathForRelativePath(pathRelativeToProjectRootOrJustAbsolute);
return delegate.computeSha1(path);
}
@Override
public Path getPathForRelativePath(Path pathRelativeToProjectRootOrJustAbsolute) {
Path path = delegate.getPathForRelativePath(pathRelativeToProjectRootOrJustAbsolute);
if (path.startsWith(scRoot)) {
includeInSparse(path);
}
return path;
}
@Override
public boolean isExecutable(Path child) {
ManifestInfo manifestentry = autoSparseState.getManifestInfoForFile(child);
if (manifestentry != null) {
return manifestentry.isExecutable();
}
return delegate.isExecutable(child);
}
@Override
public boolean isSymlink(Path path) {
path = getPathForRelativePath(path);
ManifestInfo manifestentry = autoSparseState.getManifestInfoForFile(path);
if (manifestentry != null) {
return manifestentry.isLink();
} else if (autoSparseState.existsInManifest(path)) {
// it is a directory that exists in the manifest
return false;
}
return delegate.isSymlink(path);
}
@Override
public boolean exists(Path pathRelativeToProjectRoot, LinkOption... options) {
Path path = getPathForRelativePath(pathRelativeToProjectRoot);
boolean existsInManifest = autoSparseState.existsInManifest(path);
return existsInManifest || delegate.exists(path);
}
private void includeInSparse(Path path) {
autoSparseState.addToSparseProfile(path);
}
}