| // 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.analysis.config; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.actions.EnvironmentalExecException; |
| import com.google.devtools.build.lib.actions.ExecException; |
| import com.google.devtools.build.lib.analysis.BlazeDirectories; |
| import com.google.devtools.build.lib.vfs.Dirent; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| 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 java.io.IOException; |
| |
| /** |
| * Initializes the <execRoot>/_bin/ directory that contains auxiliary tools used during action |
| * execution (alarm, etc). The main purpose of this is to make sure that those tools are accessible |
| * using relative paths from the execution root. |
| */ |
| public final class BinTools { |
| private final BlazeDirectories directories; |
| private final Path execrootParent; |
| private final ImmutableList<String> embeddedTools; |
| |
| private Path binDir; // the working bin directory under execRoot |
| |
| private BinTools(BlazeDirectories directories, ImmutableList<String> tools) { |
| this.directories = directories; |
| this.execrootParent = directories.getExecRoot().getParentDirectory(); |
| 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(); |
| this.binDir = null; |
| } |
| |
| /** |
| * 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.<String>of()).setBinDir( |
| directories.getWorkspace().getBaseName()); |
| } |
| |
| /** |
| * 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)).setBinDir( |
| directories.getWorkspace().getBaseName()); |
| } |
| |
| /** |
| * Populates the _bin directory by symlinking the necessary files from the given |
| * srcDir, and returns the corresponding BinTools. |
| */ |
| @VisibleForTesting |
| public static BinTools forIntegrationTesting( |
| BlazeDirectories directories, String srcDir, Iterable<String> tools, String repositoryName) |
| throws IOException { |
| Path srcPath = directories.getOutputBase().getFileSystem().getPath(srcDir); |
| for (String embedded : tools) { |
| Path runfilesPath = srcPath.getRelative(embedded); |
| if (!runfilesPath.isFile()) { |
| // The file isn't there - nothing to symlink! |
| // |
| // Note: This path is usually taken by the tests using the in-memory |
| // file system. They can't run the embedded scripts anyhow, so there isn't |
| // much point in creating a symlink to a non-existent binary here. |
| continue; |
| } |
| Path outputPath = directories.getExecRoot(repositoryName).getChild("_bin").getChild(embedded); |
| if (outputPath.exists()) { |
| outputPath.delete(); |
| } |
| FileSystemUtils.createDirectoryAndParents(outputPath.getParentDirectory()); |
| outputPath.createSymbolicLink(runfilesPath); |
| } |
| |
| return new BinTools(directories, ImmutableList.copyOf(tools)).setBinDir(repositoryName); |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| public PathFragment getExecPath(String embedPath) { |
| if (!embeddedTools.contains(embedPath)) { |
| return null; |
| } |
| return PathFragment.create("_bin").getRelative(PathFragment.create(embedPath).getBaseName()); |
| } |
| |
| private BinTools setBinDir(String workspaceName) { |
| binDir = execrootParent.getRelative(workspaceName).getRelative("_bin"); |
| return this; |
| } |
| |
| /** |
| * Initializes the build tools not available at absolute paths. Note that |
| * these must be constant across all configurations. |
| */ |
| public void setupBuildTools(String workspaceName) throws ExecException { |
| setBinDir(workspaceName); |
| try { |
| FileSystemUtils.createDirectoryAndParents(binDir); |
| } catch (IOException e) { |
| throw new EnvironmentalExecException("could not create directory '" + binDir + "'", e); |
| } |
| |
| for (String embeddedPath : embeddedTools) { |
| setupTool(embeddedPath); |
| } |
| } |
| |
| private void setupTool(String embeddedPath) throws ExecException { |
| Preconditions.checkNotNull(binDir); |
| Path sourcePath = directories.getEmbeddedBinariesRoot().getRelative(embeddedPath); |
| Path linkPath = binDir.getRelative(PathFragment.create(embeddedPath).getBaseName()); |
| linkTool(sourcePath, linkPath); |
| } |
| |
| private void linkTool(Path sourcePath, Path linkPath) throws ExecException { |
| if (linkPath.getFileSystem().supportsSymbolicLinksNatively(linkPath)) { |
| try { |
| if (!linkPath.isSymbolicLink()) { |
| // ensureSymbolicLink() does not handle the case where there is already |
| // a file with the same name, so we need to handle it here. |
| linkPath.delete(); |
| } |
| FileSystemUtils.ensureSymbolicLink(linkPath, sourcePath); |
| } catch (IOException e) { |
| throw new EnvironmentalExecException("failed to link '" + sourcePath + "'", e); |
| } |
| } else { |
| // For file systems that do not support linking, copy. |
| try { |
| FileSystemUtils.copyTool(sourcePath, linkPath); |
| } catch (IOException e) { |
| throw new EnvironmentalExecException("failed to copy '" + sourcePath + "'" , e); |
| } |
| } |
| } |
| } |