Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 1 | // 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. |
| 14 | package com.google.devtools.build.lib.exec; |
| 15 | |
ajurkowski | b7590a0 | 2020-09-11 11:42:56 -0700 | [diff] [blame] | 16 | |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 17 | import com.google.common.annotations.VisibleForTesting; |
| 18 | import com.google.common.base.Preconditions; |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 19 | import com.google.common.collect.ImmutableList; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 20 | import com.google.devtools.build.lib.actions.ActionInput; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 21 | import com.google.devtools.build.lib.actions.ActionInputHelper; |
| 22 | import com.google.devtools.build.lib.actions.Artifact; |
| 23 | import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
ajurkowski | b7590a0 | 2020-09-11 11:42:56 -0700 | [diff] [blame] | 24 | import com.google.devtools.build.lib.actions.Artifact.MissingExpansionException; |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; |
| 26 | import com.google.devtools.build.lib.actions.FileArtifactValue; |
felly | f6408d6 | 2019-07-16 12:51:14 -0700 | [diff] [blame] | 27 | import com.google.devtools.build.lib.actions.FilesetManifest; |
| 28 | import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior; |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 29 | import com.google.devtools.build.lib.actions.FilesetOutputSymlink; |
shahan | 499503b | 2018-06-07 18:57:07 -0700 | [diff] [blame] | 30 | import com.google.devtools.build.lib.actions.MetadataProvider; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 31 | import com.google.devtools.build.lib.actions.RunfilesSupplier; |
| 32 | import com.google.devtools.build.lib.actions.Spawn; |
aiuto | 0f626a4 | 2021-01-08 10:51:30 -0800 | [diff] [blame^] | 33 | import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput; |
ulfjack | a928a5f | 2020-01-15 02:55:43 -0800 | [diff] [blame] | 34 | import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| 35 | import com.google.devtools.build.lib.collect.nestedset.Order; |
tomlu | 1a19b62 | 2018-01-11 15:17:28 -0800 | [diff] [blame] | 36 | import com.google.devtools.build.lib.vfs.Path; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 37 | import com.google.devtools.build.lib.vfs.PathFragment; |
| 38 | import java.io.IOException; |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 39 | import java.util.HashMap; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 40 | import java.util.List; |
| 41 | import java.util.Map; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 42 | import java.util.SortedMap; |
| 43 | import 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 | */ |
| 50 | public class SpawnInputExpander { |
aiuto | 0f626a4 | 2021-01-08 10:51:30 -0800 | [diff] [blame^] | 51 | public static final ActionInput EMPTY_FILE = new EmptyActionInput("/dev/null"); |
| 52 | |
tomlu | 1a19b62 | 2018-01-11 15:17:28 -0800 | [diff] [blame] | 53 | private final Path execRoot; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 54 | private final boolean strict; |
ulfjack | 1973be4 | 2018-05-30 03:25:35 -0700 | [diff] [blame] | 55 | private final RelativeSymlinkBehavior relSymlinkBehavior; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 56 | |
| 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 |
tomlu | 1a19b62 | 2018-01-11 15:17:28 -0800 | [diff] [blame] | 60 | * and adds a mapping for them. At this time, directories in filesets are always silently added as |
| 61 | * mappings. |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 62 | * |
| 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 | */ |
tomlu | 1a19b62 | 2018-01-11 15:17:28 -0800 | [diff] [blame] | 72 | public SpawnInputExpander(Path execRoot, boolean strict) { |
ulfjack | 1973be4 | 2018-05-30 03:25:35 -0700 | [diff] [blame] | 73 | 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) { |
tomlu | 1a19b62 | 2018-01-11 15:17:28 -0800 | [diff] [blame] | 93 | this.execRoot = execRoot; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 94 | this.strict = strict; |
ulfjack | 1973be4 | 2018-05-30 03:25:35 -0700 | [diff] [blame] | 95 | this.relSymlinkBehavior = relSymlinkBehavior; |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | private void addMapping( |
| 99 | Map<PathFragment, ActionInput> inputMappings, |
| 100 | PathFragment targetLocation, |
| 101 | ActionInput input) { |
| 102 | Preconditions.checkArgument(!targetLocation.isAbsolute(), targetLocation); |
Benjamin Peterson | ce49490 | 2019-06-17 06:18:25 -0700 | [diff] [blame] | 103 | inputMappings.put(targetLocation, input); |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | /** Adds runfiles inputs from runfilesSupplier to inputMappings. */ |
| 107 | @VisibleForTesting |
| 108 | void addRunfilesToInputs( |
| 109 | Map<PathFragment, ActionInput> inputMap, |
| 110 | RunfilesSupplier runfilesSupplier, |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 111 | MetadataProvider actionFileCache, |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 112 | ArtifactExpander artifactExpander) |
shahan | 499503b | 2018-06-07 18:57:07 -0700 | [diff] [blame] | 113 | throws IOException { |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 114 | Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings = |
lberki | b3d66445 | 2020-05-27 01:50:23 -0700 | [diff] [blame] | 115 | runfilesSupplier.getMappings(); |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 116 | |
jcater | 3674591 | 2018-05-01 13:20:00 -0700 | [diff] [blame] | 117 | for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings : |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 118 | rootsAndMappings.entrySet()) { |
| 119 | PathFragment root = rootAndMappings.getKey(); |
| 120 | Preconditions.checkState(!root.isAbsolute(), root); |
jcater | 3674591 | 2018-05-01 13:20:00 -0700 | [diff] [blame] | 121 | for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) { |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 122 | PathFragment location = root.getRelative(mapping.getKey()); |
| 123 | Artifact localArtifact = mapping.getValue(); |
| 124 | if (localArtifact != null) { |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 125 | Preconditions.checkState(!localArtifact.isMiddlemanArtifact()); |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 126 | if (localArtifact.isTreeArtifact()) { |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 127 | List<ActionInput> expandedInputs = |
| 128 | ActionInputHelper.expandArtifacts( |
ulfjack | a928a5f | 2020-01-15 02:55:43 -0800 | [diff] [blame] | 129 | NestedSetBuilder.create(Order.STABLE_ORDER, localArtifact), artifactExpander); |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 130 | for (ActionInput input : expandedInputs) { |
| 131 | addMapping( |
| 132 | inputMap, |
| 133 | location.getRelative(((TreeFileArtifact) input).getParentRelativePath()), |
| 134 | input); |
| 135 | } |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 136 | } else if (localArtifact.isFileset()) { |
ajurkowski | b7590a0 | 2020-09-11 11:42:56 -0700 | [diff] [blame] | 137 | 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); |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 144 | } else { |
| 145 | if (strict) { |
| 146 | failIfDirectory(actionFileCache, localArtifact); |
| 147 | } |
| 148 | addMapping(inputMap, location, localArtifact); |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 149 | } |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 150 | } else { |
aiuto | 0f626a4 | 2021-01-08 10:51:30 -0800 | [diff] [blame^] | 151 | addMapping(inputMap, location, EMPTY_FILE); |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 152 | } |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 157 | /** Adds runfiles inputs from runfilesSupplier to inputMappings. */ |
| 158 | public Map<PathFragment, ActionInput> addRunfilesToInputs( |
| 159 | RunfilesSupplier runfilesSupplier, |
| 160 | MetadataProvider actionFileCache, |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 161 | ArtifactExpander artifactExpander) |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 162 | throws IOException { |
| 163 | Map<PathFragment, ActionInput> inputMap = new HashMap<>(); |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 164 | addRunfilesToInputs(inputMap, runfilesSupplier, actionFileCache, artifactExpander); |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 165 | return inputMap; |
| 166 | } |
| 167 | |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 168 | 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 | |
ulfjack | 1973be4 | 2018-05-30 03:25:35 -0700 | [diff] [blame] | 176 | @VisibleForTesting |
| 177 | void addFilesetManifests( |
tomlu | 73eccc2 | 2018-09-06 08:02:37 -0700 | [diff] [blame] | 178 | Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings, |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 179 | Map<PathFragment, ActionInput> inputMappings) |
| 180 | throws IOException { |
tomlu | 73eccc2 | 2018-09-06 08:02:37 -0700 | [diff] [blame] | 181 | for (Artifact fileset : filesetMappings.keySet()) { |
felly | 99ad9b9 | 2019-06-12 20:33:57 -0700 | [diff] [blame] | 182 | 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); |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 196 | |
| 197 | for (Map.Entry<PathFragment, String> mapping : filesetManifest.getEntries().entrySet()) { |
| 198 | String value = mapping.getValue(); |
aiuto | 0f626a4 | 2021-01-08 10:51:30 -0800 | [diff] [blame^] | 199 | ActionInput artifact = |
| 200 | value == null |
| 201 | ? EMPTY_FILE |
| 202 | : ActionInputHelper.fromPath(execRoot.getRelative(value).getPathString()); |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 203 | addMapping(inputMappings, mapping.getKey(), artifact); |
| 204 | } |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 205 | } |
| 206 | |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 207 | 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 | /** |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 217 | * Convert the inputs and runfiles of the given spawn to a map from exec-root relative paths to |
philwo | 9ed9d8a | 2018-09-03 07:30:26 -0700 | [diff] [blame] | 218 | * {@link ActionInput}s. The returned map does not contain tree artifacts as they are expanded to |
| 219 | * file artifacts. |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 220 | * |
| 221 | * <p>The returned map never contains {@code null} values; it uses {@link #EMPTY_FILE} for empty |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 222 | * files, which is an instance of {@link |
| 223 | * com.google.devtools.build.lib.actions.cache.VirtualActionInput}. |
buchgr | d4d3d50 | 2018-08-02 06:47:19 -0700 | [diff] [blame] | 224 | * |
| 225 | * <p>The returned map contains all runfiles, but not the {@code MANIFEST}. |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 226 | */ |
| 227 | public SortedMap<PathFragment, ActionInput> getInputMapping( |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 228 | Spawn spawn, ArtifactExpander artifactExpander, MetadataProvider actionInputFileCache) |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 229 | throws IOException { |
Jakob Buchgraber | 4b3c2eb | 2019-04-04 02:01:48 -0700 | [diff] [blame] | 230 | |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 231 | TreeMap<PathFragment, ActionInput> inputMap = new TreeMap<>(); |
| 232 | addInputs(inputMap, spawn, artifactExpander); |
| 233 | addRunfilesToInputs( |
twerth | c4dfb91 | 2020-07-20 10:32:42 -0700 | [diff] [blame] | 234 | inputMap, spawn.getRunfilesSupplier(), actionInputFileCache, artifactExpander); |
kush | 2ce45a2 | 2018-05-02 14:15:37 -0700 | [diff] [blame] | 235 | addFilesetManifests(spawn.getFilesetMappings(), inputMap); |
Ulf Adams | c0a8444 | 2017-03-21 10:08:03 +0000 | [diff] [blame] | 236 | return inputMap; |
| 237 | } |
| 238 | } |