diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/sandbox/SpawnHelpers.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/sandbox/SpawnHelpers.java | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SpawnHelpers.java b/src/main/java/com/google/devtools/build/lib/sandbox/SpawnHelpers.java new file mode 100644 index 0000000000..34ddd9e782 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SpawnHelpers.java @@ -0,0 +1,198 @@ +// Copyright 2016 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.sandbox; + +import com.google.common.io.Files; +import com.google.devtools.build.lib.actions.ActionExecutionContext; +import com.google.devtools.build.lib.actions.ActionInput; +import com.google.devtools.build.lib.actions.ActionInputHelper; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.Spawn; +import com.google.devtools.build.lib.analysis.AnalysisUtils; +import com.google.devtools.build.lib.rules.cpp.CppCompileAction; +import com.google.devtools.build.lib.rules.fileset.FilesetActionContext; +import com.google.devtools.build.lib.util.Preconditions; +import com.google.devtools.build.lib.vfs.FileSystem; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Contains common helper methods that extract information from {@link Spawn} objects. */ +public final class SpawnHelpers { + + private final Path execRoot; + + public SpawnHelpers(Path execRoot) { + this.execRoot = execRoot; + } + + /** + * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the + * host filesystem where the input files can be found. + */ + public Map<PathFragment, Path> getMounts(Spawn spawn, ActionExecutionContext executionContext) + throws IOException { + Map<PathFragment, Path> mounts = new HashMap<>(); + mountRunfilesFromManifests(mounts, spawn); + mountRunfilesFromSuppliers(mounts, spawn); + mountFilesFromFilesetManifests(mounts, spawn, executionContext); + mountInputs(mounts, spawn, executionContext); + return mounts; + } + + /** Mount all runfiles that the spawn needs as specified in its runfiles manifests. */ + void mountRunfilesFromManifests(Map<PathFragment, Path> mounts, Spawn spawn) throws IOException { + for (Map.Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) { + String manifestFilePath = manifest.getValue().getPath().getPathString(); + Preconditions.checkState(!manifest.getKey().isAbsolute()); + PathFragment targetDirectory = manifest.getKey(); + + parseManifestFile( + execRoot.getFileSystem(), mounts, targetDirectory, new File(manifestFilePath), false, ""); + } + } + + /** Mount all files that the spawn needs as specified in its fileset manifests. */ + void mountFilesFromFilesetManifests( + Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext executionContext) + throws IOException { + final FilesetActionContext filesetContext = + executionContext.getExecutor().getContext(FilesetActionContext.class); + for (Artifact fileset : spawn.getFilesetManifests()) { + File manifestFile = + new File( + execRoot.getPathString(), + AnalysisUtils.getManifestPathFromFilesetPath(fileset.getExecPath()).getPathString()); + PathFragment targetDirectory = fileset.getExecPath(); + + parseManifestFile( + execRoot.getFileSystem(), + mounts, + targetDirectory, + manifestFile, + true, + filesetContext.getWorkspaceName()); + } + } + + /** A parser for the MANIFEST files used by Filesets and runfiles. */ + static void parseManifestFile( + FileSystem fs, + Map<PathFragment, Path> mounts, + PathFragment targetDirectory, + File manifestFile, + boolean isFilesetManifest, + String workspaceName) + throws IOException { + int lineNum = 0; + for (String line : Files.readLines(manifestFile, StandardCharsets.UTF_8)) { + if (isFilesetManifest && (++lineNum % 2 == 0)) { + continue; + } + if (line.isEmpty()) { + continue; + } + + String[] fields = line.trim().split(" "); + + // The "target" field is always a relative path that is to be interpreted in this way: + // (1) If this is a fileset manifest and our workspace name is not empty, the first segment + // of each "target" path must be the workspace name, which is then stripped before further + // processing. + // (2) The "target" path is then appended to the "targetDirectory", which is a path relative + // to the execRoot. Together, this results in the full path in the execRoot in which place a + // symlink referring to "source" has to be created (see below). + PathFragment targetPath; + if (isFilesetManifest) { + PathFragment targetPathFragment = new PathFragment(fields[0]); + if (!workspaceName.isEmpty()) { + Preconditions.checkState( + targetPathFragment.getSegment(0).equals(workspaceName), + "Fileset manifest line must start with workspace name"); + targetPathFragment = targetPathFragment.subFragment(1, targetPathFragment.segmentCount()); + } + targetPath = targetDirectory.getRelative(targetPathFragment); + } else { + targetPath = targetDirectory.getRelative(fields[0]); + } + + // The "source" field, if it exists, is always an absolute path and may point to any file in + // the filesystem (it is not limited to files in the workspace or execroot). + Path source; + switch (fields.length) { + case 1: + source = fs.getPath("/dev/null"); + break; + case 2: + source = fs.getPath(fields[1]); + break; + default: + throw new IllegalStateException("'" + line + "' splits into more than 2 parts"); + } + + mounts.put(targetPath, source); + } + } + + /** Mount all runfiles that the spawn needs as specified via its runfiles suppliers. */ + void mountRunfilesFromSuppliers(Map<PathFragment, Path> mounts, Spawn spawn) throws IOException { + Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings = + spawn.getRunfilesSupplier().getMappings(); + for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings : + rootsAndMappings.entrySet()) { + PathFragment root = rootAndMappings.getKey(); + if (root.isAbsolute()) { + root = root.relativeTo(execRoot.asFragment()); + } + for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) { + Artifact sourceArtifact = mapping.getValue(); + PathFragment source = + (sourceArtifact != null) ? sourceArtifact.getExecPath() : new PathFragment("/dev/null"); + + Preconditions.checkArgument(!mapping.getKey().isAbsolute()); + PathFragment target = root.getRelative(mapping.getKey()); + mounts.put(target, execRoot.getRelative(source)); + } + } + } + + /** Mount all inputs of the spawn. */ + void mountInputs( + Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext actionExecutionContext) { + List<ActionInput> inputs = + ActionInputHelper.expandArtifacts( + spawn.getInputFiles(), actionExecutionContext.getArtifactExpander()); + + if (spawn.getResourceOwner() instanceof CppCompileAction) { + CppCompileAction action = (CppCompileAction) spawn.getResourceOwner(); + if (action.shouldScanIncludes()) { + inputs.addAll(action.getAdditionalInputs()); + } + } + + for (ActionInput input : inputs) { + if (input.getExecPathString().contains("internal/_middlemen/")) { + continue; + } + PathFragment mount = new PathFragment(input.getExecPathString()); + mounts.put(mount, execRoot.getRelative(mount)); + } + } +} |