Build a version of Bazel with a bundled OpenJDK inside the binary.

We're using Azul Systems, Inc.'s ZuluĀ® OpenJDK build[1], as it's a good
vanilla build of OpenJDK available for our three most important
platforms:

  zulu8.20.0.5-jdk8.0.121-linux_x64.tar.gz
  zulu8.20.0.5-jdk8.0.121-macosx_x64.zip
  zulu8.20.0.5-jdk8.0.121-win_x64.zip

You can build & run a Bazel binary with an embedded JDK by simple doing:

  bazel build //src:bazel_with_jdk
  bazel-bin/src/bazel_with_jdk info

The "bazel license" command prints the license of the embedded OpenJDK.

We mirror the binaries and sources of the OpenJDK used for bundling on
this website:

https://bazel-mirror.storage.googleapis.com/openjdk/index.html

RELNOTES: Bazel can now be built with a bundled version of the OpenJDK.
This makes it possible to use Bazel on systems without a JDK, or where
the installed JDK is too old.

[1] http://www.azul.com/downloads/zulu/

--
PiperOrigin-RevId: 150440467
MOS_MIGRATED_REVID=150440467
diff --git a/src/BUILD b/src/BUILD
index fb39187..416ffb0 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -40,6 +40,7 @@
     }),
 ) for suffix, embedded_tools in {
     "": [":embedded_tools"],
+    "_with_jdk": [":embedded_tools_with_jdk"],
     "_notools": [],
 }.items()]
 
@@ -127,8 +128,8 @@
     ],
 )
 
-genrule(
-    name = "embedded_tools",
+[genrule(
+    name = "embedded_tools" + suffix,
     srcs = [
         ":create_embedded_tools.sh",
         "//tools:embedded_tools_srcs",
@@ -171,14 +172,33 @@
         "//conditions:default": [
             "//src/java_tools/buildjar/java/com/google/devtools/build/java/turbine:turbine_deploy.jar",
         ],
-    }),
-    outs = ["embedded_tools.zip"],
+    }) + (select({
+        ":darwin": [
+            "@openjdk_macos//file",
+        ],
+        ":darwin_x86_64": [
+            "@openjdk_macos//file",
+        ],
+        ":windows": [
+            "@openjdk_win//file",
+        ],
+        ":windows_msvc": [
+            "@openjdk_win//file",
+        ],
+        "//conditions:default": [
+            "@openjdk_linux//file",
+        ],
+    }) if (suffix == "_with_jdk") else []),
+    outs = ["embedded_tools" + suffix + ".zip"],
     cmd = "$(location :create_embedded_tools.sh) $@ $(SRCS)",
-)
+) for suffix in [
+    "",
+    "_with_jdk",
+]]
 
 [genrule(
     name = "package-zip" + suffix,
-    srcs = ([":embedded_tools.zip"] if embed else []) + [
+    srcs = ([":embedded_tools" + suffix + ".zip"] if embed else []) + [
         # The script assumes that the embedded tools zip (if exists) is the
         # first item here, the deploy jar the second and install base key is the
         # third
@@ -199,6 +219,7 @@
 ) for suffix, embed in [
     ("", True),
     ("_notools", False),
+    ("_with_jdk", True),
 ]]
 
 [genrule(
@@ -223,6 +244,7 @@
 ) for suffix in [
     "",
     "_notools",
+    "_with_jdk",
 ]]
 
 # Build an executable named `bazel.exe`.
@@ -243,6 +265,7 @@
 ) for suffix in [
     "",
     "_notools",
+    "_with_jdk",
 ]]
 
 config_setting(
diff --git a/src/create_embedded_tools.sh b/src/create_embedded_tools.sh
index a9b27f0..a883d95 100755
--- a/src/create_embedded_tools.sh
+++ b/src/create_embedded_tools.sh
@@ -63,6 +63,8 @@
     *xcode*xcode-locator) OUTPUT_PATH=tools/objc/xcode-locator ;;
     *src/tools/xcode/*.sh) OUTPUT_PATH=tools/objc/${i##*/} ;;
     *src/tools/xcode/*) OUTPUT_PATH=tools/objc/${i##*/}.sh ;;
+    *external/openjdk_*/file/*.tar.gz) OUTPUT_PATH=jdk.tar.gz ;;
+    *external/openjdk_*/file/*.zip) OUTPUT_PATH=jdk.zip ;;
     *) OUTPUT_PATH=$(echo $i | sed 's_^.*bazel-out/[^/]*/bin/__') ;;
   esac
 
