blob: c10608166cad5e795a5e865411e353646cff02da [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. 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
16import com.google.common.base.Preconditions;
17import com.google.common.collect.ImmutableCollection;
18import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Iterables;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Sets;
24import com.google.common.eventbus.EventBus;
25import com.google.devtools.build.lib.Constants;
26import com.google.devtools.build.lib.events.Event;
27import com.google.devtools.build.lib.events.EventHandler;
28import com.google.devtools.build.lib.events.Location;
29import com.google.devtools.build.lib.events.Reporter;
30import com.google.devtools.build.lib.events.StoredEventHandler;
31import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
32import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
33import com.google.devtools.build.lib.packages.CachingPackageLocator;
34import com.google.devtools.build.lib.packages.InvalidPackageNameException;
35import com.google.devtools.build.lib.packages.NoSuchPackageException;
36import com.google.devtools.build.lib.packages.Package;
37import com.google.devtools.build.lib.packages.PackageFactory;
38import com.google.devtools.build.lib.packages.PackageFactory.Globber;
39import com.google.devtools.build.lib.packages.PackageIdentifier;
40import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
41import com.google.devtools.build.lib.packages.PackageLoadedEvent;
42import com.google.devtools.build.lib.packages.Preprocessor;
43import com.google.devtools.build.lib.packages.RuleVisibility;
44import com.google.devtools.build.lib.packages.Target;
45import com.google.devtools.build.lib.profiler.Profiler;
46import com.google.devtools.build.lib.profiler.ProfilerTask;
47import com.google.devtools.build.lib.skyframe.ASTFileLookupValue.ASTLookupInputException;
48import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
49import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
50import com.google.devtools.build.lib.syntax.BuildFileAST;
51import com.google.devtools.build.lib.syntax.EvalException;
52import com.google.devtools.build.lib.syntax.Label;
53import com.google.devtools.build.lib.syntax.ParserInputSource;
54import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
55import com.google.devtools.build.lib.syntax.Statement;
56import com.google.devtools.build.lib.util.Clock;
57import com.google.devtools.build.lib.util.JavaClock;
58import com.google.devtools.build.lib.util.Pair;
59import com.google.devtools.build.lib.vfs.Path;
60import com.google.devtools.build.lib.vfs.PathFragment;
61import com.google.devtools.build.lib.vfs.RootedPath;
62import com.google.devtools.build.skyframe.SkyFunction;
63import com.google.devtools.build.skyframe.SkyFunctionException;
64import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
65import com.google.devtools.build.skyframe.SkyKey;
66import com.google.devtools.build.skyframe.SkyValue;
67import com.google.devtools.build.skyframe.ValueOrException3;
68import com.google.devtools.build.skyframe.ValueOrException4;
69
70import java.io.IOException;
71import java.util.Collection;
72import java.util.HashMap;
73import java.util.List;
74import java.util.Map;
75import java.util.Map.Entry;
76import java.util.Set;
77import java.util.concurrent.ConcurrentMap;
78import java.util.concurrent.atomic.AtomicBoolean;
79import java.util.concurrent.atomic.AtomicInteger;
80import java.util.concurrent.atomic.AtomicReference;
81
82import javax.annotation.Nullable;
83
84/**
85 * A SkyFunction for {@link PackageValue}s.
86 */
87public class PackageFunction implements SkyFunction {
88
89 private final EventHandler reporter;
90 private final PackageFactory packageFactory;
91 private final CachingPackageLocator packageLocator;
92 private final ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache;
93 private final AtomicBoolean showLoadingProgress;
94 private final AtomicReference<EventBus> eventBus;
95 private final AtomicInteger numPackagesLoaded;
96 private final Profiler profiler = Profiler.instance();
97
98 private static final PathFragment PRELUDE_FILE_FRAGMENT =
99 new PathFragment(Constants.PRELUDE_FILE_DEPOT_RELATIVE_PATH);
100
101 static final String DEFAULTS_PACKAGE_NAME = "tools/defaults";
102 public static final String EXTERNAL_PACKAGE_NAME = "external";
103
104 static {
105 Preconditions.checkArgument(!PRELUDE_FILE_FRAGMENT.isAbsolute());
106 }
107
108 public PackageFunction(Reporter reporter, PackageFactory packageFactory,
109 CachingPackageLocator pkgLocator, AtomicBoolean showLoadingProgress,
110 ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache,
111 AtomicReference<EventBus> eventBus, AtomicInteger numPackagesLoaded) {
112 this.reporter = reporter;
113
114 this.packageFactory = packageFactory;
115 this.packageLocator = pkgLocator;
116 this.showLoadingProgress = showLoadingProgress;
117 this.packageFunctionCache = packageFunctionCache;
118 this.eventBus = eventBus;
119 this.numPackagesLoaded = numPackagesLoaded;
120 }
121
122 private static void maybeThrowFilesystemInconsistency(String packageName,
123 Exception skyframeException, boolean packageWasInError)
124 throws InternalInconsistentFilesystemException {
125 if (!packageWasInError) {
126 throw new InternalInconsistentFilesystemException(packageName, "Encountered error '"
127 + skyframeException.getMessage() + "' but didn't encounter it when doing the same thing "
128 + "earlier in the build");
129 }
130 }
131
132 /**
133 * Marks the given dependencies, and returns those already present. Ignores any exception
134 * thrown while building the dependency, except for filesystem inconsistencies.
135 *
136 * <p>We need to mark dependencies implicitly used by the legacy package loading code, but we
137 * don't care about any skyframe errors since the package knows whether it's in error or not.
138 */
139 private static Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean>
140 getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(String packageName,
141 Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
142 throws InternalInconsistentFilesystemException {
143 Preconditions.checkState(
144 Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE_LOOKUP)), depKeys);
145 boolean packageShouldBeInError = packageWasInError;
146 ImmutableMap.Builder<PathFragment, PackageLookupValue> builder = ImmutableMap.builder();
147 for (Map.Entry<SkyKey, ValueOrException3<BuildFileNotFoundException,
148 InconsistentFilesystemException, FileSymlinkCycleException>> entry :
149 env.getValuesOrThrow(depKeys, BuildFileNotFoundException.class,
150 InconsistentFilesystemException.class,
151 FileSymlinkCycleException.class).entrySet()) {
152 PathFragment pkgName = ((PackageIdentifier) entry.getKey().argument()).getPackageFragment();
153 try {
154 PackageLookupValue value = (PackageLookupValue) entry.getValue().get();
155 if (value != null) {
156 builder.put(pkgName, value);
157 }
158 } catch (BuildFileNotFoundException e) {
159 maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
160 } catch (InconsistentFilesystemException e) {
161 throw new InternalInconsistentFilesystemException(packageName, e);
162 } catch (FileSymlinkCycleException e) {
163 // Legacy doesn't detect symlink cycles.
164 packageShouldBeInError = true;
165 }
166 }
167 return Pair.of(builder.build(), packageShouldBeInError);
168 }
169
170 private static boolean markFileDepsAndPropagateInconsistentFilesystemExceptions(
171 String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
172 throws InternalInconsistentFilesystemException {
173 Preconditions.checkState(
174 Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.FILE)), depKeys);
175 boolean packageShouldBeInError = packageWasInError;
176 for (Map.Entry<SkyKey, ValueOrException3<IOException, FileSymlinkCycleException,
177 InconsistentFilesystemException>> entry : env.getValuesOrThrow(depKeys, IOException.class,
178 FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
179 try {
180 entry.getValue().get();
181 } catch (IOException e) {
182 maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
183 } catch (FileSymlinkCycleException e) {
184 // Legacy doesn't detect symlink cycles.
185 packageShouldBeInError = true;
186 } catch (InconsistentFilesystemException e) {
187 throw new InternalInconsistentFilesystemException(packageName, e);
188 }
189 }
190 return packageShouldBeInError;
191 }
192
193 private static boolean markGlobDepsAndPropagateInconsistentFilesystemExceptions(
194 String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
195 throws InternalInconsistentFilesystemException {
196 Preconditions.checkState(
197 Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.GLOB)), depKeys);
198 boolean packageShouldBeInError = packageWasInError;
199 for (Map.Entry<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException,
200 FileSymlinkCycleException, InconsistentFilesystemException>> entry :
201 env.getValuesOrThrow(depKeys, IOException.class, BuildFileNotFoundException.class,
202 FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
203 try {
204 entry.getValue().get();
205 } catch (IOException | BuildFileNotFoundException e) {
206 maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
207 } catch (FileSymlinkCycleException e) {
208 // Legacy doesn't detect symlink cycles.
209 packageShouldBeInError = true;
210 } catch (InconsistentFilesystemException e) {
211 throw new InternalInconsistentFilesystemException(packageName, e);
212 }
213 }
214 return packageShouldBeInError;
215 }
216
217 /**
218 * Marks dependencies implicitly used by legacy package loading code, after the fact. Note that
219 * the given package might already be in error.
220 *
221 * <p>Any skyframe exceptions encountered here are ignored, as similar errors should have
222 * already been encountered by legacy package loading (if not, then the filesystem is
223 * inconsistent).
224 */
225 private static boolean markDependenciesAndPropagateInconsistentFilesystemExceptions(
226 Package pkg, Environment env, Collection<Pair<String, Boolean>> globPatterns,
227 Map<Label, Path> subincludes) throws InternalInconsistentFilesystemException {
228 boolean packageShouldBeInError = pkg.containsErrors();
229
230 // TODO(bazel-team): This means that many packages will have to be preprocessed twice. Ouch!
231 // We need a better continuation mechanism to avoid repeating work. [skyframe-loading]
232
233 // TODO(bazel-team): It would be preferable to perform I/O from the package preprocessor via
234 // Skyframe rather than add (potentially incomplete) dependencies after the fact.
235 // [skyframe-loading]
236
237 Set<SkyKey> subincludePackageLookupDepKeys = Sets.newHashSet();
238 for (Label label : pkg.getSubincludeLabels()) {
239 // Declare a dependency on the package lookup for the package giving access to the label.
240 subincludePackageLookupDepKeys.add(PackageLookupValue.key(label.getPackageFragment()));
241 }
242 Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean> subincludePackageLookupResult =
243 getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(pkg.getName(),
244 subincludePackageLookupDepKeys, env, pkg.containsErrors());
245 Map<PathFragment, PackageLookupValue> subincludePackageLookupDeps =
246 subincludePackageLookupResult.getFirst();
247 packageShouldBeInError = subincludePackageLookupResult.getSecond();
248 List<SkyKey> subincludeFileDepKeys = Lists.newArrayList();
249 for (Entry<Label, Path> subincludeEntry : subincludes.entrySet()) {
250 // Ideally, we would have a direct dependency on the target with the given label, but then
251 // subincluding a file from the same package will cause a dependency cycle, since targets
252 // depend on their containing packages.
253 Label label = subincludeEntry.getKey();
254 PackageLookupValue subincludePackageLookupValue =
255 subincludePackageLookupDeps.get(label.getPackageFragment());
256 if (subincludePackageLookupValue != null) {
257 // Declare a dependency on the actual file that was subincluded.
258 Path subincludeFilePath = subincludeEntry.getValue();
259 if (subincludeFilePath != null) {
260 if (!subincludePackageLookupValue.packageExists()) {
261 // Legacy blaze puts a non-null path when only when the package does indeed exist.
262 throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
263 "Unexpected package in %s. Was it modified during the build?", subincludeFilePath));
264 }
265 // Sanity check for consistency of Skyframe and legacy blaze.
266 Path subincludeFilePathSkyframe =
267 subincludePackageLookupValue.getRoot().getRelative(label.toPathFragment());
268 if (!subincludeFilePathSkyframe.equals(subincludeFilePath)) {
269 throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
270 "Inconsistent package location for %s: '%s' vs '%s'. "
271 + "Was the source tree modified during the build?",
272 label.getPackageFragment(), subincludeFilePathSkyframe, subincludeFilePath));
273 }
274 // The actual file may be under a different package root than the package being
275 // constructed.
276 SkyKey subincludeSkyKey =
277 FileValue.key(RootedPath.toRootedPath(subincludePackageLookupValue.getRoot(),
278 subincludeFilePath));
279 subincludeFileDepKeys.add(subincludeSkyKey);
280 }
281 }
282 }
283 packageShouldBeInError = markFileDepsAndPropagateInconsistentFilesystemExceptions(
284 pkg.getName(), subincludeFileDepKeys, env, pkg.containsErrors());
285 // Another concern is a subpackage cutting off the subinclude label, but this is already
286 // handled by the legacy package loading code which calls into our SkyframePackageLocator.
287
288 // TODO(bazel-team): In the long term, we want to actually resolve the glob patterns within
289 // Skyframe. For now, just logging the glob requests provides correct incrementality and
290 // adequate performance.
291 PackageIdentifier packageId = pkg.getPackageIdentifier();
292 List<SkyKey> globDepKeys = Lists.newArrayList();
293 for (Pair<String, Boolean> globPattern : globPatterns) {
294 String pattern = globPattern.getFirst();
295 boolean excludeDirs = globPattern.getSecond();
296 SkyKey globSkyKey;
297 try {
298 globSkyKey = GlobValue.key(packageId, pattern, excludeDirs);
299 } catch (InvalidGlobPatternException e) {
300 // Globs that make it to pkg.getGlobPatterns() should already be filtered for errors.
301 throw new IllegalStateException(e);
302 }
303 globDepKeys.add(globSkyKey);
304 }
305 packageShouldBeInError = markGlobDepsAndPropagateInconsistentFilesystemExceptions(
306 pkg.getName(), globDepKeys, env, pkg.containsErrors());
307 return packageShouldBeInError;
308 }
309
310 /**
311 * Adds a dependency on the WORKSPACE file, representing it as a special type of package.
312 * @throws PackageFunctionException if there is an error computing the workspace file or adding
313 * its rules to the //external package.
314 */
315 private SkyValue getExternalPackage(Environment env, Path packageLookupPath)
316 throws PackageFunctionException {
317 RootedPath workspacePath = RootedPath.toRootedPath(
318 packageLookupPath, new PathFragment("WORKSPACE"));
319 SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath);
320 WorkspaceFileValue workspace = null;
321 try {
322 workspace = (WorkspaceFileValue) env.getValueOrThrow(workspaceKey, IOException.class,
323 FileSymlinkCycleException.class, InconsistentFilesystemException.class,
324 EvalException.class);
325 } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException
326 | EvalException e) {
327 throw new PackageFunctionException(new BadWorkspaceFileException(e.getMessage()),
328 Transience.PERSISTENT);
329 }
330 if (workspace == null) {
331 return null;
332 }
333
334 Package pkg = workspace.getPackage();
335 Event.replayEventsOn(env.getListener(), pkg.getEvents());
336 if (pkg.containsErrors()) {
337 throw new PackageFunctionException(new BuildFileContainsErrorsException("external",
338 "Package 'external' contains errors"),
339 pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
340 }
341
342 return new PackageValue(pkg);
343 }
344
345 @Override
346 public SkyValue compute(SkyKey key, Environment env) throws PackageFunctionException,
347 InterruptedException {
348 PackageIdentifier packageId = (PackageIdentifier) key.argument();
349 PathFragment packageNameFragment = packageId.getPackageFragment();
350 String packageName = packageNameFragment.getPathString();
351
352 SkyKey packageLookupKey = PackageLookupValue.key(packageId);
353 PackageLookupValue packageLookupValue;
354 try {
355 packageLookupValue = (PackageLookupValue)
356 env.getValueOrThrow(packageLookupKey, BuildFileNotFoundException.class,
357 InconsistentFilesystemException.class);
358 } catch (BuildFileNotFoundException e) {
359 throw new PackageFunctionException(e, Transience.PERSISTENT);
360 } catch (InconsistentFilesystemException e) {
361 // This error is not transient from the perspective of the PackageFunction.
362 throw new PackageFunctionException(
363 new InternalInconsistentFilesystemException(packageName, e), Transience.PERSISTENT);
364 }
365 if (packageLookupValue == null) {
366 return null;
367 }
368
369 if (!packageLookupValue.packageExists()) {
370 switch (packageLookupValue.getErrorReason()) {
371 case NO_BUILD_FILE:
372 case DELETED_PACKAGE:
373 case NO_EXTERNAL_PACKAGE:
374 throw new PackageFunctionException(new BuildFileNotFoundException(packageName,
375 packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
376 case INVALID_PACKAGE_NAME:
377 throw new PackageFunctionException(new InvalidPackageNameException(packageName,
378 packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
379 default:
380 // We should never get here.
381 Preconditions.checkState(false);
382 }
383 }
384
385 if (packageName.equals(EXTERNAL_PACKAGE_NAME)) {
386 return getExternalPackage(env, packageLookupValue.getRoot());
387 }
388
Googler9f212f72015-02-09 14:42:21 +0000389 PathFragment buildFileFragment = packageNameFragment.getChild("BUILD");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 RootedPath buildFileRootedPath = RootedPath.toRootedPath(packageLookupValue.getRoot(),
Googler9f212f72015-02-09 14:42:21 +0000391 buildFileFragment);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100392 FileValue buildFileValue;
393 try {
394 buildFileValue = (FileValue) env.getValueOrThrow(FileValue.key(buildFileRootedPath),
395 IOException.class, FileSymlinkCycleException.class,
396 InconsistentFilesystemException.class);
397 } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
398 throw new IllegalStateException("Package lookup succeeded but encountered error when "
399 + "getting FileValue for BUILD file directly.", e);
400 }
401 if (buildFileValue == null) {
402 return null;
403 }
404 Preconditions.checkState(buildFileValue.exists(),
405 "Package lookup succeeded but BUILD file doesn't exist");
406 Path buildFilePath = buildFileRootedPath.asPath();
407
408 String replacementContents = null;
409 if (packageName.equals(DEFAULTS_PACKAGE_NAME)) {
410 replacementContents = PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.get(env);
411 if (replacementContents == null) {
412 return null;
413 }
414 }
415
416 RuleVisibility defaultVisibility = PrecomputedValue.DEFAULT_VISIBILITY.get(env);
417 if (defaultVisibility == null) {
418 return null;
419 }
420
421 ASTFileLookupValue astLookupValue = null;
422 SkyKey astLookupKey = null;
423 try {
424 astLookupKey = ASTFileLookupValue.key(PRELUDE_FILE_FRAGMENT);
425 } catch (ASTLookupInputException e) {
426 // There's a static check ensuring that PRELUDE_FILE_FRAGMENT is relative.
427 throw new IllegalStateException(e);
428 }
429 try {
430 astLookupValue = (ASTFileLookupValue) env.getValueOrThrow(astLookupKey,
431 ErrorReadingSkylarkExtensionException.class, InconsistentFilesystemException.class);
432 } catch (ErrorReadingSkylarkExtensionException | InconsistentFilesystemException e) {
433 throw new PackageFunctionException(new BadPreludeFileException(packageName, e.getMessage()),
434 Transience.PERSISTENT);
435 }
436 if (astLookupValue == null) {
437 return null;
438 }
439 List<Statement> preludeStatements = astLookupValue == ASTFileLookupValue.NO_FILE
440 ? ImmutableList.<Statement>of() : astLookupValue.getAST().getStatements();
441
442 // Load the BUILD file AST and handle Skylark dependencies. This way BUILD files are
443 // only loaded twice if there are unavailable Skylark or package dependencies or an
444 // IOException occurs. Note that the BUILD files are still parsed two times.
445 ParserInputSource inputSource;
446 try {
447 if (showLoadingProgress.get() && !packageFunctionCache.containsKey(packageId)) {
448 // TODO(bazel-team): don't duplicate the loading message if there are unavailable
449 // Skylark dependencies.
450 reporter.handle(Event.progress("Loading package: " + packageName));
451 }
452 inputSource = ParserInputSource.create(buildFilePath);
453 } catch (IOException e) {
454 env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
455 // Note that we did this work, so we should conservatively report this error as transient.
456 throw new PackageFunctionException(new BuildFileContainsErrorsException(
457 packageName, e.getMessage()), Transience.TRANSIENT);
458 }
Googler9f212f72015-02-09 14:42:21 +0000459 SkylarkImportResult importResult = fetchImportsFromBuildFile(buildFilePath, buildFileFragment,
460 packageId.getRepository(), preludeStatements, inputSource, packageName, env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100461 if (importResult == null) {
462 return null;
463 }
464
465 Package.LegacyBuilder legacyPkgBuilder = loadPackage(inputSource, replacementContents,
466 packageId, buildFilePath, defaultVisibility, preludeStatements, importResult);
467 legacyPkgBuilder.buildPartial();
468 try {
469 handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
470 packageLookupValue.getRoot(), packageId, legacyPkgBuilder, env);
471 } catch (InternalInconsistentFilesystemException e) {
472 packageFunctionCache.remove(packageId);
473 throw new PackageFunctionException(e,
474 e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
475 }
476 if (env.valuesMissing()) {
477 // The package we just loaded will be in the {@code packageFunctionCache} next when this
478 // SkyFunction is called again.
479 return null;
480 }
481 Collection<Pair<String, Boolean>> globPatterns = legacyPkgBuilder.getGlobPatterns();
482 Map<Label, Path> subincludes = legacyPkgBuilder.getSubincludes();
483 Package pkg = legacyPkgBuilder.finishBuild();
484 Event.replayEventsOn(env.getListener(), pkg.getEvents());
485 boolean packageShouldBeConsideredInError = pkg.containsErrors();
486 try {
487 packageShouldBeConsideredInError =
488 markDependenciesAndPropagateInconsistentFilesystemExceptions(pkg, env,
489 globPatterns, subincludes);
490 } catch (InternalInconsistentFilesystemException e) {
491 packageFunctionCache.remove(packageId);
492 throw new PackageFunctionException(e,
493 e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
494 }
495
496 if (env.valuesMissing()) {
497 return null;
498 }
499 // We know this SkyFunction will not be called again, so we can remove the cache entry.
500 packageFunctionCache.remove(packageId);
501
502 if (packageShouldBeConsideredInError) {
503 throw new PackageFunctionException(new BuildFileContainsErrorsException(pkg,
504 "Package '" + packageName + "' contains errors"),
505 pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
506 }
507 return new PackageValue(pkg);
508 }
509
Googler9f212f72015-02-09 14:42:21 +0000510 private SkylarkImportResult fetchImportsFromBuildFile(Path buildFilePath,
511 PathFragment buildFileFragment, RepositoryName repo,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100512 List<Statement> preludeStatements, ParserInputSource inputSource,
513 String packageName, Environment env) throws PackageFunctionException {
514 StoredEventHandler eventHandler = new StoredEventHandler();
515 BuildFileAST buildFileAST = BuildFileAST.parseBuildFile(
516 inputSource, preludeStatements, eventHandler, null, true);
517
518 if (eventHandler.hasErrors()) {
519 // In case of Python preprocessing, errors have already been reported (see checkSyntax).
520 // In other cases, errors will be reported later.
521 // TODO(bazel-team): maybe we could get rid of checkSyntax and always report errors here?
522 return new SkylarkImportResult(
523 ImmutableMap.<PathFragment, SkylarkEnvironment>of(),
524 ImmutableList.<Label>of());
525 }
526
527 ImmutableCollection<PathFragment> imports = buildFileAST.getImports();
528 Map<PathFragment, SkylarkEnvironment> importMap = new HashMap<>();
529 ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
530 try {
531 for (PathFragment importFile : imports) {
Googler9f212f72015-02-09 14:42:21 +0000532 SkyKey importsLookupKey =
533 SkylarkImportLookupValue.key(repo, buildFileFragment, importFile);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100534 SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue)
535 env.getValueOrThrow(importsLookupKey, SkylarkImportFailedException.class,
536 InconsistentFilesystemException.class, ASTLookupInputException.class,
537 BuildFileNotFoundException.class);
538 if (importLookupValue != null) {
539 importMap.put(importFile, importLookupValue.getImportedEnvironment());
540 fileDependencies.add(importLookupValue.getDependency());
541 }
542 }
543 } catch (SkylarkImportFailedException e) {
544 env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
545 throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
546 e.getMessage()), Transience.PERSISTENT);
547 } catch (InconsistentFilesystemException e) {
548 throw new PackageFunctionException(new InternalInconsistentFilesystemException(packageName,
549 e), Transience.PERSISTENT);
550 } catch (ASTLookupInputException e) {
551 // The load syntax is bad in the BUILD file so BuildFileContainsErrorsException is OK.
552 throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
553 e.getMessage()), Transience.PERSISTENT);
554 } catch (BuildFileNotFoundException e) {
555 throw new PackageFunctionException(e, Transience.PERSISTENT);
556 }
557 if (env.valuesMissing()) {
558 // There are unavailable Skylark dependencies.
559 return null;
560 }
561 return new SkylarkImportResult(importMap, transitiveClosureOfLabels(fileDependencies.build()));
562 }
563
564 private ImmutableList<Label> transitiveClosureOfLabels(
565 ImmutableList<SkylarkFileDependency> immediateDeps) {
566 Set<Label> transitiveClosure = Sets.newHashSet();
567 transitiveClosureOfLabels(immediateDeps, transitiveClosure);
568 return ImmutableList.copyOf(transitiveClosure);
569 }
570
571 private void transitiveClosureOfLabels(
572 ImmutableList<SkylarkFileDependency> immediateDeps, Set<Label> transitiveClosure) {
573 for (SkylarkFileDependency dep : immediateDeps) {
574 if (!transitiveClosure.contains(dep.getLabel())) {
575 transitiveClosure.add(dep.getLabel());
576 transitiveClosureOfLabels(dep.getDependencies(), transitiveClosure);
577 }
578 }
579 }
580
581 @Nullable
582 @Override
583 public String extractTag(SkyKey skyKey) {
584 return null;
585 }
586
587 private static void handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
588 Path pkgRoot, PackageIdentifier pkgId, Package.LegacyBuilder pkgBuilder, Environment env)
589 throws InternalInconsistentFilesystemException {
590 Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet();
591 Map<Target, SkyKey> targetToKey = new HashMap<>();
592 for (Target target : pkgBuilder.getTargets()) {
593 PathFragment dir = target.getLabel().toPathFragment().getParentDirectory();
594 PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
595 if (dir.equals(pkgId.getPackageFragment())) {
596 continue;
597 }
598 SkyKey key = ContainingPackageLookupValue.key(dirId);
599 targetToKey.put(target, key);
600 containingPkgLookupKeys.add(key);
601 }
602 Map<Label, SkyKey> subincludeToKey = new HashMap<>();
603 for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
604 PathFragment dir = subincludeLabel.toPathFragment().getParentDirectory();
605 PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
606 if (dir.equals(pkgId.getPackageFragment())) {
607 continue;
608 }
609 SkyKey key = ContainingPackageLookupValue.key(dirId);
610 subincludeToKey.put(subincludeLabel, key);
611 containingPkgLookupKeys.add(ContainingPackageLookupValue.key(dirId));
612 }
613 Map<SkyKey, ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
614 FileSymlinkCycleException>> containingPkgLookupValues = env.getValuesOrThrow(
615 containingPkgLookupKeys, BuildFileNotFoundException.class,
616 InconsistentFilesystemException.class, FileSymlinkCycleException.class);
617 if (env.valuesMissing()) {
618 return;
619 }
620 for (Target target : ImmutableSet.copyOf(pkgBuilder.getTargets())) {
621 SkyKey key = targetToKey.get(target);
622 if (!containingPkgLookupValues.containsKey(key)) {
623 continue;
624 }
625 ContainingPackageLookupValue containingPackageLookupValue =
626 getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
627 pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
628 if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, target.getLabel(),
629 target.getLocation(), containingPackageLookupValue)) {
630 pkgBuilder.removeTarget(target);
631 pkgBuilder.setContainsErrors();
632 }
633 }
634 for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
635 SkyKey key = subincludeToKey.get(subincludeLabel);
636 if (!containingPkgLookupValues.containsKey(key)) {
637 continue;
638 }
639 ContainingPackageLookupValue containingPackageLookupValue =
640 getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
641 pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
642 if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, subincludeLabel,
643 /*location=*/null, containingPackageLookupValue)) {
644 pkgBuilder.setContainsErrors();
645 }
646 }
647 }
648
649 @Nullable
650 private static ContainingPackageLookupValue
651 getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(String packageName,
652 ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
653 FileSymlinkCycleException> containingPkgLookupValueOrException, Environment env)
654 throws InternalInconsistentFilesystemException {
655 try {
656 return (ContainingPackageLookupValue) containingPkgLookupValueOrException.get();
657 } catch (BuildFileNotFoundException | FileSymlinkCycleException e) {
658 env.getListener().handle(Event.error(null, e.getMessage()));
659 return null;
660 } catch (InconsistentFilesystemException e) {
661 throw new InternalInconsistentFilesystemException(packageName, e);
662 }
663 }
664
665 private static boolean maybeAddEventAboutLabelCrossingSubpackage(
666 Package.LegacyBuilder pkgBuilder, Path pkgRoot, Label label, @Nullable Location location,
667 @Nullable ContainingPackageLookupValue containingPkgLookupValue) {
668 if (containingPkgLookupValue == null) {
669 return true;
670 }
671 if (!containingPkgLookupValue.hasContainingPackage()) {
672 // The missing package here is a problem, but it's not an error from the perspective of
673 // PackageFunction.
674 return false;
675 }
676 PackageIdentifier containingPkg = containingPkgLookupValue.getContainingPackageName();
677 if (containingPkg.equals(label.getPackageIdentifier())) {
678 // The label does not cross a subpackage boundary.
679 return false;
680 }
681 if (!containingPkg.getPackageFragment().startsWith(label.getPackageFragment())) {
682 // This label is referencing an imaginary package, because the containing package should
683 // extend the label's package: if the label is //a/b:c/d, the containing package could be
684 // //a/b/c or //a/b, but should never be //a. Usually such errors will be caught earlier, but
685 // in some exceptional cases (such as a Python-aware BUILD file catching its own io
686 // exceptions), it reaches here, and we tolerate it.
687 return false;
688 }
689 PathFragment labelNameFragment = new PathFragment(label.getName());
690 String message = String.format("Label '%s' crosses boundary of subpackage '%s'",
691 label, containingPkg);
692 Path containingRoot = containingPkgLookupValue.getContainingPackageRoot();
693 if (pkgRoot.equals(containingRoot)) {
694 PathFragment labelNameInContainingPackage = labelNameFragment.subFragment(
695 containingPkg.getPackageFragment().segmentCount()
696 - label.getPackageFragment().segmentCount(),
697 labelNameFragment.segmentCount());
698 message += " (perhaps you meant to put the colon here: "
699 + "'//" + containingPkg + ":" + labelNameInContainingPackage + "'?)";
700 } else {
701 message += " (have you deleted " + containingPkg + "/BUILD? "
702 + "If so, use the --deleted_packages=" + containingPkg + " option)";
703 }
704 pkgBuilder.addEvent(Event.error(location, message));
705 return true;
706 }
707
708 /**
709 * Constructs a {@link Package} object for the given package using legacy package loading.
710 * Note that the returned package may be in error.
711 */
712 private Package.LegacyBuilder loadPackage(ParserInputSource inputSource,
713 @Nullable String replacementContents,
714 PackageIdentifier packageId, Path buildFilePath, RuleVisibility defaultVisibility,
715 List<Statement> preludeStatements, SkylarkImportResult importResult)
716 throws InterruptedException {
717 ParserInputSource replacementSource = replacementContents == null ? null
718 : ParserInputSource.create(replacementContents, buildFilePath);
719 Package.LegacyBuilder pkgBuilder = packageFunctionCache.get(packageId);
720 if (pkgBuilder == null) {
721 Clock clock = new JavaClock();
722 long startTime = clock.nanoTime();
723 profiler.startTask(ProfilerTask.CREATE_PACKAGE, packageId.toString());
724 try {
725 Globber globber = packageFactory.createLegacyGlobber(buildFilePath.getParentDirectory(),
726 packageId, packageLocator);
727 StoredEventHandler localReporter = new StoredEventHandler();
728 Preprocessor.Result preprocessingResult = replacementSource == null
729 ? packageFactory.preprocess(packageId, buildFilePath, inputSource, globber,
730 localReporter)
731 : Preprocessor.Result.noPreprocessing(replacementSource);
732 pkgBuilder = packageFactory.createPackageFromPreprocessingResult(packageId, buildFilePath,
733 preprocessingResult, localReporter.getEvents(), preludeStatements,
734 importResult.importMap, importResult.fileDependencies, packageLocator,
735 defaultVisibility, globber);
736 if (eventBus.get() != null) {
737 eventBus.get().post(new PackageLoadedEvent(packageId.toString(),
738 (clock.nanoTime() - startTime) / (1000 * 1000),
739 // It's impossible to tell if the package was loaded before, so we always pass false.
740 /*reloading=*/false,
741 // This isn't completely correct since we may encounter errors later (e.g. filesystem
742 // inconsistencies)
743 !pkgBuilder.containsErrors()));
744 }
745 numPackagesLoaded.incrementAndGet();
746 packageFunctionCache.put(packageId, pkgBuilder);
747 } finally {
748 profiler.completeTask(ProfilerTask.CREATE_PACKAGE);
749 }
750 }
751 return pkgBuilder;
752 }
753
754 private static class InternalInconsistentFilesystemException extends NoSuchPackageException {
755 private boolean isTransient;
756
757 /**
758 * Used to represent a filesystem inconsistency discovered outside the
759 * {@link PackageFunction}.
760 */
761 public InternalInconsistentFilesystemException(String packageName,
762 InconsistentFilesystemException e) {
763 super(packageName, e.getMessage(), e);
764 // This is not a transient error from the perspective of the PackageFunction.
765 this.isTransient = false;
766 }
767
768 /** Used to represent a filesystem inconsistency discovered by the {@link PackageFunction}. */
769 public InternalInconsistentFilesystemException(String packageName,
770 String inconsistencyMessage) {
771 this(packageName, new InconsistentFilesystemException(inconsistencyMessage));
772 this.isTransient = true;
773 }
774
775 public boolean isTransient() {
776 return isTransient;
777 }
778 }
779
780 private static class BadWorkspaceFileException extends NoSuchPackageException {
781 private BadWorkspaceFileException(String message) {
782 super("external", "Error encountered while dealing with the WORKSPACE file: " + message);
783 }
784 }
785
786 private static class BadPreludeFileException extends NoSuchPackageException {
787 private BadPreludeFileException(String packageName, String message) {
788 super(packageName, "Error encountered while reading the prelude file: " + message);
789 }
790 }
791
792 /**
793 * Used to declare all the exception types that can be wrapped in the exception thrown by
794 * {@link PackageFunction#compute}.
795 */
796 private static class PackageFunctionException extends SkyFunctionException {
797 public PackageFunctionException(NoSuchPackageException e, Transience transience) {
798 super(e, transience);
799 }
800 }
801
802 /** A simple value class to store the result of the Skylark imports.*/
803 private static final class SkylarkImportResult {
804 private final Map<PathFragment, SkylarkEnvironment> importMap;
805 private final ImmutableList<Label> fileDependencies;
806 private SkylarkImportResult(Map<PathFragment, SkylarkEnvironment> importMap,
807 ImmutableList<Label> fileDependencies) {
808 this.importMap = importMap;
809 this.fileDependencies = fileDependencies;
810 }
811 }
812}