aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java809
1 files changed, 809 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
new file mode 100644
index 0000000000..2404b99436
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -0,0 +1,809 @@
+// Copyright 2014 Google Inc. 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 com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.Constants;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
+import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
+import com.google.devtools.build.lib.packages.CachingPackageLocator;
+import com.google.devtools.build.lib.packages.InvalidPackageNameException;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageFactory.Globber;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.PackageLoadedEvent;
+import com.google.devtools.build.lib.packages.Preprocessor;
+import com.google.devtools.build.lib.packages.RuleVisibility;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.skyframe.ASTFileLookupValue.ASTLookupInputException;
+import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
+import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.Statement;
+import com.google.devtools.build.lib.util.Clock;
+import com.google.devtools.build.lib.util.JavaClock;
+import com.google.devtools.build.lib.util.Pair;
+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.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrException3;
+import com.google.devtools.build.skyframe.ValueOrException4;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
+
+/**
+ * A SkyFunction for {@link PackageValue}s.
+ */
+public class PackageFunction implements SkyFunction {
+
+ private final EventHandler reporter;
+ private final PackageFactory packageFactory;
+ private final CachingPackageLocator packageLocator;
+ private final ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache;
+ private final AtomicBoolean showLoadingProgress;
+ private final AtomicReference<EventBus> eventBus;
+ private final AtomicInteger numPackagesLoaded;
+ private final Profiler profiler = Profiler.instance();
+
+ private static final PathFragment PRELUDE_FILE_FRAGMENT =
+ new PathFragment(Constants.PRELUDE_FILE_DEPOT_RELATIVE_PATH);
+
+ static final String DEFAULTS_PACKAGE_NAME = "tools/defaults";
+ public static final String EXTERNAL_PACKAGE_NAME = "external";
+
+ static {
+ Preconditions.checkArgument(!PRELUDE_FILE_FRAGMENT.isAbsolute());
+ }
+
+ public PackageFunction(Reporter reporter, PackageFactory packageFactory,
+ CachingPackageLocator pkgLocator, AtomicBoolean showLoadingProgress,
+ ConcurrentMap<PackageIdentifier, Package.LegacyBuilder> packageFunctionCache,
+ AtomicReference<EventBus> eventBus, AtomicInteger numPackagesLoaded) {
+ this.reporter = reporter;
+
+ this.packageFactory = packageFactory;
+ this.packageLocator = pkgLocator;
+ this.showLoadingProgress = showLoadingProgress;
+ this.packageFunctionCache = packageFunctionCache;
+ this.eventBus = eventBus;
+ this.numPackagesLoaded = numPackagesLoaded;
+ }
+
+ private static void maybeThrowFilesystemInconsistency(String packageName,
+ Exception skyframeException, boolean packageWasInError)
+ throws InternalInconsistentFilesystemException {
+ if (!packageWasInError) {
+ throw new InternalInconsistentFilesystemException(packageName, "Encountered error '"
+ + skyframeException.getMessage() + "' but didn't encounter it when doing the same thing "
+ + "earlier in the build");
+ }
+ }
+
+ /**
+ * Marks the given dependencies, and returns those already present. Ignores any exception
+ * thrown while building the dependency, except for filesystem inconsistencies.
+ *
+ * <p>We need to mark dependencies implicitly used by the legacy package loading code, but we
+ * don't care about any skyframe errors since the package knows whether it's in error or not.
+ */
+ private static Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean>
+ getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(String packageName,
+ Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+ throws InternalInconsistentFilesystemException {
+ Preconditions.checkState(
+ Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE_LOOKUP)), depKeys);
+ boolean packageShouldBeInError = packageWasInError;
+ ImmutableMap.Builder<PathFragment, PackageLookupValue> builder = ImmutableMap.builder();
+ for (Map.Entry<SkyKey, ValueOrException3<BuildFileNotFoundException,
+ InconsistentFilesystemException, FileSymlinkCycleException>> entry :
+ env.getValuesOrThrow(depKeys, BuildFileNotFoundException.class,
+ InconsistentFilesystemException.class,
+ FileSymlinkCycleException.class).entrySet()) {
+ PathFragment pkgName = ((PackageIdentifier) entry.getKey().argument()).getPackageFragment();
+ try {
+ PackageLookupValue value = (PackageLookupValue) entry.getValue().get();
+ if (value != null) {
+ builder.put(pkgName, value);
+ }
+ } catch (BuildFileNotFoundException e) {
+ maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+ } catch (InconsistentFilesystemException e) {
+ throw new InternalInconsistentFilesystemException(packageName, e);
+ } catch (FileSymlinkCycleException e) {
+ // Legacy doesn't detect symlink cycles.
+ packageShouldBeInError = true;
+ }
+ }
+ return Pair.of(builder.build(), packageShouldBeInError);
+ }
+
+ private static boolean markFileDepsAndPropagateInconsistentFilesystemExceptions(
+ String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+ throws InternalInconsistentFilesystemException {
+ Preconditions.checkState(
+ Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.FILE)), depKeys);
+ boolean packageShouldBeInError = packageWasInError;
+ for (Map.Entry<SkyKey, ValueOrException3<IOException, FileSymlinkCycleException,
+ InconsistentFilesystemException>> entry : env.getValuesOrThrow(depKeys, IOException.class,
+ FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
+ try {
+ entry.getValue().get();
+ } catch (IOException e) {
+ maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+ } catch (FileSymlinkCycleException e) {
+ // Legacy doesn't detect symlink cycles.
+ packageShouldBeInError = true;
+ } catch (InconsistentFilesystemException e) {
+ throw new InternalInconsistentFilesystemException(packageName, e);
+ }
+ }
+ return packageShouldBeInError;
+ }
+
+ private static boolean markGlobDepsAndPropagateInconsistentFilesystemExceptions(
+ String packageName, Iterable<SkyKey> depKeys, Environment env, boolean packageWasInError)
+ throws InternalInconsistentFilesystemException {
+ Preconditions.checkState(
+ Iterables.all(depKeys, SkyFunctions.isSkyFunction(SkyFunctions.GLOB)), depKeys);
+ boolean packageShouldBeInError = packageWasInError;
+ for (Map.Entry<SkyKey, ValueOrException4<IOException, BuildFileNotFoundException,
+ FileSymlinkCycleException, InconsistentFilesystemException>> entry :
+ env.getValuesOrThrow(depKeys, IOException.class, BuildFileNotFoundException.class,
+ FileSymlinkCycleException.class, InconsistentFilesystemException.class).entrySet()) {
+ try {
+ entry.getValue().get();
+ } catch (IOException | BuildFileNotFoundException e) {
+ maybeThrowFilesystemInconsistency(packageName, e, packageWasInError);
+ } catch (FileSymlinkCycleException e) {
+ // Legacy doesn't detect symlink cycles.
+ packageShouldBeInError = true;
+ } catch (InconsistentFilesystemException e) {
+ throw new InternalInconsistentFilesystemException(packageName, e);
+ }
+ }
+ return packageShouldBeInError;
+ }
+
+ /**
+ * Marks dependencies implicitly used by legacy package loading code, after the fact. Note that
+ * the given package might already be in error.
+ *
+ * <p>Any skyframe exceptions encountered here are ignored, as similar errors should have
+ * already been encountered by legacy package loading (if not, then the filesystem is
+ * inconsistent).
+ */
+ private static boolean markDependenciesAndPropagateInconsistentFilesystemExceptions(
+ Package pkg, Environment env, Collection<Pair<String, Boolean>> globPatterns,
+ Map<Label, Path> subincludes) throws InternalInconsistentFilesystemException {
+ boolean packageShouldBeInError = pkg.containsErrors();
+
+ // TODO(bazel-team): This means that many packages will have to be preprocessed twice. Ouch!
+ // We need a better continuation mechanism to avoid repeating work. [skyframe-loading]
+
+ // TODO(bazel-team): It would be preferable to perform I/O from the package preprocessor via
+ // Skyframe rather than add (potentially incomplete) dependencies after the fact.
+ // [skyframe-loading]
+
+ Set<SkyKey> subincludePackageLookupDepKeys = Sets.newHashSet();
+ for (Label label : pkg.getSubincludeLabels()) {
+ // Declare a dependency on the package lookup for the package giving access to the label.
+ subincludePackageLookupDepKeys.add(PackageLookupValue.key(label.getPackageFragment()));
+ }
+ Pair<? extends Map<PathFragment, PackageLookupValue>, Boolean> subincludePackageLookupResult =
+ getPackageLookupDepsAndPropagateInconsistentFilesystemExceptions(pkg.getName(),
+ subincludePackageLookupDepKeys, env, pkg.containsErrors());
+ Map<PathFragment, PackageLookupValue> subincludePackageLookupDeps =
+ subincludePackageLookupResult.getFirst();
+ packageShouldBeInError = subincludePackageLookupResult.getSecond();
+ List<SkyKey> subincludeFileDepKeys = Lists.newArrayList();
+ for (Entry<Label, Path> subincludeEntry : subincludes.entrySet()) {
+ // Ideally, we would have a direct dependency on the target with the given label, but then
+ // subincluding a file from the same package will cause a dependency cycle, since targets
+ // depend on their containing packages.
+ Label label = subincludeEntry.getKey();
+ PackageLookupValue subincludePackageLookupValue =
+ subincludePackageLookupDeps.get(label.getPackageFragment());
+ if (subincludePackageLookupValue != null) {
+ // Declare a dependency on the actual file that was subincluded.
+ Path subincludeFilePath = subincludeEntry.getValue();
+ if (subincludeFilePath != null) {
+ if (!subincludePackageLookupValue.packageExists()) {
+ // Legacy blaze puts a non-null path when only when the package does indeed exist.
+ throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
+ "Unexpected package in %s. Was it modified during the build?", subincludeFilePath));
+ }
+ // Sanity check for consistency of Skyframe and legacy blaze.
+ Path subincludeFilePathSkyframe =
+ subincludePackageLookupValue.getRoot().getRelative(label.toPathFragment());
+ if (!subincludeFilePathSkyframe.equals(subincludeFilePath)) {
+ throw new InternalInconsistentFilesystemException(pkg.getName(), String.format(
+ "Inconsistent package location for %s: '%s' vs '%s'. "
+ + "Was the source tree modified during the build?",
+ label.getPackageFragment(), subincludeFilePathSkyframe, subincludeFilePath));
+ }
+ // The actual file may be under a different package root than the package being
+ // constructed.
+ SkyKey subincludeSkyKey =
+ FileValue.key(RootedPath.toRootedPath(subincludePackageLookupValue.getRoot(),
+ subincludeFilePath));
+ subincludeFileDepKeys.add(subincludeSkyKey);
+ }
+ }
+ }
+ packageShouldBeInError = markFileDepsAndPropagateInconsistentFilesystemExceptions(
+ pkg.getName(), subincludeFileDepKeys, env, pkg.containsErrors());
+ // Another concern is a subpackage cutting off the subinclude label, but this is already
+ // handled by the legacy package loading code which calls into our SkyframePackageLocator.
+
+ // TODO(bazel-team): In the long term, we want to actually resolve the glob patterns within
+ // Skyframe. For now, just logging the glob requests provides correct incrementality and
+ // adequate performance.
+ PackageIdentifier packageId = pkg.getPackageIdentifier();
+ List<SkyKey> globDepKeys = Lists.newArrayList();
+ for (Pair<String, Boolean> globPattern : globPatterns) {
+ String pattern = globPattern.getFirst();
+ boolean excludeDirs = globPattern.getSecond();
+ SkyKey globSkyKey;
+ try {
+ globSkyKey = GlobValue.key(packageId, pattern, excludeDirs);
+ } catch (InvalidGlobPatternException e) {
+ // Globs that make it to pkg.getGlobPatterns() should already be filtered for errors.
+ throw new IllegalStateException(e);
+ }
+ globDepKeys.add(globSkyKey);
+ }
+ packageShouldBeInError = markGlobDepsAndPropagateInconsistentFilesystemExceptions(
+ pkg.getName(), globDepKeys, env, pkg.containsErrors());
+ return packageShouldBeInError;
+ }
+
+ /**
+ * Adds a dependency on the WORKSPACE file, representing it as a special type of package.
+ * @throws PackageFunctionException if there is an error computing the workspace file or adding
+ * its rules to the //external package.
+ */
+ private SkyValue getExternalPackage(Environment env, Path packageLookupPath)
+ throws PackageFunctionException {
+ RootedPath workspacePath = RootedPath.toRootedPath(
+ packageLookupPath, new PathFragment("WORKSPACE"));
+ SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath);
+ WorkspaceFileValue workspace = null;
+ try {
+ workspace = (WorkspaceFileValue) env.getValueOrThrow(workspaceKey, IOException.class,
+ FileSymlinkCycleException.class, InconsistentFilesystemException.class,
+ EvalException.class);
+ } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException
+ | EvalException e) {
+ throw new PackageFunctionException(new BadWorkspaceFileException(e.getMessage()),
+ Transience.PERSISTENT);
+ }
+ if (workspace == null) {
+ return null;
+ }
+
+ Package pkg = workspace.getPackage();
+ Event.replayEventsOn(env.getListener(), pkg.getEvents());
+ if (pkg.containsErrors()) {
+ throw new PackageFunctionException(new BuildFileContainsErrorsException("external",
+ "Package 'external' contains errors"),
+ pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
+ }
+
+ return new PackageValue(pkg);
+ }
+
+ @Override
+ public SkyValue compute(SkyKey key, Environment env) throws PackageFunctionException,
+ InterruptedException {
+ PackageIdentifier packageId = (PackageIdentifier) key.argument();
+ PathFragment packageNameFragment = packageId.getPackageFragment();
+ String packageName = packageNameFragment.getPathString();
+
+ SkyKey packageLookupKey = PackageLookupValue.key(packageId);
+ PackageLookupValue packageLookupValue;
+ try {
+ packageLookupValue = (PackageLookupValue)
+ env.getValueOrThrow(packageLookupKey, BuildFileNotFoundException.class,
+ InconsistentFilesystemException.class);
+ } catch (BuildFileNotFoundException e) {
+ throw new PackageFunctionException(e, Transience.PERSISTENT);
+ } catch (InconsistentFilesystemException e) {
+ // This error is not transient from the perspective of the PackageFunction.
+ throw new PackageFunctionException(
+ new InternalInconsistentFilesystemException(packageName, e), Transience.PERSISTENT);
+ }
+ if (packageLookupValue == null) {
+ return null;
+ }
+
+ if (!packageLookupValue.packageExists()) {
+ switch (packageLookupValue.getErrorReason()) {
+ case NO_BUILD_FILE:
+ case DELETED_PACKAGE:
+ case NO_EXTERNAL_PACKAGE:
+ throw new PackageFunctionException(new BuildFileNotFoundException(packageName,
+ packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
+ case INVALID_PACKAGE_NAME:
+ throw new PackageFunctionException(new InvalidPackageNameException(packageName,
+ packageLookupValue.getErrorMsg()), Transience.PERSISTENT);
+ default:
+ // We should never get here.
+ Preconditions.checkState(false);
+ }
+ }
+
+ if (packageName.equals(EXTERNAL_PACKAGE_NAME)) {
+ return getExternalPackage(env, packageLookupValue.getRoot());
+ }
+
+ RootedPath buildFileRootedPath = RootedPath.toRootedPath(packageLookupValue.getRoot(),
+ packageNameFragment.getChild("BUILD"));
+ FileValue buildFileValue;
+ try {
+ buildFileValue = (FileValue) env.getValueOrThrow(FileValue.key(buildFileRootedPath),
+ IOException.class, FileSymlinkCycleException.class,
+ InconsistentFilesystemException.class);
+ } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
+ throw new IllegalStateException("Package lookup succeeded but encountered error when "
+ + "getting FileValue for BUILD file directly.", e);
+ }
+ if (buildFileValue == null) {
+ return null;
+ }
+ Preconditions.checkState(buildFileValue.exists(),
+ "Package lookup succeeded but BUILD file doesn't exist");
+ Path buildFilePath = buildFileRootedPath.asPath();
+
+ String replacementContents = null;
+ if (packageName.equals(DEFAULTS_PACKAGE_NAME)) {
+ replacementContents = PrecomputedValue.DEFAULTS_PACKAGE_CONTENTS.get(env);
+ if (replacementContents == null) {
+ return null;
+ }
+ }
+
+ RuleVisibility defaultVisibility = PrecomputedValue.DEFAULT_VISIBILITY.get(env);
+ if (defaultVisibility == null) {
+ return null;
+ }
+
+ ASTFileLookupValue astLookupValue = null;
+ SkyKey astLookupKey = null;
+ try {
+ astLookupKey = ASTFileLookupValue.key(PRELUDE_FILE_FRAGMENT);
+ } catch (ASTLookupInputException e) {
+ // There's a static check ensuring that PRELUDE_FILE_FRAGMENT is relative.
+ throw new IllegalStateException(e);
+ }
+ try {
+ astLookupValue = (ASTFileLookupValue) env.getValueOrThrow(astLookupKey,
+ ErrorReadingSkylarkExtensionException.class, InconsistentFilesystemException.class);
+ } catch (ErrorReadingSkylarkExtensionException | InconsistentFilesystemException e) {
+ throw new PackageFunctionException(new BadPreludeFileException(packageName, e.getMessage()),
+ Transience.PERSISTENT);
+ }
+ if (astLookupValue == null) {
+ return null;
+ }
+ List<Statement> preludeStatements = astLookupValue == ASTFileLookupValue.NO_FILE
+ ? ImmutableList.<Statement>of() : astLookupValue.getAST().getStatements();
+
+ // Load the BUILD file AST and handle Skylark dependencies. This way BUILD files are
+ // only loaded twice if there are unavailable Skylark or package dependencies or an
+ // IOException occurs. Note that the BUILD files are still parsed two times.
+ ParserInputSource inputSource;
+ try {
+ if (showLoadingProgress.get() && !packageFunctionCache.containsKey(packageId)) {
+ // TODO(bazel-team): don't duplicate the loading message if there are unavailable
+ // Skylark dependencies.
+ reporter.handle(Event.progress("Loading package: " + packageName));
+ }
+ inputSource = ParserInputSource.create(buildFilePath);
+ } catch (IOException e) {
+ env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
+ // Note that we did this work, so we should conservatively report this error as transient.
+ throw new PackageFunctionException(new BuildFileContainsErrorsException(
+ packageName, e.getMessage()), Transience.TRANSIENT);
+ }
+ SkylarkImportResult importResult = fetchImportsFromBuildFile(
+ buildFilePath, packageId.getRepository(), preludeStatements, inputSource, packageName, env);
+ if (importResult == null) {
+ return null;
+ }
+
+ Package.LegacyBuilder legacyPkgBuilder = loadPackage(inputSource, replacementContents,
+ packageId, buildFilePath, defaultVisibility, preludeStatements, importResult);
+ legacyPkgBuilder.buildPartial();
+ try {
+ handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
+ packageLookupValue.getRoot(), packageId, legacyPkgBuilder, env);
+ } catch (InternalInconsistentFilesystemException e) {
+ packageFunctionCache.remove(packageId);
+ throw new PackageFunctionException(e,
+ e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
+ }
+ if (env.valuesMissing()) {
+ // The package we just loaded will be in the {@code packageFunctionCache} next when this
+ // SkyFunction is called again.
+ return null;
+ }
+ Collection<Pair<String, Boolean>> globPatterns = legacyPkgBuilder.getGlobPatterns();
+ Map<Label, Path> subincludes = legacyPkgBuilder.getSubincludes();
+ Package pkg = legacyPkgBuilder.finishBuild();
+ Event.replayEventsOn(env.getListener(), pkg.getEvents());
+ boolean packageShouldBeConsideredInError = pkg.containsErrors();
+ try {
+ packageShouldBeConsideredInError =
+ markDependenciesAndPropagateInconsistentFilesystemExceptions(pkg, env,
+ globPatterns, subincludes);
+ } catch (InternalInconsistentFilesystemException e) {
+ packageFunctionCache.remove(packageId);
+ throw new PackageFunctionException(e,
+ e.isTransient() ? Transience.TRANSIENT : Transience.PERSISTENT);
+ }
+
+ if (env.valuesMissing()) {
+ return null;
+ }
+ // We know this SkyFunction will not be called again, so we can remove the cache entry.
+ packageFunctionCache.remove(packageId);
+
+ if (packageShouldBeConsideredInError) {
+ throw new PackageFunctionException(new BuildFileContainsErrorsException(pkg,
+ "Package '" + packageName + "' contains errors"),
+ pkg.containsTemporaryErrors() ? Transience.TRANSIENT : Transience.PERSISTENT);
+ }
+ return new PackageValue(pkg);
+ }
+
+ private SkylarkImportResult fetchImportsFromBuildFile(Path buildFilePath, RepositoryName repo,
+ List<Statement> preludeStatements, ParserInputSource inputSource,
+ String packageName, Environment env) throws PackageFunctionException {
+ StoredEventHandler eventHandler = new StoredEventHandler();
+ BuildFileAST buildFileAST = BuildFileAST.parseBuildFile(
+ inputSource, preludeStatements, eventHandler, null, true);
+
+ if (eventHandler.hasErrors()) {
+ // In case of Python preprocessing, errors have already been reported (see checkSyntax).
+ // In other cases, errors will be reported later.
+ // TODO(bazel-team): maybe we could get rid of checkSyntax and always report errors here?
+ return new SkylarkImportResult(
+ ImmutableMap.<PathFragment, SkylarkEnvironment>of(),
+ ImmutableList.<Label>of());
+ }
+
+ ImmutableCollection<PathFragment> imports = buildFileAST.getImports();
+ Map<PathFragment, SkylarkEnvironment> importMap = new HashMap<>();
+ ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
+ try {
+ for (PathFragment importFile : imports) {
+ SkyKey importsLookupKey = SkylarkImportLookupValue.key(repo, importFile);
+ SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue)
+ env.getValueOrThrow(importsLookupKey, SkylarkImportFailedException.class,
+ InconsistentFilesystemException.class, ASTLookupInputException.class,
+ BuildFileNotFoundException.class);
+ if (importLookupValue != null) {
+ importMap.put(importFile, importLookupValue.getImportedEnvironment());
+ fileDependencies.add(importLookupValue.getDependency());
+ }
+ }
+ } catch (SkylarkImportFailedException e) {
+ env.getListener().handle(Event.error(Location.fromFile(buildFilePath), e.getMessage()));
+ throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
+ e.getMessage()), Transience.PERSISTENT);
+ } catch (InconsistentFilesystemException e) {
+ throw new PackageFunctionException(new InternalInconsistentFilesystemException(packageName,
+ e), Transience.PERSISTENT);
+ } catch (ASTLookupInputException e) {
+ // The load syntax is bad in the BUILD file so BuildFileContainsErrorsException is OK.
+ throw new PackageFunctionException(new BuildFileContainsErrorsException(packageName,
+ e.getMessage()), Transience.PERSISTENT);
+ } catch (BuildFileNotFoundException e) {
+ throw new PackageFunctionException(e, Transience.PERSISTENT);
+ }
+ if (env.valuesMissing()) {
+ // There are unavailable Skylark dependencies.
+ return null;
+ }
+ return new SkylarkImportResult(importMap, transitiveClosureOfLabels(fileDependencies.build()));
+ }
+
+ private ImmutableList<Label> transitiveClosureOfLabels(
+ ImmutableList<SkylarkFileDependency> immediateDeps) {
+ Set<Label> transitiveClosure = Sets.newHashSet();
+ transitiveClosureOfLabels(immediateDeps, transitiveClosure);
+ return ImmutableList.copyOf(transitiveClosure);
+ }
+
+ private void transitiveClosureOfLabels(
+ ImmutableList<SkylarkFileDependency> immediateDeps, Set<Label> transitiveClosure) {
+ for (SkylarkFileDependency dep : immediateDeps) {
+ if (!transitiveClosure.contains(dep.getLabel())) {
+ transitiveClosure.add(dep.getLabel());
+ transitiveClosureOfLabels(dep.getDependencies(), transitiveClosure);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public String extractTag(SkyKey skyKey) {
+ return null;
+ }
+
+ private static void handleLabelsCrossingSubpackagesAndPropagateInconsistentFilesystemExceptions(
+ Path pkgRoot, PackageIdentifier pkgId, Package.LegacyBuilder pkgBuilder, Environment env)
+ throws InternalInconsistentFilesystemException {
+ Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet();
+ Map<Target, SkyKey> targetToKey = new HashMap<>();
+ for (Target target : pkgBuilder.getTargets()) {
+ PathFragment dir = target.getLabel().toPathFragment().getParentDirectory();
+ PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
+ if (dir.equals(pkgId.getPackageFragment())) {
+ continue;
+ }
+ SkyKey key = ContainingPackageLookupValue.key(dirId);
+ targetToKey.put(target, key);
+ containingPkgLookupKeys.add(key);
+ }
+ Map<Label, SkyKey> subincludeToKey = new HashMap<>();
+ for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
+ PathFragment dir = subincludeLabel.toPathFragment().getParentDirectory();
+ PackageIdentifier dirId = new PackageIdentifier(pkgId.getRepository(), dir);
+ if (dir.equals(pkgId.getPackageFragment())) {
+ continue;
+ }
+ SkyKey key = ContainingPackageLookupValue.key(dirId);
+ subincludeToKey.put(subincludeLabel, key);
+ containingPkgLookupKeys.add(ContainingPackageLookupValue.key(dirId));
+ }
+ Map<SkyKey, ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
+ FileSymlinkCycleException>> containingPkgLookupValues = env.getValuesOrThrow(
+ containingPkgLookupKeys, BuildFileNotFoundException.class,
+ InconsistentFilesystemException.class, FileSymlinkCycleException.class);
+ if (env.valuesMissing()) {
+ return;
+ }
+ for (Target target : ImmutableSet.copyOf(pkgBuilder.getTargets())) {
+ SkyKey key = targetToKey.get(target);
+ if (!containingPkgLookupValues.containsKey(key)) {
+ continue;
+ }
+ ContainingPackageLookupValue containingPackageLookupValue =
+ getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
+ pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
+ if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, target.getLabel(),
+ target.getLocation(), containingPackageLookupValue)) {
+ pkgBuilder.removeTarget(target);
+ pkgBuilder.setContainsErrors();
+ }
+ }
+ for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
+ SkyKey key = subincludeToKey.get(subincludeLabel);
+ if (!containingPkgLookupValues.containsKey(key)) {
+ continue;
+ }
+ ContainingPackageLookupValue containingPackageLookupValue =
+ getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
+ pkgId.getPackageFragment().getPathString(), containingPkgLookupValues.get(key), env);
+ if (maybeAddEventAboutLabelCrossingSubpackage(pkgBuilder, pkgRoot, subincludeLabel,
+ /*location=*/null, containingPackageLookupValue)) {
+ pkgBuilder.setContainsErrors();
+ }
+ }
+ }
+
+ @Nullable
+ private static ContainingPackageLookupValue
+ getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(String packageName,
+ ValueOrException3<BuildFileNotFoundException, InconsistentFilesystemException,
+ FileSymlinkCycleException> containingPkgLookupValueOrException, Environment env)
+ throws InternalInconsistentFilesystemException {
+ try {
+ return (ContainingPackageLookupValue) containingPkgLookupValueOrException.get();
+ } catch (BuildFileNotFoundException | FileSymlinkCycleException e) {
+ env.getListener().handle(Event.error(null, e.getMessage()));
+ return null;
+ } catch (InconsistentFilesystemException e) {
+ throw new InternalInconsistentFilesystemException(packageName, e);
+ }
+ }
+
+ private static boolean maybeAddEventAboutLabelCrossingSubpackage(
+ Package.LegacyBuilder pkgBuilder, Path pkgRoot, Label label, @Nullable Location location,
+ @Nullable ContainingPackageLookupValue containingPkgLookupValue) {
+ if (containingPkgLookupValue == null) {
+ return true;
+ }
+ if (!containingPkgLookupValue.hasContainingPackage()) {
+ // The missing package here is a problem, but it's not an error from the perspective of
+ // PackageFunction.
+ return false;
+ }
+ PackageIdentifier containingPkg = containingPkgLookupValue.getContainingPackageName();
+ if (containingPkg.equals(label.getPackageIdentifier())) {
+ // The label does not cross a subpackage boundary.
+ return false;
+ }
+ if (!containingPkg.getPackageFragment().startsWith(label.getPackageFragment())) {
+ // This label is referencing an imaginary package, because the containing package should
+ // extend the label's package: if the label is //a/b:c/d, the containing package could be
+ // //a/b/c or //a/b, but should never be //a. Usually such errors will be caught earlier, but
+ // in some exceptional cases (such as a Python-aware BUILD file catching its own io
+ // exceptions), it reaches here, and we tolerate it.
+ return false;
+ }
+ PathFragment labelNameFragment = new PathFragment(label.getName());
+ String message = String.format("Label '%s' crosses boundary of subpackage '%s'",
+ label, containingPkg);
+ Path containingRoot = containingPkgLookupValue.getContainingPackageRoot();
+ if (pkgRoot.equals(containingRoot)) {
+ PathFragment labelNameInContainingPackage = labelNameFragment.subFragment(
+ containingPkg.getPackageFragment().segmentCount()
+ - label.getPackageFragment().segmentCount(),
+ labelNameFragment.segmentCount());
+ message += " (perhaps you meant to put the colon here: "
+ + "'//" + containingPkg + ":" + labelNameInContainingPackage + "'?)";
+ } else {
+ message += " (have you deleted " + containingPkg + "/BUILD? "
+ + "If so, use the --deleted_packages=" + containingPkg + " option)";
+ }
+ pkgBuilder.addEvent(Event.error(location, message));
+ return true;
+ }
+
+ /**
+ * Constructs a {@link Package} object for the given package using legacy package loading.
+ * Note that the returned package may be in error.
+ */
+ private Package.LegacyBuilder loadPackage(ParserInputSource inputSource,
+ @Nullable String replacementContents,
+ PackageIdentifier packageId, Path buildFilePath, RuleVisibility defaultVisibility,
+ List<Statement> preludeStatements, SkylarkImportResult importResult)
+ throws InterruptedException {
+ ParserInputSource replacementSource = replacementContents == null ? null
+ : ParserInputSource.create(replacementContents, buildFilePath);
+ Package.LegacyBuilder pkgBuilder = packageFunctionCache.get(packageId);
+ if (pkgBuilder == null) {
+ Clock clock = new JavaClock();
+ long startTime = clock.nanoTime();
+ profiler.startTask(ProfilerTask.CREATE_PACKAGE, packageId.toString());
+ try {
+ Globber globber = packageFactory.createLegacyGlobber(buildFilePath.getParentDirectory(),
+ packageId, packageLocator);
+ StoredEventHandler localReporter = new StoredEventHandler();
+ Preprocessor.Result preprocessingResult = replacementSource == null
+ ? packageFactory.preprocess(packageId, buildFilePath, inputSource, globber,
+ localReporter)
+ : Preprocessor.Result.noPreprocessing(replacementSource);
+ pkgBuilder = packageFactory.createPackageFromPreprocessingResult(packageId, buildFilePath,
+ preprocessingResult, localReporter.getEvents(), preludeStatements,
+ importResult.importMap, importResult.fileDependencies, packageLocator,
+ defaultVisibility, globber);
+ if (eventBus.get() != null) {
+ eventBus.get().post(new PackageLoadedEvent(packageId.toString(),
+ (clock.nanoTime() - startTime) / (1000 * 1000),
+ // It's impossible to tell if the package was loaded before, so we always pass false.
+ /*reloading=*/false,
+ // This isn't completely correct since we may encounter errors later (e.g. filesystem
+ // inconsistencies)
+ !pkgBuilder.containsErrors()));
+ }
+ numPackagesLoaded.incrementAndGet();
+ packageFunctionCache.put(packageId, pkgBuilder);
+ } finally {
+ profiler.completeTask(ProfilerTask.CREATE_PACKAGE);
+ }
+ }
+ return pkgBuilder;
+ }
+
+ private static class InternalInconsistentFilesystemException extends NoSuchPackageException {
+ private boolean isTransient;
+
+ /**
+ * Used to represent a filesystem inconsistency discovered outside the
+ * {@link PackageFunction}.
+ */
+ public InternalInconsistentFilesystemException(String packageName,
+ InconsistentFilesystemException e) {
+ super(packageName, e.getMessage(), e);
+ // This is not a transient error from the perspective of the PackageFunction.
+ this.isTransient = false;
+ }
+
+ /** Used to represent a filesystem inconsistency discovered by the {@link PackageFunction}. */
+ public InternalInconsistentFilesystemException(String packageName,
+ String inconsistencyMessage) {
+ this(packageName, new InconsistentFilesystemException(inconsistencyMessage));
+ this.isTransient = true;
+ }
+
+ public boolean isTransient() {
+ return isTransient;
+ }
+ }
+
+ private static class BadWorkspaceFileException extends NoSuchPackageException {
+ private BadWorkspaceFileException(String message) {
+ super("external", "Error encountered while dealing with the WORKSPACE file: " + message);
+ }
+ }
+
+ private static class BadPreludeFileException extends NoSuchPackageException {
+ private BadPreludeFileException(String packageName, String message) {
+ super(packageName, "Error encountered while reading the prelude file: " + message);
+ }
+ }
+
+ /**
+ * Used to declare all the exception types that can be wrapped in the exception thrown by
+ * {@link PackageFunction#compute}.
+ */
+ private static class PackageFunctionException extends SkyFunctionException {
+ public PackageFunctionException(NoSuchPackageException e, Transience transience) {
+ super(e, transience);
+ }
+ }
+
+ /** A simple value class to store the result of the Skylark imports.*/
+ private static final class SkylarkImportResult {
+ private final Map<PathFragment, SkylarkEnvironment> importMap;
+ private final ImmutableList<Label> fileDependencies;
+ private SkylarkImportResult(Map<PathFragment, SkylarkEnvironment> importMap,
+ ImmutableList<Label> fileDependencies) {
+ this.importMap = importMap;
+ this.fileDependencies = fileDependencies;
+ }
+ }
+}