blob: 34849069cb3c0e54b21630fa106167e2ae7f8961 [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;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.collect.Iterables;
Rumou Duan33bab462016-04-25 17:55:12 +000022import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.actions.cache.ActionCache;
Shreya Bhattarai141b6c22016-08-22 22:00:24 +000024import com.google.devtools.build.lib.actions.cache.DigestUtils;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.actions.cache.MetadataHandler;
jmmv9573a0d2017-09-26 11:59:22 -040026import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.events.Event;
28import com.google.devtools.build.lib.events.EventHandler;
29import com.google.devtools.build.lib.events.EventKind;
shahan602cc852018-06-06 20:09:57 -070030import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032import java.io.IOException;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import 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 */
52public class ActionCacheChecker {
ulfjack4abd6c32017-12-21 10:52:16 -080053 private static final byte[] EMPTY_DIGEST = new byte[0];
shahan602cc852018-06-06 20:09:57 -070054 private static final FileArtifactValue CONSTANT_METADATA = new ConstantMetadataValue();
ulfjack01776ee2017-06-26 10:33:05 +020055
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056 private final ActionCache actionCache;
tomlu3d1a1942017-11-29 14:01:21 -080057 private final ActionKeyContext actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058 private final Predicate<? super Action> executionFilter;
59 private final ArtifactResolver artifactResolver;
Googler2f111922017-02-28 20:58:45 +000060 private final CacheConfig cacheConfig;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061
Googler2f111922017-02-28 20:58:45 +000062 /** 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,
tomlu3d1a1942017-11-29 14:01:21 -080087 ActionKeyContext actionKeyContext,
Googler2f111922017-02-28 20:58:45 +000088 Predicate<? super Action> executionFilter,
89 @Nullable CacheConfig cacheConfig) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010090 this.actionCache = actionCache;
91 this.executionFilter = executionFilter;
tomlu3d1a1942017-11-29 14:01:21 -080092 this.actionKeyContext = actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010093 this.artifactResolver = artifactResolver;
Googler2f111922017-02-28 20:58:45 +000094 this.cacheConfig =
95 cacheConfig != null
96 ? cacheConfig
97 : CacheConfig.builder().setEnabled(true).setVerboseExplanations(false).build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010098 }
99
100 public boolean isActionExecutionProhibited(Action action) {
101 return !executionFilter.apply(action);
102 }
103
mschallercff660b2019-01-07 13:57:11 -0800104 /** Whether the action cache is enabled. */
105 public boolean enabled() {
106 return cacheConfig.enabled();
107 }
108
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100109 /**
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) {
Googler2f111922017-02-28 20:58:45 +0000114 if (!cacheConfig.enabled()) {
115 return null; // ignore existing cache when disabled.
116 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100117 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 Ramakrishnane3f04b82015-03-27 16:45:34 +0000126 private void removeCacheEntry(Action action) {
127 for (Artifact output : action.getOutputs()) {
128 actionCache.remove(output.getExecPathString());
129 }
130 }
131
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100132 /**
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 Ramakrishnana7c84b52015-03-18 21:59:16 +0000137 * @param actionInputs the inputs of the action. Normally just the result of action.getInputs(),
jcater26ff4b32018-05-01 13:19:16 -0700138 * 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 +0100139 * @param metadataHandler provider of metadata for the artifacts this action interacts with.
jcater26ff4b32018-05-01 13:19:16 -0700140 * @param checkOutput true to validate output artifacts, Otherwise, just validate inputs.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100141 * @return true if at least one artifact has changed, false - otherwise.
142 */
ulfjack4db80dc2017-06-21 10:02:05 +0200143 private boolean validateArtifacts(
jcater26ff4b32018-05-01 13:19:16 -0700144 ActionCache.Entry entry,
145 Action action,
146 Iterable<Artifact> actionInputs,
147 MetadataHandler metadataHandler,
ulfjack4db80dc2017-06-21 10:02:05 +0200148 boolean checkOutput) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100149 Iterable<Artifact> artifacts = checkOutput
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000150 ? Iterables.concat(action.getOutputs(), actionInputs)
151 : actionInputs;
shahan602cc852018-06-06 20:09:57 -0700152 Map<String, FileArtifactValue> mdMap = new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100153 for (Artifact artifact : artifacts) {
ulfjack4db80dc2017-06-21 10:02:05 +0200154 mdMap.put(artifact.getExecPathString(), getMetadataMaybe(metadataHandler, artifact));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100155 }
Shreya Bhattarai141b6c22016-08-22 22:00:24 +0000156 return !DigestUtils.fromMetadata(mdMap).equals(entry.getFileDigest());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 }
158
159 private void reportCommand(EventHandler handler, Action action) {
160 if (handler != null) {
Googler2f111922017-02-28 20:58:45 +0000161 if (cacheConfig.verboseExplanations()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100162 String keyDescription = action.describeKey();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000163 reportRebuild(handler, action, keyDescription == null
164 ? "action command has changed"
165 : "action command has changed.\nNew action: " + keyDescription);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100166 } else {
167 reportRebuild(handler, action,
168 "action command has changed (try --verbose_explanations for more info)");
169 }
170 }
171 }
172
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000173 private void reportClientEnv(EventHandler handler, Action action, Map<String, String> used) {
174 if (handler != null) {
Googler2f111922017-02-28 20:58:45 +0000175 if (cacheConfig.verboseExplanations()) {
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000176 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100192 protected boolean unconditionalExecution(Action action) {
193 return !isActionExecutionProhibited(action) && action.executeUnconditionally();
194 }
195
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000196 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100208 /**
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 Ramakrishnan73055be2015-04-13 18:32:49 +0000215 * 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100217 */
218 // Note: the handler should only be used for DEPCHECKER events; there's no
219 // guarantee it will be available for other events.
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000220 public Token getTokenIfNeedToExecute(
221 Action action,
222 Iterable<Artifact> resolvedCacheArtifacts,
223 Map<String, String> clientEnv,
224 EventHandler handler,
225 MetadataHandler metadataHandler) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 // 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 }
Googler2f111922017-02-28 20:58:45 +0000241 if (!cacheConfig.enabled()) {
242 return new Token(getKeyString(action));
243 }
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000244 Iterable<Artifact> actionInputs = action.getInputs();
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000245 // Resolve action inputs from cache, if necessary.
Lukacs Berki5ea2b142017-02-28 10:46:53 +0000246 boolean inputsDiscovered = action.inputsDiscovered();
247 if (!inputsDiscovered && resolvedCacheArtifacts != null) {
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000248 // 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100252 }
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000253 ActionCache.Entry entry = getCacheEntry(action);
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000254 if (mustExecute(action, entry, handler, metadataHandler, actionInputs, clientEnv)) {
Janak Ramakrishnane3f04b82015-03-27 16:45:34 +0000255 if (entry != null) {
256 removeCacheEntry(action);
257 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100258 return new Token(getKeyString(action));
259 }
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000260
Lukacs Berki5ea2b142017-02-28 10:46:53 +0000261 if (!inputsDiscovered) {
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000262 action.updateInputs(actionInputs);
263 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 return null;
265 }
266
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000267 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100274 // 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);
jmmv9573a0d2017-09-26 11:59:22 -0400278 actionCache.accountMiss(MissReason.UNCONDITIONAL_EXECUTION);
279 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100280 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281 if (entry == null) {
282 reportNewAction(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400283 actionCache.accountMiss(MissReason.NOT_CACHED);
284 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 }
286
287 if (entry.isCorrupted()) {
288 reportCorruptedCacheEntry(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400289 actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
290 return true;
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000291 } else if (validateArtifacts(entry, action, actionInputs, metadataHandler, true)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 reportChanged(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400293 actionCache.accountMiss(MissReason.DIFFERENT_FILES);
294 return true;
tomlu3d1a1942017-11-29 14:01:21 -0800295 } else if (!entry.getActionKey().equals(action.getKey(actionKeyContext))) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100296 reportCommand(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400297 actionCache.accountMiss(MissReason.DIFFERENT_ACTION_KEY);
298 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299 }
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000300 Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
301 if (!entry.getUsedClientEnvDigest().equals(DigestUtils.fromEnv(usedClientEnv))) {
302 reportClientEnv(handler, action, usedClientEnv);
jmmv9573a0d2017-09-26 11:59:22 -0400303 actionCache.accountMiss(MissReason.DIFFERENT_ENVIRONMENT);
304 return true;
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000305 }
306
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 entry.getFileDigest();
jmmv9573a0d2017-09-26 11:59:22 -0400308 actionCache.accountHit();
309 return false;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100310 }
311
shahan602cc852018-06-06 20:09:57 -0700312 private static FileArtifactValue getMetadataOrConstant(
313 MetadataHandler metadataHandler, Artifact artifact) throws IOException {
ulfjack4db80dc2017-06-21 10:02:05 +0200314 if (artifact.isConstantMetadata()) {
ulfjack01776ee2017-06-26 10:33:05 +0200315 return CONSTANT_METADATA;
ulfjack4db80dc2017-06-21 10:02:05 +0200316 } 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
shahan602cc852018-06-06 20:09:57 -0700325 private static FileArtifactValue getMetadataMaybe(
326 MetadataHandler metadataHandler, Artifact artifact) {
ulfjack4db80dc2017-06-21 10:02:05 +0200327 try {
328 return getMetadataOrConstant(metadataHandler, artifact);
329 } catch (IOException e) {
330 return null;
331 }
332 }
333
mschallercff660b2019-01-07 13:57:11 -0800334 public void updateActionCache(
Klaus Aehlig6f33a1c2016-09-13 16:46:10 +0000335 Action action, Token token, MetadataHandler metadataHandler, Map<String, String> clientEnv)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100336 throws IOException {
mschallercff660b2019-01-07 13:57:11 -0800337 Preconditions.checkState(
338 cacheConfig.enabled(), "cache unexpectedly disabled, action: %s", action);
339 Preconditions.checkArgument(token != null, "token unexpectedly null, action: %s", action);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100340 String key = token.cacheKey;
Janak Ramakrishnane3f04b82015-03-27 16:45:34 +0000341 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 Aehlig6f33a1c2016-09-13 16:46:10 +0000345 Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
346 ActionCache.Entry entry =
tomlu3d1a1942017-11-29 14:01:21 -0800347 new ActionCache.Entry(
348 action.getKey(actionKeyContext), usedClientEnv, action.discoversInputs());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100349 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 Wollermanne03758b2015-06-15 08:10:47 +0000353 actionCache.remove(execPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 }
Michajlo Matijkiw13459b42015-03-12 19:43:20 +0000355 if (!metadataHandler.artifactOmitted(output)) {
ulfjack4db80dc2017-06-21 10:02:05 +0200356 // 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.
shahan602cc852018-06-06 20:09:57 -0700360 FileArtifactValue metadata = getMetadataOrConstant(metadataHandler, output);
Michajlo Matijkiw13459b42015-03-12 19:43:20 +0000361 Preconditions.checkState(metadata != null);
362 entry.addFile(output.getExecPath(), metadata);
363 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100364 }
365 for (Artifact input : action.getInputs()) {
ulfjack4db80dc2017-06-21 10:02:05 +0200366 entry.addFile(input.getExecPath(), getMetadataMaybe(metadataHandler, input));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368 entry.getFileDigest();
369 actionCache.put(key, entry);
370 }
371
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000372 @Nullable
Michajlo Matijkiw331633c2015-06-09 22:09:03 +0000373 public Iterable<Artifact> getCachedInputs(Action action, PackageRootResolver resolver)
ulfjack8989e192017-04-20 13:45:25 +0200374 throws InterruptedException {
Janak Ramakrishnan90f3d342015-03-27 19:45:18 +0000375 ActionCache.Entry entry = getCacheEntry(action);
376 if (entry == null || entry.isCorrupted()) {
377 return ImmutableList.of();
378 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100379
380 List<PathFragment> outputs = new ArrayList<>();
381 for (Artifact output : action.getOutputs()) {
382 outputs.add(output.getExecPath());
383 }
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000384 List<PathFragment> inputExecPaths = new ArrayList<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 for (String path : entry.getPaths()) {
nharmatab4060b62017-04-04 17:11:39 +0000386 PathFragment execPath = PathFragment.create(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 // Code assumes that action has only 1-2 outputs and ArrayList.contains() will be
388 // most efficient.
389 if (!outputs.contains(execPath)) {
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000390 inputExecPaths.add(execPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100391 }
392 }
Lukacs Berki1f2caa52017-02-02 10:47:07 +0000393
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 Nienhuysd08b27f2015-02-25 16:45:20 +0100435 }
436
437 /**
ulfjack4db80dc2017-06-21 10:02:05 +0200438 * 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100445 */
ulfjack4db80dc2017-06-21 10:02:05 +0200446 protected void checkMiddlemanAction(
447 Action action, EventHandler handler, MetadataHandler metadataHandler) {
Googler2f111922017-02-28 20:58:45 +0000448 if (!cacheConfig.enabled()) {
449 // Action cache is disabled, don't generate digests.
450 return;
451 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100452 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);
jmmv9573a0d2017-09-26 11:59:22 -0400459 actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100460 changed = true;
Janak Ramakrishnana7c84b52015-03-18 21:59:16 +0000461 } else if (validateArtifacts(entry, action, action.getInputs(), metadataHandler, false)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100462 reportChanged(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400463 actionCache.accountMiss(MissReason.DIFFERENT_FILES);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100464 changed = true;
465 }
466 } else {
467 reportChangedDeps(handler, action);
jmmv9573a0d2017-09-26 11:59:22 -0400468 actionCache.accountMiss(MissReason.DIFFERENT_DEPS);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100469 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.
Googler2f111922017-02-28 20:58:45 +0000475 entry = new ActionCache.Entry("", ImmutableMap.<String, String>of(), false);
476 for (Artifact input : action.getInputs()) {
ulfjack4db80dc2017-06-21 10:02:05 +0200477 entry.addFile(input.getExecPath(), getMetadataMaybe(metadataHandler, input));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100478 }
479 }
480
481 metadataHandler.setDigestForVirtualArtifact(middleman, entry.getFileDigest());
482 if (changed) {
483 actionCache.put(cacheKey, entry);
jmmv9573a0d2017-09-26 11:59:22 -0400484 } else {
485 actionCache.accountHit();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100486 }
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 Adams760e7092016-04-21 08:09:51 +0000506 handler.handle(Event.of(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100507 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100535 private final String cacheKey;
536
537 private Token(String cacheKey) {
538 this.cacheKey = Preconditions.checkNotNull(cacheKey);
539 }
540 }
shahan602cc852018-06-06 20:09:57 -0700541
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 Nienhuysd08b27f2015-02-25 16:45:20 +0100570}