@@ -71,6 +73,18 @@
   chmod u+w "${PACKAGE_DIR}/${OUTPUT_PATH}"
 done
 
+if [ -f ${PACKAGE_DIR}/jdk.tar.gz ]; then
+  tar xz -C ${PACKAGE_DIR} -f ${PACKAGE_DIR}/jdk.tar.gz
+  rm ${PACKAGE_DIR}/jdk.tar.gz
+  mv ${PACKAGE_DIR}/zulu* ${PACKAGE_DIR}/jdk
+fi
+
+if [ -f ${PACKAGE_DIR}/jdk.zip ]; then
+  unzip -d ${PACKAGE_DIR} ${PACKAGE_DIR}/jdk.zip > /dev/null
+  rm ${PACKAGE_DIR}/jdk.zip
+  mv ${PACKAGE_DIR}/zulu* ${PACKAGE_DIR}/jdk
+fi
+
 if [ ! -f ${PACKAGE_DIR}/third_party/java/jdk/langtools/javac-9-dev-r3297-4.jar ]; then
   cp ${PACKAGE_DIR}/third_party/java/jdk/langtools/javac7.jar \
       ${PACKAGE_DIR}/third_party/java/jdk/langtools/javac-9-dev-r3297-4.jar
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index 414e95d..55cc85d 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -347,9 +347,19 @@
 }
 
 string StartupOptions::GetHostJavabase() {
+  // 1) Allow overriding the host_javabase via --host_javabase.
   if (host_javabase.empty()) {
     if (default_host_javabase.empty()) {
-      default_host_javabase = GetDefaultHostJavabase();
+      string bundled_jre_path = blaze_util::JoinPath(
+          install_base, "_embedded_binaries/embedded_tools/jdk");
+      if (blaze_util::CanExecuteFile(blaze_util::JoinPath(
+              bundled_jre_path, GetJavaBinaryUnderJavabase()))) {
+        // 2) Use a bundled JVM if we have one.
+        default_host_javabase = bundled_jre_path;
+      } else {
+        // 3) Otherwise fall back to using the default system JVM.
+        default_host_javabase = GetDefaultHostJavabase();
+      }
     }
 
     return default_host_javabase;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/LicenseCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/LicenseCommand.java
index 5dc8a3a..8902b07 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/LicenseCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/LicenseCommand.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.runtime.commands;
 
+import com.google.common.io.Files;
 import com.google.devtools.build.lib.analysis.NoBuildEvent;
 import com.google.devtools.build.lib.runtime.BlazeCommand;
 import com.google.devtools.build.lib.runtime.Command;
@@ -20,9 +21,11 @@
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.ResourceFileLoader;
 import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.common.options.OptionsParser;
 import com.google.devtools.common.options.OptionsProvider;
 import java.io.IOException;
+import java.util.TreeSet;
 
 /** A command that prints an embedded license text. */
 @Command(
@@ -34,6 +37,16 @@
 )
 public class LicenseCommand implements BlazeCommand {
 
+  private static final TreeSet<String> JAVA_LICENSE_FILES =
+      new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+  static {
+    JAVA_LICENSE_FILES.add("ASSEMBLY_EXCEPTION");
+    JAVA_LICENSE_FILES.add("DISCLAIMER");
+    JAVA_LICENSE_FILES.add("LICENSE");
+    JAVA_LICENSE_FILES.add("THIRD_PARTY_README");
+  }
+
   public static boolean isSupported() {
     return ResourceFileLoader.resourceExists(LicenseCommand.class, "LICENSE");
   }
@@ -52,9 +65,41 @@
           "I/O error while trying to print 'LICENSE' resource: " + e.getMessage(), e);
     }
 
+    Path bundledJdk =
+        env.getDirectories().getEmbeddedBinariesRoot().getRelative("embedded_tools/jdk");
+    if (bundledJdk.exists()) {
+      outErr.printOutLn(
+          "This binary comes with a bundled JDK, which contains the following license files:\n");
+      printJavaLicenseFiles(outErr, bundledJdk);
+    }
+
+    Path bundledJre =
+        env.getDirectories().getEmbeddedBinariesRoot().getRelative("embedded_tools/jre");
+    if (bundledJre.exists()) {
+      outErr.printOutLn(
+          "This binary comes with a bundled JRE, which contains the following license files:\n");
+      printJavaLicenseFiles(outErr, bundledJre);
+    }
+
     return ExitCode.SUCCESS;
   }
 
