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