/* * 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.eden; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.sha1.Sha1HashCode; import com.facebook.eden.thrift.EdenError; import com.facebook.eden.thrift.EdenService; import com.facebook.eden.thrift.SHA1Result; import com.facebook.thrift.TException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Optional; /** * Utility to make requests to the Eden thrift API for an (Eden mount point, Buck project root) * pair. The Buck project root must be contained by the Eden mount point. */ public class EdenMount { private final ThreadLocal<EdenService.Client> client; /** Value of the mountPoint argument to use when communicating with Eden via the Thrift API. */ private final String mountPoint; /** Root of the Buck project of interest that is contained by this {@link EdenMount}. */ private final Path projectRoot; /** * Relative path used to resolve paths under the {@link #projectRoot} in the context of the {@link * #mountPoint}. */ private final Path prefix; /** * Creates a new object for communicating with Eden that is bound to the specified (Eden mount * point, Buck project root) pair. It must be the case that {@code * projectRoot.startsWith(mountPoint)}. */ EdenMount(ThreadLocal<EdenService.Client> client, Path mountPoint, Path projectRoot) { Preconditions.checkArgument( projectRoot.startsWith(mountPoint), "Eden mount point %s must contain the Buck project at %s.", mountPoint, projectRoot); this.client = client; this.mountPoint = mountPoint.toString(); this.projectRoot = projectRoot; this.prefix = mountPoint.relativize(projectRoot); } /** @return The root to the Buck project that this {@link EdenMount} represents. */ public Path getProjectRoot() { return projectRoot; } @VisibleForTesting Path getPrefix() { return prefix; } /** @param entry is a path that is relative to {@link #getProjectRoot()}. */ public Sha1HashCode getSha1(Path entry) throws EdenError, TException { List<SHA1Result> results = client.get().getSHA1(mountPoint, ImmutableList.of(normalizePathArg(entry))); SHA1Result result = Iterables.getOnlyElement(results); if (result.getSetField() == SHA1Result.SHA1) { return Sha1HashCode.fromBytes(result.getSha1()); } else { throw result.getError(); } } public ImmutableList<Path> getBindMounts() { List<String> bindMounts; try { bindMounts = client.get().getBindMounts(mountPoint); } catch (TException e) { throw new RuntimeException(e); } return bindMounts.stream().map(Paths::get).collect(MoreCollectors.toImmutableList()); } /** * Returns the path relative to {@link #getProjectRoot()} if {@code path} is contained by {@link * #getProjectRoot()}; otherwise, returns {@link Optional#empty()}. */ Optional<Path> getPathRelativeToProjectRoot(Path path) { if (path.isAbsolute()) { if (path.startsWith(projectRoot)) { return Optional.of(projectRoot.relativize(path)); } else { return Optional.empty(); } } else { return Optional.of(path); } } /** * @param entry is a path that is relative to {@link #getProjectRoot()}. * @return a path that is relative to {@link #mountPoint}. */ private String normalizePathArg(Path entry) { return prefix.resolve(entry).toString(); } @Override public String toString() { return String.format("EdenMount{mountPoint=%s, prefix=%s}", mountPoint, prefix); } }