| // Copyright 2014 The Bazel Authors. 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.exec; |
| |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.hash.Hasher; |
| import com.google.common.io.ByteStreams; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.FileArtifactValue; |
| import com.google.devtools.build.lib.actions.cache.VirtualActionInput; |
| import com.google.devtools.build.lib.analysis.BlazeDirectories; |
| import com.google.devtools.build.lib.vfs.DigestHashFunction; |
| import com.google.devtools.build.lib.vfs.Dirent; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Symlinks; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import com.google.protobuf.ByteString; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Maintains a mapping between relative path (from the execution root) to {@link ActionInput}, for |
| * various auxiliary binaries used during action execution (alarm. etc). |
| */ |
| public final class BinTools { |
| private final Path embeddedBinariesRoot; |
| private final ImmutableList<String> embeddedTools; |
| private final ImmutableMap<String, ActionInput> actionInputs; |
| |
| private BinTools(BlazeDirectories directories, ImmutableList<String> tools) { |
| this(directories.getEmbeddedBinariesRoot(), tools); |
| } |
| |
| private BinTools(Path embeddedBinariesRoot, ImmutableList<String> tools) { |
| this.embeddedBinariesRoot = embeddedBinariesRoot; |
| ImmutableList.Builder<String> builder = ImmutableList.builder(); |
| // Files under embedded_tools shouldn't be copied to under _bin dir |
| // They won't be used during action execution time. |
| for (String tool : tools) { |
| if (!tool.startsWith("embedded_tools/")) { |
| builder.add(tool); |
| } |
| } |
| this.embeddedTools = builder.build(); |
| |
| ImmutableMap.Builder<String, ActionInput> result = ImmutableMap.builder(); |
| for (String embeddedPath : embeddedTools) { |
| Path path = getEmbeddedPath(embeddedPath); |
| PathFragment execPath = PathFragment.create("_bin").getRelative(embeddedPath); |
| result.put(embeddedPath, new PathActionInput(path, execPath)); |
| } |
| actionInputs = result.buildOrThrow(); |
| } |
| |
| |
| /** |
| * Creates an instance with the list of embedded tools obtained from scanning the directory |
| * into which said binaries were extracted by the launcher. |
| */ |
| public static BinTools forProduction(BlazeDirectories directories) throws IOException { |
| ImmutableList.Builder<String> builder = ImmutableList.builder(); |
| scanDirectoryRecursively(builder, directories.getEmbeddedBinariesRoot(), ""); |
| return new BinTools(directories, builder.build()); |
| } |
| |
| /** |
| * Creates an empty instance for testing. |
| */ |
| @VisibleForTesting |
| public static BinTools empty(BlazeDirectories directories) { |
| return new BinTools(directories, ImmutableList.of()); |
| } |
| |
| /** |
| * Creates an instance for testing with the given embedded binaries root. |
| */ |
| @VisibleForTesting |
| public static BinTools forEmbeddedBin(Path embeddedBinariesRoot, Iterable<String> tools) { |
| return new BinTools(embeddedBinariesRoot, ImmutableList.copyOf(tools)); |
| } |
| |
| /** |
| * Creates an instance for testing without actually symlinking the tools. |
| * |
| * <p>Used for tests that need a set of embedded tools to be present, but not the actual files. |
| */ |
| @VisibleForTesting |
| public static BinTools forUnitTesting(BlazeDirectories directories, Iterable<String> tools) { |
| return new BinTools(directories, ImmutableList.copyOf(tools)); |
| } |
| |
| /** |
| * Creates an instance for testing without actually symlinking the tools. |
| * |
| * <p>Used for tests that need a set of embedded tools to be present, but not the actual files. |
| */ |
| @VisibleForTesting |
| public static BinTools forUnitTesting(Path execroot, Iterable<String> tools) { |
| return new BinTools(execroot.getRelative("/fake/embedded/tools"), ImmutableList.copyOf(tools)); |
| } |
| |
| /** |
| * Returns a BinTools instance. Before calling this method, you have to populate the |
| * {@link BlazeDirectories#getEmbeddedBinariesRoot} directory. |
| */ |
| @VisibleForTesting |
| public static BinTools forIntegrationTesting( |
| BlazeDirectories directories, Iterable<String> tools) { |
| return new BinTools(directories, ImmutableList.copyOf(tools)); |
| } |
| |
| private static void scanDirectoryRecursively( |
| ImmutableList.Builder<String> result, Path root, String relative) throws IOException { |
| for (Dirent dirent : root.readdir(Symlinks.NOFOLLOW)) { |
| String childRelative = relative.isEmpty() |
| ? dirent.getName() |
| : relative + "/" + dirent.getName(); |
| switch (dirent.getType()) { |
| case FILE: |
| result.add(childRelative); |
| break; |
| |
| case DIRECTORY: |
| scanDirectoryRecursively(result, root.getChild(dirent.getName()), childRelative); |
| break; |
| |
| default: |
| // Nothing to do here -- we ignore symlinks, since they should not be present in the |
| // embedded binaries tree. |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Returns an action input for the given embedded tool. |
| */ |
| public ActionInput getActionInput(String embeddedPath) { |
| return actionInputs.get(embeddedPath); |
| } |
| |
| @Nullable |
| public Path getEmbeddedPath(String embedPath) { |
| if (!embeddedTools.contains(embedPath)) { |
| return null; |
| } |
| return embeddedBinariesRoot.getRelative(embedPath); |
| } |
| |
| /** An ActionInput pointing at an absolute path. */ |
| @VisibleForTesting |
| public static final class PathActionInput extends VirtualActionInput { |
| private final Path path; |
| private final PathFragment execPath; |
| private FileArtifactValue metadata; |
| /** Contains the digest of the input once it has been written. */ |
| private volatile byte[] digest; |
| |
| public PathActionInput(Path path, PathFragment execPath) { |
| this.path = path; |
| this.execPath = execPath; |
| } |
| |
| @Override |
| public void writeTo(OutputStream out) throws IOException { |
| try (InputStream in = path.getInputStream()) { |
| ByteStreams.copy(in, out); |
| } |
| } |
| |
| @Override |
| @CanIgnoreReturnValue |
| protected byte[] atomicallyWriteTo(Path outputPath, String uniqueSuffix) throws IOException { |
| // The embedded tools do not change, but we need to be sure they're written out without race |
| // conditions. |
| if (digest == null || !outputPath.exists()) { |
| synchronized (this) { |
| if (digest == null || !outputPath.exists()) { |
| outputPath.getParentDirectory().createDirectoryAndParents(); |
| digest = writeTo(outputPath); |
| } |
| } |
| } |
| return digest; |
| } |
| |
| @Override |
| public ByteString getBytes() throws IOException { |
| ByteString.Output out = ByteString.newOutput(); |
| writeTo(out); |
| return out.toByteString(); |
| } |
| |
| @Override |
| public synchronized FileArtifactValue getMetadata() throws IOException { |
| // We intentionally delay hashing until it is necessary. |
| if (metadata == null) { |
| metadata = hash(path); |
| } |
| return metadata; |
| } |
| |
| private static FileArtifactValue hash(Path path) throws IOException { |
| DigestHashFunction hashFn = path.getFileSystem().getDigestFunction(); |
| Hasher hasher = hashFn.getHashFunction().newHasher(); |
| int bytesCopied = 0; |
| try (InputStream in = path.getInputStream()) { |
| byte[] buffer = new byte[1024]; |
| int len; |
| while ((len = in.read(buffer)) > 0) { |
| hasher.putBytes(buffer, 0, len); |
| bytesCopied += len; |
| } |
| } |
| return FileArtifactValue.createForVirtualActionInput( |
| hasher.hash().asBytes(), |
| bytesCopied); |
| } |
| |
| @Override |
| public String getExecPathString() { |
| return execPath.getPathString(); |
| } |
| |
| @Override |
| public PathFragment getExecPath() { |
| return execPath; |
| } |
| } |
| } |