diff --git a/site/en/external/mod-command.md b/site/en/external/mod-command.md index 3e2a169cf46e3b..7359ef9bc6a5e3 100644 --- a/site/en/external/mod-command.md +++ b/site/en/external/mod-command.md @@ -83,6 +83,8 @@ modules can stand in for the corresponding repos. The `` part must be a repo-relative label (for example, `//pkg/path:file.bzl`). +### Graph command options + The following options only affect the subcommands that print graphs (`graph`, `deps`, `all_paths`, `path`, and `explain`): @@ -143,7 +145,7 @@ The following options only affect the subcommands that print graphs (`graph`, legacy platforms which cannot use Unicode. * `--output `: Include information about the module extension usages as - part of the output graph. ` can be one of: + part of the output graph. `` can be one of: * `text` *(default)*: A human-readable representation of the output graph (flattened as a tree). @@ -160,6 +162,31 @@ The following options only affect the subcommands that print graphs (`graph`, bazel mod graph --output graph | dot -Tsvg > /tmp/graph.svg ``` +### show_repo options + +`show_repo` supports a different set of output formats: + +* `--output `: Change how repository definitions are displayed. + `` can be one of: + + * `text` *(default)*: Display repository definitions in Starlark. + + * `streamed_proto`: Prints a + [length-delimited](https://protobuf.dev/programming-guides/encoding/#siz + e-limit) + stream of + [`Repository`](https://github.com/bazelbuild/bazel/blob/master/src/main/ + protobuf/build.proto) + protocol buffers. + + * `streamed_jsonproto`: Similar to `--output streamed_proto`, prints a + stream of [`Repository`](https://github.com/bazelbuild/bazel/blob/master + /src/main/protobuf/build.proto) + protocol buffers but in [NDJSON](https://github.com/ndjson/ndjson-spec) + format. + +### Other options + Other options include: * `--base_module ` *default: ``*: Specify a module relative to diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/BUILD index 56aecf3c78583c..daea66265c7f60 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/BUILD +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/BUILD @@ -24,13 +24,17 @@ java_library( "//src/main/java/com/google/devtools/build/lib/packages:label_printer", "//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/build/lib/util:string_encoding", "//src/main/java/com/google/devtools/common/options", "//src/main/java/net/starlark/java/eval", + "//src/main/protobuf:build_java_proto", "//src/main/protobuf:failure_details_java_proto", "//third_party:auto_value", "//third_party:error_prone_annotations", "//third_party:gson", "//third_party:guava", "//third_party:jsr305", + "@com_google_protobuf//:protobuf_java", + "@com_google_protobuf//:protobuf_java_util", ], ) diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutor.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutor.java index 98502f26fab846..9aa512b310606c 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutor.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutor.java @@ -17,6 +17,8 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Comparator.reverseOrder; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; @@ -39,17 +41,12 @@ import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModExecutor.ResultNode.IsExpanded; import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModExecutor.ResultNode.IsIndirect; import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModExecutor.ResultNode.NodeMetadata; -import com.google.devtools.build.lib.packages.LabelPrinter; -import com.google.devtools.build.lib.packages.RawAttributeMapper; -import com.google.devtools.build.lib.packages.Rule; -import com.google.devtools.build.lib.query2.query.output.BuildOutputFormatter.AttributeReader; -import com.google.devtools.build.lib.query2.query.output.BuildOutputFormatter.TargetOutputter; -import com.google.devtools.build.lib.query2.query.output.PossibleAttributeValues; import com.google.devtools.build.lib.util.MaybeCompleteSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.io.Writer; import java.util.ArrayDeque; import java.util.Collections; import java.util.Comparator; @@ -75,19 +72,22 @@ public class ModExecutor { private final ImmutableSetMultimap extensionRepos; private final Optional> extensionFilter; private final ModOptions options; + private final OutputStream outputStream; private final PrintWriter printer; private ImmutableMap> extensionRepoImports; public ModExecutor( - ImmutableMap depGraph, ModOptions options, Writer writer) { + ImmutableMap depGraph, + ModOptions options, + OutputStream outputStream) { this( depGraph, ImmutableTable.of(), ImmutableSetMultimap.of(), Optional.of(MaybeCompleteSet.completeSet()), options, - writer); + outputStream); } public ModExecutor( @@ -96,13 +96,17 @@ public ModExecutor( ImmutableSetMultimap extensionRepos, Optional> extensionFilter, ModOptions options, - Writer writer) { + OutputStream outputStream) { this.depGraph = depGraph; this.extensionUsages = extensionUsages; this.extensionRepos = extensionRepos; this.extensionFilter = extensionFilter; this.options = options; - this.printer = new PrintWriter(writer); + this.outputStream = outputStream; + this.printer = + new PrintWriter( + new OutputStreamWriter( + outputStream, options.charset == ModOptions.Charset.UTF8 ? UTF_8 : US_ASCII)); // Easier lookup table for repo imports by module. // It is updated after pruneByDepthAndLink to filter out pruned modules. this.extensionRepoImports = computeRepoImportsTable(depGraph.keySet()); @@ -167,10 +171,15 @@ public void allPaths(ImmutableSet from, ImmutableSet to) { } public void showRepo(ImmutableMap targetRepoRuleValues) { - RuleDisplayOutputter outputter = new RuleDisplayOutputter(printer); + var formatter = new RepoOutputFormatter(printer, outputStream, options.outputFormat); for (Entry e : targetRepoRuleValues.entrySet()) { - printer.printf("## %s:\n", e.getKey()); - outputter.outputRule(e.getValue().getRule()); + formatter.print(e.getKey(), e.getValue()); + } + + try { + outputStream.flush(); + } catch (IOException ex) { + // Ignore IOException like PrintWriter. } printer.flush(); } @@ -748,35 +757,4 @@ final Builder addCycle(ModuleKey value) { abstract ResultNode build(); } } - - /** - * Uses Query's {@link TargetOutputter} to display the generating repo rule and other information. - */ - static class RuleDisplayOutputter { - private static final AttributeReader attrReader = - (rule, attr) -> - // Query's implementation copied - PossibleAttributeValues.forRuleAndAttribute( - rule, attr, /* mayTreatMultipleAsNone= */ true); - private final TargetOutputter targetOutputter; - private final PrintWriter printer; - - RuleDisplayOutputter(PrintWriter printer) { - this.printer = printer; - this.targetOutputter = - new TargetOutputter( - this.printer, - (rule, attr) -> RawAttributeMapper.of(rule).isConfigurable(attr.getName()), - "\n", - LabelPrinter.legacy()); - } - - private void outputRule(Rule rule) { - try { - targetOutputter.outputRule(rule, attrReader, this.printer); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - } } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModOptions.java index 93e42e91a41c7f..87d2025952e1be 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModOptions.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModOptions.java @@ -253,9 +253,21 @@ public CharsetConverter() { /** Possible formats of the `mod` command result. */ public enum OutputFormat { + // Default TEXT, + + // For graph commands: JSON, - GRAPH + GRAPH, + + // For show_repo: + STREAMED_PROTO, + STREAMED_JSONPROTO; + + @Override + public String toString() { + return Ascii.toLowerCase(this.name()); + } } /** Converts an output format option string to a properly typed {@link OutputFormat} */ diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/OutputFormatters.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/OutputFormatters.java index 4f8170bab48299..160fbaf796ce26 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/OutputFormatters.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/OutputFormatters.java @@ -49,6 +49,7 @@ static OutputFormatter getFormatter(OutputFormat format) { case JSON -> jsonFormatter; case GRAPH -> graphvizFormatter; case null -> throw new IllegalArgumentException("Output format cannot be null."); + default -> throw new IllegalArgumentException("Unsupported output format: " + format); }; } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/RepoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/RepoOutputFormatter.java new file mode 100644 index 00000000000000..eead5e0c11c5f5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/RepoOutputFormatter.java @@ -0,0 +1,173 @@ +// Copyright 2025 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.modcommand; + +import static com.google.devtools.build.lib.util.StringEncoding.internalToUnicode; + +import com.google.common.base.Splitter; +import com.google.devtools.build.lib.bazel.bzlmod.BzlmodRepoRuleValue; +import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModOptions.OutputFormat; +import com.google.devtools.build.lib.packages.LabelPrinter; +import com.google.devtools.build.lib.packages.RawAttributeMapper; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import com.google.devtools.build.lib.query2.query.output.BuildOutputFormatter.AttributeReader; +import com.google.devtools.build.lib.query2.query.output.BuildOutputFormatter.TargetOutputter; +import com.google.devtools.build.lib.query2.query.output.PossibleAttributeValues; +import com.google.devtools.build.lib.query2.query.output.ProtoOutputFormatter; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.List; + +/** Outputs repository definitions for {@code mod show_repo}. */ +public class RepoOutputFormatter { + private static final JsonFormat.Printer jsonPrinter = + JsonFormat.printer().omittingInsignificantWhitespace(); + private static final Splitter PCT_SPLITTER = Splitter.on('%').limit(2); + + private final PrintWriter printer; + private final OutputStream outputStream; + private final OutputFormat outputFormat; + + public RepoOutputFormatter( + PrintWriter printer, OutputStream outputStream, OutputFormat outputFormat) { + this.printer = printer; + this.outputStream = outputStream; + this.outputFormat = outputFormat; + } + + public void print(String key, BzlmodRepoRuleValue repoRuleValue) { + switch (outputFormat) { + case TEXT -> printStarlark(key, repoRuleValue.getRule()); + case STREAMED_JSONPROTO, STREAMED_PROTO -> { + if (outputFormat == OutputFormat.STREAMED_JSONPROTO) { + printProtoJson(key, repoRuleValue.getRule()); + } else { + printStreamedProto(key, repoRuleValue.getRule()); + } + } + default -> throw new IllegalArgumentException("Unknown output format: " + outputFormat); + } + } + + private void printStarlark(String key, Rule repoRule) { + RuleDisplayOutputter outputter = new RuleDisplayOutputter(printer); + printer.printf("## %s:\n", key); + outputter.outputRule(repoRule); + } + + private void printStreamedProto(String key, Rule repoRule) { + Build.Repository serialized = serializeRepoDefinitionAsProto(key, repoRule); + try { + serialized.writeDelimitedTo(outputStream); + } catch (IOException e) { + // Ignore IOException like PrintWriter. + } + } + + private void printProtoJson(String key, Rule repoRule) { + Build.Repository serialized = serializeRepoDefinitionAsProto(key, repoRule); + try { + printer.println(jsonPrinter.print(serialized)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException(e); + } + } + + private Build.Repository serializeRepoDefinitionAsProto(String key, Rule repoRule) { + // First use ProtoOutputFormatter to convert the Rule into a Target proto. + // Then convert the Target proto to a Repository proto. + + var formatter = new ProtoOutputFormatter(); + try { + Build.Target targetPb = formatter.toTargetProtoBuffer(repoRule, LabelPrinter.legacy()); + Build.Rule rulePb = targetPb.getRule(); + + Build.Repository.Builder pbBuilder = Build.Repository.newBuilder(); + pbBuilder.setCanonicalName(internalToUnicode(repoRule.getName())); + pbBuilder.setRepoRuleName(internalToUnicode(repoRule.getRuleClassObject().getName())); + + // ruleKey is "@@//:foo.bzl%name", while repo_rule_bzl_label should just be "@@//:foo.bzl". + String ruleKey = repoRule.getRuleClassObject().getKey(); + List ruleKeyParts = PCT_SPLITTER.splitToList(ruleKey); + if (ruleKeyParts.size() >= 2) { + pbBuilder.setRepoRuleBzlLabel(internalToUnicode(ruleKeyParts.get(0))); + } + + // TODO: record and print the call stack for the repo definition itself? + + if (key.startsWith("@")) { + if (!key.startsWith("@@")) { + pbBuilder.setApparentName(internalToUnicode(key)); + } + } else { + pbBuilder.setModuleKey(internalToUnicode(key)); + } + + for (Build.Attribute attr : rulePb.getAttributeList()) { + if (attr.getName().equals("name") + && attr.getType() == Build.Attribute.Discriminator.STRING) { + continue; + } else if (attr.getName().equals("$original_name") + && attr.getType() == Build.Attribute.Discriminator.STRING) { + if (!attr.getStringValue().isEmpty()) { + pbBuilder.setOriginalName(attr.getStringValue()); + } + continue; + } + pbBuilder.addAttribute(attr); + } + + return pbBuilder.build(); + } catch (InterruptedException ex) { + // should never happen + throw new RuntimeException(ex); + } + } + + /** + * Uses Query's {@link TargetOutputter} to display the generating repo rule and other information. + */ + static class RuleDisplayOutputter { + private static final AttributeReader attrReader = + (rule, attr) -> + // Query's implementation copied + PossibleAttributeValues.forRuleAndAttribute( + rule, attr, /* mayTreatMultipleAsNone= */ true); + private final TargetOutputter targetOutputter; + private final PrintWriter printer; + + RuleDisplayOutputter(PrintWriter printer) { + this.printer = printer; + this.targetOutputter = + new TargetOutputter( + this.printer, + (rule, attr) -> RawAttributeMapper.of(rule).isConfigurable(attr.getName()), + "\n", + LabelPrinter.legacy()); + } + + private void outputRule(Rule rule) { + try { + targetOutputter.outputRule(rule, attrReader, this.printer); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/ModCommand.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModCommand.java index d82094f76a9a32..d12407f0127677 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/commands/ModCommand.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModCommand.java @@ -150,7 +150,45 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti private void validateArgs(ModSubcommand subcommand, ModOptions modOptions, List args) throws InvalidArgumentException { - // More validations can be added here in the future... + // Validate output format. + switch (subcommand) { + case SHOW_REPO -> { + switch (modOptions.outputFormat) { + case TEXT, STREAMED_JSONPROTO, STREAMED_PROTO -> {} // supported + default -> + throw new InvalidArgumentException( + String.format( + "Invalid --output '%s' for the 'show_repo' subcommand. Only 'text'," + + " 'streamed_jsonproto', and 'streamed_proto' are supported.", + modOptions.outputFormat), + Code.INVALID_ARGUMENTS); + } + } + case SHOW_EXTENSION -> { + if (modOptions.outputFormat != ModOptions.OutputFormat.TEXT) { + throw new InvalidArgumentException( + String.format( + "Invalid --output '%s' for the 'show_extension' subcommand. Only 'text' is" + + " supported.", + modOptions.outputFormat), + Code.INVALID_ARGUMENTS); + } + } + case ModSubcommand sub when sub.isGraph() -> { + switch (modOptions.outputFormat) { + case TEXT, JSON, GRAPH -> {} // supported + default -> + throw new InvalidArgumentException( + String.format( + "Invalid --output '%s' for the '%s' subcommand. " + + "Only 'text', 'json', and 'graph' are supported.", + modOptions.outputFormat, sub), + Code.INVALID_ARGUMENTS); + } + } + // We don't validate other subcommands yet since they are less confusing. + default -> {} + } if (subcommand == ModSubcommand.SHOW_REPO) { int selectedModes = 0; @@ -567,9 +605,7 @@ private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingRe moduleInspector.extensionToRepoInternalNames(), filterExtensions, modOptions, - new OutputStreamWriter( - env.getReporter().getOutErr().getOutputStream(), - modOptions.charset == UTF8 ? UTF_8 : US_ASCII)); + env.getReporter().getOutErr().getOutputStream()); try (SilentCloseable c = Profiler.instance().profile(ProfilerTask.BZLMOD, "execute mod " + subcommand)) { diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java index 9c63a3cf54fd93..08dd7e7e80581b 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/ProtoOutputFormatter.java @@ -105,7 +105,7 @@ public class ProtoOutputFormatter extends AbstractUnorderedFormatter { BuildType.LICENSE); private AspectResolver aspectResolver; - private DependencyFilter dependencyFilter; + private DependencyFilter dependencyFilter = DependencyFilter.ALL_DEPS; private boolean packageGroupIncludesDoubleSlash; private boolean relativeLocations; private boolean includeDefaultValues = true; @@ -213,7 +213,10 @@ public Build.Target toTargetProtoBuffer( } ImmutableMap> aspectsDependencies = - aspectResolver.computeAspectDependencies(target, dependencyFilter); + ImmutableMap.of(); + if (aspectResolver != null) { + aspectsDependencies = aspectResolver.computeAspectDependencies(target, dependencyFilter); + } if (!aspectsDependencies.isEmpty()) { // Add information about additional attributes from aspects. List attributes = new ArrayList<>(); diff --git a/src/main/protobuf/build.proto b/src/main/protobuf/build.proto index 737375dbfcd662..e665f5e7d3f3e1 100644 --- a/src/main/protobuf/build.proto +++ b/src/main/protobuf/build.proto @@ -361,6 +361,32 @@ message Rule { optional stardoc_output.RuleInfo rule_class_info = 17; } +// A Bazel repository, either representing a module, or created by a module +// extension. +message Repository { + // The canonical name of the repository. + optional string canonical_name = 1; + + // The name of the repository rule (e.g., http_archive). + optional string repo_rule_name = 2; + + // The canonical label of the bzl file that defined the repository rule. + optional string repo_rule_bzl_label = 3; + + // The apparent name of the repository, as visible to --base_module. + optional string apparent_name = 4; + + // If this repository is a module (not created by a module extension), + // this is the module key. + optional string module_key = 5; + + // Original name in the repo as created by a module extension. + optional string original_name = 6; + + // All of the attributes that describe the repository rule. + repeated Attribute attribute = 7; +} + // Direct dependencies of a rule in form. message ConfiguredRuleInput { // Dep's target label. diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutorTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutorTest.java index 5a7d16db4d406f..9192340a942249 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutorTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/modcommand/ModExecutorTest.java @@ -18,7 +18,6 @@ import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.AugmentedModuleBuilder.buildAugmentedModule; import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.buildTag; import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey; -import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; @@ -44,12 +43,11 @@ import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.util.MaybeCompleteSet; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.StringWriter; -import java.io.Writer; +import java.io.OutputStream; import java.nio.file.Files; import java.util.List; import java.util.Optional; @@ -65,7 +63,7 @@ public class ModExecutorTest { // TODO(andreisolo): Add a Json output test // TODO(andreisolo): Add a PATH query test - private final Writer writer = new StringWriter(); + private final OutputStream outputStream = new ByteArrayOutputStream(); // Tests for the ModExecutor::expandAndPrune core function. // @@ -95,7 +93,7 @@ public void testExpandFromTargetsFirst() throws ParseException { .buildOrThrow(); ModOptions options = ModOptions.getDefaultOptions(); - ModExecutor executor = new ModExecutor(depGraph, options, writer); + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); // RESULT: // ...> ccc -> ddd @@ -170,7 +168,7 @@ public void testPathsDepth1_containsAllTargetsWithNestedIndirect() throws ParseE ModOptions options = ModOptions.getDefaultOptions(); options.cycles = true; options.depth = 1; - ModExecutor executor = new ModExecutor(depGraph, options, writer); + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); ImmutableSet targets = ImmutableSet.of(createModuleKey("eee", "1.0"), createModuleKey("hhh", "1.0")); @@ -246,7 +244,7 @@ public void testPathsDepth1_targetParentIsDirectAndIndirectChild() throws ParseE ModOptions options = ModOptions.getDefaultOptions(); options.cycles = true; options.depth = 1; - ModExecutor executor = new ModExecutor(depGraph, options, writer); + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); ImmutableSet targets = ImmutableSet.of(createModuleKey("eee", "1.0")); // RESULT: @@ -464,9 +462,7 @@ public void testTextAndGraphOutput_indirectAndNestedTargetPathsWithUnused() File file = File.createTempFile("output_text", "txt"); file.deleteOnExit(); - Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); - ModExecutor executor = new ModExecutor(depGraph, options, writer); ImmutableSet targets = ImmutableSet.of( createModuleKey("C", "0.1"), @@ -476,11 +472,16 @@ public void testTextAndGraphOutput_indirectAndNestedTargetPathsWithUnused() createModuleKey("E", "1.0"), createModuleKey("H", "1.0")); - // Double check for human error - assertThat(executor.expandPathsToTargets(ImmutableSet.of(ModuleKey.ROOT), targets, false)) - .isEqualTo(result); + try (var outputStream = new FileOutputStream(file)) { + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); + + // Double check for human error + assertThat(executor.expandPathsToTargets(ImmutableSet.of(ModuleKey.ROOT), targets, false)) + .isEqualTo(result); + + executor.allPaths(ImmutableSet.of(ModuleKey.ROOT), targets); + } - executor.allPaths(ImmutableSet.of(ModuleKey.ROOT), targets); List textOutput = Files.readAllLines(file.toPath()); assertThat(textOutput) @@ -502,10 +503,10 @@ public void testTextAndGraphOutput_indirectAndNestedTargetPathsWithUnused() options.outputFormat = OutputFormat.GRAPH; File fileGraph = File.createTempFile("output_graph", "txt"); fileGraph.deleteOnExit(); - writer = new OutputStreamWriter(new FileOutputStream(fileGraph), UTF_8); - executor = new ModExecutor(depGraph, options, writer); - - executor.allPaths(ImmutableSet.of(ModuleKey.ROOT), targets); + try (var outputStream = new FileOutputStream(fileGraph)) { + var executor = new ModExecutor(depGraph, options, outputStream); + executor.allPaths(ImmutableSet.of(ModuleKey.ROOT), targets); + } List graphOutput = Files.readAllLines(fileGraph.toPath()); assertThat(graphOutput) @@ -662,7 +663,6 @@ public void testExtensionsInfoTextAndGraph() throws Exception { File file = File.createTempFile("output_text", "txt"); file.deleteOnExit(); - Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); // Contains the already-filtered map of target extensions along with their full list of repos ImmutableSetMultimap extensionRepos = @@ -675,11 +675,12 @@ public void testExtensionsInfoTextAndGraph() throws Exception { options.outputFormat = OutputFormat.TEXT; options.extensionInfo = ExtensionShow.ALL; - ModExecutor executor = - new ModExecutor( - depGraph, extensionUsages, extensionRepos, Optional.empty(), options, writer); - - executor.graph(ImmutableSet.of(ModuleKey.ROOT)); + try (var outputStream = new FileOutputStream(file)) { + ModExecutor executor = + new ModExecutor( + depGraph, extensionUsages, extensionRepos, Optional.empty(), options, outputStream); + executor.graph(ImmutableSet.of(ModuleKey.ROOT)); + } List textOutput = Files.readAllLines(file.toPath()); @@ -714,12 +715,13 @@ public void testExtensionsInfoTextAndGraph() throws Exception { options.outputFormat = OutputFormat.GRAPH; File fileGraph = File.createTempFile("output_graph", "txt"); fileGraph.deleteOnExit(); - writer = new OutputStreamWriter(new FileOutputStream(fileGraph), UTF_8); - executor = - new ModExecutor( - depGraph, extensionUsages, extensionRepos, Optional.empty(), options, writer); - executor.graph(ImmutableSet.of(ModuleKey.ROOT)); + try (var outputStream = new FileOutputStream(fileGraph)) { + var executor = + new ModExecutor( + depGraph, extensionUsages, extensionRepos, Optional.empty(), options, outputStream); + executor.graph(ImmutableSet.of(ModuleKey.ROOT)); + } List graphOutput = Files.readAllLines(fileGraph.toPath()); assertThat(graphOutput) @@ -764,18 +766,19 @@ public void testExtensionsInfoTextAndGraph() throws Exception { options.depth = 1; File fileText2 = File.createTempFile("output_text2", "txt"); fileText2.deleteOnExit(); - writer = new OutputStreamWriter(new FileOutputStream(fileText2), UTF_8); - executor = - new ModExecutor( - depGraph, - extensionUsages, - extensionRepos, - Optional.of(MaybeCompleteSet.copyOf(ImmutableSet.of(mavenId))), - options, - writer); - - executor.allPaths( - ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("Y", "2.0"))); + try (var outputStream = new FileOutputStream(fileText2)) { + var executor = + new ModExecutor( + depGraph, + extensionUsages, + extensionRepos, + Optional.of(MaybeCompleteSet.copyOf(ImmutableSet.of(mavenId))), + options, + outputStream); + executor.allPaths( + ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("Y", "2.0"))); + } + List textOutput2 = Files.readAllLines(fileText2.toPath()); assertThat(textOutput2) @@ -887,13 +890,13 @@ public void testModCommandPath_complexGraphFiltersCorrectly() throws ParseExcept File file = File.createTempFile("output_text", "txt"); file.deleteOnExit(); - Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); - ModExecutor executor = new ModExecutor(depGraph, options, writer); - // Test `executor.allPaths`, it should output all "interesting" paths to the target modules. - executor.allPaths( - ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("D", "1.0"))); - writer.close(); + try (var outputStream = new FileOutputStream(file)) { + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); + // Test `executor.allPaths`, it should output all "interesting" paths to the target modules. + executor.allPaths( + ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("D", "1.0"))); + } List textOutput = Files.readAllLines(file.toPath()); @@ -913,8 +916,8 @@ public void testModCommandPath_complexGraphFiltersCorrectly() throws ParseExcept // be the shortest one File file2 = File.createTempFile("output_text", "txt"); file2.deleteOnExit(); - try (Writer writer2 = new OutputStreamWriter(new FileOutputStream(file2), UTF_8)) { - ModExecutor executor2 = new ModExecutor(depGraph, options, writer2); + try (var outputStream2 = new FileOutputStream(file2)) { + ModExecutor executor2 = new ModExecutor(depGraph, options, outputStream2); executor2.path(ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("D", "1.0"))); } @@ -926,8 +929,8 @@ public void testModCommandPath_complexGraphFiltersCorrectly() throws ParseExcept // Test multiple targets D and G for allPaths File file3 = File.createTempFile("output_text_multi_all", "txt"); file3.deleteOnExit(); - try (Writer writer3 = new OutputStreamWriter(new FileOutputStream(file3), UTF_8)) { - ModExecutor executor3 = new ModExecutor(depGraph, options, writer3); + try (var outputStream3 = new FileOutputStream(file3)) { + ModExecutor executor3 = new ModExecutor(depGraph, options, outputStream3); executor3.allPaths( ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("D", "1.0"), createModuleKey("G", "1.0"))); @@ -950,8 +953,8 @@ public void testModCommandPath_complexGraphFiltersCorrectly() throws ParseExcept // Test multiple targets D and G for path (shortest path) File file4 = File.createTempFile("output_text_multi_path", "txt"); file4.deleteOnExit(); - try (Writer writer4 = new OutputStreamWriter(new FileOutputStream(file4), UTF_8)) { - ModExecutor executor4 = new ModExecutor(depGraph, options, writer4); + try (var outputStream4 = new FileOutputStream(file4)) { + ModExecutor executor4 = new ModExecutor(depGraph, options, outputStream4); executor4.path( ImmutableSet.of(ModuleKey.ROOT), ImmutableSet.of(createModuleKey("D", "1.0"), createModuleKey("G", "1.0"))); @@ -965,8 +968,8 @@ public void testModCommandPath_complexGraphFiltersCorrectly() throws ParseExcept // Test starting from E to D for allPaths File file5 = File.createTempFile("output_text_E_to_D_all", "txt"); file5.deleteOnExit(); - try (Writer writer5 = new OutputStreamWriter(new FileOutputStream(file5), UTF_8)) { - ModExecutor executor5 = new ModExecutor(depGraph, options, writer5); + try (var outputStream5 = new FileOutputStream(file5)) { + ModExecutor executor5 = new ModExecutor(depGraph, options, outputStream5); executor5.allPaths( ImmutableSet.of(createModuleKey("E", "1.0")), ImmutableSet.of(createModuleKey("D", "1.0"))); @@ -1012,8 +1015,8 @@ public void testModCommandGraph_withCycle() throws ParseException, IOException { File file = File.createTempFile("output_text_cycle", "txt"); file.deleteOnExit(); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8)) { - ModExecutor executor = new ModExecutor(depGraph, options, writer); + try (var outputStream = new FileOutputStream(file)) { + ModExecutor executor = new ModExecutor(depGraph, options, outputStream); executor.graph(ImmutableSet.of(ModuleKey.ROOT)); } @@ -1065,20 +1068,18 @@ public void testGraphWithExtensionFilterOnRoot() throws Exception { File file = File.createTempFile("output_text_repro", "txt"); file.deleteOnExit(); - Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); - - ModExecutor executor = - new ModExecutor( - depGraph, - extensionUsages, - extensionRepos, - Optional.of(MaybeCompleteSet.copyOf(ImmutableSet.of(mavenId))), - options, - writer); - - // This should not throw NPE - executor.graph(ImmutableSet.of(ModuleKey.ROOT)); - writer.close(); + try (var outputStream = new FileOutputStream(file)) { + ModExecutor executor = + new ModExecutor( + depGraph, + extensionUsages, + extensionRepos, + Optional.of(MaybeCompleteSet.copyOf(ImmutableSet.of(mavenId))), + options, + outputStream); + // This should not throw NPE + executor.graph(ImmutableSet.of(ModuleKey.ROOT)); + } List textOutput = Files.readAllLines(file.toPath()); assertThat(textOutput) @@ -1159,7 +1160,7 @@ public void testGraphWithExtensionFilterAndCycle() throws Exception { File file = File.createTempFile("output_text_cycle_ext", "txt"); file.deleteOnExit(); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8)) { + try (var outputStream = new FileOutputStream(file)) { ModExecutor executor = new ModExecutor( depGraph, @@ -1167,7 +1168,7 @@ public void testGraphWithExtensionFilterAndCycle() throws Exception { extensionRepos, Optional.of(MaybeCompleteSet.copyOf(ImmutableSet.of(extensionId))), options, - writer); + outputStream); executor.graph(ImmutableSet.of(ModuleKey.ROOT)); } diff --git a/src/test/py/bazel/bzlmod/mod_command_test.py b/src/test/py/bazel/bzlmod/mod_command_test.py index 3647d199067931..4a80306151a7a5 100644 --- a/src/test/py/bazel/bzlmod/mod_command_test.py +++ b/src/test/py/bazel/bzlmod/mod_command_test.py @@ -452,6 +452,145 @@ def testShowExtensionWithUnknownExtension(self): '\n'.join(stderr), ) + def testShowModuleAndExtensionReposFromBaseModuleJson(self): + _, stdout, _ = self.RunBazel( + [ + 'mod', + 'show_repo', + '--base_module=foo@2.0', + '--output=streamed_jsonproto', + '@bar_from_foo2', + 'ext@1.0', + '@my_repo3', + 'bar', + ], + rstrip=True, + ) + repos = [json.loads(line) for line in stdout] + + ignored_attrs = { + 'integrity', + 'path', + 'remote_module_file_urls', + 'remote_module_file_integrity', + 'urls', + } + for repo in repos: + attrs = repo.get('attribute') + if attrs: + repo['attribute'] = [ + attr + for attr in attrs + if attr.get('explicitlySpecified', False) + and attr['name'] not in ignored_attrs + ] + + self.assertListEqual( + repos, + [ + { + 'canonicalName': 'bar+', + 'repoRuleName': 'http_archive', + 'repoRuleBzlLabel': ( + '@@bazel_tools//tools/build_defs/repo:http.bzl' + ), + 'apparentName': '@bar_from_foo2', + 'attribute': [ + { + 'name': 'remote_file_integrity', + 'type': 'STRING_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'remote_file_urls', + 'type': 'STRING_LIST_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'remote_patch_strip', + 'type': 'INTEGER', + 'intValue': 0, + 'explicitlySpecified': True, + }, + { + 'name': 'remote_patches', + 'type': 'STRING_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'strip_prefix', + 'type': 'STRING', + 'stringValue': '', + 'explicitlySpecified': True, + 'nodep': False, + }, + ], + }, + { + 'canonicalName': 'ext+', + 'repoRuleName': 'local_repository', + 'moduleKey': 'ext@1.0', + 'attribute': [], + }, + { + 'canonicalName': 'ext++ext+repo3', + 'repoRuleName': 'data_repo', + 'repoRuleBzlLabel': '@@ext+//:ext.bzl', + 'apparentName': '@my_repo3', + 'originalName': 'repo3', + 'attribute': [ + { + 'name': 'data', + 'type': 'STRING', + 'stringValue': 'requested repo', + 'nodep': False, + 'explicitlySpecified': True, + }, + ], + }, + { + 'canonicalName': 'bar+', + 'repoRuleName': 'http_archive', + 'repoRuleBzlLabel': ( + '@@bazel_tools//tools/build_defs/repo:http.bzl' + ), + 'moduleKey': 'bar@2.0', + 'attribute': [ + { + 'name': 'remote_file_integrity', + 'type': 'STRING_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'remote_file_urls', + 'type': 'STRING_LIST_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'remote_patch_strip', + 'type': 'INTEGER', + 'intValue': 0, + 'explicitlySpecified': True, + }, + { + 'name': 'remote_patches', + 'type': 'STRING_DICT', + 'explicitlySpecified': True, + }, + { + 'name': 'strip_prefix', + 'type': 'STRING', + 'stringValue': '', + 'explicitlySpecified': True, + 'nodep': False, + }, + ], + }, + ], + 'wrong output in the show query for module and extension-generated' + ' repos', + ) + def testShowModuleAndExtensionReposFromBaseModule(self): _, stdout, _ = self.RunBazel( [ @@ -745,6 +884,30 @@ def testShowRepoThrowsNonexistentRepo(self): stderr, ) + # make sure https://github.com/bazelbuild/bazel/issues/27233 doesn't happen in 8.x + def testShowRepoBazelTools(self): + exit_code, stdout, stderr = self.RunBazel( + ['mod', 'show_repo', '@bazel_tools'], + rstrip=True, + ) + self.AssertExitCode(exit_code, 0, stderr) + stdout = '\n'.join(stdout) + self.assertIn('## @bazel_tools:', stdout) + self.assertIn('local_repository(', stdout) + self.assertIn('name = "bazel_tools"', stdout) + + def testShowRepoBazelToolsJson(self): + exit_code, stdout, stderr = self.RunBazel( + ['mod', 'show_repo', '--output=streamed_jsonproto', '@bazel_tools'], + rstrip=True, + ) + self.AssertExitCode(exit_code, 0, stderr) + repos = [json.loads(line) for line in stdout] + self.assertEqual(len(repos), 1) + self.assertEqual(repos[0]["canonicalName"], "bazel_tools") + self.assertEqual(repos[0]["repoRuleName"], "local_repository") + self.assertEqual(repos[0]["apparentName"], "@bazel_tools") + def testDumpRepoMapping(self): _, stdout, _ = self.RunBazel( [