blob: ca8293f9c6d7369939fa9c5909e43e2f20b97fbd [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.skyframe;
15
tomlua155b532017-11-08 20:12:47 +010016import com.google.common.base.Preconditions;
steinman04dff442021-09-27 08:18:05 -070017import com.google.common.collect.Sets;
janakrc3bcb982020-04-14 06:50:08 -070018import com.google.common.flogger.GoogleLogger;
Kristina Chodorow5a2936f2016-04-22 17:02:19 +000019import com.google.devtools.build.lib.analysis.BlazeDirectories;
brandjonb2825302021-03-16 12:35:36 -070020import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.BundledFileSystem;
dannarkbe3cefc2018-12-13 11:52:45 -080021import com.google.devtools.build.lib.cmdline.LabelConstants;
Googlerba4862d2019-05-03 06:13:23 -070022import com.google.devtools.build.lib.cmdline.RepositoryName;
Nathan Harmata60108832016-03-25 08:02:42 +000023import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
24import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
Damien Martin-Guillerez847a4182016-02-10 12:44:25 +000026import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
Googlerba4862d2019-05-03 06:13:23 -070027import com.google.devtools.build.lib.util.Pair;
janakr2143cf82020-05-06 16:20:37 -070028import com.google.devtools.build.lib.util.TestType;
Ulf Adamsef7e0452015-12-21 09:26:43 +000029import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.vfs.RootedPath;
31import com.google.devtools.build.skyframe.SkyFunction;
Kristina Chodorow5a2936f2016-04-22 17:02:19 +000032import java.io.IOException;
steinman04dff442021-09-27 08:18:05 -070033import java.util.Set;
Nathan Harmatafee19282017-02-14 20:47:50 +000034import java.util.concurrent.atomic.AtomicInteger;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.concurrent.atomic.AtomicReference;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036
Nathan Harmatad4f75942016-10-18 08:55:17 +000037/** Common utilities for dealing with paths outside the package roots. */
Nathan Harmata029de3d2015-07-27 18:08:09 +000038public class ExternalFilesHelper {
janakrc3bcb982020-04-14 06:50:08 -070039 private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
Nathan Harmatafee19282017-02-14 20:47:50 +000040
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041 private final AtomicReference<PathPackageLocator> pkgLocator;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000042 private final ExternalFileAction externalFileAction;
Kristina Chodorow5a2936f2016-04-22 17:02:19 +000043 private final BlazeDirectories directories;
nharmata3fb7d342018-02-23 11:37:51 -080044 private final int maxNumExternalFilesToLog;
Nathan Harmatafee19282017-02-14 20:47:50 +000045 private final AtomicInteger numExternalFilesLogged = new AtomicInteger(0);
steinman04dff442021-09-27 08:18:05 -070046 private static final int MAX_EXTERNAL_FILES_TO_TRACK = 2500;
Michajlo Matijkiwdb110942015-03-31 23:41:02 +000047
Nathan Harmata60108832016-03-25 08:02:42 +000048 // These variables are set to true from multiple threads, but only read in the main thread.
Janak Ramakrishnan83431062015-12-08 18:42:16 +000049 // So volatility or an AtomicBoolean is not needed.
Nathan Harmata60108832016-03-25 08:02:42 +000050 private boolean anyOutputFilesSeen = false;
steinman04dff442021-09-27 08:18:05 -070051 private boolean tooManyNonOutputExternalFilesSeen = false;
52 private boolean anyFilesInExternalReposSeen = false;
Janak Ramakrishnan83431062015-12-08 18:42:16 +000053
Googlercbf81592022-06-21 05:00:30 -070054 // This is a set of external files that are not in external repositories.
steinman04dff442021-09-27 08:18:05 -070055 private Set<RootedPath> nonOutputExternalFilesSeen = Sets.newConcurrentHashSet();
Googlerba4862d2019-05-03 06:13:23 -070056
nharmata3fb7d342018-02-23 11:37:51 -080057 private ExternalFilesHelper(
Nathan Harmatad4f75942016-10-18 08:55:17 +000058 AtomicReference<PathPackageLocator> pkgLocator,
Kristina Chodorow5a2936f2016-04-22 17:02:19 +000059 ExternalFileAction externalFileAction,
nharmata3fb7d342018-02-23 11:37:51 -080060 BlazeDirectories directories,
Googlercbf81592022-06-21 05:00:30 -070061 int maxNumExternalFilesToLog) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010062 this.pkgLocator = pkgLocator;
Nathan Harmata60108832016-03-25 08:02:42 +000063 this.externalFileAction = externalFileAction;
Kristina Chodorow5a2936f2016-04-22 17:02:19 +000064 this.directories = directories;
nharmata3fb7d342018-02-23 11:37:51 -080065 this.maxNumExternalFilesToLog = maxNumExternalFilesToLog;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000066 }
67
nharmata3fb7d342018-02-23 11:37:51 -080068 public static ExternalFilesHelper create(
69 AtomicReference<PathPackageLocator> pkgLocator,
70 ExternalFileAction externalFileAction,
Googlercbf81592022-06-21 05:00:30 -070071 BlazeDirectories directories) {
janakr2143cf82020-05-06 16:20:37 -070072 return TestType.isInTest()
Googlercbf81592022-06-21 05:00:30 -070073 ? createForTesting(pkgLocator, externalFileAction, directories)
nharmata3fb7d342018-02-23 11:37:51 -080074 : new ExternalFilesHelper(
Googlercbf81592022-06-21 05:00:30 -070075 pkgLocator, externalFileAction, directories, /*maxNumExternalFilesToLog=*/ 100);
nharmata3fb7d342018-02-23 11:37:51 -080076 }
77
78 public static ExternalFilesHelper createForTesting(
79 AtomicReference<PathPackageLocator> pkgLocator,
80 ExternalFileAction externalFileAction,
81 BlazeDirectories directories) {
nharmata3fb7d342018-02-23 11:37:51 -080082 return new ExternalFilesHelper(
83 pkgLocator,
84 externalFileAction,
85 directories,
86 // These log lines are mostly spam during unit and integration tests.
Googlercbf81592022-06-21 05:00:30 -070087 /*maxNumExternalFilesToLog=*/ 0);
nharmata3fb7d342018-02-23 11:37:51 -080088 }
89
90
Nathan Harmatad4f75942016-10-18 08:55:17 +000091 /**
92 * The action to take when an external path is encountered. See {@link FileType} for the
93 * definition of "external".
94 */
95 public enum ExternalFileAction {
96 /**
97 * For paths of type {@link FileType#EXTERNAL_REPO}, introduce a Skyframe dependency on the
98 * 'external' package.
99 *
100 * <p>This is the default for Bazel, since it's required for correctness of the external
101 * repositories feature.
102 */
103 DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
Nathan Harmata0c7a42a2016-10-13 18:17:48 +0000104
105 /**
Nathan Harmatad4f75942016-10-18 08:55:17 +0000106 * For paths of type {@link FileType#EXTERNAL} or {@link FileType#OUTPUT}, assume the path does
107 * not exist and will never exist.
108 */
109 ASSUME_NON_EXISTENT_AND_IMMUTABLE_FOR_EXTERNAL_PATHS,
110 }
111
112 /** Classification of a path encountered by Bazel. */
113 public enum FileType {
brandjonb2825302021-03-16 12:35:36 -0700114 /**
115 * A path to a file located inside of Bazel, e.g. the bundled builtins_bzl root (for {@code
116 * --experimental_builtins_bzl_root=%bundled%}) that live in an InMemoryFileSystem. These files
117 * must not change throughout the lifetime of the server.
118 */
119 BUNDLED,
120
Googlerba4862d2019-05-03 06:13:23 -0700121 /** A path inside the package roots. */
Nathan Harmatad4f75942016-10-18 08:55:17 +0000122 INTERNAL,
123
124 /**
Googlercbf81592022-06-21 05:00:30 -0700125 * A non {@link #EXTERNAL_REPO} path outside the package roots about which we may make no other
126 * assumptions.
Nathan Harmatad4f75942016-10-18 08:55:17 +0000127 */
128 EXTERNAL,
129
130 /**
131 * A path in Bazel's output tree that's a proper output of an action (*not* a source file in an
132 * external repository). Such files are theoretically mutable, but certain Bazel flags may tell
133 * Bazel to assume these paths are immutable.
Nathan Harmata60108832016-03-25 08:02:42 +0000134 *
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000135 * <p>Note that {@link ExternalFilesHelper#maybeHandleExternalFile} is only used for {@link
jcater02ce2a62020-04-07 08:06:00 -0700136 * com.google.devtools.build.lib.actions.FileStateValue} and {@link DirectoryListingStateValue},
137 * and also note that output files do not normally have corresponding {@link
138 * com.google.devtools.build.lib.actions.FileValue} instances (and thus also {@link
139 * com.google.devtools.build.lib.actions.FileStateValue} instances) in the Skyframe graph
140 * ({@link ArtifactFunction} only uses {@link com.google.devtools.build.lib.actions.FileValue}s
141 * for source files). But {@link com.google.devtools.build.lib.actions.FileStateValue}s for
142 * output files can still make their way into the Skyframe graph if e.g. a source file is a
143 * symlink to an output file.
Nathan Harmata60108832016-03-25 08:02:42 +0000144 */
145 // TODO(nharmata): Consider an alternative design where we have an OutputFileDiffAwareness. This
146 // could work but would first require that we clean up all RootedPath usage.
147 OUTPUT,
148
149 /**
Nathan Harmatad4f75942016-10-18 08:55:17 +0000150 * A path in the part of Bazel's output tree that contains (/ symlinks to) to external
Googlerba4862d2019-05-03 06:13:23 -0700151 * repositories. Every such path under the external repository is generated by the execution of
152 * the corresponding repository rule, so these paths should not be cached by Skyframe before the
153 * RepositoryDirectoryValue is computed.
Nathan Harmata60108832016-03-25 08:02:42 +0000154 */
155 EXTERNAL_REPO,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 }
157
Nathan Harmatad4f75942016-10-18 08:55:17 +0000158 /**
159 * Thrown by {@link #maybeHandleExternalFile} when an applicable path is processed (see
160 * {@link ExternalFileAction#ASSUME_NON_EXISTENT_AND_IMMUTABLE_FOR_EXTERNAL_PATHS}.
161 */
162 static class NonexistentImmutableExternalFileException extends Exception {
163 }
164
Nathan Harmata60108832016-03-25 08:02:42 +0000165 static class ExternalFilesKnowledge {
166 final boolean anyOutputFilesSeen;
steinman04dff442021-09-27 08:18:05 -0700167 final Set<RootedPath> nonOutputExternalFilesSeen;
168 final boolean anyFilesInExternalReposSeen;
169 final boolean tooManyNonOutputExternalFilesSeen;
Nathan Harmata60108832016-03-25 08:02:42 +0000170
steinman04dff442021-09-27 08:18:05 -0700171 private ExternalFilesKnowledge(
172 boolean anyOutputFilesSeen,
173 Set<RootedPath> nonOutputExternalFilesSeen,
174 boolean anyFilesInExternalReposSeen,
175 boolean tooManyNonOutputExternalFilesSeen) {
Nathan Harmata60108832016-03-25 08:02:42 +0000176 this.anyOutputFilesSeen = anyOutputFilesSeen;
steinman04dff442021-09-27 08:18:05 -0700177 this.nonOutputExternalFilesSeen = nonOutputExternalFilesSeen;
178 this.anyFilesInExternalReposSeen = anyFilesInExternalReposSeen;
179 this.tooManyNonOutputExternalFilesSeen = tooManyNonOutputExternalFilesSeen;
Nathan Harmata60108832016-03-25 08:02:42 +0000180 }
181 }
182
183 @ThreadCompatible
184 ExternalFilesKnowledge getExternalFilesKnowledge() {
steinman04dff442021-09-27 08:18:05 -0700185 return new ExternalFilesKnowledge(
186 anyOutputFilesSeen,
187 nonOutputExternalFilesSeen,
188 anyFilesInExternalReposSeen,
189 tooManyNonOutputExternalFilesSeen);
Nathan Harmata60108832016-03-25 08:02:42 +0000190 }
191
192 @ThreadCompatible
193 void setExternalFilesKnowledge(ExternalFilesKnowledge externalFilesKnowledge) {
194 anyOutputFilesSeen = externalFilesKnowledge.anyOutputFilesSeen;
steinman04dff442021-09-27 08:18:05 -0700195 nonOutputExternalFilesSeen = externalFilesKnowledge.nonOutputExternalFilesSeen;
196 anyFilesInExternalReposSeen = externalFilesKnowledge.anyFilesInExternalReposSeen;
197 tooManyNonOutputExternalFilesSeen = externalFilesKnowledge.tooManyNonOutputExternalFilesSeen;
Nathan Harmata60108832016-03-25 08:02:42 +0000198 }
199
200 ExternalFilesHelper cloneWithFreshExternalFilesKnowledge() {
nharmata3fb7d342018-02-23 11:37:51 -0800201 return new ExternalFilesHelper(
Googlercbf81592022-06-21 05:00:30 -0700202 pkgLocator, externalFileAction, directories, maxNumExternalFilesToLog);
Nathan Harmata60108832016-03-25 08:02:42 +0000203 }
204
Googlerba4862d2019-05-03 06:13:23 -0700205 public FileType getAndNoteFileType(RootedPath rootedPath) {
206 return getFileTypeAndRepository(rootedPath).getFirst();
207 }
208
209 private Pair<FileType, RepositoryName> getFileTypeAndRepository(RootedPath rootedPath) {
Googlerba4862d2019-05-03 06:13:23 -0700210 FileType fileType = detectFileType(rootedPath);
steinman04dff442021-09-27 08:18:05 -0700211 if (FileType.EXTERNAL == fileType) {
212 if (nonOutputExternalFilesSeen.size() >= MAX_EXTERNAL_FILES_TO_TRACK) {
213 tooManyNonOutputExternalFilesSeen = true;
214 } else {
215 nonOutputExternalFilesSeen.add(rootedPath);
216 }
217 }
218 if (FileType.EXTERNAL_REPO == fileType) {
219 anyFilesInExternalReposSeen = true;
Googlerba4862d2019-05-03 06:13:23 -0700220 }
221 if (FileType.OUTPUT == fileType) {
222 anyOutputFilesSeen = true;
223 }
224 return Pair.of(fileType, null);
225 }
226
brandjonb2825302021-03-16 12:35:36 -0700227 /**
228 * Returns the classification of a path, and updates this instance's state regarding what kinds of
229 * paths have been seen.
230 */
Googlerba4862d2019-05-03 06:13:23 -0700231 private FileType detectFileType(RootedPath rootedPath) {
brandjonb2825302021-03-16 12:35:36 -0700232 if (rootedPath.getRoot().getFileSystem() instanceof BundledFileSystem) {
233 return FileType.BUNDLED;
234 }
Nathan Harmata60108832016-03-25 08:02:42 +0000235 PathPackageLocator packageLocator = pkgLocator.get();
236 if (packageLocator.getPathEntries().contains(rootedPath.getRoot())) {
237 return FileType.INTERNAL;
238 }
239 // The outputBase may be null if we're not actually running a build.
240 Path outputBase = packageLocator.getOutputBase();
241 if (outputBase == null) {
Nathan Harmatad4f75942016-10-18 08:55:17 +0000242 return FileType.EXTERNAL;
Nathan Harmata60108832016-03-25 08:02:42 +0000243 }
244 if (rootedPath.asPath().startsWith(outputBase)) {
lberki015f5862020-02-20 10:08:29 -0800245 Path externalRepoDir = outputBase.getRelative(LabelConstants.EXTERNAL_REPOSITORY_LOCATION);
Nathan Harmata60108832016-03-25 08:02:42 +0000246 if (rootedPath.asPath().startsWith(externalRepoDir)) {
Nathan Harmata60108832016-03-25 08:02:42 +0000247 return FileType.EXTERNAL_REPO;
248 } else {
Nathan Harmata60108832016-03-25 08:02:42 +0000249 return FileType.OUTPUT;
250 }
251 }
Nathan Harmatad4f75942016-10-18 08:55:17 +0000252 return FileType.EXTERNAL;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 }
254
Michajlo Matijkiwdb110942015-03-31 23:41:02 +0000255 /**
Laszlo Csomordda67a22019-02-20 23:37:05 -0800256 * If this instance is configured with {@link
257 * ExternalFileAction#DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS} and {@code rootedPath} isn't
258 * under a package root then this adds a dependency on the //external package. If the action is
Nathan Harmatad4f75942016-10-18 08:55:17 +0000259 * {@link ExternalFileAction#ASSUME_NON_EXISTENT_AND_IMMUTABLE_FOR_EXTERNAL_PATHS}, it will throw
260 * a {@link NonexistentImmutableExternalFileException} instead.
Michajlo Matijkiwdb110942015-03-31 23:41:02 +0000261 */
Nathan Harmata60108832016-03-25 08:02:42 +0000262 @ThreadSafe
wyv9732d232021-05-20 02:57:24 -0700263 FileType maybeHandleExternalFile(RootedPath rootedPath, SkyFunction.Environment env)
Nathan Harmatad4f75942016-10-18 08:55:17 +0000264 throws NonexistentImmutableExternalFileException, IOException, InterruptedException {
Googlerba4862d2019-05-03 06:13:23 -0700265 Pair<FileType, RepositoryName> pair = getFileTypeAndRepository(rootedPath);
266
267 FileType fileType = Preconditions.checkNotNull(pair.getFirst());
268 switch (fileType) {
brandjonb2825302021-03-16 12:35:36 -0700269 case BUNDLED:
Googlerba4862d2019-05-03 06:13:23 -0700270 case INTERNAL:
271 break;
272 case EXTERNAL:
273 if (numExternalFilesLogged.incrementAndGet() < maxNumExternalFilesToLog) {
janakrc3bcb982020-04-14 06:50:08 -0700274 logger.atInfo().log("Encountered an external path %s", rootedPath);
Googlerba4862d2019-05-03 06:13:23 -0700275 }
276 // fall through
277 case OUTPUT:
278 if (externalFileAction
279 == ExternalFileAction.ASSUME_NON_EXISTENT_AND_IMMUTABLE_FOR_EXTERNAL_PATHS) {
280 throw new NonexistentImmutableExternalFileException();
281 }
282 break;
283 case EXTERNAL_REPO:
284 Preconditions.checkState(
285 externalFileAction == ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
286 externalFileAction);
wyv9732d232021-05-20 02:57:24 -0700287 RepositoryFunction.addExternalFilesDependencies(rootedPath, directories, env);
Googlerba4862d2019-05-03 06:13:23 -0700288 break;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000289 }
Googlerba4862d2019-05-03 06:13:23 -0700290 return fileType;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 }
292}