+  private static void printJavaLicenseFiles(OutErr outErr, Path bundledJdkOrJre) {
+    try {
+      for (Path path : bundledJdkOrJre.getDirectoryEntries()) {
+        if (JAVA_LICENSE_FILES.contains(path.getBaseName().toLowerCase())) {
+          outErr.printOutLn(path.getPathString() + ":\n");
+          Files.copy(path.getPathFile(), outErr.getOutputStream());
+          outErr.printOutLn("\n");
+        }
+      }
+    } catch (IOException e) {
+      throw new IllegalStateException(
+          "I/O error while trying to print license file of bundled JDK or JRE: " + e.getMessage(),
+          e);
+    }
+  }
+
   @Override
   public void editOptions(CommandEnvironment env, OptionsParser optionsParser) {}
 }
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index ba16b20..4dcf277 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -391,6 +391,16 @@
 )
 
 sh_test(
+    name = "bazel_with_jdk_test",
+    size = "medium",
+    srcs = ["bazel_with_jdk_test.sh"],
+    data = [
+        ":test-deps",
+        "//src:bazel_with_jdk",
+    ],
+)
+
+sh_test(
     name = "build_files_test",
     size = "medium",
     srcs = ["build_files_test.sh"],
diff --git a/src/test/shell/bazel/bazel_with_jdk_test.sh b/src/test/shell/bazel/bazel_with_jdk_test.sh
new file mode 100755
index 0000000..d4a51f3
--- /dev/null
+++ b/src/test/shell/bazel/bazel_with_jdk_test.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+#
+# Tests that the version of Bazel with a bundled JDK works.
+#
+
+# Load the test setup defined in the parent directory
+source $(rlocation io_bazel/src/test/shell/integration_test_setup.sh) \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function bazel_with_jdk() {
+  $(rlocation io_bazel/src/bazel_with_jdk) --bazelrc=$TEST_TMPDIR/bazelrc "$@"
+  return $?
+}
+
+function set_up() {
+  # TODO(philwo) remove this when the testenv improvement change is in
+  if is_windows; then
+    export PATH=/c/python_27_amd64/files:$PATH
+    EXTRA_BAZELRC="build --cpu=x64_windows_msvc"
+    setup_bazelrc
+  fi
+
+  # The default test setup adds a --host_javabase flag, which prevents us from
+  # actually using the bundled one. Remove it.
+  fgrep -v -- "--host_javabase" "$TEST_TMPDIR/bazelrc" > "$TEST_TMPDIR/bazelrc.new"
+  mv "$TEST_TMPDIR/bazelrc.new" "$TEST_TMPDIR/bazelrc"
+}
+
+function cleanup() {
+  # Prevent the default "cleanup" function from running, which fails on Windows.
+  return 0
+}
+
+function test_bazel_uses_bundled_jdk() {
+  bazel_with_jdk --batch info &> "$TEST_log" || fail "bazel_with_jdk info failed"
+  install_base="$(bazel_with_jdk --batch info install_base)"
+
+  # Case-insensitive match, because Windows paths are case-insensitive.
+  grep -sqi -- "^java-home: ${install_base}/_embedded_binaries/embedded_tools/jdk" $TEST_log || \
+      fail "bazel_with_jdk's java-home is not inside the install base"
+}
+
+# Tests that "bazel_with_jdk license" prints the license of the bundled JDK by grepping for
+# representative strings from those files. If this test breaks after upgrading the version of the
+# bundled JDK, the strings may have to be updated.
+function test_bazel_license_prints_jdk_license() {
+  bazel_with_jdk --batch license \
+      &> "$TEST_log" || fail "running bazel_with_jdk license failed"
+
+  expect_log "OPENJDK ASSEMBLY EXCEPTION" || \
+      fail "'bazel_with_jdk license' did not print an expected string from ASSEMBLY_EXCEPTION"
+
+  expect_log "Provided you have not received the software directly from Azul and have already" || \
+      fail "'bazel_with_jdk license' did not print an expected string from DISCLAIMER"
+
+  expect_log '"CLASSPATH" EXCEPTION TO THE GPL' || \
+      fail "'bazel_with_jdk license' did not print an expected string from LICENSE"
+
+  expect_log "which may be included with JRE [0-9]\+, JDK [0-9]\+, and OpenJDK [0-9]\+" || \
+      fail "'bazel_with_jdk license' did not print an expected string from THIRD_PARTY_README"
+}
+
+run_suite "bazel_with_jdk test suite"