blob: ec5f9bf2e54503e83e63af7ac3a270dc9c0e89bf [file] [log] [blame]
// Copyright 2022 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.Preconditions;
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.bazel.bzlmod.BzlmodRepoRuleValue;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionId;
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.repository.RequestRepositoryInformationEvent;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.skyframe.CycleInfo;
import com.google.devtools.build.skyframe.CyclesReporter;
import com.google.devtools.build.skyframe.SkyKey;
/** Reports cycles introduced by module extensions and .bzl files where they are declared. */
public class BzlmodRepoCycleReporter implements CyclesReporter.SingleCycleReporter {
private static final Predicate<SkyKey> IS_BZL_LOAD =
SkyFunctions.isSkyFunction(SkyFunctions.BZL_LOAD);
private static final Predicate<SkyKey> IS_PACKAGE_LOOKUP =
SkyFunctions.isSkyFunction(SkyFunctions.PACKAGE_LOOKUP);
private static final Predicate<SkyKey> IS_CONTAINING_PACKAGE =
SkyFunctions.isSkyFunction(SkyFunctions.CONTAINING_PACKAGE_LOOKUP);
private static final Predicate<SkyKey> IS_REPOSITORY_DIRECTORY =
SkyFunctions.isSkyFunction(SkyFunctions.REPOSITORY_DIRECTORY);
private static final Predicate<SkyKey> IS_REPO_RULE =
SkyFunctions.isSkyFunction(BzlmodRepoRuleValue.BZLMOD_REPO_RULE);
private static final Predicate<SkyKey> IS_EXTENSION_IMPL =
SkyFunctions.isSkyFunction(SkyFunctions.SINGLE_EXTENSION_EVAL);
private static void requestRepoDefinitions(
ExtendedEventHandler eventHandler, Iterable<SkyKey> repos) {
for (SkyKey repo : repos) {
if (repo instanceof RepositoryDirectoryValue.Key) {
eventHandler.post(
new RequestRepositoryInformationEvent(
((RepositoryDirectoryValue.Key) repo).argument().getName()));
}
}
}
@Override
public boolean maybeReportCycle(
SkyKey topLevelKey,
CycleInfo cycleInfo,
boolean alreadyReported,
ExtendedEventHandler eventHandler) {
ImmutableList<SkyKey> cycle = cycleInfo.getCycle();
if (alreadyReported) {
return true;
}
// This cycle reporter is aimed to handle cycles between any chaining of general .bzl
// files, extension-generated repositories, extension evaluations, and the .bzl files where
// they are declared. The state machine that describes this kind of cycles is:
// ________________________
// V |
// PACKAGE -> REPOSITORY -> EXT -> BZL_LOAD -
// ^ ^ \
// |___________________|___|
// TODO(andreisolo): Figure out how to detect and print this kind of cycles more specifically.
if (Iterables.all(
cycle,
Predicates.or(
IS_REPOSITORY_DIRECTORY,
IS_PACKAGE_LOOKUP,
IS_REPO_RULE,
IS_EXTENSION_IMPL,
IS_BZL_LOAD,
IS_CONTAINING_PACKAGE))) {
StringBuilder cycleMessage =
new StringBuilder(
"Circular definition of repositories generated by module extensions and/or .bzl"
+ " files:");
Iterable<SkyKey> repos =
Iterables.filter(
cycle, Predicates.or(IS_REPOSITORY_DIRECTORY, IS_EXTENSION_IMPL, IS_BZL_LOAD));
Function<SkyKey, String> printer =
input -> {
if (input instanceof RepositoryDirectoryValue.Key) {
return ((RepositoryDirectoryValue.Key) input).argument().getNameWithAt();
} else if (input.argument() instanceof ModuleExtensionId) {
ModuleExtensionId id = (ModuleExtensionId) input.argument();
return String.format(
"extension '%s' defined in %s",
id.getExtensionName(), id.getBzlFileLabel().getCanonicalForm());
} else {
Preconditions.checkArgument(input.argument() instanceof BzlLoadValue.Key);
return ((BzlLoadValue.Key) input.argument()).getLabel().toString();
}
};
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_BZL_LOAD)) {
Label fileLabel =
((BzlLoadValue.Key) Iterables.getLast(Iterables.filter(cycle, IS_BZL_LOAD))).getLabel();
eventHandler.handle(
Event.error(
null,
String.format(
"Failed to load .bzl file '%s': possible dependency cycle detected.\n",
fileLabel)));
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,
String.format(
"cannot load package '%s': possible dependency cycle detected.\n", pkg)));
return true;
}
return false;
}
}