blob: 08dcda04b67bbeb03e6af649afa06697c5adc4c1 [file] [log] [blame]
Ulf Adamsc0a84442017-03-21 10:08:03 +00001// Copyright 2017 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.
14package com.google.devtools.build.lib.exec;
15
ajurkowskib7590a02020-09-11 11:42:56 -070016
Ulf Adamsc0a84442017-03-21 10:08:03 +000017import com.google.common.annotations.VisibleForTesting;
18import com.google.common.base.Preconditions;
kush2ce45a22018-05-02 14:15:37 -070019import com.google.common.collect.ImmutableList;
Ulf Adamsc0a84442017-03-21 10:08:03 +000020import com.google.devtools.build.lib.actions.ActionInput;
Ulf Adamsc0a84442017-03-21 10:08:03 +000021import com.google.devtools.build.lib.actions.ActionInputHelper;
22import com.google.devtools.build.lib.actions.Artifact;
23import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
ajurkowskib7590a02020-09-11 11:42:56 -070024import com.google.devtools.build.lib.actions.Artifact.MissingExpansionException;
buchgrd4d3d502018-08-02 06:47:19 -070025import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
26import com.google.devtools.build.lib.actions.FileArtifactValue;
fellyf6408d62019-07-16 12:51:14 -070027import com.google.devtools.build.lib.actions.FilesetManifest;
28import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior;
kush2ce45a22018-05-02 14:15:37 -070029import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
shahan499503b2018-06-07 18:57:07 -070030import com.google.devtools.build.lib.actions.MetadataProvider;
Ulf Adamsc0a84442017-03-21 10:08:03 +000031import com.google.devtools.build.lib.actions.RunfilesSupplier;
32import com.google.devtools.build.lib.actions.Spawn;
aiuto0f626a42021-01-08 10:51:30 -080033import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput;
ulfjacka928a5f2020-01-15 02:55:43 -080034import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
35import com.google.devtools.build.lib.collect.nestedset.Order;
tomlu1a19b622018-01-11 15:17:28 -080036import com.google.devtools.build.lib.vfs.Path;
Ulf Adamsc0a84442017-03-21 10:08:03 +000037import com.google.devtools.build.lib.vfs.PathFragment;
38import java.io.IOException;
felly99ad9b92019-06-12 20:33:57 -070039import java.util.HashMap;
Ulf Adamsc0a84442017-03-21 10:08:03 +000040import java.util.List;
41import java.util.Map;
Ulf Adamsc0a84442017-03-21 10:08:03 +000042import java.util.SortedMap;
43import java.util.TreeMap;
44
45/**
46 * A helper class for spawn strategies to turn runfiles suppliers into input mappings. This class
47 * performs no I/O operations, but only rearranges the files according to how the runfiles should be
48 * laid out.
49 */
50public class SpawnInputExpander {
aiuto0f626a42021-01-08 10:51:30 -080051 public static final ActionInput EMPTY_FILE = new EmptyActionInput("/dev/null");
52
tomlu1a19b622018-01-11 15:17:28 -080053 private final Path execRoot;
Ulf Adamsc0a84442017-03-21 10:08:03 +000054 private final boolean strict;
ulfjack1973be42018-05-30 03:25:35 -070055 private final RelativeSymlinkBehavior relSymlinkBehavior;
Ulf Adamsc0a84442017-03-21 10:08:03 +000056
57 /**
58 * Creates a new instance. If strict is true, then the expander checks for directories in runfiles
59 * and throws an exception if it finds any. Otherwise it silently ignores directories in runfiles
tomlu1a19b622018-01-11 15:17:28 -080060 * and adds a mapping for them. At this time, directories in filesets are always silently added as
61 * mappings.
Ulf Adamsc0a84442017-03-21 10:08:03 +000062 *
63 * <p>Directories in inputs are a correctness issue: Bazel only tracks dependencies at the action
64 * level, and it does not track dependencies on directories. Making a directory available to a
65 * spawn even though it's contents are not tracked as dependencies leads to incorrect incremental
66 * builds, since changes to the contents do not trigger action invalidation.
67 *
68 * <p>As such, all spawn strategies should always be strict and not make directories available to
69 * the subprocess. However, that's a breaking change, and therefore we make it depend on this flag
70 * for now.
71 */
tomlu1a19b622018-01-11 15:17:28 -080072 public SpawnInputExpander(Path execRoot, boolean strict) {
ulfjack1973be42018-05-30 03:25:35 -070073 this(execRoot, strict, RelativeSymlinkBehavior.ERROR);
74 }
75
76 /**
77 * Creates a new instance. If strict is true, then the expander checks for directories in runfiles
78 * and throws an exception if it finds any. Otherwise it silently ignores directories in runfiles
79 * and adds a mapping for them. At this time, directories in filesets are always silently added as
80 * mappings.
81 *
82 * <p>Directories in inputs are a correctness issue: Bazel only tracks dependencies at the action
83 * level, and it does not track dependencies on directories. Making a directory available to a
84 * spawn even though it's contents are not tracked as dependencies leads to incorrect incremental
85 * builds, since changes to the contents do not trigger action invalidation.
86 *
87 * <p>As such, all spawn strategies should always be strict and not make directories available to
88 * the subprocess. However, that's a breaking change, and therefore we make it depend on this flag
89 * for now.
90 */
91 public SpawnInputExpander(
92 Path execRoot, boolean strict, RelativeSymlinkBehavior relSymlinkBehavior) {
tomlu1a19b622018-01-11 15:17:28 -080093 this.execRoot = execRoot;
Ulf Adamsc0a84442017-03-21 10:08:03 +000094 this.strict = strict;
ulfjack1973be42018-05-30 03:25:35 -070095 this.relSymlinkBehavior = relSymlinkBehavior;
Ulf Adamsc0a84442017-03-21 10:08:03 +000096 }
97
98 private void addMapping(
99 Map<PathFragment, ActionInput> inputMappings,
100 PathFragment targetLocation,
101 ActionInput input) {
102 Preconditions.checkArgument(!targetLocation.isAbsolute(), targetLocation);
Benjamin Petersonce494902019-06-17 06:18:25 -0700103 inputMappings.put(targetLocation, input);
Ulf Adamsc0a84442017-03-21 10:08:03 +0000104 }
105
106 /** Adds runfiles inputs from runfilesSupplier to inputMappings. */
107 @VisibleForTesting
108 void addRunfilesToInputs(
109 Map<PathFragment, ActionInput> inputMap,
110 RunfilesSupplier runfilesSupplier,
buchgrd4d3d502018-08-02 06:47:19 -0700111 MetadataProvider actionFileCache,
twerthc4dfb912020-07-20 10:32:42 -0700112 ArtifactExpander artifactExpander)
shahan499503b2018-06-07 18:57:07 -0700113 throws IOException {
buchgrd4d3d502018-08-02 06:47:19 -0700114 Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
lberkib3d664452020-05-27 01:50:23 -0700115 runfilesSupplier.getMappings();
Ulf Adamsc0a84442017-03-21 10:08:03 +0000116
jcater36745912018-05-01 13:20:00 -0700117 for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
Ulf Adamsc0a84442017-03-21 10:08:03 +0000118 rootsAndMappings.entrySet()) {
119 PathFragment root = rootAndMappings.getKey();
120 Preconditions.checkState(!root.isAbsolute(), root);
jcater36745912018-05-01 13:20:00 -0700121 for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
Ulf Adamsc0a84442017-03-21 10:08:03 +0000122 PathFragment location = root.getRelative(mapping.getKey());
123 Artifact localArtifact = mapping.getValue();
124 if (localArtifact != null) {
buchgrd4d3d502018-08-02 06:47:19 -0700125 Preconditions.checkState(!localArtifact.isMiddlemanArtifact());
twerthc4dfb912020-07-20 10:32:42 -0700126 if (localArtifact.isTreeArtifact()) {
buchgrd4d3d502018-08-02 06:47:19 -0700127 List<ActionInput> expandedInputs =
128 ActionInputHelper.expandArtifacts(
ulfjacka928a5f2020-01-15 02:55:43 -0800129 NestedSetBuilder.create(Order.STABLE_ORDER, localArtifact), artifactExpander);
buchgrd4d3d502018-08-02 06:47:19 -0700130 for (ActionInput input : expandedInputs) {
131 addMapping(
132 inputMap,
133 location.getRelative(((TreeFileArtifact) input).getParentRelativePath()),
134 input);
135 }
felly99ad9b92019-06-12 20:33:57 -0700136 } else if (localArtifact.isFileset()) {
ajurkowskib7590a02020-09-11 11:42:56 -0700137 ImmutableList<FilesetOutputSymlink> filesetLinks;
138 try {
139 filesetLinks = artifactExpander.getFileset(localArtifact);
140 } catch (MissingExpansionException e) {
141 throw new IllegalStateException(e);
142 }
143 addFilesetManifest(location, localArtifact, filesetLinks, inputMap);
buchgrd4d3d502018-08-02 06:47:19 -0700144 } else {
145 if (strict) {
146 failIfDirectory(actionFileCache, localArtifact);
147 }
148 addMapping(inputMap, location, localArtifact);
Ulf Adamsc0a84442017-03-21 10:08:03 +0000149 }
Ulf Adamsc0a84442017-03-21 10:08:03 +0000150 } else {
aiuto0f626a42021-01-08 10:51:30 -0800151 addMapping(inputMap, location, EMPTY_FILE);
Ulf Adamsc0a84442017-03-21 10:08:03 +0000152 }
153 }
154 }
155 }
156
felly99ad9b92019-06-12 20:33:57 -0700157 /** Adds runfiles inputs from runfilesSupplier to inputMappings. */
158 public Map<PathFragment, ActionInput> addRunfilesToInputs(
159 RunfilesSupplier runfilesSupplier,
160 MetadataProvider actionFileCache,
twerthc4dfb912020-07-20 10:32:42 -0700161 ArtifactExpander artifactExpander)
felly99ad9b92019-06-12 20:33:57 -0700162 throws IOException {
163 Map<PathFragment, ActionInput> inputMap = new HashMap<>();
twerthc4dfb912020-07-20 10:32:42 -0700164 addRunfilesToInputs(inputMap, runfilesSupplier, actionFileCache, artifactExpander);
felly99ad9b92019-06-12 20:33:57 -0700165 return inputMap;
166 }
167
buchgrd4d3d502018-08-02 06:47:19 -0700168 private static void failIfDirectory(MetadataProvider actionFileCache, ActionInput input)
169 throws IOException {
170 FileArtifactValue metadata = actionFileCache.getMetadata(input);
171 if (metadata != null && !metadata.getType().isFile()) {
172 throw new IOException("Not a file: " + input.getExecPathString());
173 }
174 }
175
ulfjack1973be42018-05-30 03:25:35 -0700176 @VisibleForTesting
177 void addFilesetManifests(
tomlu73eccc22018-09-06 08:02:37 -0700178 Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings,
kush2ce45a22018-05-02 14:15:37 -0700179 Map<PathFragment, ActionInput> inputMappings)
180 throws IOException {
tomlu73eccc22018-09-06 08:02:37 -0700181 for (Artifact fileset : filesetMappings.keySet()) {
felly99ad9b92019-06-12 20:33:57 -0700182 addFilesetManifest(
183 fileset.getExecPath(), fileset, filesetMappings.get(fileset), inputMappings);
184 }
185 }
186
187 void addFilesetManifest(
188 PathFragment location,
189 Artifact filesetArtifact,
190 ImmutableList<FilesetOutputSymlink> filesetLinks,
191 Map<PathFragment, ActionInput> inputMappings)
192 throws IOException {
193 Preconditions.checkState(filesetArtifact.isFileset(), filesetArtifact);
194 FilesetManifest filesetManifest =
195 FilesetManifest.constructFilesetManifest(filesetLinks, location, relSymlinkBehavior);
kush2ce45a22018-05-02 14:15:37 -0700196
197 for (Map.Entry<PathFragment, String> mapping : filesetManifest.getEntries().entrySet()) {
198 String value = mapping.getValue();
aiuto0f626a42021-01-08 10:51:30 -0800199 ActionInput artifact =
200 value == null
201 ? EMPTY_FILE
202 : ActionInputHelper.fromPath(execRoot.getRelative(value).getPathString());
kush2ce45a22018-05-02 14:15:37 -0700203 addMapping(inputMappings, mapping.getKey(), artifact);
204 }
kush2ce45a22018-05-02 14:15:37 -0700205 }
206
Ulf Adamsc0a84442017-03-21 10:08:03 +0000207 private void addInputs(
208 Map<PathFragment, ActionInput> inputMap, Spawn spawn, ArtifactExpander artifactExpander) {
209 List<ActionInput> inputs =
210 ActionInputHelper.expandArtifacts(spawn.getInputFiles(), artifactExpander);
211 for (ActionInput input : inputs) {
212 addMapping(inputMap, input.getExecPath(), input);
213 }
214 }
215
216 /**
buchgrd4d3d502018-08-02 06:47:19 -0700217 * Convert the inputs and runfiles of the given spawn to a map from exec-root relative paths to
philwo9ed9d8a2018-09-03 07:30:26 -0700218 * {@link ActionInput}s. The returned map does not contain tree artifacts as they are expanded to
219 * file artifacts.
buchgrd4d3d502018-08-02 06:47:19 -0700220 *
221 * <p>The returned map never contains {@code null} values; it uses {@link #EMPTY_FILE} for empty
kush2ce45a22018-05-02 14:15:37 -0700222 * files, which is an instance of {@link
223 * com.google.devtools.build.lib.actions.cache.VirtualActionInput}.
buchgrd4d3d502018-08-02 06:47:19 -0700224 *
225 * <p>The returned map contains all runfiles, but not the {@code MANIFEST}.
Ulf Adamsc0a84442017-03-21 10:08:03 +0000226 */
227 public SortedMap<PathFragment, ActionInput> getInputMapping(
twerthc4dfb912020-07-20 10:32:42 -0700228 Spawn spawn, ArtifactExpander artifactExpander, MetadataProvider actionInputFileCache)
kush2ce45a22018-05-02 14:15:37 -0700229 throws IOException {
Jakob Buchgraber4b3c2eb2019-04-04 02:01:48 -0700230
Ulf Adamsc0a84442017-03-21 10:08:03 +0000231 TreeMap<PathFragment, ActionInput> inputMap = new TreeMap<>();
232 addInputs(inputMap, spawn, artifactExpander);
233 addRunfilesToInputs(
twerthc4dfb912020-07-20 10:32:42 -0700234 inputMap, spawn.getRunfilesSupplier(), actionInputFileCache, artifactExpander);
kush2ce45a22018-05-02 14:15:37 -0700235 addFilesetManifests(spawn.getFilesetMappings(), inputMap);
Ulf Adamsc0a84442017-03-21 10:08:03 +0000236 return inputMap;
237 }
238}