RELNOTES[NEW]: bazel info: Allow to specify multiple keys.

If you specify more than one key, then the value will prefixed with the
key.

Closes #11591.

PiperOrigin-RevId: 316845962
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
index 282ff8e..7ee7ea3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.analysis.NoBuildEvent;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
@@ -80,6 +81,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 /**
  * Implementation of 'blaze info'.
@@ -213,38 +215,37 @@
                     "Blaze info does not support starlark options. Ignoring options: "
                         + removedStarlarkOptions));
       }
-      if (residue.size() > 1) {
-        String message = "at most one key may be specified";
-        env.getReporter().handle(Event.error(message));
-        return createFailureResult(
-            message, ExitCode.COMMAND_LINE_ERROR, FailureDetails.InfoCommand.Code.TOO_MANY_KEYS);
-      }
 
-      String key = residue.size() == 1 ? residue.get(0) : null;
       env.getEventBus().post(new NoBuildEvent());
-      if (key != null) { // print just the value for the specified key:
-        byte[] value;
-        if (items.containsKey(key)) {
-          value = items.get(key).get(configurationSupplier, env);
-        } else {
-          String message = "unknown key: '" + key + "'";
+      if (!residue.isEmpty()) {
+        ImmutableSet.Builder<String> unknownKeysBuilder = ImmutableSet.builder();
+        for (String key : residue) {
+          byte[] value;
+          if (items.containsKey(key)) {
+            try (SilentCloseable c = Profiler.instance().profile(key + ".infoItem")) {
+              value = items.get(key).get(configurationSupplier, env);
+              if (residue.size() > 1) {
+                outErr.getOutputStream().write((key + ": ").getBytes(StandardCharsets.UTF_8));
+              }
+              outErr.getOutputStream().write(value);
+            }
+          } else {
+            unknownKeysBuilder.add(key);
+          }
+        }
+        ImmutableSet<String> unknownKeys = unknownKeysBuilder.build();
+        if (!unknownKeys.isEmpty()) {
+          String message =
+              "unknown key(s): "
+                  + unknownKeys.stream()
+                      .map(key -> "'" + key + "'")
+                      .collect(Collectors.joining(", "));
           env.getReporter().handle(Event.error(message));
           return createFailureResult(
               message,
               ExitCode.COMMAND_LINE_ERROR,
               FailureDetails.InfoCommand.Code.KEY_NOT_RECOGNIZED);
         }
-        try {
-          outErr.getOutputStream().write(value);
-          outErr.getOutputStream().flush();
-        } catch (IOException e) {
-          String message = "Cannot write info block: " + e.getMessage();
-          env.getReporter().handle(Event.error(message));
-          return createFailureResult(
-              message,
-              ExitCode.ANALYSIS_FAILURE,
-              FailureDetails.InfoCommand.Code.INFO_BLOCK_WRITE_FAILURE);
-        }
       } else { // print them all
         configurationSupplier.get();  // We'll need this later anyway
         for (InfoItem infoItem : items.values()) {
@@ -258,6 +259,7 @@
           }
         }
       }
+      outErr.getOutputStream().flush();
     } catch (AbruptExitException e) {
       return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
     } catch (AbruptExitRuntimeException e) {
diff --git a/src/test/shell/integration/BUILD b/src/test/shell/integration/BUILD
index 382f31a..bdb9d63 100644
--- a/src/test/shell/integration/BUILD
+++ b/src/test/shell/integration/BUILD
@@ -693,6 +693,15 @@
     ],
 )
 
+sh_test(
+    name = "info_test",
+    srcs = ["info_test.sh"],
+    data = [
+        ":test-deps",
+        "@bazel_tools//tools/bash/runfiles",
+    ],
+)
+
 ########################################################################
 # Test suites.
 
diff --git a/src/test/shell/integration/info_test.sh b/src/test/shell/integration/info_test.sh
new file mode 100755
index 0000000..d19dfdc
--- /dev/null
+++ b/src/test/shell/integration/info_test.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+#
+# Copyright 2020 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.
+#
+# An end-to-end test that Bazel info command reasonable output.
+
+# --- begin runfiles.bash initialization ---
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  if [[ -f "$0.runfiles_manifest" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+  elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+  elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+    export RUNFILES_DIR="$0.runfiles"
+  fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+  source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+            "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+  echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+  exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+
+#### TESTS #############################################################
+
+function test_info() {
+  bazel info >$TEST_log \
+    || fail "${PRODUCT_NAME} info failed"
+
+  # Test some arbitrary keys.
+  expect_log 'max-heap-size: [0-9]*MB'
+  expect_log 'server_pid: [0-9]*'
+  expect_log 'command_log: .*/command\.log'
+  expect_log 'release: development version'
+
+  # Make sure that hidden keys are not shown.
+  expect_not_log 'used-heap-size-after-gc'
+  expect_not_log 'starlark-semantics'
+}
+
+function test_server_pid() {
+  bazel info server_pid >$TEST_log \
+    || fail "${PRODUCT_NAME} info failed"
+  expect_log '[0-9]*'
+}
+
+function test_used_heap_size_after_gc() {
+  bazel info used-heap-size-after-gc >$TEST_log \
+    || fail "${PRODUCT_NAME} info failed"
+  expect_log '[0-9]*MB'
+}
+
+function test_starlark_semantics() {
+  bazel info starlark-semantics >$TEST_log \
+    || fail "${PRODUCT_NAME} info failed"
+  expect_log 'StarlarkSemantics{.*}'
+}
+
+function test_multiple_keys() {
+  bazel info release used-heap-size gc-count >$TEST_log \
+    || fail "${PRODUCT_NAME} info failed"
+  expect_log 'release: development version'
+  expect_log 'used-heap-size: [0-9]*MB'
+  expect_log 'gc-count: [0-9]*'
+}
+
+function test_multiple_keys_wrong_keys() {
+  bazel info command_log foo used-heap-size-after-gc bar gc-count foo &>$TEST_log \
+    && fail "expected ${PRODUCT_NAME} info to fail with unknown keys"
+
+  # First test the valid keys.
+  expect_log 'command_log: .*/command\.log'
+  expect_log 'used-heap-size-after-gc: [0-9]*MB'
+  expect_log 'gc-count: [0-9]*'
+
+  # Then the error message.
+  expect_log "ERROR: unknown key(s): 'foo', 'bar'"
+}
+
+run_suite "Integration tests for ${PRODUCT_NAME} info."