-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
ActionMetadataHandler.java
637 lines (565 loc) · 26 KB
/
ActionMetadataHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.GoogleLogger;
import com.google.common.io.BaseEncoding;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.actions.FileStateType;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FilesetManifest;
import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior;
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
import com.google.devtools.build.lib.actions.cache.DigestUtils;
import com.google.devtools.build.lib.actions.cache.MetadataHandler;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
import com.google.devtools.build.lib.vfs.FileStatusWithDigestAdapter;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.Symlinks;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
/**
* Handler provided by {@link ActionExecutionFunction} which allows the execution engine to obtain
* {@linkplain FileArtifactValue metadata} about inputs and outputs and to store metadata about an
* action's outputs for purposes of creating the final {@link ActionExecutionValue}.
*
* <p>The handler can be in one of two modes. After construction, it acts as a cache for input and
* output metadata while {@link ActionCacheChecker} determines whether the action needs to be
* executed. If the action needs to be executed (i.e. no action cache hit), {@link
* #prepareForActionExecution} is called. This call switches the handler to a mode where it accepts
* {@linkplain MetadataInjector injected output data}, or otherwise obtains metadata from the
* filesystem. Freshly created output files are set read-only and executable <em>before</em>
* statting them to ensure that the stat's ctime is up to date.
*
* <p>After action execution, {@link #getMetadata} should be called on each of the action's outputs
* (except those that were {@linkplain #artifactOmitted omitted}) to ensure that declared outputs
* were in fact created and are valid.
*/
final class ActionMetadataHandler implements MetadataHandler {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
/**
* Creates a new metadata handler.
*
* <p>If the handler is for use during input discovery, calling {@link #getMetadata} with an
* artifact which is neither in {@code inputArtifactData} nor {@code outputs} is tolerated and
* will return {@code null}. To subsequently transform the handler for regular action execution
* (where such a call is not permitted), use {@link #transformAfterInputDiscovery}.
*/
static ActionMetadataHandler create(
ActionInputMap inputArtifactData,
boolean forInputDiscovery,
ImmutableSet<Artifact> outputs,
TimestampGranularityMonitor tsgm,
ArtifactPathResolver artifactPathResolver,
PathFragment execRoot,
Map<Artifact, ImmutableList<FilesetOutputSymlink>> expandedFilesets) {
return new ActionMetadataHandler(
inputArtifactData,
forInputDiscovery,
outputs,
tsgm,
artifactPathResolver,
execRoot,
createFilesetMapping(expandedFilesets, execRoot),
new OutputStore());
}
private final ActionInputMap inputArtifactData;
private final boolean forInputDiscovery;
private final ImmutableMap<PathFragment, FileArtifactValue> filesetMapping;
private final Set<Artifact> omittedOutputs = Sets.newConcurrentHashSet();
private final ImmutableSet<Artifact> outputs;
private final TimestampGranularityMonitor tsgm;
private final ArtifactPathResolver artifactPathResolver;
private final PathFragment execRoot;
private final AtomicBoolean executionMode = new AtomicBoolean(false);
private final OutputStore store;
private ActionMetadataHandler(
ActionInputMap inputArtifactData,
boolean forInputDiscovery,
ImmutableSet<Artifact> outputs,
TimestampGranularityMonitor tsgm,
ArtifactPathResolver artifactPathResolver,
PathFragment execRoot,
ImmutableMap<PathFragment, FileArtifactValue> filesetMapping,
OutputStore store) {
this.inputArtifactData = checkNotNull(inputArtifactData);
this.forInputDiscovery = forInputDiscovery;
this.outputs = checkNotNull(outputs);
this.tsgm = checkNotNull(tsgm);
this.artifactPathResolver = checkNotNull(artifactPathResolver);
this.execRoot = checkNotNull(execRoot);
this.filesetMapping = checkNotNull(filesetMapping);
this.store = checkNotNull(store);
}
/**
* Returns a new handler mostly identical to this one, except uses the given {@code store} and
* does not permit {@link #getMetadata} to be called with an artifact which is neither in inputs
* nor outputs.
*
* <p>The returned handler will be in the mode for action cache checking. To prepare it for action
* execution, call {@link #prepareForActionExecution}.
*
* <p>This method is designed to be called after input discovery when a fresh handler is needed
* but all of the parameters in {@link #create} would be the same as the original handler.
*/
ActionMetadataHandler transformAfterInputDiscovery(OutputStore store) {
return new ActionMetadataHandler(
inputArtifactData,
/*forInputDiscovery=*/ false,
outputs,
tsgm,
artifactPathResolver,
execRoot,
filesetMapping,
store);
}
/**
* If {@code value} represents an existing file, returns it as is, otherwise throws {@link
* FileNotFoundException}.
*/
private static FileArtifactValue checkExists(FileArtifactValue value, Artifact artifact)
throws FileNotFoundException {
if (FileArtifactValue.MISSING_FILE_MARKER.equals(value)
|| FileArtifactValue.OMITTED_FILE_MARKER.equals(value)) {
throw new FileNotFoundException(artifact + " does not exist");
}
return checkNotNull(value, artifact);
}
/**
* If {@code value} represents an existing tree artifact, returns it as is, otherwise throws
* {@link FileNotFoundException}.
*/
private static TreeArtifactValue checkExists(TreeArtifactValue value, Artifact artifact)
throws FileNotFoundException {
if (TreeArtifactValue.MISSING_TREE_ARTIFACT.equals(value)
|| TreeArtifactValue.OMITTED_TREE_MARKER.equals(value)) {
throw new FileNotFoundException(artifact + " does not exist");
}
return checkNotNull(value, artifact);
}
private static ImmutableMap<PathFragment, FileArtifactValue> createFilesetMapping(
Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesets, PathFragment execRoot) {
Map<PathFragment, FileArtifactValue> filesetMap = new HashMap<>();
for (Map.Entry<Artifact, ImmutableList<FilesetOutputSymlink>> entry : filesets.entrySet()) {
try {
FilesetManifest fileset =
FilesetManifest.constructFilesetManifest(
entry.getValue(), execRoot, RelativeSymlinkBehavior.RESOLVE);
for (Map.Entry<String, FileArtifactValue> favEntry :
fileset.getArtifactValues().entrySet()) {
if (favEntry.getValue().getDigest() != null) {
filesetMap.put(PathFragment.create(favEntry.getKey()), favEntry.getValue());
}
}
} catch (IOException e) {
// If we cannot get the FileArtifactValue, then we will make a FileSystem call to get the
// digest, so it is okay to skip and continue here.
logger.atWarning().withCause(e).log(
"Could not properly get digest for %s", entry.getKey().getExecPath());
}
}
return ImmutableMap.copyOf(filesetMap);
}
private boolean isKnownOutput(Artifact artifact) {
return outputs.contains(artifact)
|| (artifact.hasParent() && outputs.contains(artifact.getParent()));
}
@Override
@Nullable
public FileArtifactValue getMetadata(ActionInput actionInput) throws IOException {
if (!(actionInput instanceof Artifact)) {
PathFragment inputPath = actionInput.getExecPath();
PathFragment filesetKeyPath =
inputPath.startsWith(execRoot) ? inputPath.relativeTo(execRoot) : inputPath;
return filesetMapping.get(filesetKeyPath);
}
Artifact artifact = (Artifact) actionInput;
FileArtifactValue value;
if (!isKnownOutput(artifact)) {
value = inputArtifactData.getMetadata(artifact);
if (value != null) {
return checkExists(value, artifact);
}
checkState(forInputDiscovery, "%s is not present in declared outputs: %s", artifact, outputs);
return null;
}
if (artifact.isMiddlemanArtifact()) {
// A middleman artifact's data was either already injected from the action cache checker using
// #setDigestForVirtualArtifact, or it has the default middleman value.
value = store.getArtifactData(artifact);
if (value != null) {
return checkExists(value, artifact);
}
store.putArtifactData(artifact, FileArtifactValue.DEFAULT_MIDDLEMAN);
return FileArtifactValue.DEFAULT_MIDDLEMAN;
}
if (artifact.isTreeArtifact()) {
TreeArtifactValue tree = getTreeArtifactValue((SpecialArtifact) artifact);
return tree.getMetadata();
}
if (artifact.isChildOfDeclaredDirectory()) {
TreeArtifactValue tree = getTreeArtifactValue(artifact.getParent());
value = tree.getChildValues().getOrDefault(artifact, FileArtifactValue.MISSING_FILE_MARKER);
return checkExists(value, artifact);
}
value = store.getArtifactData(artifact);
if (value != null) {
return checkExists(value, artifact);
}
// No existing metadata; this can happen if the output metadata is not injected after a spawn
// is executed. SkyframeActionExecutor.checkOutputs calls this method for every output file of
// the action, which hits this code path. Another possibility is that an action runs multiple
// spawns, and a subsequent spawn requests the metadata of an output of a previous spawn.
// If necessary, we first call chmod the output file. The FileArtifactValue may use a
// FileContentsProxy, which is based on ctime (affected by chmod).
if (executionMode.get()) {
setPathReadOnlyAndExecutableIfFile(artifactPathResolver.toPath(artifact));
}
value = constructFileArtifactValueFromFilesystem(artifact);
store.putArtifactData(artifact, value);
return checkExists(value, artifact);
}
@Override
public ActionInput getInput(String execPath) {
return inputArtifactData.getInput(execPath);
}
@Override
public void setDigestForVirtualArtifact(Artifact artifact, byte[] digest) {
checkArgument(artifact.isMiddlemanArtifact(), artifact);
checkNotNull(digest, artifact);
store.putArtifactData(artifact, FileArtifactValue.createProxy(digest));
}
private TreeArtifactValue getTreeArtifactValue(SpecialArtifact artifact) throws IOException {
checkState(artifact.isTreeArtifact(), "%s is not a tree artifact", artifact);
TreeArtifactValue value = store.getTreeArtifactData(artifact);
if (value != null) {
return checkExists(value, artifact);
}
value = constructTreeArtifactValueFromFilesystem(artifact);
store.putTreeArtifactData(artifact, value);
return checkExists(value, artifact);
}
private TreeArtifactValue constructTreeArtifactValueFromFilesystem(SpecialArtifact parent)
throws IOException {
Path treeDir = artifactPathResolver.toPath(parent);
boolean chmod = executionMode.get();
// Make sure the tree artifact root is a regular directory. Note that this is how the action is
// initialized, so this should hold unless the action itself has deleted the root.
if (!treeDir.isDirectory(Symlinks.NOFOLLOW)) {
if (chmod) {
setPathReadOnlyAndExecutableIfFile(treeDir);
}
return TreeArtifactValue.MISSING_TREE_ARTIFACT;
}
if (chmod) {
setPathReadOnlyAndExecutable(treeDir);
}
TreeArtifactValue.Builder tree = TreeArtifactValue.newBuilder(parent);
TreeArtifactValue.visitTree(
treeDir,
(parentRelativePath, type) -> {
if (chmod && type != Dirent.Type.SYMLINK) {
setPathReadOnlyAndExecutable(treeDir.getRelative(parentRelativePath));
}
if (type == Dirent.Type.DIRECTORY) {
return; // The final TreeArtifactValue does not contain child directories.
}
TreeFileArtifact child = TreeFileArtifact.createTreeOutput(parent, parentRelativePath);
FileArtifactValue metadata;
try {
metadata = constructFileArtifactValueFromFilesystem(child);
} catch (FileNotFoundException e) {
String errorMessage =
String.format(
"Failed to resolve relative path %s inside TreeArtifact %s. "
+ "The associated file is either missing or is an invalid symlink.",
parentRelativePath, treeDir);
throw new IOException(errorMessage, e);
}
tree.putChild(child, metadata);
});
return tree.build();
}
@Override
public ImmutableSet<TreeFileArtifact> getTreeArtifactChildren(SpecialArtifact treeArtifact) {
checkArgument(treeArtifact.isTreeArtifact(), "%s is not a tree artifact", treeArtifact);
TreeArtifactValue tree = store.getTreeArtifactData(treeArtifact);
return tree != null ? tree.getChildren() : ImmutableSet.of();
}
@Override
public FileArtifactValue constructMetadataForDigest(
Artifact output, FileStatus statNoFollow, byte[] digest) throws IOException {
checkArgument(!output.isSymlink(), "%s is a symlink", output);
checkNotNull(digest, "Missing digest for %s", output);
checkNotNull(statNoFollow, "Missing stat for %s", output);
checkState(
executionMode.get(), "Tried to construct metadata for %s outside of execution", output);
// We already have a stat, so no need to call chmod.
return constructFileArtifactValue(
output, FileStatusWithDigestAdapter.adapt(statNoFollow), digest);
}
@Override
public void injectFile(Artifact output, FileArtifactValue metadata) {
checkArgument(isKnownOutput(output), "%s is not a declared output of this action", output);
checkArgument(
!output.isTreeArtifact() && !output.isChildOfDeclaredDirectory(),
"Tree artifacts and their children must be injected via injectTree: %s",
output);
checkState(executionMode.get(), "Tried to inject %s outside of execution", output);
store.putArtifactData(output, metadata);
}
@Override
public void injectTree(SpecialArtifact output, TreeArtifactValue tree) {
checkArgument(isKnownOutput(output), "%s is not a declared output of this action", output);
checkArgument(output.isTreeArtifact(), "Output must be a tree artifact: %s", output);
checkState(executionMode.get(), "Tried to inject %s outside of execution", output);
store.putTreeArtifactData(output, tree);
}
@Override
public void markOmitted(Artifact output) {
checkState(executionMode.get(), "Tried to mark %s omitted outside of execution", output);
boolean newlyOmitted = omittedOutputs.add(output);
if (output.isTreeArtifact()) {
// Tolerate marking a tree artifact as omitted multiple times so that callers don't have to
// deduplicate when a tree artifact has several omitted children.
if (newlyOmitted) {
store.putTreeArtifactData((SpecialArtifact) output, TreeArtifactValue.OMITTED_TREE_MARKER);
}
} else {
checkState(newlyOmitted, "%s marked as omitted twice", output);
store.putArtifactData(output, FileArtifactValue.OMITTED_FILE_MARKER);
}
}
@Override
public boolean artifactOmitted(Artifact artifact) {
return omittedOutputs.contains(artifact);
}
@Override
public void resetOutputs(Iterable<Artifact> outputs) {
checkState(
executionMode.get(), "resetOutputs() should only be called from within a running action.");
for (Artifact output : outputs) {
omittedOutputs.remove(output);
store.remove(output);
}
}
/**
* Informs this handler that the action is about to be executed.
*
* <p>Any stale metadata cached in the underlying {@link OutputStore} from action cache checking
* is cleared.
*/
void prepareForActionExecution() {
checkState(!executionMode.getAndSet(true), "Already in execution mode");
store.clear();
}
/**
* Returns the underlying {@link OutputStore} containing metadata cached during the lifetime of
* this handler.
*
* <p>The store may be passed to {@link ActionExecutionValue#createFromOutputStore}.
*/
OutputStore getOutputStore() {
return store;
}
/** Constructs a new {@link FileArtifactValue} by reading from the file system. */
private FileArtifactValue constructFileArtifactValueFromFilesystem(Artifact artifact)
throws IOException {
return constructFileArtifactValue(artifact, /*statNoFollow=*/ null, /*injectedDigest=*/ null);
}
/** Constructs a new {@link FileArtifactValue}, optionally taking a known stat and digest. */
private FileArtifactValue constructFileArtifactValue(
Artifact artifact,
@Nullable FileStatusWithDigest statNoFollow,
@Nullable byte[] injectedDigest)
throws IOException {
checkState(!artifact.isTreeArtifact(), "%s is a tree artifact", artifact);
FileArtifactValue value =
fileArtifactValueFromArtifact(
artifact,
artifactPathResolver,
statNoFollow,
injectedDigest != null,
// Prevent constant metadata artifacts from notifying the timestamp granularity monitor
// and potentially delaying the build for no reason.
artifact.isConstantMetadata() ? null : tsgm);
// Ensure that we don't have both an injected digest and a digest from the filesystem.
byte[] fileDigest = value.getDigest();
if (fileDigest != null && injectedDigest != null) {
throw new IllegalStateException(
String.format(
"Digest %s was injected for artifact %s, but got %s from the filesystem (%s)",
BaseEncoding.base16().encode(injectedDigest),
artifact,
BaseEncoding.base16().encode(fileDigest),
value));
}
FileStateType type = value.getType();
if (!type.exists()) {
// Nonexistent files should only occur before executing an action.
throw new FileNotFoundException(artifact.prettyPrint() + " does not exist");
}
if (type.isSymlink()) {
// We never create a FileArtifactValue for an unresolved symlink without a digest (calling
// readlink() is easy, unlike checksumming a potentially huge file).
checkNotNull(fileDigest, "%s missing digest", value);
return value;
}
if (type.isFile() && fileDigest != null) {
// The digest is in the file value and that is all that is needed for this file's metadata.
return value;
}
if (type.isDirectory()) {
// This branch is taken when the output of an action is a directory:
// - A Fileset (in this case, Blaze is correct)
// - A directory someone created in a local action (in this case, changes under the
// directory may not be detected since we use the mtime of the directory for
// up-to-dateness checks)
// - A symlink to a source directory due to Filesets
return FileArtifactValue.createForDirectoryWithMtime(
artifactPathResolver.toPath(artifact).getLastModifiedTime());
}
if (injectedDigest == null && type.isFile()) {
// We don't have an injected digest and there is no digest in the file value (which attempts a
// fast digest). Manually compute the digest instead.
injectedDigest =
DigestUtils.manuallyComputeDigest(artifactPathResolver.toPath(artifact), value.getSize());
}
return FileArtifactValue.createFromInjectedDigest(
value, injectedDigest, /*isShareable=*/ !artifact.isConstantMetadata());
}
/**
* Constructs a {@link FileArtifactValue} for a regular (non-tree, non-middleman) artifact for the
* purpose of determining whether an existing {@link FileArtifactValue} is still valid.
*
* <p>The returned metadata may be compared with metadata present in an {@link
* ActionExecutionValue} using {@link FileArtifactValue#couldBeModifiedSince} to check for
* inter-build modifications.
*/
static FileArtifactValue fileArtifactValueFromArtifact(
Artifact artifact,
@Nullable FileStatusWithDigest statNoFollow,
@Nullable TimestampGranularityMonitor tsgm)
throws IOException {
return fileArtifactValueFromArtifact(
artifact,
ArtifactPathResolver.IDENTITY,
statNoFollow,
/*digestWillBeInjected=*/ false,
tsgm);
}
private static FileArtifactValue fileArtifactValueFromArtifact(
Artifact artifact,
ArtifactPathResolver artifactPathResolver,
@Nullable FileStatusWithDigest statNoFollow,
boolean digestWillBeInjected,
@Nullable TimestampGranularityMonitor tsgm)
throws IOException {
checkState(!artifact.isTreeArtifact() && !artifact.isMiddlemanArtifact(), artifact);
Path pathNoFollow = artifactPathResolver.toPath(artifact);
RootedPath rootedPathNoFollow =
RootedPath.toRootedPath(
artifactPathResolver.transformRoot(artifact.getRoot().getRoot()),
artifact.getRootRelativePath());
if (statNoFollow == null) {
// Stat the file. All output artifacts of an action are deleted before execution, so if a file
// exists, it was most likely created by the current action. There is a race condition here if
// an external process creates (or modifies) the file between the deletion and this stat,
// which we cannot solve.
statNoFollow = FileStatusWithDigestAdapter.adapt(pathNoFollow.statIfFound(Symlinks.NOFOLLOW));
}
if (statNoFollow == null || !statNoFollow.isSymbolicLink()) {
return fileArtifactValueFromStat(
rootedPathNoFollow,
statNoFollow,
digestWillBeInjected,
artifact.isConstantMetadata(),
tsgm);
}
if (artifact.isSymlink()) {
return FileArtifactValue.createForUnresolvedSymlink(pathNoFollow.readSymbolicLink());
}
// We use FileStatus#isSymbolicLink over Path#isSymbolicLink to avoid the unnecessary stat
// done by the latter. We need to protect against symlink cycles since
// ArtifactFileMetadata#value assumes it's dealing with a file that's not in a symlink cycle.
Path realPath = pathNoFollow.resolveSymbolicLinks();
if (realPath.equals(pathNoFollow)) {
throw new IOException("symlink cycle");
}
RootedPath realRootedPath =
RootedPath.toRootedPathMaybeUnderRoot(
realPath,
ImmutableList.of(artifactPathResolver.transformRoot(artifact.getRoot().getRoot())));
// TODO(bazel-team): consider avoiding a 'stat' here when the symlink target hasn't changed
// and is a source file (since changes to those are checked separately).
FileStatus realStat = realRootedPath.asPath().statIfFound(Symlinks.NOFOLLOW);
FileStatusWithDigest realStatWithDigest = FileStatusWithDigestAdapter.adapt(realStat);
return fileArtifactValueFromStat(
realRootedPath,
realStatWithDigest,
digestWillBeInjected,
artifact.isConstantMetadata(),
tsgm);
}
private static FileArtifactValue fileArtifactValueFromStat(
RootedPath rootedPath,
FileStatusWithDigest stat,
boolean digestWillBeInjected,
boolean isConstantMetadata,
@Nullable TimestampGranularityMonitor tsgm)
throws IOException {
if (stat == null) {
return FileArtifactValue.MISSING_FILE_MARKER;
}
FileStateValue fileStateValue =
FileStateValue.createWithStatNoFollow(rootedPath, stat, digestWillBeInjected, tsgm);
return stat.isDirectory()
? FileArtifactValue.createForDirectoryWithMtime(stat.getLastModifiedTime())
: FileArtifactValue.createForNormalFile(
fileStateValue.getDigest(),
fileStateValue.getContentsProxy(),
stat.getSize(),
/*isShareable=*/ !isConstantMetadata);
}
private static void setPathReadOnlyAndExecutableIfFile(Path path) throws IOException {
if (path.isFile(Symlinks.NOFOLLOW)) {
setPathReadOnlyAndExecutable(path);
}
}
private static void setPathReadOnlyAndExecutable(Path path) throws IOException {
path.chmod(0555);
}
}