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