| // Copyright 2014 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.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; |
| import com.google.devtools.build.lib.packages.WorkspaceFileValue; |
| import com.google.devtools.build.lib.repository.RequestRepositoryInformationEvent; |
| import com.google.devtools.build.skyframe.CycleInfo; |
| import com.google.devtools.build.skyframe.CyclesReporter; |
| import com.google.devtools.build.skyframe.SkyKey; |
| |
| /** Reports cycles of recursive import of Starlark files. */ |
| public class StarlarkModuleCycleReporter implements CyclesReporter.SingleCycleReporter { |
| |
| private static final Predicate<SkyKey> IS_STARLARK_MODULE_SKY_KEY = |
| SkyFunctions.isSkyFunction(SkyFunctions.BZL_LOAD); |
| |
| private static final Predicate<SkyKey> IS_PACKAGE_SKY_KEY = |
| SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE); |
| |
| private static final Predicate<SkyKey> IS_PACKAGE_LOOKUP = |
| SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE_LOOKUP); |
| |
| private static final Predicate<SkyKey> IS_WORKSPACE_FILE = |
| SkyFunctions.isSkyFunction(WorkspaceFileValue.WORKSPACE_FILE); |
| |
| private static final Predicate<SkyKey> IS_REPOSITORY = |
| SkyFunctions.isSkyFunction(SkyFunctions.REPOSITORY); |
| |
| private static final Predicate<SkyKey> IS_REPOSITORY_DIRECTORY = |
| SkyFunctions.isSkyFunction(SkyFunctions.REPOSITORY_DIRECTORY); |
| |
| private static final Predicate<SkyKey> IS_BZL_LOAD = |
| SkyFunctions.isSkyFunction(SkyFunctions.BZL_LOAD); |
| |
| private static final Predicate<SkyKey> IS_EXTERNAL_PACKAGE = |
| SkyFunctions.isSkyFunction(SkyFunctions.EXTERNAL_PACKAGE); |
| |
| private static final Predicate<SkyKey> IS_LOCAL_REPOSITORY_LOOKUP = |
| SkyFunctions.isSkyFunction(SkyFunctions.LOCAL_REPOSITORY_LOOKUP); |
| |
| private static void requestRepoDefinitions( |
| ExtendedEventHandler eventHandler, Iterable<SkyKey> repos) { |
| for (SkyKey repo : repos) { |
| if (repo instanceof RepositoryValue.Key) { |
| eventHandler.post( |
| new RequestRepositoryInformationEvent( |
| ((RepositoryValue.Key) repo).argument().strippedName())); |
| } |
| } |
| } |
| |
| @Override |
| public boolean maybeReportCycle( |
| SkyKey topLevelKey, |
| CycleInfo cycleInfo, |
| boolean alreadyReported, |
| ExtendedEventHandler eventHandler) { |
| ImmutableList<SkyKey> pathToCycle = cycleInfo.getPathToCycle(); |
| ImmutableList<SkyKey> cycle = cycleInfo.getCycle(); |
| if (pathToCycle.isEmpty()) { |
| return false; |
| } |
| SkyKey lastPathElement = pathToCycle.get(pathToCycle.size() - 1); |
| if (alreadyReported) { |
| return true; |
| } else if (Iterables.all(cycle, IS_STARLARK_MODULE_SKY_KEY) |
| // The last element before the cycle has to be a PackageFunction, Starlark module, or the |
| // WORKSPACE |
| && (IS_PACKAGE_SKY_KEY.apply(lastPathElement) |
| || IS_STARLARK_MODULE_SKY_KEY.apply(lastPathElement) |
| || IS_WORKSPACE_FILE.apply(lastPathElement))) { |
| |
| Function<SkyKey, String> printer = |
| new Function<SkyKey, String>() { |
| @Override |
| public String apply(SkyKey input) { |
| if (input.argument() instanceof BzlLoadValue.Key) { |
| return ((BzlLoadValue.Key) input.argument()).getLabel().toString(); |
| } else if (input.argument() instanceof PackageIdentifier) { |
| return ((PackageIdentifier) input.argument()) + "/BUILD"; |
| } else if (input.argument() instanceof WorkspaceFileValue.WorkspaceFileKey) { |
| return ((WorkspaceFileValue.WorkspaceFileKey) input.argument()) |
| .getPath() |
| .getRootRelativePath() |
| .toString(); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| }; |
| |
| StringBuilder cycleMessage = |
| new StringBuilder().append("cycle detected in extension files: "); |
| |
| // go back the path that lead to the cycle till we found the BUILD or WORKSPACE |
| // file that lead to the circular load. |
| int startIndex = pathToCycle.size() - 1; |
| while (startIndex > 0 |
| && (IS_PACKAGE_SKY_KEY.apply(pathToCycle.get(startIndex - 1)) |
| || IS_STARLARK_MODULE_SKY_KEY.apply(pathToCycle.get(startIndex - 1)) |
| || IS_WORKSPACE_FILE.apply(pathToCycle.get(startIndex - 1)))) { |
| startIndex--; |
| } |
| for (int i = startIndex; i < pathToCycle.size(); i++) { |
| cycleMessage.append("\n ").append(printer.apply(pathToCycle.get(i))); |
| } |
| AbstractLabelCycleReporter.printCycle(cycleInfo.getCycle(), cycleMessage, printer); |
| // TODO(bazel-team): it would be nice to pass the Location of the load Statement in the |
| // BUILD file. |
| eventHandler.handle(Event.error(null, cycleMessage.toString())); |
| return true; |
| } else if (Iterables.all( |
| cycle, Predicates.or(IS_PACKAGE_LOOKUP, IS_REPOSITORY, IS_REPOSITORY_DIRECTORY))) { |
| StringBuilder cycleMessage = |
| new StringBuilder().append("Circular definition of repositories:"); |
| Iterable<SkyKey> repos = Iterables.filter(cycle, IS_REPOSITORY); |
| Function<SkyKey, String> printer = |
| new Function<SkyKey, String>() { |
| @Override |
| public String apply(SkyKey input) { |
| if (input instanceof RepositoryValue.Key) { |
| return ((RepositoryValue.Key) input).argument().getName(); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| }; |
| AbstractLabelCycleReporter.printCycle(ImmutableList.copyOf(repos), cycleMessage, printer); |
| eventHandler.handle(Event.error(null, cycleMessage.toString())); |
| // To help debugging, request that the information be printed about where the respective |
| // repositories were defined. |
| requestRepoDefinitions(eventHandler, repos); |
| return true; |
| } else if (Iterables.any(cycle, IS_REPOSITORY) && Iterables.any(cycle, IS_WORKSPACE_FILE)) { |
| Iterable<SkyKey> repos = |
| Iterables.filter(Iterables.concat(pathToCycle, cycle), IS_REPOSITORY); |
| |
| StringBuilder message = new StringBuilder(); |
| |
| if (Iterables.any(cycle, IS_BZL_LOAD)) { |
| Label fileLabel = |
| ((BzlLoadValue.Key) Iterables.getLast(Iterables.filter(cycle, IS_BZL_LOAD))).getLabel(); |
| message.append("Failed to load Starlark extension '").append(fileLabel).append("'.\n"); |
| } |
| |
| message |
| .append("Cycle in the workspace file detected. ") |
| .append("This indicates that a repository is used prior to being defined.\n") |
| .append( |
| "The following chain of repository dependencies lead to the missing definition.\n"); |
| for (SkyKey repo : repos) { |
| if (repo instanceof RepositoryValue.Key) { |
| message |
| .append(" - ") |
| .append(((RepositoryValue.Key) repo).argument().getName()) |
| .append("\n"); |
| } |
| } |
| SkyKey missingRepo = Iterables.getLast(repos); |
| if (missingRepo instanceof RepositoryValue.Key) { |
| message |
| .append("This could either mean you have to add the '") |
| .append(((RepositoryValue.Key) missingRepo).argument().getName()) |
| .append("' repository with a statement like `http_archive` in your WORKSPACE file") |
| .append(" (note that transitive dependencies are not added automatically), or move") |
| .append(" an existing definition earlier in your WORKSPACE file."); |
| } |
| eventHandler.handle(Event.error(message.toString())); |
| // To help debugging, request that the information be printed about where the respective |
| // repositories were defined. |
| requestRepoDefinitions(eventHandler, repos); |
| return true; |
| } else if (Iterables.any(cycle, IS_BZL_LOAD)) { |
| Label fileLabel = |
| ((BzlLoadValue.Key) Iterables.getLast(Iterables.filter(cycle, IS_BZL_LOAD))).getLabel(); |
| eventHandler.handle( |
| Event.error(null, "Failed to load Starlark extension '" + fileLabel + "'.\n")); |
| return true; |
| } else if (Iterables.any(cycle, IS_PACKAGE_LOOKUP)) { |
| PackageIdentifier pkg = |
| (PackageIdentifier) |
| Iterables.getLast(Iterables.filter(cycle, IS_PACKAGE_LOOKUP)).argument(); |
| eventHandler.handle(Event.error(null, "cannot load package '" + pkg + "'")); |
| return true; |
| } |
| return false; |
| } |
| } |