blob: 115cf2868d679a41f7bc917a43cb44ae65cdbd8d [file] [log] [blame]
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
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.
14package com.google.devtools.build.lib.skyframe;
15
jhorvitzed7ec3b2020-07-24 15:08:03 -070016import static com.google.common.base.Preconditions.checkArgument;
17import static com.google.common.base.Preconditions.checkNotNull;
ajurkowski280bbe22020-08-19 11:26:20 -070018import static com.google.common.base.Preconditions.checkState;
Googler9bd88332019-03-19 18:09:44 -070019import static com.google.common.collect.ImmutableSet.toImmutableSet;
20
ajurkowski280bbe22020-08-19 11:26:20 -070021import com.google.auto.value.AutoValue;
22import com.google.common.annotations.VisibleForTesting;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000023import com.google.common.base.MoreObjects;
24import com.google.common.collect.ImmutableMap;
Michael Thvedte4a7b0792016-02-09 12:15:53 +000025import com.google.common.collect.ImmutableSet;
Googler9bd88332019-03-19 18:09:44 -070026import com.google.common.collect.ImmutableSortedMap;
ajurkowskiaad74a32021-07-12 12:56:47 -070027import com.google.devtools.build.lib.actions.ActionInput;
28import com.google.devtools.build.lib.actions.ActionInputHelper;
ajurkowski280bbe22020-08-19 11:26:20 -070029import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact;
Googlerdce08002020-07-07 11:49:31 -070030import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
Rumou Duana77f32c2016-04-13 21:59:21 +000031import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
shahan602cc852018-06-06 20:09:57 -070032import com.google.devtools.build.lib.actions.FileArtifactValue;
janakrdd8afa02021-01-29 14:35:33 -080033import com.google.devtools.build.lib.actions.FileStateType;
fellyf1e30f32019-07-09 12:26:10 -070034import com.google.devtools.build.lib.actions.HasDigest;
janakr95704d12020-09-11 07:11:28 -070035import com.google.devtools.build.lib.actions.cache.MetadataDigestUtils;
janakrb4e8cd72018-02-27 15:39:08 -080036import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
Googler61a9f572020-06-02 10:28:24 -070037import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
jhorvitz1397e1e2020-07-21 10:02:17 -070038import com.google.devtools.build.lib.util.Fingerprint;
Googlerfeb26702018-11-06 10:23:45 -080039import com.google.devtools.build.lib.vfs.Dirent;
Michael Thvedte4a7b0792016-02-09 12:15:53 +000040import com.google.devtools.build.lib.vfs.Path;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000041import com.google.devtools.build.lib.vfs.PathFragment;
Googlerfeb26702018-11-06 10:23:45 -080042import com.google.devtools.build.lib.vfs.Symlinks;
Janak Ramakrishnanad77f972016-07-29 20:58:42 +000043import com.google.devtools.build.skyframe.SkyValue;
Michael Thvedte4a7b0792016-02-09 12:15:53 +000044import java.io.IOException;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000045import java.util.Arrays;
ajurkowskiaad74a32021-07-12 12:56:47 -070046import java.util.Comparator;
Googlerdce08002020-07-07 11:49:31 -070047import java.util.HashMap;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000048import java.util.Map;
ajurkowski280bbe22020-08-19 11:26:20 -070049import java.util.Optional;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000050import javax.annotation.Nullable;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000051
52/**
Janak Ramakrishnanad77f972016-07-29 20:58:42 +000053 * Value for TreeArtifacts, which contains a digest and the {@link FileArtifactValue}s of its child
54 * {@link TreeFileArtifact}s.
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000055 */
fellyf1e30f32019-07-09 12:26:10 -070056public class TreeArtifactValue implements HasDigest, SkyValue {
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +000057
ajurkowskiaad74a32021-07-12 12:56:47 -070058 /**
59 * Comparator based on exec path which works on {@link ActionInput} as opposed to {@link
60 * com.google.devtools.build.lib.actions.Artifact}. This way, we can use an {@link ActionInput} to
61 * search {@link #childData}.
62 */
63 @SerializationConstant @AutoCodec.VisibleForSerialization
64 static final Comparator<ActionInput> EXEC_PATH_COMPARATOR =
65 (input1, input2) -> input1.getExecPath().compareTo(input2.getExecPath());
66
67 private static final ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> EMPTY_MAP =
Googlerf53ab602022-01-11 10:07:28 -080068 childDataBuilder().buildOrThrow();
ajurkowskiaad74a32021-07-12 12:56:47 -070069
70 @SuppressWarnings("unchecked")
71 private static ImmutableSortedMap.Builder<TreeFileArtifact, FileArtifactValue>
72 childDataBuilder() {
73 return new ImmutableSortedMap.Builder<>(EXEC_PATH_COMPARATOR);
74 }
75
jhorvitzed7ec3b2020-07-24 15:08:03 -070076 /** Returns an empty {@link TreeArtifactValue}. */
77 public static TreeArtifactValue empty() {
78 return EMPTY;
79 }
80
81 /**
82 * Returns a new {@link Builder} for the given parent tree artifact.
83 *
84 * <p>The returned builder only supports adding children under this parent. To build multiple tree
85 * artifacts at once, use {@link MultiBuilder}.
86 */
87 public static Builder newBuilder(SpecialArtifact parent) {
88 return new Builder(parent);
89 }
90
Googlerdce08002020-07-07 11:49:31 -070091 /** Builder for constructing multiple instances of {@link TreeArtifactValue} at once. */
ajurkowski49c9475a2020-09-17 16:43:29 -070092 public static final class MultiBuilder {
93
94 private final Map<SpecialArtifact, Builder> map = new HashMap<>();
95
96 private MultiBuilder() {}
97
Googlerdce08002020-07-07 11:49:31 -070098 /**
99 * Puts a child tree file into this builder under its {@linkplain TreeFileArtifact#getParent
100 * parent}.
jhorvitzed7ec3b2020-07-24 15:08:03 -0700101 *
102 * @return {@code this} for convenience
Googlerdce08002020-07-07 11:49:31 -0700103 */
ajurkowski49c9475a2020-09-17 16:43:29 -0700104 public MultiBuilder putChild(TreeFileArtifact child, FileArtifactValue metadata) {
105 map.computeIfAbsent(child.getParent(), Builder::new).putChild(child, metadata);
106 return this;
107 }
Googlerdce08002020-07-07 11:49:31 -0700108
109 /**
ajurkowski280bbe22020-08-19 11:26:20 -0700110 * Sets the archived representation and its metadata for the {@linkplain
111 * ArchivedTreeArtifact#getParent parent} of the provided tree artifact.
112 *
113 * <p>Setting an archived representation is only allowed once per {@linkplain SpecialArtifact
114 * tree artifact}.
115 */
ajurkowski49c9475a2020-09-17 16:43:29 -0700116 public MultiBuilder setArchivedRepresentation(
117 ArchivedTreeArtifact archivedArtifact, FileArtifactValue metadata) {
118 map.computeIfAbsent(archivedArtifact.getParent(), Builder::new)
119 .setArchivedRepresentation(ArchivedRepresentation.create(archivedArtifact, metadata));
120 return this;
121 }
ajurkowski280bbe22020-08-19 11:26:20 -0700122
123 /**
ajurkowskidd299d12020-09-22 19:22:20 -0700124 * Removes all of collected data for a given tree artifact.
125 *
126 * <p>No-op if there is no data for a given tree artifact.
127 */
128 public MultiBuilder remove(SpecialArtifact treeArtifact) {
129 checkArgument(treeArtifact.isTreeArtifact(), "Not a tree artifact: %s", treeArtifact);
130 map.remove(treeArtifact);
131 return this;
132 }
133
134 /**
Googlerdce08002020-07-07 11:49:31 -0700135 * For each unique parent seen by this builder, passes the aggregated metadata to {@link
jhorvitzed7ec3b2020-07-24 15:08:03 -0700136 * TreeArtifactInjector#injectTree}.
Googlerdce08002020-07-07 11:49:31 -0700137 */
ajurkowski49c9475a2020-09-17 16:43:29 -0700138 public void injectTo(TreeArtifactInjector treeInjector) {
139 map.forEach((parent, builder) -> treeInjector.injectTree(parent, builder.build()));
140 }
Googlerdce08002020-07-07 11:49:31 -0700141 }
142
143 /** Returns a new {@link MultiBuilder}. */
144 public static MultiBuilder newMultiBuilder() {
ajurkowski49c9475a2020-09-17 16:43:29 -0700145 return new MultiBuilder();
Googlerdce08002020-07-07 11:49:31 -0700146 }
147
ajurkowski280bbe22020-08-19 11:26:20 -0700148 /**
149 * Archived representation of a tree artifact which contains a representation of the filesystem
150 * tree starting with the tree artifact directory.
151 *
152 * <p>Contains both the {@linkplain ArchivedTreeArtifact artifact} for the archived file and the
153 * metadata for it.
154 */
155 @AutoValue
Chi Wang4e290422021-08-03 17:56:19 -0700156 public abstract static class ArchivedRepresentation {
157 public abstract ArchivedTreeArtifact archivedTreeFileArtifact();
ajurkowski280bbe22020-08-19 11:26:20 -0700158
Chi Wang4e290422021-08-03 17:56:19 -0700159 public abstract FileArtifactValue archivedFileValue();
ajurkowski280bbe22020-08-19 11:26:20 -0700160
Chi Wang4e290422021-08-03 17:56:19 -0700161 public static ArchivedRepresentation create(
ajurkowski280bbe22020-08-19 11:26:20 -0700162 ArchivedTreeArtifact archivedTreeFileArtifact, FileArtifactValue fileArtifactValue) {
163 return new AutoValue_TreeArtifactValue_ArchivedRepresentation(
164 archivedTreeFileArtifact, fileArtifactValue);
165 }
166 }
167
168 @SuppressWarnings("WeakerAccess") // Serialization constant.
169 @SerializationConstant
170 @AutoCodec.VisibleForSerialization
Googler61a9f572020-06-02 10:28:24 -0700171 static final TreeArtifactValue EMPTY =
buchgr4992ae22019-03-20 04:23:32 -0700172 new TreeArtifactValue(
janakr95704d12020-09-11 07:11:28 -0700173 MetadataDigestUtils.fromMetadata(ImmutableMap.of()),
ajurkowskiaad74a32021-07-12 12:56:47 -0700174 EMPTY_MAP,
janakrdd8afa02021-01-29 14:35:33 -0800175 0L,
ajurkowski280bbe22020-08-19 11:26:20 -0700176 /*archivedRepresentation=*/ null,
Googlera4fff872020-06-17 14:26:35 -0700177 /*entirelyRemote=*/ false);
buchgr4992ae22019-03-20 04:23:32 -0700178
Rumou Duana77f32c2016-04-13 21:59:21 +0000179 private final byte[] digest;
Googler9bd88332019-03-19 18:09:44 -0700180 private final ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> childData;
janakrdd8afa02021-01-29 14:35:33 -0800181 private final long totalChildSize;
ajurkowski49c9475a2020-09-17 16:43:29 -0700182
ajurkowski280bbe22020-08-19 11:26:20 -0700183 /**
184 * Optional archived representation of the entire tree artifact which can be sent instead of all
185 * the items in the directory.
186 */
187 @Nullable private final ArchivedRepresentation archivedRepresentation;
188
Googlera4fff872020-06-17 14:26:35 -0700189 private final boolean entirelyRemote;
Rumou Duana77f32c2016-04-13 21:59:21 +0000190
jhorvitzed7ec3b2020-07-24 15:08:03 -0700191 private TreeArtifactValue(
buchgr4992ae22019-03-20 04:23:32 -0700192 byte[] digest,
193 ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> childData,
janakrdd8afa02021-01-29 14:35:33 -0800194 long totalChildSize,
ajurkowski280bbe22020-08-19 11:26:20 -0700195 @Nullable ArchivedRepresentation archivedRepresentation,
Googlera4fff872020-06-17 14:26:35 -0700196 boolean entirelyRemote) {
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000197 this.digest = digest;
Googler9bd88332019-03-19 18:09:44 -0700198 this.childData = childData;
janakrdd8afa02021-01-29 14:35:33 -0800199 this.totalChildSize = totalChildSize;
ajurkowski280bbe22020-08-19 11:26:20 -0700200 this.archivedRepresentation = archivedRepresentation;
Googlera4fff872020-06-17 14:26:35 -0700201 this.entirelyRemote = entirelyRemote;
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000202 }
203
ajurkowskiaad74a32021-07-12 12:56:47 -0700204 public FileArtifactValue getMetadata() {
Googler206d6e42020-06-18 12:39:17 -0700205 return FileArtifactValue.createProxy(digest);
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000206 }
207
Googler9bd88332019-03-19 18:09:44 -0700208 ImmutableSet<PathFragment> getChildPaths() {
209 return childData.keySet().stream()
210 .map(TreeFileArtifact::getParentRelativePath)
211 .collect(toImmutableSet());
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000212 }
213
fellyf1e30f32019-07-09 12:26:10 -0700214 @Override
215 public byte[] getDigest() {
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000216 return digest.clone();
217 }
218
Googler04c546f2020-05-12 18:22:18 -0700219 public ImmutableSet<TreeFileArtifact> getChildren() {
Rumou Duana77f32c2016-04-13 21:59:21 +0000220 return childData.keySet();
221 }
222
janakrdd8afa02021-01-29 14:35:33 -0800223 public long getTotalChildBytes() {
224 return totalChildSize;
225 }
226
ajurkowski280bbe22020-08-19 11:26:20 -0700227 /** Return archived representation of the tree artifact (if present). */
Chi Wang4e290422021-08-03 17:56:19 -0700228 public Optional<ArchivedRepresentation> getArchivedRepresentation() {
ajurkowski280bbe22020-08-19 11:26:20 -0700229 return Optional.ofNullable(archivedRepresentation);
230 }
231
232 @VisibleForTesting
ajurkowskidd299d12020-09-22 19:22:20 -0700233 @Nullable
234 public ArchivedTreeArtifact getArchivedArtifactForTesting() {
235 return archivedRepresentation != null
236 ? archivedRepresentation.archivedTreeFileArtifact()
237 : null;
ajurkowski280bbe22020-08-19 11:26:20 -0700238 }
239
ajurkowskiaad74a32021-07-12 12:56:47 -0700240 public ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> getChildValues() {
Rumou Duanbaeed332016-10-18 15:30:25 +0000241 return childData;
Rumou Duana77f32c2016-04-13 21:59:21 +0000242 }
243
ajurkowskiaad74a32021-07-12 12:56:47 -0700244 /** Returns an entry for child with given exec path or null if no such child is present. */
245 @SuppressWarnings("unchecked")
246 @Nullable
247 public Map.Entry<TreeFileArtifact, FileArtifactValue> findChildEntryByExecPath(
248 PathFragment execPath) {
249 ActionInput searchToken = ActionInputHelper.fromPath(execPath);
250 // Not really a copy -- original map is already an ImmutableSortedMap using the same comparator.
251 ImmutableSortedMap<ActionInput, FileArtifactValue> casted =
252 ImmutableSortedMap.copyOf(childData, EXEC_PATH_COMPARATOR);
253 checkState(casted == (Object) childData, "Casting children resulted with a copy");
254 Map.Entry<? extends ActionInput, FileArtifactValue> entry = casted.floorEntry(searchToken);
255 return entry != null && entry.getKey().getExecPath().equals(execPath)
256 ? (Map.Entry<TreeFileArtifact, FileArtifactValue>) entry
257 : null;
258 }
259
buchgr4992ae22019-03-20 04:23:32 -0700260 /** Returns true if the {@link TreeFileArtifact}s are only stored remotely. */
Googlera4fff872020-06-17 14:26:35 -0700261 public boolean isEntirelyRemote() {
262 return entirelyRemote;
buchgr4992ae22019-03-20 04:23:32 -0700263 }
264
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000265 @Override
266 public int hashCode() {
267 return Arrays.hashCode(digest);
268 }
269
270 @Override
271 public boolean equals(Object other) {
272 if (this == other) {
273 return true;
274 }
275
276 if (!(other instanceof TreeArtifactValue)) {
277 return false;
278 }
279
280 TreeArtifactValue that = (TreeArtifactValue) other;
Shreya Bhattarai3b096802016-08-17 23:14:52 +0000281 if (!Arrays.equals(digest, that.digest)) {
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000282 return false;
283 }
284
285 return childData.equals(that.childData);
286 }
287
288 @Override
289 public String toString() {
jhorvitzed7ec3b2020-07-24 15:08:03 -0700290 return MoreObjects.toStringHelper(this)
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000291 .add("digest", digest)
292 .add("childData", childData)
293 .toString();
294 }
295
296 /**
Googler61a9f572020-06-02 10:28:24 -0700297 * Represents a tree artifact that was intentionally omitted, similar to {@link
298 * FileArtifactValue#OMITTED_FILE_MARKER}.
299 */
300 @SerializationConstant
301 public static final TreeArtifactValue OMITTED_TREE_MARKER = createMarker("OMITTED_TREE_MARKER");
302
303 /**
shahan602cc852018-06-06 20:09:57 -0700304 * A TreeArtifactValue that represents a missing TreeArtifact. This is occasionally useful because
305 * Java's concurrent collections disallow null members.
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000306 */
Chi Wang4e290422021-08-03 17:56:19 -0700307 public static final TreeArtifactValue MISSING_TREE_ARTIFACT =
308 createMarker("MISSING_TREE_ARTIFACT");
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000309
Googler61a9f572020-06-02 10:28:24 -0700310 private static TreeArtifactValue createMarker(String toStringRepresentation) {
ajurkowski280bbe22020-08-19 11:26:20 -0700311 return new TreeArtifactValue(
ajurkowskiaad74a32021-07-12 12:56:47 -0700312 null, EMPTY_MAP, 0L, /*archivedRepresentation=*/ null, /*entirelyRemote=*/ false) {
Googler61a9f572020-06-02 10:28:24 -0700313 @Override
Googler61a9f572020-06-02 10:28:24 -0700314 public ImmutableSet<TreeFileArtifact> getChildren() {
315 throw new UnsupportedOperationException(toString());
316 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000317
Googler61a9f572020-06-02 10:28:24 -0700318 @Override
ajurkowskiaad74a32021-07-12 12:56:47 -0700319 public ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> getChildValues() {
Googler61a9f572020-06-02 10:28:24 -0700320 throw new UnsupportedOperationException(toString());
321 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000322
Googler61a9f572020-06-02 10:28:24 -0700323 @Override
ajurkowskiaad74a32021-07-12 12:56:47 -0700324 public FileArtifactValue getMetadata() {
Googler61a9f572020-06-02 10:28:24 -0700325 throw new UnsupportedOperationException(toString());
326 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000327
Googler61a9f572020-06-02 10:28:24 -0700328 @Override
329 ImmutableSet<PathFragment> getChildPaths() {
330 throw new UnsupportedOperationException(toString());
331 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000332
Googler61a9f572020-06-02 10:28:24 -0700333 @Nullable
334 @Override
335 public byte[] getDigest() {
336 throw new UnsupportedOperationException(toString());
337 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000338
Googler61a9f572020-06-02 10:28:24 -0700339 @Override
340 public int hashCode() {
341 return System.identityHashCode(this);
342 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000343
Googler61a9f572020-06-02 10:28:24 -0700344 @Override
345 public boolean equals(Object other) {
346 return this == other;
347 }
348
349 @Override
350 public String toString() {
351 return toStringRepresentation;
352 }
353 };
354 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000355
jhorvitz0d0758a2020-07-14 12:50:24 -0700356 /** Visitor for use in {@link #visitTree}. */
357 @FunctionalInterface
358 public interface TreeArtifactVisitor {
359 /**
360 * Called for every directory entry encountered during tree traversal.
361 *
362 * <p>Symlinks are not followed during traversal and are simply reported as {@link
363 * Dirent.Type#SYMLINK} regardless of whether they point to a file, directory, or are dangling.
364 *
365 * <p>{@code type} is guaranteed never to be {@link Dirent.Type#UNKNOWN}, since if this type is
366 * encountered, {@link IOException} is immediately thrown without invoking the visitor.
367 *
368 * <p>If the implementation throws {@link IOException}, traversal is immediately halted and the
369 * exception is propagated.
370 */
371 void visit(PathFragment parentRelativePath, Dirent.Type type) throws IOException;
372 }
373
374 /**
375 * Recursively visits all descendants under a directory.
376 *
377 * <p>{@link TreeArtifactVisitor#visit} is invoked on {@code visitor} for each directory, file,
378 * and symlink under the given {@code parentDir}.
379 *
380 * <p>This method is intended to provide uniform semantics for constructing a tree artifact,
381 * including special logic that validates directory entries. Invalid directory entries include a
382 * symlink that traverses outside of the tree artifact and any entry of {@link
383 * Dirent.Type#UNKNOWN}, such as a named pipe.
384 *
385 * @throws IOException if there is any problem reading or validating outputs under the given tree
386 * artifact directory, or if {@link TreeArtifactVisitor#visit} throws {@link IOException}
387 */
388 public static void visitTree(Path parentDir, TreeArtifactVisitor visitor) throws IOException {
jhorvitzed7ec3b2020-07-24 15:08:03 -0700389 visitTree(parentDir, PathFragment.EMPTY_FRAGMENT, checkNotNull(visitor));
jhorvitz0d0758a2020-07-14 12:50:24 -0700390 }
391
392 private static void visitTree(Path parentDir, PathFragment subdir, TreeArtifactVisitor visitor)
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000393 throws IOException {
jhorvitz0d0758a2020-07-14 12:50:24 -0700394 for (Dirent dirent : parentDir.getRelative(subdir).readdir(Symlinks.NOFOLLOW)) {
395 PathFragment parentRelativePath = subdir.getChild(dirent.getName());
396 Dirent.Type type = dirent.getType();
397
398 if (type == Dirent.Type.UNKNOWN) {
399 throw new IOException(
400 "Could not determine type of file for " + parentRelativePath + " under " + parentDir);
401 }
402
403 if (type == Dirent.Type.SYMLINK) {
404 checkSymlink(subdir, parentDir.getRelative(parentRelativePath));
405 }
406
407 visitor.visit(parentRelativePath, type);
408
409 if (type == Dirent.Type.DIRECTORY) {
410 visitTree(parentDir, parentRelativePath, visitor);
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000411 }
412 }
413 }
414
jhorvitz0d0758a2020-07-14 12:50:24 -0700415 private static void checkSymlink(PathFragment subDir, Path path) throws IOException {
416 PathFragment linkTarget = path.readSymbolicLinkUnchecked();
417 if (linkTarget.isAbsolute()) {
418 // We tolerate absolute symlinks here. They will probably be dangling if any downstream
419 // consumer tries to read them, but let that be downstream's problem.
420 return;
421 }
422
423 // Visit each path segment of the link target to catch any path traversal outside of the
424 // TreeArtifact root directory. For example, for TreeArtifact a/b/c, it is possible to have a
425 // symlink, a/b/c/sym_link that points to ../outside_dir/../c/link_target. Although this symlink
426 // points to a file under the TreeArtifact, the link target traverses outside of the
427 // TreeArtifact into a/b/outside_dir.
428 PathFragment intermediatePath = subDir;
jhorvitz60347952021-02-18 10:05:05 -0800429 for (String pathSegment : linkTarget.segments()) {
jhorvitz0d0758a2020-07-14 12:50:24 -0700430 intermediatePath = intermediatePath.getRelative(pathSegment);
431 if (intermediatePath.containsUplevelReferences()) {
432 String errorMessage =
433 String.format(
434 "A TreeArtifact may not contain relative symlinks whose target paths traverse "
435 + "outside of the TreeArtifact, found %s pointing to %s.",
436 path, linkTarget);
437 throw new IOException(errorMessage);
438 }
439 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000440 }
Googlerdce08002020-07-07 11:49:31 -0700441
jhorvitzed7ec3b2020-07-24 15:08:03 -0700442 /** Builder for a {@link TreeArtifactValue}. */
443 public static final class Builder {
444 private final ImmutableSortedMap.Builder<TreeFileArtifact, FileArtifactValue> childData =
ajurkowskiaad74a32021-07-12 12:56:47 -0700445 childDataBuilder();
ajurkowski280bbe22020-08-19 11:26:20 -0700446 private ArchivedRepresentation archivedRepresentation;
jhorvitzed7ec3b2020-07-24 15:08:03 -0700447 private final SpecialArtifact parent;
448
449 Builder(SpecialArtifact parent) {
450 checkArgument(parent.isTreeArtifact(), "%s is not a tree artifact", parent);
451 this.parent = parent;
452 }
453
454 /**
455 * Adds a child to this builder.
456 *
457 * <p>The child's {@linkplain TreeFileArtifact#getParent parent} <em>must</em> match the parent
458 * with which this builder was initialized.
459 *
460 * <p>Children may be added in any order. The children are sorted prior to constructing the
461 * final {@link TreeArtifactValue}.
462 *
ajurkowski280bbe22020-08-19 11:26:20 -0700463 * <p>It is illegal to call this method with {@link FileArtifactValue#OMITTED_FILE_MARKER}. When
464 *
465 * <p>It is illegal to call this method with {@link FileArtifactValue#OMITTED_FILE_MARKER}. When
jhorvitzed7ec3b2020-07-24 15:08:03 -0700466 * children are omitted, use {@link TreeArtifactValue#OMITTED_TREE_MARKER}.
467 *
468 * @return {@code this} for convenience
469 */
470 public Builder putChild(TreeFileArtifact child, FileArtifactValue metadata) {
471 checkArgument(
472 child.getParent().equals(parent),
473 "While building TreeArtifactValue for %s, got %s with parent %s",
474 parent,
475 child,
476 child.getParent());
477 checkArgument(
478 !FileArtifactValue.OMITTED_FILE_MARKER.equals(metadata),
479 "Cannot construct TreeArtifactValue for %s because child %s was omitted",
480 parent,
481 child);
482 childData.put(child, metadata);
483 return this;
484 }
485
ajurkowskid83b1242020-08-31 10:02:05 -0700486 public Builder setArchivedRepresentation(
487 ArchivedTreeArtifact archivedTreeArtifact, FileArtifactValue metadata) {
488 return setArchivedRepresentation(
489 ArchivedRepresentation.create(archivedTreeArtifact, metadata));
490 }
491
Chi Wang4e290422021-08-03 17:56:19 -0700492 public Builder setArchivedRepresentation(ArchivedRepresentation archivedRepresentation) {
ajurkowski280bbe22020-08-19 11:26:20 -0700493 checkState(
494 this.archivedRepresentation == null,
495 "Tried to add 2 archived representations for: %s",
ajurkowskid83b1242020-08-31 10:02:05 -0700496 parent);
ajurkowski280bbe22020-08-19 11:26:20 -0700497 checkArgument(
ajurkowskid83b1242020-08-31 10:02:05 -0700498 parent.equals(archivedRepresentation.archivedTreeFileArtifact().getParent()),
ajurkowski280bbe22020-08-19 11:26:20 -0700499 "Cannot add archived representation: %s for a mismatching tree artifact: %s",
500 archivedRepresentation,
501 parent);
ajurkowskid83b1242020-08-31 10:02:05 -0700502 checkArgument(
503 !archivedRepresentation.archivedFileValue().equals(FileArtifactValue.OMITTED_FILE_MARKER),
504 "Cannot add archived representation: %s to %s because it has omitted metadata.",
505 archivedRepresentation,
506 parent);
ajurkowski280bbe22020-08-19 11:26:20 -0700507 this.archivedRepresentation = archivedRepresentation;
508 return this;
509 }
510
jhorvitzed7ec3b2020-07-24 15:08:03 -0700511 /** Builds the final {@link TreeArtifactValue}. */
512 public TreeArtifactValue build() {
Googlerf53ab602022-01-11 10:07:28 -0800513 ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> finalChildData =
514 childData.buildOrThrow();
ajurkowski280bbe22020-08-19 11:26:20 -0700515 if (finalChildData.isEmpty() && archivedRepresentation == null) {
jhorvitzed7ec3b2020-07-24 15:08:03 -0700516 return EMPTY;
517 }
518
519 Fingerprint fingerprint = new Fingerprint();
ajurkowski280bbe22020-08-19 11:26:20 -0700520 boolean entirelyRemote =
521 archivedRepresentation == null || archivedRepresentation.archivedFileValue().isRemote();
jhorvitzed7ec3b2020-07-24 15:08:03 -0700522
janakrdd8afa02021-01-29 14:35:33 -0800523 long totalChildSize = 0;
jhorvitzed7ec3b2020-07-24 15:08:03 -0700524 for (Map.Entry<TreeFileArtifact, FileArtifactValue> childData : finalChildData.entrySet()) {
525 // Digest will be deterministic because children are sorted.
526 fingerprint.addPath(childData.getKey().getParentRelativePath());
janakrdd8afa02021-01-29 14:35:33 -0800527 FileArtifactValue metadata = childData.getValue();
528 metadata.addTo(fingerprint);
jhorvitzed7ec3b2020-07-24 15:08:03 -0700529
jhorvitz77497412020-08-07 12:38:22 -0700530 // Tolerate a mix of local and remote children (b/152496153#comment80).
janakrdd8afa02021-01-29 14:35:33 -0800531 entirelyRemote &= metadata.isRemote();
532
533 if (metadata.getType() == FileStateType.REGULAR_FILE) {
534 totalChildSize += metadata.getSize();
535 }
jhorvitzed7ec3b2020-07-24 15:08:03 -0700536 }
537
ajurkowski280bbe22020-08-19 11:26:20 -0700538 if (archivedRepresentation != null) {
539 archivedRepresentation.archivedFileValue().addTo(fingerprint);
540 }
541
542 return new TreeArtifactValue(
janakrdd8afa02021-01-29 14:35:33 -0800543 fingerprint.digestAndReset(),
544 finalChildData,
545 totalChildSize,
546 archivedRepresentation,
547 entirelyRemote);
jhorvitzed7ec3b2020-07-24 15:08:03 -0700548 }
549 }
Michael Thvedt8d5a7bb2016-02-09 03:06:34 +0000550}