diff options
author | John Cater <jcater@google.com> | 2016-10-25 16:16:35 +0000 |
---|---|---|
committer | John Cater <jcater@google.com> | 2016-10-25 20:19:29 +0000 |
commit | b4f461ecc183d9adc9482f4cad848687ed0227ee (patch) | |
tree | 148126cf5b6f1002c1d4391fe74c13af736fd74e /src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java | |
parent | 7260f0a2c69bfe0fec187099fcea2dd16c331729 (diff) |
Add new skyframe function to lookup the repository given a path, and use that
to report invalid package references. Fixes #1592.
--
MOS_MIGRATED_REVID=137164164
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java new file mode 100644 index 0000000000..4c97875c11 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java @@ -0,0 +1,246 @@ +// Copyright 2016 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.skyframe; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.cmdline.RepositoryName; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.packages.AggregatingAttributeMapper; +import com.google.devtools.build.lib.packages.BuildFileNotFoundException; +import com.google.devtools.build.lib.packages.ErrorDeterminingRepositoryException; +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.packages.Package.NameConflictException; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule; +import com.google.devtools.build.lib.skyframe.PackageFunction.PackageFunctionException; +import com.google.devtools.build.lib.syntax.Type; +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 java.io.IOException; +import javax.annotation.Nullable; + +/** SkyFunction for {@link LocalRepositoryLookupValue}s. */ +public class LocalRepositoryLookupFunction implements SkyFunction { + + @Override + @Nullable + public String extractTag(SkyKey skyKey) { + return null; + } + + // Implementation note: Although LocalRepositoryLookupValue.NOT_FOUND exists, it should never be + // returned from this method. + @Override + public SkyValue compute(SkyKey skyKey, Environment env) + throws SkyFunctionException, InterruptedException { + RootedPath directory = (RootedPath) skyKey.argument(); + + // Is this the root directory? If so, we're in the MAIN repository. This assumes that the main + // repository has a WORKSPACE in the root directory, but Bazel will have failed with an error + // before this can be called if that is incorrect. + if (directory.getRelativePath().equals(PathFragment.EMPTY_FRAGMENT)) { + return LocalRepositoryLookupValue.mainRepository(); + } + + // Does this directory contain a WORKSPACE file? + Optional<Boolean> maybeWorkspaceFileExists = maybeGetWorkspaceFileExistence(env, directory); + if (!maybeWorkspaceFileExists.isPresent()) { + return null; + } else if (maybeWorkspaceFileExists.get()) { + Optional<LocalRepositoryLookupValue> maybeRepository = + maybeCheckWorkspaceForRepository(env, directory); + if (!maybeRepository.isPresent()) { + return null; + } + LocalRepositoryLookupValue repository = maybeRepository.get(); + // If the repository that was discovered doesn't exist, continue recursing. + if (repository.exists()) { + return repository; + } + } + + // If we haven't found a repository yet, check the parent directory. + RootedPath parentDirectory = + RootedPath.toRootedPath( + directory.getRoot(), directory.getRelativePath().getParentDirectory()); + return env.getValue(LocalRepositoryLookupValue.key(parentDirectory)); + } + + private Optional<Boolean> maybeGetWorkspaceFileExistence(Environment env, RootedPath directory) + throws InterruptedException, LocalRepositoryLookupFunctionException { + try { + RootedPath workspaceRootedFile = + RootedPath.toRootedPath( + directory.getRoot(), + directory + .getRelativePath() + .getChild(PackageLookupValue.BuildFileName.WORKSPACE.getFilename())); + FileValue workspaceFileValue = + (FileValue) + env.getValueOrThrow( + FileValue.key(workspaceRootedFile), + IOException.class, + FileSymlinkException.class, + InconsistentFilesystemException.class); + if (workspaceFileValue == null) { + return Optional.absent(); + } + return Optional.of(workspaceFileValue.exists()); + } catch (IOException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "IOException while checking if there is a WORKSPACE file in " + + directory.asPath().getPathString(), + e), + Transience.PERSISTENT); + } catch (FileSymlinkException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "FileSymlinkException while checking if there is a WORKSPACE file in " + + directory.asPath().getPathString(), + e), + Transience.PERSISTENT); + } catch (InconsistentFilesystemException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "InconsistentFilesystemException while checking if there is a WORKSPACE file in " + + directory.asPath().getPathString(), + e), + Transience.PERSISTENT); + } + } + + /** + * Checks whether the directory exists and is a workspace root. Returns {@link Optional#absent()} + * if Skyframe needs to re-run, {@link Optional#of(LocalRepositoryLookupValue)} otherwise. + */ + private Optional<LocalRepositoryLookupValue> maybeCheckWorkspaceForRepository( + Environment env, RootedPath directory) + throws InterruptedException, LocalRepositoryLookupFunctionException { + // Look up the main WORKSPACE file by the external package, to find all repositories. + PackageLookupValue externalPackageLookupValue; + try { + externalPackageLookupValue = + (PackageLookupValue) + env.getValueOrThrow( + PackageLookupValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER), + BuildFileNotFoundException.class, + InconsistentFilesystemException.class); + if (externalPackageLookupValue == null) { + return Optional.absent(); + } + } catch (BuildFileNotFoundException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "BuildFileNotFoundException while loading the //external package", e), + Transience.PERSISTENT); + } catch (InconsistentFilesystemException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "InconsistentFilesystemException while loading the //external package", e), + Transience.PERSISTENT); + } + + RootedPath workspacePath = + externalPackageLookupValue.getRootedPath(Label.EXTERNAL_PACKAGE_IDENTIFIER); + + SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath); + do { + WorkspaceFileValue value; + try { + value = + (WorkspaceFileValue) + env.getValueOrThrow( + workspaceKey, PackageFunctionException.class, NameConflictException.class); + if (value == null) { + return Optional.absent(); + } + } catch (PackageFunctionException e) { + // TODO(jcater): When WFF is rewritten to not throw a PFE, update this. + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "PackageFunctionException while loading the root WORKSPACE file", e), + Transience.PERSISTENT); + } catch (NameConflictException e) { + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "NameConflictException while loading the root WORKSPACE file", e), + Transience.PERSISTENT); + } + + Package externalPackage = value.getPackage(); + if (externalPackage.containsErrors()) { + Event.replayEventsOn(env.getListener(), externalPackage.getEvents()); + } + + // Find all local_repository rules in the WORKSPACE, and check if any have a "path" attribute + // the same as the requested directory. + Iterable<Rule> localRepositories = + externalPackage.getRulesMatchingRuleClass(LocalRepositoryRule.NAME); + Rule rule = + Iterables.find( + localRepositories, + new Predicate<Rule>() { + @Override + public boolean apply(@Nullable Rule rule) { + AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); + PathFragment pathAttr = new PathFragment(mapper.get("path", Type.STRING)); + return directory.getRelativePath().equals(pathAttr); + } + }, + null); + if (rule != null) { + try { + return Optional.of( + LocalRepositoryLookupValue.success(RepositoryName.create("@" + rule.getName()))); + } catch (LabelSyntaxException e) { + // This shouldn't be possible if the rule name is valid, and it should already have been + // validated. + throw new LocalRepositoryLookupFunctionException( + new ErrorDeterminingRepositoryException( + "LabelSyntaxException while creating the repository name from the rule " + + rule.getName(), + e), + Transience.PERSISTENT); + } + } + workspaceKey = value.next(); + + // TODO(bazel-team): This loop can be quadratic in the number of load() statements, consider + // rewriting or unrolling. + } while (workspaceKey != null); + + return Optional.of(LocalRepositoryLookupValue.notFound()); + } + + /** + * Used to declare all the exception types that can be wrapped in the exception thrown by {@link + * LocalRepositoryLookupFunction#compute}. + */ + private static final class LocalRepositoryLookupFunctionException extends SkyFunctionException { + public LocalRepositoryLookupFunctionException( + ErrorDeterminingRepositoryException e, Transience transience) { + super(e, transience); + } + } +} |