Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 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.actions; |
| 15 | |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 16 | import com.google.auto.value.AutoValue; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 17 | import com.google.common.base.Predicate; |
Janak Ramakrishnan | 90f3d34 | 2015-03-27 19:45:18 +0000 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableList; |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 19 | import com.google.common.collect.ImmutableMap; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 20 | import com.google.common.collect.Iterables; |
Rumou Duan | 33bab46 | 2016-04-25 17:55:12 +0000 | [diff] [blame] | 21 | import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 22 | import com.google.devtools.build.lib.actions.cache.ActionCache; |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 23 | import com.google.devtools.build.lib.actions.cache.ActionCache.Entry; |
Shreya Bhattarai | 141b6c2 | 2016-08-22 22:00:24 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.actions.cache.DigestUtils; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.cache.Metadata; |
| 26 | import com.google.devtools.build.lib.actions.cache.MetadataHandler; |
| 27 | import com.google.devtools.build.lib.events.Event; |
| 28 | import com.google.devtools.build.lib.events.EventHandler; |
| 29 | import com.google.devtools.build.lib.events.EventKind; |
Mark Schaller | 6df8179 | 2015-12-10 18:47:47 +0000 | [diff] [blame] | 30 | import com.google.devtools.build.lib.util.Preconditions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 31 | import com.google.devtools.build.lib.vfs.PathFragment; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 32 | import java.io.IOException; |
| 33 | import java.util.ArrayList; |
| 34 | import java.util.HashMap; |
| 35 | import java.util.List; |
| 36 | import java.util.Map; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | import javax.annotation.Nullable; |
| 38 | |
| 39 | /** |
| 40 | * Checks whether an {@link Action} needs to be executed, or whether it has not changed since it was |
| 41 | * last stored in the action cache. Must be informed of the new Action data after execution as well. |
| 42 | * |
| 43 | * <p>The fingerprint, input files names, and metadata (either mtimes or MD5sums) of each action are |
| 44 | * cached in the action cache to avoid unnecessary rebuilds. Middleman artifacts are handled |
| 45 | * specially, avoiding the need to create actual files corresponding to the middleman artifacts. |
| 46 | * Instead of that, results of MiddlemanAction dependency checks are cached internally and then |
| 47 | * reused whenever an input middleman artifact is encountered. |
| 48 | * |
| 49 | * <p>While instances of this class hold references to action and metadata cache instances, they are |
| 50 | * otherwise lightweight, and should be constructed anew and discarded for each build request. |
| 51 | */ |
| 52 | public class ActionCacheChecker { |
| 53 | private final ActionCache actionCache; |
| 54 | private final Predicate<? super Action> executionFilter; |
| 55 | private final ArtifactResolver artifactResolver; |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 56 | private final CacheConfig cacheConfig; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 57 | |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 58 | /** Cache config parameters for ActionCacheChecker. */ |
| 59 | @AutoValue |
| 60 | public abstract static class CacheConfig { |
| 61 | abstract boolean enabled(); |
| 62 | // True iff --verbose_explanations flag is set. |
| 63 | abstract boolean verboseExplanations(); |
| 64 | |
| 65 | public static Builder builder() { |
| 66 | return new AutoValue_ActionCacheChecker_CacheConfig.Builder(); |
| 67 | } |
| 68 | |
| 69 | /** Builder for ActionCacheChecker.CacheConfig. */ |
| 70 | @AutoValue.Builder |
| 71 | public abstract static class Builder { |
| 72 | public abstract Builder setVerboseExplanations(boolean value); |
| 73 | |
| 74 | public abstract Builder setEnabled(boolean value); |
| 75 | |
| 76 | public abstract CacheConfig build(); |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | public ActionCacheChecker( |
| 81 | ActionCache actionCache, |
| 82 | ArtifactResolver artifactResolver, |
| 83 | Predicate<? super Action> executionFilter, |
| 84 | @Nullable CacheConfig cacheConfig) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 85 | this.actionCache = actionCache; |
| 86 | this.executionFilter = executionFilter; |
| 87 | this.artifactResolver = artifactResolver; |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 88 | this.cacheConfig = |
| 89 | cacheConfig != null |
| 90 | ? cacheConfig |
| 91 | : CacheConfig.builder().setEnabled(true).setVerboseExplanations(false).build(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 92 | } |
| 93 | |
| 94 | public boolean isActionExecutionProhibited(Action action) { |
| 95 | return !executionFilter.apply(action); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Checks whether one of existing output paths is already used as a key. |
| 100 | * If yes, returns it - otherwise uses first output file as a key |
| 101 | */ |
| 102 | private ActionCache.Entry getCacheEntry(Action action) { |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 103 | if (!cacheConfig.enabled()) { |
| 104 | return null; // ignore existing cache when disabled. |
| 105 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 106 | for (Artifact output : action.getOutputs()) { |
| 107 | ActionCache.Entry entry = actionCache.get(output.getExecPathString()); |
| 108 | if (entry != null) { |
| 109 | return entry; |
| 110 | } |
| 111 | } |
| 112 | return null; |
| 113 | } |
| 114 | |
Janak Ramakrishnan | e3f04b8 | 2015-03-27 16:45:34 +0000 | [diff] [blame] | 115 | private void removeCacheEntry(Action action) { |
| 116 | for (Artifact output : action.getOutputs()) { |
| 117 | actionCache.remove(output.getExecPathString()); |
| 118 | } |
| 119 | } |
| 120 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 121 | /** |
| 122 | * Validate metadata state for action input or output artifacts. |
| 123 | * |
| 124 | * @param entry cached action information. |
| 125 | * @param action action to be validated. |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 126 | * @param actionInputs the inputs of the action. Normally just the result of action.getInputs(), |
| 127 | * but if this action doesn't yet know its inputs, we check the inputs from the cache. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 128 | * @param metadataHandler provider of metadata for the artifacts this action interacts with. |
| 129 | * @param checkOutput true to validate output artifacts, Otherwise, just |
| 130 | * validate inputs. |
| 131 | * |
| 132 | * @return true if at least one artifact has changed, false - otherwise. |
| 133 | */ |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 134 | private boolean validateArtifacts(Entry entry, Action action, |
| 135 | Iterable<Artifact> actionInputs, MetadataHandler metadataHandler, boolean checkOutput) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 136 | Iterable<Artifact> artifacts = checkOutput |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 137 | ? Iterables.concat(action.getOutputs(), actionInputs) |
| 138 | : actionInputs; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 139 | Map<String, Metadata> mdMap = new HashMap<>(); |
| 140 | for (Artifact artifact : artifacts) { |
| 141 | mdMap.put(artifact.getExecPathString(), metadataHandler.getMetadataMaybe(artifact)); |
| 142 | } |
Shreya Bhattarai | 141b6c2 | 2016-08-22 22:00:24 +0000 | [diff] [blame] | 143 | return !DigestUtils.fromMetadata(mdMap).equals(entry.getFileDigest()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | private void reportCommand(EventHandler handler, Action action) { |
| 147 | if (handler != null) { |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 148 | if (cacheConfig.verboseExplanations()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 149 | String keyDescription = action.describeKey(); |
Laszlo Csomor | f04efcc | 2015-02-12 17:08:06 +0000 | [diff] [blame] | 150 | reportRebuild(handler, action, keyDescription == null |
| 151 | ? "action command has changed" |
| 152 | : "action command has changed.\nNew action: " + keyDescription); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 153 | } else { |
| 154 | reportRebuild(handler, action, |
| 155 | "action command has changed (try --verbose_explanations for more info)"); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 160 | private void reportClientEnv(EventHandler handler, Action action, Map<String, String> used) { |
| 161 | if (handler != null) { |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 162 | if (cacheConfig.verboseExplanations()) { |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 163 | StringBuilder message = new StringBuilder(); |
| 164 | message.append("Effective client environment has changed. Now using\n"); |
| 165 | for (Map.Entry<String, String> entry : used.entrySet()) { |
| 166 | message.append(" ").append(entry.getKey()).append("=").append(entry.getValue()) |
| 167 | .append("\n"); |
| 168 | } |
| 169 | reportRebuild(handler, action, message.toString()); |
| 170 | } else { |
| 171 | reportRebuild( |
| 172 | handler, |
| 173 | action, |
| 174 | "Effective client environment has changed (try --verbose_explanations for more info)"); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 179 | protected boolean unconditionalExecution(Action action) { |
| 180 | return !isActionExecutionProhibited(action) && action.executeUnconditionally(); |
| 181 | } |
| 182 | |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 183 | private static Map<String, String> computeUsedClientEnv( |
| 184 | Action action, Map<String, String> clientEnv) { |
| 185 | Map<String, String> used = new HashMap<>(); |
| 186 | for (String var : action.getClientEnvironmentVariables()) { |
| 187 | String value = clientEnv.get(var); |
| 188 | if (value != null) { |
| 189 | used.put(var, value); |
| 190 | } |
| 191 | } |
| 192 | return used; |
| 193 | } |
| 194 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 195 | /** |
| 196 | * Checks whether {@code action} needs to be executed and returns a non-null Token if so. |
| 197 | * |
| 198 | * <p>The method checks if any of the action's inputs or outputs have changed. Returns a non-null |
| 199 | * {@link Token} if the action needs to be executed, and null otherwise. |
| 200 | * |
| 201 | * <p>If this method returns non-null, indicating that the action will be executed, the |
Janak Ramakrishnan | 73055be | 2015-04-13 18:32:49 +0000 | [diff] [blame] | 202 | * metadataHandler's {@link MetadataHandler#discardOutputMetadata} method must be called, so that |
| 203 | * it does not serve stale metadata for the action's outputs after the action is executed. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 204 | */ |
| 205 | // Note: the handler should only be used for DEPCHECKER events; there's no |
| 206 | // guarantee it will be available for other events. |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 207 | public Token getTokenIfNeedToExecute( |
| 208 | Action action, |
| 209 | Iterable<Artifact> resolvedCacheArtifacts, |
| 210 | Map<String, String> clientEnv, |
| 211 | EventHandler handler, |
| 212 | MetadataHandler metadataHandler) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 213 | // TODO(bazel-team): (2010) For RunfilesAction/SymlinkAction and similar actions that |
| 214 | // produce only symlinks we should not check whether inputs are valid at all - all that matters |
| 215 | // that inputs and outputs are still exist (and new inputs have not appeared). All other checks |
| 216 | // are unnecessary. In other words, the only metadata we should check for them is file existence |
| 217 | // itself. |
| 218 | |
| 219 | MiddlemanType middlemanType = action.getActionType(); |
| 220 | if (middlemanType.isMiddleman()) { |
| 221 | // Some types of middlemen are not checked because they should not |
| 222 | // propagate invalidation of their inputs. |
| 223 | if (middlemanType != MiddlemanType.ERROR_PROPAGATING_MIDDLEMAN) { |
| 224 | checkMiddlemanAction(action, handler, metadataHandler); |
| 225 | } |
| 226 | return null; |
| 227 | } |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 228 | if (!cacheConfig.enabled()) { |
| 229 | return new Token(getKeyString(action)); |
| 230 | } |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 231 | Iterable<Artifact> actionInputs = action.getInputs(); |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 232 | // Resolve action inputs from cache, if necessary. |
Lukacs Berki | 5ea2b14 | 2017-02-28 10:46:53 +0000 | [diff] [blame] | 233 | boolean inputsDiscovered = action.inputsDiscovered(); |
| 234 | if (!inputsDiscovered && resolvedCacheArtifacts != null) { |
Janak Ramakrishnan | 90f3d34 | 2015-03-27 19:45:18 +0000 | [diff] [blame] | 235 | // The action doesn't know its inputs, but the caller has a good idea of what they are. |
| 236 | Preconditions.checkState(action.discoversInputs(), |
| 237 | "Actions that don't know their inputs must discover them: %s", action); |
| 238 | actionInputs = resolvedCacheArtifacts; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 239 | } |
Janak Ramakrishnan | 90f3d34 | 2015-03-27 19:45:18 +0000 | [diff] [blame] | 240 | ActionCache.Entry entry = getCacheEntry(action); |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 241 | if (mustExecute(action, entry, handler, metadataHandler, actionInputs, clientEnv)) { |
Janak Ramakrishnan | e3f04b8 | 2015-03-27 16:45:34 +0000 | [diff] [blame] | 242 | if (entry != null) { |
| 243 | removeCacheEntry(action); |
| 244 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 245 | return new Token(getKeyString(action)); |
| 246 | } |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 247 | |
Lukacs Berki | 5ea2b14 | 2017-02-28 10:46:53 +0000 | [diff] [blame] | 248 | if (!inputsDiscovered) { |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 249 | action.updateInputs(actionInputs); |
| 250 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 251 | return null; |
| 252 | } |
| 253 | |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 254 | protected boolean mustExecute( |
| 255 | Action action, |
| 256 | @Nullable ActionCache.Entry entry, |
| 257 | EventHandler handler, |
| 258 | MetadataHandler metadataHandler, |
| 259 | Iterable<Artifact> actionInputs, |
| 260 | Map<String, String> clientEnv) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 261 | // Unconditional execution can be applied only for actions that are allowed to be executed. |
| 262 | if (unconditionalExecution(action)) { |
| 263 | Preconditions.checkState(action.isVolatile()); |
| 264 | reportUnconditionalExecution(handler, action); |
| 265 | return true; // must execute - unconditional execution is requested. |
| 266 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 267 | if (entry == null) { |
| 268 | reportNewAction(handler, action); |
| 269 | return true; // must execute -- no cache entry (e.g. first build) |
| 270 | } |
| 271 | |
| 272 | if (entry.isCorrupted()) { |
| 273 | reportCorruptedCacheEntry(handler, action); |
| 274 | return true; // cache entry is corrupted - must execute |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 275 | } else if (validateArtifacts(entry, action, actionInputs, metadataHandler, true)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 276 | reportChanged(handler, action); |
| 277 | return true; // files have changed |
Eric Fellheimer | 1a34a24 | 2016-01-21 19:17:19 +0000 | [diff] [blame] | 278 | } else if (!entry.getActionKey().equals(action.getKey())) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 279 | reportCommand(handler, action); |
| 280 | return true; // must execute -- action key is different |
| 281 | } |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 282 | Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv); |
| 283 | if (!entry.getUsedClientEnvDigest().equals(DigestUtils.fromEnv(usedClientEnv))) { |
| 284 | reportClientEnv(handler, action, usedClientEnv); |
| 285 | return true; // different values taken from the environment -- must execute |
| 286 | } |
| 287 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 288 | |
| 289 | entry.getFileDigest(); |
| 290 | return false; // cache hit |
| 291 | } |
| 292 | |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 293 | public void afterExecution( |
| 294 | Action action, Token token, MetadataHandler metadataHandler, Map<String, String> clientEnv) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 295 | throws IOException { |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 296 | if (!cacheConfig.enabled()) { |
| 297 | // Action cache is disabled, don't generate digests. |
| 298 | return; |
| 299 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 300 | Preconditions.checkArgument(token != null); |
| 301 | String key = token.cacheKey; |
Janak Ramakrishnan | e3f04b8 | 2015-03-27 16:45:34 +0000 | [diff] [blame] | 302 | if (actionCache.get(key) != null) { |
| 303 | // This cache entry has already been updated by a shared action. We don't need to do it again. |
| 304 | return; |
| 305 | } |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 306 | Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv); |
| 307 | ActionCache.Entry entry = |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 308 | new ActionCache.Entry(action.getKey(), usedClientEnv, action.discoversInputs()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 309 | for (Artifact output : action.getOutputs()) { |
| 310 | // Remove old records from the cache if they used different key. |
| 311 | String execPath = output.getExecPathString(); |
| 312 | if (!key.equals(execPath)) { |
Philipp Wollermann | e03758b | 2015-06-15 08:10:47 +0000 | [diff] [blame] | 313 | actionCache.remove(execPath); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 314 | } |
Michajlo Matijkiw | 13459b4 | 2015-03-12 19:43:20 +0000 | [diff] [blame] | 315 | if (!metadataHandler.artifactOmitted(output)) { |
| 316 | // Output files *must* exist and be accessible after successful action execution. |
| 317 | Metadata metadata = metadataHandler.getMetadata(output); |
| 318 | Preconditions.checkState(metadata != null); |
| 319 | entry.addFile(output.getExecPath(), metadata); |
| 320 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 321 | } |
| 322 | for (Artifact input : action.getInputs()) { |
| 323 | entry.addFile(input.getExecPath(), metadataHandler.getMetadataMaybe(input)); |
| 324 | } |
| 325 | entry.getFileDigest(); |
| 326 | actionCache.put(key, entry); |
| 327 | } |
| 328 | |
Janak Ramakrishnan | 90f3d34 | 2015-03-27 19:45:18 +0000 | [diff] [blame] | 329 | @Nullable |
Michajlo Matijkiw | 331633c | 2015-06-09 22:09:03 +0000 | [diff] [blame] | 330 | public Iterable<Artifact> getCachedInputs(Action action, PackageRootResolver resolver) |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 331 | throws PackageRootResolutionException, InterruptedException { |
Janak Ramakrishnan | 90f3d34 | 2015-03-27 19:45:18 +0000 | [diff] [blame] | 332 | ActionCache.Entry entry = getCacheEntry(action); |
| 333 | if (entry == null || entry.isCorrupted()) { |
| 334 | return ImmutableList.of(); |
| 335 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 336 | |
| 337 | List<PathFragment> outputs = new ArrayList<>(); |
| 338 | for (Artifact output : action.getOutputs()) { |
| 339 | outputs.add(output.getExecPath()); |
| 340 | } |
Lukacs Berki | 1f2caa5 | 2017-02-02 10:47:07 +0000 | [diff] [blame] | 341 | List<PathFragment> inputExecPaths = new ArrayList<>(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 342 | for (String path : entry.getPaths()) { |
| 343 | PathFragment execPath = new PathFragment(path); |
| 344 | // Code assumes that action has only 1-2 outputs and ArrayList.contains() will be |
| 345 | // most efficient. |
| 346 | if (!outputs.contains(execPath)) { |
Lukacs Berki | 1f2caa5 | 2017-02-02 10:47:07 +0000 | [diff] [blame] | 347 | inputExecPaths.add(execPath); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 348 | } |
| 349 | } |
Lukacs Berki | 1f2caa5 | 2017-02-02 10:47:07 +0000 | [diff] [blame] | 350 | |
| 351 | // Note that this method may trigger a violation of the desirable invariant that getInputs() |
| 352 | // is a superset of getMandatoryInputs(). See bug about an "action not in canonical form" |
| 353 | // error message and the integration test test_crosstool_change_and_failure(). |
| 354 | Map<PathFragment, Artifact> allowedDerivedInputsMap = new HashMap<>(); |
| 355 | for (Artifact derivedInput : action.getAllowedDerivedInputs()) { |
| 356 | if (!derivedInput.isSourceArtifact()) { |
| 357 | allowedDerivedInputsMap.put(derivedInput.getExecPath(), derivedInput); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | List<Artifact> inputArtifacts = new ArrayList<>(); |
| 362 | List<PathFragment> unresolvedPaths = new ArrayList<>(); |
| 363 | for (PathFragment execPath : inputExecPaths) { |
| 364 | Artifact artifact = allowedDerivedInputsMap.get(execPath); |
| 365 | if (artifact != null) { |
| 366 | inputArtifacts.add(artifact); |
| 367 | } else { |
| 368 | // Remember this execPath, we will try to resolve it as a source artifact. |
| 369 | unresolvedPaths.add(execPath); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | Map<PathFragment, Artifact> resolvedArtifacts = |
| 374 | artifactResolver.resolveSourceArtifacts(unresolvedPaths, resolver); |
| 375 | if (resolvedArtifacts == null) { |
| 376 | // We are missing some dependencies. We need to rerun this update later. |
| 377 | return null; |
| 378 | } |
| 379 | |
| 380 | for (PathFragment execPath : unresolvedPaths) { |
| 381 | Artifact artifact = resolvedArtifacts.get(execPath); |
| 382 | // If PathFragment cannot be resolved into the artifact, ignore it. This could happen if the |
| 383 | // rule has changed and the action no longer depends on, e.g., an additional source file in a |
| 384 | // separate package and that package is no longer referenced anywhere else. It is safe to |
| 385 | // ignore such paths because dependency checker would identify changes in inputs (ignored path |
| 386 | // was used before) and will force action execution. |
| 387 | if (artifact != null) { |
| 388 | inputArtifacts.add(artifact); |
| 389 | } |
| 390 | } |
| 391 | return inputArtifacts; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Special handling for the MiddlemanAction. Since MiddlemanAction output |
| 396 | * artifacts are purely fictional and used only to stay within dependency |
| 397 | * graph model limitations (action has to depend on artifacts, not on other |
| 398 | * actions), we do not need to validate metadata for the outputs - only for |
| 399 | * inputs. We also do not need to validate MiddlemanAction key, since action |
| 400 | * cache entry key already incorporates that information for the middlemen |
| 401 | * and we will experience a cache miss when it is different. Whenever it |
| 402 | * encounters middleman artifacts as input artifacts for other actions, it |
| 403 | * consults with the aggregated middleman digest computed here. |
| 404 | */ |
| 405 | protected void checkMiddlemanAction(Action action, EventHandler handler, |
| 406 | MetadataHandler metadataHandler) { |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 407 | if (!cacheConfig.enabled()) { |
| 408 | // Action cache is disabled, don't generate digests. |
| 409 | return; |
| 410 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 411 | Artifact middleman = action.getPrimaryOutput(); |
| 412 | String cacheKey = middleman.getExecPathString(); |
| 413 | ActionCache.Entry entry = actionCache.get(cacheKey); |
| 414 | boolean changed = false; |
| 415 | if (entry != null) { |
| 416 | if (entry.isCorrupted()) { |
| 417 | reportCorruptedCacheEntry(handler, action); |
| 418 | changed = true; |
Janak Ramakrishnan | a7c84b5 | 2015-03-18 21:59:16 +0000 | [diff] [blame] | 419 | } else if (validateArtifacts(entry, action, action.getInputs(), metadataHandler, false)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 420 | reportChanged(handler, action); |
| 421 | changed = true; |
| 422 | } |
| 423 | } else { |
| 424 | reportChangedDeps(handler, action); |
| 425 | changed = true; |
| 426 | } |
| 427 | if (changed) { |
| 428 | // Compute the aggregated middleman digest. |
| 429 | // Since we never validate action key for middlemen, we should not store |
| 430 | // it in the cache entry and just use empty string instead. |
Googler | 2f11192 | 2017-02-28 20:58:45 +0000 | [diff] [blame^] | 431 | entry = new ActionCache.Entry("", ImmutableMap.<String, String>of(), false); |
| 432 | for (Artifact input : action.getInputs()) { |
| 433 | entry.addFile(input.getExecPath(), metadataHandler.getMetadataMaybe(input)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 434 | } |
| 435 | } |
| 436 | |
| 437 | metadataHandler.setDigestForVirtualArtifact(middleman, entry.getFileDigest()); |
| 438 | if (changed) { |
| 439 | actionCache.put(cacheKey, entry); |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | /** |
| 444 | * Returns an action key. It is always set to the first output exec path string. |
| 445 | */ |
| 446 | private static String getKeyString(Action action) { |
| 447 | Preconditions.checkState(!action.getOutputs().isEmpty()); |
| 448 | return action.getOutputs().iterator().next().getExecPathString(); |
| 449 | } |
| 450 | |
| 451 | |
| 452 | /** |
| 453 | * In most cases, this method should not be called directly - reportXXX() methods |
| 454 | * should be used instead. This is done to avoid cost associated with building |
| 455 | * the message. |
| 456 | */ |
| 457 | private static void reportRebuild(@Nullable EventHandler handler, Action action, String message) { |
| 458 | // For MiddlemanAction, do not report rebuild. |
| 459 | if (handler != null && !action.getActionType().isMiddleman()) { |
Ulf Adams | 760e709 | 2016-04-21 08:09:51 +0000 | [diff] [blame] | 460 | handler.handle(Event.of( |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 461 | EventKind.DEPCHECKER, null, "Executing " + action.prettyPrint() + ": " + message + ".")); |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | // Called by IncrementalDependencyChecker. |
| 466 | protected static void reportUnconditionalExecution( |
| 467 | @Nullable EventHandler handler, Action action) { |
| 468 | reportRebuild(handler, action, "unconditional execution is requested"); |
| 469 | } |
| 470 | |
| 471 | private static void reportChanged(@Nullable EventHandler handler, Action action) { |
| 472 | reportRebuild(handler, action, "One of the files has changed"); |
| 473 | } |
| 474 | |
| 475 | private static void reportChangedDeps(@Nullable EventHandler handler, Action action) { |
| 476 | reportRebuild(handler, action, "the set of files on which this action depends has changed"); |
| 477 | } |
| 478 | |
| 479 | private static void reportNewAction(@Nullable EventHandler handler, Action action) { |
| 480 | reportRebuild(handler, action, "no entry in the cache (action is new)"); |
| 481 | } |
| 482 | |
| 483 | private static void reportCorruptedCacheEntry(@Nullable EventHandler handler, Action action) { |
| 484 | reportRebuild(handler, action, "cache entry is corrupted"); |
| 485 | } |
| 486 | |
| 487 | /** Wrapper for all context needed by the ActionCacheChecker to handle a single action. */ |
| 488 | public static final class Token { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 489 | private final String cacheKey; |
| 490 | |
| 491 | private Token(String cacheKey) { |
| 492 | this.cacheKey = Preconditions.checkNotNull(cacheKey); |
| 493 | } |
| 494 | } |
| 495 | } |