blob: 34ddd9e7823d64b0f9acbeacdfaf657ac9759dfe [file] [log] [blame]
// 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));
}
}
}