// 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() .getRelative(PackageLookupValue.BuildFileName.WORKSPACE.getFilenameFragment())); FileValue workspaceFileValue = (FileValue) env.getValueOrThrow( FileValue.key(workspaceRootedFile), IOException.class, FileSymlinkException.class, InconsistentFilesystemException.class); if (workspaceFileValue == null) { return Optional.absent(); } if (workspaceFileValue.isDirectory()) { // There is a directory named WORKSPACE, ignore it for checking repository existence. return Optional.of(false); } 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, final 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 = PathFragment.create(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); } } }