Skip to content

Commit

Permalink
Add module extensions to modquery
Browse files Browse the repository at this point in the history
- Now includes extension usages and repositories inside query graphs along related options
- `show` now supports extension-generated repos
- Added new subcommand `show_extension` which displays the list of repos generated by that extension and its usages by each module
- Since this CL introduces a new argument type to modquery (`<extension>`), refactored modquery argument parsing logic (see `ModuleArg` and `ExtensionArg`). For a user-friendly description, see the `modquery.txt` file.
- Added some basic Python integration tests for modquery (more to come).

#15365

Co-authored-by: Xùdōng Yáng <wyverald@gmail.com>
PiperOrigin-RevId: 547524086
Change-Id: If1364f01c3be871343edcd5cee94b1180b4b930f
  • Loading branch information
andyrinne12 and Wyverald committed Jul 12, 2023
1 parent 1169a56 commit e8a3097
Show file tree
Hide file tree
Showing 32 changed files with 3,014 additions and 762 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ java_library(
],
deps = [
":common",
":module_extension",
"//src/main/java/com/google/devtools/build/lib/skyframe:sky_functions",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:serialization-constant",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
Expand All @@ -274,6 +275,7 @@ java_library(
deps = [
":common",
":inspection",
":module_extension",
":resolution",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,23 @@

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;
Expand All @@ -49,6 +53,10 @@ public SkyValue compute(SkyKey skyKey, Environment env)
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) {
Expand All @@ -61,6 +69,12 @@ public SkyValue compute(SkyKey skyKey, Environment env)
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(
Expand All @@ -70,7 +84,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
AugmentedModule::getName,
Collectors.mapping(AugmentedModule::getKey, toImmutableSet()))));

return BazelModuleInspectorValue.create(depGraph, modulesIndex);
return BazelModuleInspectorValue.create(depGraph, modulesIndex, extensionToRepoInternalNames);
}

