blob: 4abf6684015b3f039ddd56c69dd337dcc237d16a [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.actions;
15
Googler2f111922017-02-28 20:58:45 +000016import com.google.auto.value.AutoValue;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.base.Predicate;
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +000019import com.google.common.collect.ImmutableList;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +000020import com.google.common.collect.ImmutableMap;
Rumou Duan33bab462016-04-25 17:55:12 +000021import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import com.google.devtools.build.lib.actions.cache.ActionCache;
Shreya Bhattarai141b6c22016-08-22 22:00:24 +000023import com.google.devtools.build.lib.actions.cache.DigestUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.actions.cache.MetadataHandler;
jmmv9573a0d2017-09-26 11:59:22 -040025import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.events.Event;
27import com.google.devtools.build.lib.events.EventHandler;
28import com.google.devtools.build.lib.events.EventKind;
shahan602cc852018-06-06 20:09:57 -070029import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import java.io.IOException;
32import java.util.ArrayList;
33import java.util.HashMap;
34import java.util.List;
35import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import 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 */
51public class ActionCacheChecker {
ulfjack4abd6c32017-12-21 10:52:16 -080052 private static final byte[] EMPTY_DIGEST = new byte[0];
shahan602cc852018-06-06 20:09:57 -070053 private static final FileArtifactValue CONSTANT_METADATA = new ConstantMetadataValue();
ulfjack01776ee2017-06-26 10:33:05 +020054
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010055 private final ActionCache actionCache;
tomlu3d1a1942017-11-29 14:01:21 -080056 private final ActionKeyContext actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010057 private final Predicate<? super Action> executionFilter;
58 private final ArtifactResolver artifactResolver;
Googler2f111922017-02-28 20:58:45 +000059 private final CacheConfig cacheConfig;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060
Googler2f111922017-02-28 20:58:45 +000061 /** 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,
tomlu3d1a1942017-11-29 14:01:21 -080086 ActionKeyContext actionKeyContext,
Googler2f111922017-02-28 20:58:45 +000087 Predicate<? super Action> executionFilter,
88 @Nullable CacheConfig cacheConfig) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 this.actionCache = actionCache;
90 this.executionFilter = executionFilter;
tomlu3d1a1942017-11-29 14:01:21 -080091 this.actionKeyContext = actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010092 this.artifactResolver = artifactResolver;
Googler2f111922017-02-28 20:58:45 +000093 this.cacheConfig =
94 cacheConfig != null
95 ? cacheConfig
96 : CacheConfig.builder().setEnabled(true).setVerboseExplanations(false).build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 }
98
99 public boolean isActionExecutionProhibited(Action action) {
100 return !executionFilter.apply(action);
101 }
102
mschallercff660b2019-01-07 13:57:11 -0800103 /** Whether the action cache is enabled. */
104 public boolean enabled() {
105 return cacheConfig.enabled();
106 }
107
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100108 /**
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) {
Googler2f111922017-02-28 20:58:45 +0000113 if (!cacheConfig.enabled()) {
114 return null; // ignore existing cache when disabled.
115 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100116 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 Ramakrishnane3f04b82015-03-27 16:45:34 +0000125 private void removeCacheEntry(Action action) {
126 for (Artifact output : action.getOutputs()) {
127 actionCache.remove(output.getExecPathString());
128 }
129 }
130
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131 /**
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 Ramakrishnana7c84b52015-03-18 21:59:16 +0000136 * @param actionInputs the inputs of the action. Normally just the result of action.getInputs(),
jcater26ff4b32018-05-01 13:19:16 -0700137 * but if this action doesn't yet know its inputs, we check the inputs from the cache.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100138 * @param metadataHandler provider of metadata for the artifacts this action interacts with.
jcater26ff4b32018-05-01 13:19:16 -0700139 * @param checkOutput true to validate output artifacts, Otherwise, just validate inputs.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140 * @return true if at least one artifact has changed, false - otherwise.
141 */
ulfjack4db80dc2017-06-21 10:02:05 +0200142 private boolean validateArtifacts(
jcater26ff4b32018-05-01 13:19:16 -0700143 ActionCache.Entry entry,
144 Action action,
145 Iterable<Artifact> actionInputs,
146 MetadataHandler metadataHandler,
ulfjack4db80dc2017-06-21 10:02:05 +0200147 boolean checkOutput) {
shahan602cc852018-06-06 20:09:57 -0700148 Map<String, FileArtifactValue> mdMap = new HashMap<>();
djasper5be73422019-04-24 05:29:13 -0700149 if (checkOutput) {
150 for (Artifact artifact : action.getOutputs()) {
151 mdMap.put(artifact.getExecPathString(), getMetadataMaybe(metadataHandler, artifact));
152 }
153 }
154 for (Artifact artifact : actionInputs) {
ulfjack4db80dc2017-06-21 10:02:05 +0200155 mdMap.put(artifact.getExecPathString(), getMetadataMaybe(metadataHandler, artifact));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 }
Shreya Bhattarai141b6c22016-08-22 22:00:24 +0000157 return !DigestUtils.fromMetadata(mdMap).equals(entry.getFileDigest());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100158 }
159
160 private void reportCommand(EventHandler handler, Action action) {
161 if (handler != null) {
Googler2f111922017-02-28 20:58:45 +0000162 if (cacheConfig.verboseExplanations()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100163 String keyDescription = action.describeKey();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000164 reportRebuild(handler, action, keyDescription == null
165 ? "action command has changed"
166 : "action command has changed.\nNew action: " + keyDescription);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 } else {
168 reportRebuild(handler, action,
169 "action command has changed (try --verbose_explanations for more info)");
170 }
171 }
172 }
173
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000174 private void reportClientEnv(EventHandler handler, Action action, Map<String, String> used) {
175 if (handler != null) {
Googler2f111922017-02-28 20:58:45 +0000176 if (cacheConfig.verboseExplanations()) {
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000177 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100193 protected boolean unconditionalExecution(Action action) {
194 return !isActionExecutionProhibited(action) && action.executeUnconditionally();
195 }
196
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000197 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100209 /**
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 Ramakrishnan73055be2015-04-13 18:32:49 +0000216 * 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100218 */
219 // Note: the handler should only be used for DEPCHECKER events; there's no
220 // guarantee it will be available for other events.
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000221 public Token getTokenIfNeedToExecute(
222 Action action,
223 Iterable<Artifact> resolvedCacheArtifacts,
224 Map<String, String> clientEnv,
225 EventHandler handler,
226 MetadataHandler metadataHandler) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100227 // 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 }
Googler2f111922017-02-28 20:58:45 +0000242 if (!cacheConfig.enabled()) {
243 return new Token(getKeyString(action));
244 }
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000245 Iterable<Artifact> actionInputs = action.getInputs();
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000246 // Resolve action inputs from cache, if necessary.
Lukacs Berki5ea2b142017-02-28 10:46:53 +0000247 boolean inputsDiscovered = action.inputsDiscovered();
248 if (!inputsDiscovered && resolvedCacheArtifacts != null) {
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000249 // 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100253 }
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000254 ActionCache.Entry entry = getCacheEntry(action);
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000255 if (mustExecute(action, entry, handler, metadataHandler, actionInputs, clientEnv)) {
Janak Ramakrishnane3f04b82015-03-27 16:45:34 +0000256 if (entry != null) {
257 removeCacheEntry(action);
258 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100259 return new Token(getKeyString(action));
260 }
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000261
Lukacs Berki5ea2b142017-02-28 10:46:53 +0000262 if (!inputsDiscovered) {
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000263 action.updateInputs(actionInputs);
264 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100265 return null;
266 }
267
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000268 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100275 // 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);
jmmv9573a0d2017-09-26 11:59:22 -0400279 actionCache.accountMiss(MissReason.UNCONDITIONAL_EXECUTION);
280 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282 if (entry == null) {
283 reportNewAction(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400284 actionCache.accountMiss(MissReason.NOT_CACHED);
285 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100286 }
287
288 if (entry.isCorrupted()) {
289 reportCorruptedCacheEntry(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400290 actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
291 return true;
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000292 } else if (validateArtifacts(entry, action, actionInputs, metadataHandler, true)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100293 reportChanged(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400294 actionCache.accountMiss(MissReason.DIFFERENT_FILES);
295 return true;
tomlu3d1a1942017-11-29 14:01:21 -0800296 } else if (!entry.getActionKey().equals(action.getKey(actionKeyContext))) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 reportCommand(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400298 actionCache.accountMiss(MissReason.DIFFERENT_ACTION_KEY);
299 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100300 }
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000301 Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
302 if (!entry.getUsedClientEnvDigest().equals(DigestUtils.fromEnv(usedClientEnv))) {
303 reportClientEnv(handler, action, usedClientEnv);
jmmv9573a0d2017-09-26 11:59:22 -0400304 actionCache.accountMiss(MissReason.DIFFERENT_ENVIRONMENT);
305 return true;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000306 }
307
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100308 entry.getFileDigest();
jmmv9573a0d2017-09-26 11:59:22 -0400309 actionCache.accountHit();
310 return false;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 }
312
shahan602cc852018-06-06 20:09:57 -0700313 private static FileArtifactValue getMetadataOrConstant(
314 MetadataHandler metadataHandler, Artifact artifact) throws IOException {
ulfjack4db80dc2017-06-21 10:02:05 +0200315 if (artifact.isConstantMetadata()) {
ulfjack01776ee2017-06-26 10:33:05 +0200316 return CONSTANT_METADATA;
ulfjack4db80dc2017-06-21 10:02:05 +0200317 } 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
shahan602cc852018-06-06 20:09:57 -0700326 private static FileArtifactValue getMetadataMaybe(
327 MetadataHandler metadataHandler, Artifact artifact) {
ulfjack4db80dc2017-06-21 10:02:05 +0200328 try {
329 return getMetadataOrConstant(metadataHandler, artifact);
330 } catch (IOException e) {
331 return null;
332 }
333 }
334
mschallercff660b2019-01-07 13:57:11 -0800335 public void updateActionCache(
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000336 Action action, Token token, MetadataHandler metadataHandler, Map<String, String> clientEnv)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100337 throws IOException {
mschallercff660b2019-01-07 13:57:11 -0800338 Preconditions.checkState(
339 cacheConfig.enabled(), "cache unexpectedly disabled, action: %s", action);
340 Preconditions.checkArgument(token != null, "token unexpectedly null, action: %s", action);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100341 String key = token.cacheKey;
Janak Ramakrishnane3f04b82015-03-27 16:45:34 +0000342 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 Aehlig6f33a1c2016-09-13 16:46:10 +0000346 Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
347 ActionCache.Entry entry =
tomlu3d1a1942017-11-29 14:01:21 -0800348 new ActionCache.Entry(
349 action.getKey(actionKeyContext), usedClientEnv, action.discoversInputs());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350 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 Wollermanne03758b2015-06-15 08:10:47 +0000354 actionCache.remove(execPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100355 }
Michajlo Matijkiw13459b42015-03-12 19:43:20 +0000356 if (!metadataHandler.artifactOmitted(output)) {
ulfjack4db80dc2017-06-21 10:02:05 +0200357 // 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.
shahan602cc852018-06-06 20:09:57 -0700361 FileArtifactValue metadata = getMetadataOrConstant(metadataHandler, output);
Michajlo Matijkiw13459b42015-03-12 19:43:20 +0000362 Preconditions.checkState(metadata != null);
363 entry.addFile(output.getExecPath(), metadata);
364 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100365 }
366 for (Artifact input : action.getInputs()) {
ulfjack4db80dc2017-06-21 10:02:05 +0200367 entry.addFile(input.getExecPath(), getMetadataMaybe(metadataHandler, input));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100368 }
369 entry.getFileDigest();
370 actionCache.put(key, entry);
371 }
372
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000373 @Nullable
Michajlo Matijkiw331633c2015-06-09 22:09:03 +0000374 public Iterable<Artifact> getCachedInputs(Action action, PackageRootResolver resolver)
ulfjack8989e192017-04-20 13:45:25 +0200375 throws InterruptedException {
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000376 ActionCache.Entry entry = getCacheEntry(action);
377 if (entry == null || entry.isCorrupted()) {
378 return ImmutableList.of();
379 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100380
381 List<PathFragment> outputs = new ArrayList<>();
382 for (Artifact output : action.getOutputs()) {
383 outputs.add(output.getExecPath());
384 }
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000385 List<PathFragment> inputExecPaths = new ArrayList<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100386 for (String path : entry.getPaths()) {
nharmatab4060b62017-04-04 17:11:39 +0000387 PathFragment execPath = PathFragment.create(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 // Code assumes that action has only 1-2 outputs and ArrayList.contains() will be
389 // most efficient.
390 if (!outputs.contains(execPath)) {
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000391 inputExecPaths.add(execPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100392 }
393 }
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000394
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 Nienhuysd08b27f2015-02-25 16:45:20 +0100436 }
437
438 /**
ulfjack4db80dc2017-06-21 10:02:05 +0200439 * 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100446 */
ulfjack4db80dc2017-06-21 10:02:05 +0200447 protected void checkMiddlemanAction(
448 Action action, EventHandler handler, MetadataHandler metadataHandler) {
Googler2f111922017-02-28 20:58:45 +0000449 if (!cacheConfig.enabled()) {
450 // Action cache is disabled, don't generate digests.
451 return;
452 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100453 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);
jmmv9573a0d2017-09-26 11:59:22 -0400460 actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100461 changed = true;
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000462 } else if (validateArtifacts(entry, action, action.getInputs(), metadataHandler, false)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100463 reportChanged(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400464 actionCache.accountMiss(MissReason.DIFFERENT_FILES);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100465 changed = true;
466 }
467 } else {
468 reportChangedDeps(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400469 actionCache.accountMiss(MissReason.DIFFERENT_DEPS);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100470 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.
Googler2f111922017-02-28 20:58:45 +0000476 entry = new ActionCache.Entry("", ImmutableMap.<String, String>of(), false);
477 for (Artifact input : action.getInputs()) {
ulfjack4db80dc2017-06-21 10:02:05 +0200478 entry.addFile(input.getExecPath(), getMetadataMaybe(metadataHandler, input));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100479 }
480 }
481
482 metadataHandler.setDigestForVirtualArtifact(middleman, entry.getFileDigest());
483 if (changed) {
484 actionCache.put(cacheKey, entry);
jmmv9573a0d2017-09-26 11:59:22 -0400485 } else {
486 actionCache.accountHit();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100487 }
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 Adams760e7092016-04-21 08:09:51 +0000507 handler.handle(Event.of(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100508 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100536 private final String cacheKey;
537
538 private Token(String cacheKey) {
539 this.cacheKey = Preconditions.checkNotNull(cacheKey);
540 }
541 }
shahan602cc852018-06-06 20:09:57 -0700542
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 Nienhuysd08b27f2015-02-25 16:45:20 +0100571}