blob: b5b920cc31fd68f75c808eb5c016e89aab8bf530 [file] [log] [blame]
Philipp Wollermann809df532016-09-08 12:53:39 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.sandbox;
16
Lukacs Berki840acb12017-01-25 14:01:33 +000017import com.google.common.collect.Iterables;
Philipp Wollermann809df532016-09-08 12:53:39 +000018import com.google.common.io.Files;
19import com.google.devtools.build.lib.actions.ActionExecutionContext;
20import com.google.devtools.build.lib.actions.ActionInput;
21import com.google.devtools.build.lib.actions.ActionInputHelper;
22import com.google.devtools.build.lib.actions.Artifact;
23import com.google.devtools.build.lib.actions.Spawn;
24import com.google.devtools.build.lib.analysis.AnalysisUtils;
25import com.google.devtools.build.lib.rules.cpp.CppCompileAction;
26import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
27import com.google.devtools.build.lib.util.Preconditions;
28import com.google.devtools.build.lib.vfs.FileSystem;
29import com.google.devtools.build.lib.vfs.Path;
30import com.google.devtools.build.lib.vfs.PathFragment;
31import java.io.File;
32import java.io.IOException;
33import java.nio.charset.StandardCharsets;
Adam Michaelb5f480c2016-12-09 22:19:32 +000034import java.util.ArrayList;
Philipp Wollermann809df532016-09-08 12:53:39 +000035import java.util.HashMap;
36import java.util.List;
37import java.util.Map;
38
39/** Contains common helper methods that extract information from {@link Spawn} objects. */
40public final class SpawnHelpers {
41
42 private final Path execRoot;
43
44 public SpawnHelpers(Path execRoot) {
45 this.execRoot = execRoot;
46 }
47
48 /**
49 * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
50 * host filesystem where the input files can be found.
51 */
52 public Map<PathFragment, Path> getMounts(Spawn spawn, ActionExecutionContext executionContext)
53 throws IOException {
54 Map<PathFragment, Path> mounts = new HashMap<>();
55 mountRunfilesFromManifests(mounts, spawn);
56 mountRunfilesFromSuppliers(mounts, spawn);
57 mountFilesFromFilesetManifests(mounts, spawn, executionContext);
58 mountInputs(mounts, spawn, executionContext);
59 return mounts;
60 }
61
62 /** Mount all runfiles that the spawn needs as specified in its runfiles manifests. */
63 void mountRunfilesFromManifests(Map<PathFragment, Path> mounts, Spawn spawn) throws IOException {
64 for (Map.Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) {
65 String manifestFilePath = manifest.getValue().getPath().getPathString();
66 Preconditions.checkState(!manifest.getKey().isAbsolute());
67 PathFragment targetDirectory = manifest.getKey();
68
69 parseManifestFile(
70 execRoot.getFileSystem(), mounts, targetDirectory, new File(manifestFilePath), false, "");
71 }
72 }
73
74 /** Mount all files that the spawn needs as specified in its fileset manifests. */
75 void mountFilesFromFilesetManifests(
76 Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext executionContext)
77 throws IOException {
78 final FilesetActionContext filesetContext =
79 executionContext.getExecutor().getContext(FilesetActionContext.class);
80 for (Artifact fileset : spawn.getFilesetManifests()) {
81 File manifestFile =
82 new File(
83 execRoot.getPathString(),
84 AnalysisUtils.getManifestPathFromFilesetPath(fileset.getExecPath()).getPathString());
85 PathFragment targetDirectory = fileset.getExecPath();
86
87 parseManifestFile(
88 execRoot.getFileSystem(),
89 mounts,
90 targetDirectory,
91 manifestFile,
92 true,
93 filesetContext.getWorkspaceName());
94 }
95 }
96
97 /** A parser for the MANIFEST files used by Filesets and runfiles. */
98 static void parseManifestFile(
99 FileSystem fs,
100 Map<PathFragment, Path> mounts,
101 PathFragment targetDirectory,
102 File manifestFile,
103 boolean isFilesetManifest,
104 String workspaceName)
105 throws IOException {
106 int lineNum = 0;
107 for (String line : Files.readLines(manifestFile, StandardCharsets.UTF_8)) {
108 if (isFilesetManifest && (++lineNum % 2 == 0)) {
109 continue;
110 }
111 if (line.isEmpty()) {
112 continue;
113 }
114
115 String[] fields = line.trim().split(" ");
116
117 // The "target" field is always a relative path that is to be interpreted in this way:
118 // (1) If this is a fileset manifest and our workspace name is not empty, the first segment
119 // of each "target" path must be the workspace name, which is then stripped before further
120 // processing.
121 // (2) The "target" path is then appended to the "targetDirectory", which is a path relative
122 // to the execRoot. Together, this results in the full path in the execRoot in which place a
123 // symlink referring to "source" has to be created (see below).
124 PathFragment targetPath;
125 if (isFilesetManifest) {
126 PathFragment targetPathFragment = new PathFragment(fields[0]);
127 if (!workspaceName.isEmpty()) {
128 Preconditions.checkState(
129 targetPathFragment.getSegment(0).equals(workspaceName),
130 "Fileset manifest line must start with workspace name");
131 targetPathFragment = targetPathFragment.subFragment(1, targetPathFragment.segmentCount());
132 }
133 targetPath = targetDirectory.getRelative(targetPathFragment);
134 } else {
135 targetPath = targetDirectory.getRelative(fields[0]);
136 }
137
138 // The "source" field, if it exists, is always an absolute path and may point to any file in
139 // the filesystem (it is not limited to files in the workspace or execroot).
140 Path source;
141 switch (fields.length) {
142 case 1:
143 source = fs.getPath("/dev/null");
144 break;
145 case 2:
146 source = fs.getPath(fields[1]);
147 break;
148 default:
149 throw new IllegalStateException("'" + line + "' splits into more than 2 parts");
150 }
151
152 mounts.put(targetPath, source);
153 }
154 }
155
156 /** Mount all runfiles that the spawn needs as specified via its runfiles suppliers. */
157 void mountRunfilesFromSuppliers(Map<PathFragment, Path> mounts, Spawn spawn) throws IOException {
158 Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
159 spawn.getRunfilesSupplier().getMappings();
160 for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
161 rootsAndMappings.entrySet()) {
162 PathFragment root = rootAndMappings.getKey();
163 if (root.isAbsolute()) {
164 root = root.relativeTo(execRoot.asFragment());
165 }
166 for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
167 Artifact sourceArtifact = mapping.getValue();
168 PathFragment source =
169 (sourceArtifact != null) ? sourceArtifact.getExecPath() : new PathFragment("/dev/null");
170
171 Preconditions.checkArgument(!mapping.getKey().isAbsolute());
172 PathFragment target = root.getRelative(mapping.getKey());
173 mounts.put(target, execRoot.getRelative(source));
174 }
175 }
176 }
177
178 /** Mount all inputs of the spawn. */
179 void mountInputs(
180 Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext actionExecutionContext) {
181 List<ActionInput> inputs =
182 ActionInputHelper.expandArtifacts(
183 spawn.getInputFiles(), actionExecutionContext.getArtifactExpander());
184
185 if (spawn.getResourceOwner() instanceof CppCompileAction) {
186 CppCompileAction action = (CppCompileAction) spawn.getResourceOwner();
187 if (action.shouldScanIncludes()) {
Lukacs Berki840acb12017-01-25 14:01:33 +0000188 Iterables.addAll(inputs, action.getAdditionalInputs());
Philipp Wollermann809df532016-09-08 12:53:39 +0000189 }
190 }
191
Adam Michael3f008a02016-12-05 19:44:53 +0000192 // ActionInputHelper#expandArtifacts above expands empty TreeArtifacts into an empty list.
193 // However, actions that accept TreeArtifacts as inputs generally expect that the empty
194 // directory is created. So here we explicitly mount the directories of the TreeArtifacts as
195 // inputs.
196 for (ActionInput input : spawn.getInputFiles()) {
197 if (input instanceof Artifact && ((Artifact) input).isTreeArtifact()) {
Adam Michaelb5f480c2016-12-09 22:19:32 +0000198 List<Artifact> containedArtifacts = new ArrayList<>();
199 actionExecutionContext.getArtifactExpander().expand((Artifact) input, containedArtifacts);
200 // Attempting to mount a non-empty directory results in ERR_DIRECTORY_NOT_EMPTY, so we only
201 // mount empty TreeArtifacts as directories.
202 if (containedArtifacts.isEmpty()) {
203 PathFragment mount = new PathFragment(input.getExecPathString());
204 mounts.put(mount, execRoot.getRelative(mount));
205 }
Adam Michael3f008a02016-12-05 19:44:53 +0000206 }
207 }
208
Philipp Wollermann809df532016-09-08 12:53:39 +0000209 for (ActionInput input : inputs) {
210 if (input.getExecPathString().contains("internal/_middlemen/")) {
211 continue;
212 }
213 PathFragment mount = new PathFragment(input.getExecPathString());
214 mounts.put(mount, execRoot.getRelative(mount));
215 }
216 }
217}