public static ImmutableMap<ModuleKey, AugmentedModule> computeAugmentedGraph(
Expand Down Expand Up @@ -157,4 +171,27 @@ public static ImmutableMap<ModuleKey, AugmentedModule> computeAugmentedGraph(
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.skyframe.SkyKey;
Expand All @@ -39,8 +41,10 @@ public abstract class BazelModuleInspectorValue implements SkyValue {

public static BazelModuleInspectorValue create(
ImmutableMap<ModuleKey, AugmentedModule> depGraph,
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex) {
return new AutoValue_BazelModuleInspectorValue(depGraph, modulesIndex);
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
ImmutableSetMultimap<ModuleExtensionId, String> extensionToRepoInternalNames) {
return new AutoValue_BazelModuleInspectorValue(
depGraph, modulesIndex, extensionToRepoInternalNames);
}

/**
Expand All @@ -58,6 +62,13 @@ public static BazelModuleInspectorValue create(
*/
public abstract ImmutableMap<String, ImmutableSet<ModuleKey>> getModulesIndex();

/**
* A mapping from a module extension ID, to the list of "internal" names of the repos generated by
* that extension. The "internal" name is the name directly used by the extension when
* instantiating a repo rule.
*/
public abstract ImmutableSetMultimap<ModuleExtensionId, String> getExtensionToRepoInternalNames();

/**
* A wrapper for {@link Module}, augmented with references to dependants (and also those who are
* not used in the final dep graph).
Expand Down Expand Up @@ -106,15 +117,18 @@ public abstract static class AugmentedModule {
*/
public abstract ImmutableMap<String, ResolutionReason> getDepReasons();

/** Shortcut for retrieving the union of both used and unused deps based on the unused flag. */
public ImmutableMap<ModuleKey, String> getAllDeps(boolean unused) {
/**
* Shortcut for retrieving the sorted union of both used and unused deps based on the unused
* flag.
*/
public ImmutableSortedMap<ModuleKey, String> getAllDeps(boolean unused) {
if (!unused) {
return getDeps().inverse();
return ImmutableSortedMap.copyOf(getDeps().inverse(), ModuleKey.LEXICOGRAPHIC_COMPARATOR);
} else {
Map<ModuleKey, String> map = new HashMap<>();
map.putAll(getDeps().inverse());
map.putAll(getUnusedDeps().inverse());
return ImmutableMap.copyOf(map);
return ImmutableSortedMap.copyOf(map, ModuleKey.LEXICOGRAPHIC_COMPARATOR);
}
}

Expand All @@ -127,7 +141,7 @@ public ImmutableMap<ModuleKey, String> getAllDeps(boolean unused) {

/** Flag for checking whether the module is present in the resolved dep graph. */
public boolean isUsed() {
return !getDependants().isEmpty();
return getKey().equals(ModuleKey.ROOT) || !getDependants().isEmpty();
}

/** Returns a new {@link AugmentedModule.Builder} with {@code key} set. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,33 @@

package com.google.devtools.build.lib.bazel.bzlmod;

import static com.google.common.collect.Comparators.emptiesFirst;
import static com.google.common.primitives.Booleans.falseFirst;
import static java.util.Comparator.comparing;

import com.google.auto.value.AutoValue;
import com.google.devtools.build.lib.cmdline.Label;
import java.util.Comparator;
import java.util.Optional;

/** A unique identifier for a {@link ModuleExtension}. */
@AutoValue
public abstract class ModuleExtensionId {
public static final Comparator<ModuleExtensionId> LEXICOGRAPHIC_COMPARATOR =
comparing(ModuleExtensionId::getBzlFileLabel)
.thenComparing(ModuleExtensionId::getExtensionName)
.thenComparing(
ModuleExtensionId::getIsolationKey,
emptiesFirst(IsolationKey.LEXICOGRAPHIC_COMPARATOR));

/** A unique identifier for a single isolated usage of a fixed module extension. */
@AutoValue
abstract static class IsolationKey {
static final Comparator<IsolationKey> LEXICOGRAPHIC_COMPARATOR =
comparing(IsolationKey::getModule, ModuleKey.LEXICOGRAPHIC_COMPARATOR)
.thenComparing(IsolationKey::isDevUsage, falseFirst())
.thenComparing(IsolationKey::getIsolatedUsageIndex);

/** The module which contains this isolated usage of a module extension. */
public abstract ModuleKey getModule();

Expand Down Expand Up @@ -54,4 +70,9 @@ public static ModuleExtensionId create(
Label bzlFileLabel, String extensionName, Optional<IsolationKey> isolationKey) {
return new AutoValue_ModuleExtensionId(bzlFileLabel, extensionName, isolationKey);
}

public String asTargetString() {
return String.format(
"%s%%%s", getBzlFileLabel().getUnambiguousCanonicalForm(), getExtensionName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ public static Key key(ModuleExtensionId id) {
return Key.create(id);
}

/**
* The {@link com.google.devtools.build.skyframe.SkyKey} of a {@link
* com.google.devtools.build.lib.bazel.bzlmod.SingleExtensionEvalValue}
*/
@AutoCodec
static class Key extends AbstractSkyKey<ModuleExtensionId> {
public static class Key extends AbstractSkyKey<ModuleExtensionId> {
private static final Interner<Key> interner = BlazeInterners.newWeakInterner();

protected Key(ModuleExtensionId arg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ java_library(
deps = [
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:inspection",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:module_extension",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:repo_rule_value",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/query2/query/output",
"//src/main/java/com/google/devtools/build/lib/util:maybe_complete_set",
"//src/main/java/com/google/devtools/common/options",
"//src/main/java/net/starlark/java/eval",
"//src/main/protobuf:failure_details_java_proto",
"//third_party:auto_value",
"//third_party:gson",
"//third_party:guava",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2023 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.modquery;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionId;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
import com.google.devtools.build.lib.bazel.bzlmod.modquery.ModuleArg.ModuleArgConverter;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.Label.RepoContext;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.server.FailureDetails.ModqueryCommand.Code;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Converters.CommaSeparatedNonEmptyOptionListConverter;
import com.google.devtools.common.options.OptionsParsingException;
import java.util.Optional;

/**
* Represents a reference to a module extension, parsed from a command-line argument in the form of
* {@code <module><bzl_file_label>%<extension_name>}. The {@code <module>} part is parsed as a
* {@link ModuleArg}. Valid examples include {@code @rules_java//java:extensions.bzl%toolchains},
* {@code rules_java@6.1.1//java:extensions.bzl%toolchains}, etc.
*/
@AutoValue
public abstract class ExtensionArg {
public static ExtensionArg create(
ModuleArg moduleArg, String repoRelativeBzlLabel, String extensionName) {
return new AutoValue_ExtensionArg(moduleArg, repoRelativeBzlLabel, extensionName);
}

public abstract ModuleArg moduleArg();

public abstract String repoRelativeBzlLabel();

public abstract String extensionName();

/** Resolves this {@link ExtensionArg} to a {@link ModuleExtensionId}. */
public final ModuleExtensionId resolveToExtensionId(
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
ImmutableMap<ModuleKey, AugmentedModule> depGraph,
ImmutableBiMap<String, ModuleKey> baseModuleDeps,
ImmutableBiMap<String, ModuleKey> baseModuleUnusedDeps)
throws InvalidArgumentException {
ImmutableSet<ModuleKey> refModules =
moduleArg()
.resolveToModuleKeys(
modulesIndex,
depGraph,
baseModuleDeps,
baseModuleUnusedDeps,
/* includeUnused= */ false,
/* warnUnused= */ false);
if (refModules.size() != 1) {
throw new InvalidArgumentException(
String.format(
"Module %s, as part of the extension specifier, should represent exactly one module"
+ " version. Choose one of: %s.",
moduleArg(), refModules),
Code.INVALID_ARGUMENTS);
}
ModuleKey key = Iterables.getOnlyElement(refModules);
try {
Label label =
Label.parseWithRepoContext(
repoRelativeBzlLabel(),
RepoContext.of(
key.getCanonicalRepoName(),
// Intentionally allow no repo mapping here: it's a repo-relative label!
RepositoryMapping.create(ImmutableMap.of(), key.getCanonicalRepoName())));
// TODO(wyv): support isolated extension usages?
return ModuleExtensionId.create(label, extensionName(), Optional.empty());
} catch (LabelSyntaxException e) {
throw new InvalidArgumentException(
String.format("bad label format in %s: %s", repoRelativeBzlLabel(), e.getMessage()),
Code.INVALID_ARGUMENTS,
e);
}
}

@Override
public final String toString() {
return moduleArg() + repoRelativeBzlLabel() + "%" + extensionName();
}

/** Converter for {@link ExtensionArg}. */
public static class ExtensionArgConverter extends Converter.Contextless<ExtensionArg> {
public static final ExtensionArgConverter INSTANCE = new ExtensionArgConverter();

@Override
public ExtensionArg convert(String input) throws OptionsParsingException {
int slashIdx = input.indexOf('/');
if (slashIdx < 0) {
throw new OptionsParsingException("Invalid argument " + input + ": missing .bzl label");
}
int percentIdx = input.indexOf('%');
if (percentIdx < slashIdx) {
throw new OptionsParsingException("Invalid argument " + input + ": missing extension name");
}
ModuleArg moduleArg = ModuleArgConverter.INSTANCE.convert(input.substring(0, slashIdx));
return ExtensionArg.create(
moduleArg, input.substring(slashIdx, percentIdx), input.substring(percentIdx + 1));
}

@Override
public String getTypeDescription() {
return "an <extension> identifier in the format of <module><bzl_label>%<extension_name>";
}
}

/** Converter for a comma-separated list of {@link ExtensionArg}s. */
public static class CommaSeparatedExtensionArgListConverter
extends Converter.Contextless<ImmutableList<ExtensionArg>> {

@Override
public ImmutableList<ExtensionArg> convert(String input) throws OptionsParsingException {
ImmutableList<String> args = new CommaSeparatedNonEmptyOptionListConverter().convert(input);
ImmutableList.Builder<ExtensionArg> extensionArgs = new ImmutableList.Builder<>();
for (String arg : args) {
extensionArgs.add(ExtensionArgConverter.INSTANCE.convert(arg));
}
return extensionArgs.build();
}

@Override
public String getTypeDescription() {
return "a comma-separated list of <extension>s";
}
}
}
Loading

0 comments on commit e8a3097

Please sign in to comment.