| // 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)); | |
| } | |
| } | |
| } |