blob: cd6bee7fd142f900e935f5158d24236ce45ff633 [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.bazel.bzlmod;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
* Precomputes an augmented version of the un-pruned dep graph that is used for dep graph
* inspection. By this stage, the Bazel module resolution should have been completed.
*/
public class BazelModuleInspectorFunction implements SkyFunction {
@Override
@Nullable
public SkyValue compute(SkyKey skyKey, Environment env)
throws SkyFunctionException, InterruptedException {
RootModuleFileValue root =
(RootModuleFileValue) env.getValue(ModuleFileValue.KEY_FOR_ROOT_MODULE);
if (root == null) {
return null;
}
BazelDepGraphValue depGraphValue = (BazelDepGraphValue) env.getValue(BazelDepGraphValue.KEY);
if (depGraphValue == null) {
return null;
}
BazelModuleResolutionValue resolutionValue =
(BazelModuleResolutionValue) env.getValue(BazelModuleResolutionValue.KEY);
if (resolutionValue == null) {
return null;
}
ImmutableMap<String, ModuleOverride> overrides = root.getOverrides();
ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph = resolutionValue.getUnprunedDepGraph();
ImmutableMap<ModuleKey, Module> resolvedDepGraph = resolutionValue.getResolvedDepGraph();
ImmutableMap<ModuleKey, AugmentedModule> depGraph =
computeAugmentedGraph(unprunedDepGraph, resolvedDepGraph.keySet(), overrides);
ImmutableSetMultimap<ModuleExtensionId, String> extensionToRepoInternalNames =
computeExtensionToRepoInternalNames(depGraphValue, env);
if (extensionToRepoInternalNames == null) {
return null;
}
// Group all ModuleKeys seen by their module name for easy lookup
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex =
ImmutableMap.copyOf(
depGraph.values().stream()
.collect(
Collectors.groupingBy(
AugmentedModule::getName,
Collectors.mapping(AugmentedModule::getKey, toImmutableSet()))));
return BazelModuleInspectorValue.create(depGraph, modulesIndex, extensionToRepoInternalNames);
}
public static ImmutableMap<ModuleKey, AugmentedModule> computeAugmentedGraph(
ImmutableMap<ModuleKey, InterimModule> unprunedDepGraph,
ImmutableSet<ModuleKey> usedModules,
ImmutableMap<String, ModuleOverride> overrides) {
Map<ModuleKey, AugmentedModule.Builder> depGraphAugmentBuilder = new HashMap<>();
// For all Modules in the un-pruned dep graph, inspect their dependencies and add themselves
// to their children AugmentedModule as dependant. Also fill in their own AugmentedModule
// with a map from their dependencies to the resolution reason that was applied to each.
// The newly created graph will also contain ModuleAugments for non-loaded modules.
for (Entry<ModuleKey, InterimModule> e : unprunedDepGraph.entrySet()) {
ModuleKey parentKey = e.getKey();
InterimModule parentModule = e.getValue();
AugmentedModule.Builder parentBuilder =
depGraphAugmentBuilder
.computeIfAbsent(
parentKey, k -> AugmentedModule.builder(k).setName(parentModule.getName()))
.setVersion(parentModule.getVersion())
.setLoaded(true);
for (String childDep : parentModule.getDeps().keySet()) {
ModuleKey originalKey = parentModule.getOriginalDeps().get(childDep).toModuleKey();
InterimModule originalModule = unprunedDepGraph.get(originalKey);
ModuleKey key = parentModule.getDeps().get(childDep).toModuleKey();
InterimModule module = unprunedDepGraph.get(key);
AugmentedModule.Builder originalChildBuilder =
depGraphAugmentBuilder.computeIfAbsent(originalKey, AugmentedModule::builder);
if (originalModule != null) {
originalChildBuilder
.setName(originalModule.getName())
.setVersion(originalModule.getVersion())
.setLoaded(true);
}
AugmentedModule.Builder newChildBuilder =
depGraphAugmentBuilder.computeIfAbsent(
key,
k ->
AugmentedModule.builder(k)
.setName(module.getName())
.setVersion(module.getVersion())
.setLoaded(true));
// originalDependants and dependants can differ because
// parentModule could have had originalChild in the unresolved graph, but in the resolved
// graph the originalChild could have become orphan due to an override or selection
originalChildBuilder.addOriginalDependant(parentKey);
// also, even if the dep has not changed, the parentModule may not be referenced
// anymore in the resolved graph, so parentModule will only be added above
if (usedModules.contains(parentKey)) {
newChildBuilder.addDependant(parentKey);
}
ResolutionReason reason = ResolutionReason.ORIGINAL;
if (!key.getVersion().equals(originalKey.getVersion())) {
ModuleOverride override = overrides.get(key.getName());
if (override != null) {
if (override instanceof SingleVersionOverride) {
reason = ResolutionReason.SINGLE_VERSION_OVERRIDE;
} else if (override instanceof MultipleVersionOverride) {
reason = ResolutionReason.MULTIPLE_VERSION_OVERRIDE;
} else {
// There is no other possible override
Preconditions.checkArgument(override instanceof NonRegistryOverride);
reason = ((NonRegistryOverride) override).getResolutionReason();
}
} else {
reason = ResolutionReason.MINIMAL_VERSION_SELECTION;
}
}
if (!reason.equals(ResolutionReason.ORIGINAL)) {
parentBuilder.addUnusedDep(childDep, originalKey);
}
parentBuilder.addDep(childDep, key);
parentBuilder.addDepReason(childDep, reason);
}
}
return depGraphAugmentBuilder.entrySet().stream()
.collect(toImmutableMap(Entry::getKey, e -> e.getValue().build()));
}
@Nullable
private ImmutableSetMultimap<ModuleExtensionId, String> computeExtensionToRepoInternalNames(
BazelDepGraphValue depGraphValue, Environment env) throws InterruptedException {
ImmutableSet<ModuleExtensionId> extensionEvalKeys =
depGraphValue.getExtensionUsagesTable().rowKeySet();
ImmutableList<SingleExtensionEvalValue.Key> singleEvalKeys =
extensionEvalKeys.stream().map(SingleExtensionEvalValue::key).collect(toImmutableList());
SkyframeLookupResult singleEvalValues = env.getValuesAndExceptions(singleEvalKeys);
ImmutableSetMultimap.Builder<ModuleExtensionId, String> extensionToRepoInternalNames =
ImmutableSetMultimap.builder();
for (SingleExtensionEvalValue.Key singleEvalKey : singleEvalKeys) {
SingleExtensionEvalValue singleEvalValue =
(SingleExtensionEvalValue) singleEvalValues.get(singleEvalKey);
if (singleEvalValue == null) {
return null;
}
extensionToRepoInternalNames.putAll(
singleEvalKey.argument(), singleEvalValue.getGeneratedRepoSpecs().keySet());
}
return extensionToRepoInternalNames.build();
}
}