Windows: Implement Java native launcher

Now Bazel build a Windows exe binary to launch JVM for java_binary and
java_test.

The Java native launcher is implemented with the same logic and
functionalities as the original java shell stub script.

Change-Id: Ida40579bce82425f3506f9376b7256aa3edc265e
PiperOrigin-RevId: 166346445
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java
index 6323772..1e07ab5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java
@@ -32,6 +32,12 @@
 
   private NativeLauncherUtil() {}
 
+  /** Write a string to launch info buffer. */
+  public static void writeLaunchInfo(ByteArrayOutputStream launchInfo, String value)
+      throws IOException {
+    launchInfo.write(value.getBytes(UTF_8));
+  }
+
   /** Write a key-value pair launch info to buffer. */
   public static void writeLaunchInfo(ByteArrayOutputStream launchInfo, String key, String value)
       throws IOException {
@@ -42,6 +48,30 @@
   }
 
   /**
+   * Write a key-value pair launch info to buffer. The method construct the value from a list of
+   * String separated by delimiter.
+   */
+  public static void writeLaunchInfo(
+      ByteArrayOutputStream launchInfo,
+      String key,
+      final Iterable<String> valueList,
+      char delimiter)
+      throws IOException {
+    launchInfo.write(key.getBytes(UTF_8));
+    launchInfo.write('=');
+    boolean isFirst = true;
+    for (String value : valueList) {
+      if (!isFirst) {
+        launchInfo.write(delimiter);
+      } else {
+        isFirst = false;
+      }
+      launchInfo.write(value.getBytes(UTF_8));
+    }
+    launchInfo.write('\0');
+  }
+
+  /**
    * Write the size of all the launch info as a 64-bit integer at the end of the output stream in
    * little endian.
    */
@@ -64,15 +94,17 @@
    */
   public static void createNativeLauncherActions(
       RuleContext ruleContext, Artifact launcher, ByteArrayOutputStream launchInfo) {
+    createNativeLauncherActions(ruleContext, launcher, ByteSource.wrap(launchInfo.toByteArray()));
+  }
+
+  public static void createNativeLauncherActions(
+      RuleContext ruleContext, Artifact launcher, ByteSource launchInfo) {
     Artifact launchInfoFile =
         ruleContext.getRelatedArtifact(launcher.getRootRelativePath(), ".launch_info");
 
     ruleContext.registerAction(
         new BinaryFileWriteAction(
-            ruleContext.getActionOwner(),
-            launchInfoFile,
-            ByteSource.wrap(launchInfo.toByteArray()),
-            /*makeExecutable=*/ false));
+            ruleContext.getActionOwner(), launchInfoFile, launchInfo, /*makeExecutable=*/ false));
 
     Artifact baseLauncherBinary = ruleContext.getPrerequisiteArtifact("$launcher", Mode.HOST);
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaRuleClasses.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaRuleClasses.java
index 1b363de..73fbf27 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaRuleClasses.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
 import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
 import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.AttributeMap;
 import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
@@ -318,6 +319,10 @@
 
     @Override
     public RuleClass build(Builder builder, final RuleDefinitionEnvironment env) {
+      Label launcher = env.getLauncherLabel();
+      if (launcher != null) {
+        builder.add(attr("$launcher", LABEL).cfg(HOST).value(launcher));
+      }
       return builder
           /* <!-- #BLAZE_RULE($base_java_binary).ATTRIBUTE(classpath_resources) -->
           <em class="harmful">DO NOT USE THIS OPTION UNLESS THERE IS NO OTHER WAY)</em>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
index 845f481..6595641 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.io.ByteSource;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
@@ -35,6 +36,7 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.test.TestConfiguration;
 import com.google.devtools.build.lib.bazel.rules.BazelConfiguration;
+import com.google.devtools.build.lib.bazel.rules.NativeLauncherUtil;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -63,7 +65,11 @@
 import com.google.devtools.build.lib.util.ShellEscaper;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -239,6 +245,15 @@
     }
   }
 
+  /**
+   * In Bazel this {@code createStubAction} considers {@code javaExecutable} as a file path for the
+   * JVM binary (java).
+   */
+  @Override
+  public boolean isJavaExecutableSubstitution() {
+    return false;
+  }
+
   @Override
   public Artifact createStubAction(
       RuleContext ruleContext,
@@ -260,7 +275,10 @@
     final boolean isRunfilesEnabled = ruleContext.getConfiguration().runfilesEnabled();
     arguments.add(Substitution.of("%runfiles_manifest_only%", isRunfilesEnabled ? "" : "1"));
     arguments.add(Substitution.of("%workspace_prefix%", workspacePrefix));
-    arguments.add(Substitution.of("%javabin%", javaExecutable));
+    arguments.add(
+        Substitution.of(
+            "%javabin%",
+            JavaCommon.getJavaBinSubstitutionFromJavaExecutable(ruleContext, javaExecutable)));
     arguments.add(Substitution.of("%needs_runfiles%",
         JavaCommon.getJavaExecutable(ruleContext).isAbsolute() ? "0" : "1"));
 
@@ -312,7 +330,20 @@
 
     arguments.add(Substitution.of("%java_start_class%",
         ShellEscaper.escapeString(javaStartClass)));
-    arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", ImmutableList.copyOf(jvmFlags)));
+
+    ImmutableList<String> jvmFlagsList = ImmutableList.copyOf(jvmFlags);
+    arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", jvmFlagsList));
+
+    if (OS.getCurrent() == OS.WINDOWS
+        && ruleContext.getConfiguration().enableWindowsExeLauncher()) {
+      return createWindowsExeLauncher(
+          ruleContext,
+          javaExecutable,
+          classpath,
+          javaStartClass,
+          jvmFlagsList,
+          executable);
+    }
 
     ruleContext.registerAction(new TemplateExpansionAction(
         ruleContext.getActionOwner(), executable, STUB_SCRIPT, arguments, true));
@@ -345,6 +376,84 @@
     }
   }
 
+  private static class JavaLaunchInfoByteSource extends ByteSource {
+    private final String workspaceName;
+    private final String javaBinPath;
+    private final String jarBinPath;
+    private final String javaStartClass;
+    private final ImmutableList<String> jvmFlags;
+    private final NestedSet<Artifact> classpath;
+
+    private JavaLaunchInfoByteSource(
+        String workspaceName,
+        String javaBinPath,
+        String jarBinPath,
+        String javaStartClass,
+        ImmutableList<String> jvmFlags,
+        NestedSet<Artifact> classpath) {
+      this.workspaceName = workspaceName;
+      this.javaBinPath = javaBinPath;
+      this.jarBinPath = jarBinPath;
+      this.javaStartClass = javaStartClass;
+      this.jvmFlags = jvmFlags;
+      this.classpath = classpath;
+    }
+
+    @Override
+    public InputStream openStream() throws IOException {
+      ByteArrayOutputStream launchInfo = new ByteArrayOutputStream();
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "binary_type", "Java");
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "workspace_name", workspaceName);
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "java_bin_path", javaBinPath);
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "jar_bin_path", jarBinPath);
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "java_start_class", javaStartClass);
+
+      // To be more efficient, we don't construct a key-value pair for classpath.
+      // Instead, we directly write it into launchInfo.
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "classpath=");
+      boolean isFirst = true;
+      for (Artifact artifact : classpath) {
+        if (!isFirst) {
+          NativeLauncherUtil.writeLaunchInfo(launchInfo, ";");
+        } else {
+          isFirst = false;
+        }
+        NativeLauncherUtil.writeLaunchInfo(launchInfo, artifact.getRootRelativePathString());
+      }
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "\0");
+
+      NativeLauncherUtil.writeLaunchInfo(launchInfo, "jvm_flags", jvmFlags, ' ');
+
+      NativeLauncherUtil.writeDataSize(launchInfo);
+      return new ByteArrayInputStream(launchInfo.toByteArray());
+    }
+  }
+
+  private static Artifact createWindowsExeLauncher(
+      RuleContext ruleContext,
+      String javaExecutable,
+      NestedSet<Artifact> classpath,
+      String javaStartClass,
+      ImmutableList<String> jvmFlags,
+      Artifact javaLauncher) {
+
+    ByteSource launchInfoSource =
+        new JavaLaunchInfoByteSource(
+            ruleContext.getWorkspaceName(),
+            javaExecutable,
+            JavaCommon.getJavaExecutable(ruleContext)
+              .getParentDirectory()
+              .getRelative("jar.exe")
+              .getPathString(),
+            javaStartClass,
+            jvmFlags,
+            classpath);
+
+    NativeLauncherUtil.createNativeLauncherActions(ruleContext, javaLauncher, launchInfoSource);
+
+    return javaLauncher;
+  }
+
   private static boolean enforceExplicitJavaTestDeps(RuleContext ruleContext) {
     return ruleContext.getFragment(JavaConfiguration.class).explicitJavaTestDeps();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
index c28b32e..cc6aff0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java
@@ -60,6 +60,7 @@
 import com.google.devtools.build.lib.rules.java.SingleJarActionBuilder;
 import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
 import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
@@ -127,7 +128,14 @@
             attributesBuilder);
     Artifact instrumentationMetadata =
         helper.createInstrumentationMetadata(classJar, javaArtifactsBuilder);
-    Artifact executable = ruleContext.createOutputArtifact(); // the artifact for the rule itself
+    Artifact executable; // the artifact for the rule itself
+    if (OS.getCurrent() == OS.WINDOWS
+        && ruleContext.getConfiguration().enableWindowsExeLauncher()) {
+      executable =
+          ruleContext.getImplicitOutputArtifact(ruleContext.getTarget().getName() + ".exe");
+    } else {
+      executable = ruleContext.createOutputArtifact();
+    }
     NestedSetBuilder<Artifact> filesToBuildBuilder =
         NestedSetBuilder.<Artifact>stableOrder().add(classJar).add(executable);
 
@@ -181,13 +189,20 @@
 
     Artifact launcher = JavaHelper.launcherArtifactForTarget(javaSemantics, ruleContext);
 
+    String javaExecutable;
+    if (javaSemantics.isJavaExecutableSubstitution()) {
+      javaExecutable = JavaCommon.getJavaBinSubstitution(ruleContext, launcher);
+    } else {
+      javaExecutable = JavaCommon.getJavaExecutableForStub(ruleContext, launcher);
+    }
+
     javaSemantics.createStubAction(
         ruleContext,
         javaCommon,
         getJvmFlags(ruleContext, testClass),
         executable,
         mainClass,
-        JavaCommon.getJavaBinSubstitution(ruleContext, launcher));
+        javaExecutable);
 
     Artifact deployJar =
         ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_DEPLOY_JAR);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
index 9a093d1..0672fe4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
@@ -48,6 +48,7 @@
 import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput;
 import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
 import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -154,7 +155,14 @@
     Artifact executableForRunfiles = null;
     if (createExecutable) {
       // This artifact is named as the rule itself, e.g. //foo:bar_bin -> bazel-bin/foo/bar_bin
-      executableForRunfiles = ruleContext.createOutputArtifact();
+      // On Windows, it's going to be bazel-bin/foo/bar_bin.exe
+      if (OS.getCurrent() == OS.WINDOWS
+          && ruleContext.getConfiguration().enableWindowsExeLauncher()) {
+        executableForRunfiles =
+            ruleContext.getImplicitOutputArtifact(ruleContext.getTarget().getName() + ".exe");
+      } else {
+        executableForRunfiles = ruleContext.createOutputArtifact();
+      }
       filesBuilder.add(classJar).add(executableForRunfiles);
 
       if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
@@ -238,6 +246,12 @@
 
     Artifact executableToRun = executableForRunfiles;
     if (createExecutable) {
+      String javaExecutable;
+      if (semantics.isJavaExecutableSubstitution()) {
+        javaExecutable = JavaCommon.getJavaBinSubstitution(ruleContext, launcher);
+      } else {
+        javaExecutable = JavaCommon.getJavaExecutableForStub(ruleContext, launcher);
+      }
       // Create a shell stub for a Java application
       executableToRun =
           semantics.createStubAction(
@@ -246,7 +260,7 @@
               jvmFlags,
               executableForRunfiles,
               mainClass,
-              JavaCommon.getJavaBinSubstitution(ruleContext, launcher));
+              javaExecutable);
       if (!executableToRun.equals(executableForRunfiles)) {
         filesBuilder.add(executableToRun);
         runfilesBuilder.addArtifact(executableToRun);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
index 3490db7..8e7fdf2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
@@ -480,10 +480,11 @@
   }
 
   /**
-   * Returns the string that the stub should use to determine the JVM
+   * Returns the path of the java executable that the java stub should use.
+   *
    * @param launcher if non-null, the cc_binary used to launch the Java Virtual Machine
    */
-  public static String getJavaBinSubstitution(
+  public static String getJavaExecutableForStub(
       RuleContext ruleContext, @Nullable Artifact launcher) {
     Preconditions.checkState(ruleContext.getConfiguration().hasFragment(Jvm.class));
     PathFragment javaExecutable;
@@ -501,8 +502,16 @@
       javaExecutable =
           PathFragment.create(PathFragment.create(ruleContext.getWorkspaceName()), javaExecutable);
     }
-    javaExecutable = javaExecutable.normalize();
+    return javaExecutable.normalize().getPathString();
+  }
 
+  /**
+   * Returns the shell command that computes `JAVABIN`.
+   * The command derives the JVM location from a given Java executable path.
+   */
+  public static String getJavaBinSubstitutionFromJavaExecutable(
+      RuleContext ruleContext, String javaExecutableStr) {
+    PathFragment javaExecutable = PathFragment.create(javaExecutableStr);
     if (ruleContext.getConfiguration().runfilesEnabled()) {
       String prefix = "";
       if (!javaExecutable.isAbsolute()) {
@@ -514,6 +523,13 @@
     }
   }
 
+  /** Returns the string that the stub should use to determine the JVM binary (java) path */
+  public static String getJavaBinSubstitution(
+      RuleContext ruleContext, @Nullable Artifact launcher) {
+    return getJavaBinSubstitutionFromJavaExecutable(
+        ruleContext, getJavaExecutableForStub(ruleContext, launcher));
+  }
+
   /**
    * Heuristically determines the name of the primary Java class for this
    * executable, based on the rule name and the "srcs" list.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
index e6f3b9b..9e2e719 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
@@ -300,6 +300,10 @@
    * <p>For example on Windows we use a double dispatch approach: the launcher is a batch file (and
    * is created and returned by this method) which shells out to a shell script (the {@code
    * executable} argument).
+   *
+   * <p>In Blaze, this method considers {@code javaExecutable} as a substitution that can be
+   * directly used to replace %javabin% in stub script, but in Bazel this method considers {@code
+   * javaExecutable} as a file path for the JVM binary (java).
    */
   Artifact createStubAction(
       RuleContext ruleContext,
@@ -310,6 +314,12 @@
       String javaExecutable);
 
   /**
+   * Returns true if {@code createStubAction} considers {@code javaExecutable} as a substitution.
+   * Returns false if {@code createStubAction} considers {@code javaExecutable} as a file path.
+   */
+  boolean isJavaExecutableSubstitution();
+
+  /**
    * Optionally creates a file containing the relative classpaths within the runfiles tree. If
    * {@link Optional#isPresent()}, then the caller should ensure the file appears in the runfiles.
    */
diff --git a/src/test/py/bazel/launcher_test.py b/src/test/py/bazel/launcher_test.py
index d009ded..7f81864 100644
--- a/src/test/py/bazel/launcher_test.py
+++ b/src/test/py/bazel/launcher_test.py
@@ -21,48 +21,23 @@
 
 class LauncherTest(test_base.TestBase):
 
-  def testJavaBinaryLauncher(self):
-    self.ScratchFile('WORKSPACE')
-    self.ScratchFile('foo/BUILD', [
-        'java_binary(',
-        '  name = "foo",',
-        '  srcs = ["Main.java"],',
-        '  main_class = "Main",',
-        '  data = ["//bar:bar.txt"],',
-        ')',
-    ])
-    self.ScratchFile('foo/Main.java', [
-        'public class Main {',
-        '  public static void main(String[] args) {'
-        '    System.out.println("hello java");',
-        '    System.out.println("java_runfiles=" + ',
-        '        System.getenv("JAVA_RUNFILES"));',
-        '    System.out.println("runfiles_manifest_only=" + ',
-        '        System.getenv("RUNFILES_MANIFEST_ONLY"));',
-        '    System.out.println("runfiles_manifest_file=" + ',
-        '        System.getenv("RUNFILES_MANIFEST_FILE"));',
-        '  }',
-        '}',
-    ])
-    self.ScratchFile('bar/BUILD', ['exports_files(["bar.txt"])'])
-    self.ScratchFile('bar/bar.txt', ['hello'])
-
-    exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin'])
+  def _buildJavaTargets(self, bazel_bin, launcher_flag, binary_suffix):
+    exit_code, _, stderr = self.RunBazel(['build', '//foo'] + launcher_flag)
     self.AssertExitCode(exit_code, 0, stderr)
-    bazel_bin = stdout[0]
-
-    exit_code, _, stderr = self.RunBazel(['build', '//foo'])
-    self.AssertExitCode(exit_code, 0, stderr)
-    main_binary = os.path.join(bazel_bin,
-                               'foo/foo%s' % ('.cmd'
-                                              if self.IsWindows() else ''))
+    if '--windows_exe_launcher=0' in launcher_flag and self.IsWindows():
+      main_binary = os.path.join(bazel_bin, 'foo/foo%s' % '.cmd')
+    else:
+      main_binary = os.path.join(bazel_bin, 'foo/foo%s' % binary_suffix)
     self.assertTrue(os.path.isfile(main_binary))
-    self.assertTrue(os.path.isdir(os.path.join(bazel_bin, 'foo/foo.runfiles')))
+    self.assertTrue(
+        os.path.isdir(
+            os.path.join(bazel_bin, 'foo/foo%s.runfiles' % binary_suffix)))
 
     if self.IsWindows():
       self.assertTrue(os.path.isfile(main_binary))
       self.AssertRunfilesManifestContains(
-          os.path.join(bazel_bin, 'foo/foo.runfiles/MANIFEST'),
+          os.path.join(bazel_bin,
+                       'foo/foo%s.runfiles/MANIFEST' % binary_suffix),
           '__main__/bar/bar.txt')
     else:
       self.assertTrue(
@@ -74,7 +49,8 @@
     self.assertEqual(len(stdout), 4)
     self.assertEqual(stdout[0], 'hello java')
     if self.IsWindows():
-      self.assertRegexpMatches(stdout[1], r'java_runfiles=.*foo\\foo.runfiles')
+      self.assertRegexpMatches(
+          stdout[1], r'java_runfiles=.*foo\\foo%s.runfiles' % binary_suffix)
       self.assertEqual(stdout[2], 'runfiles_manifest_only=1')
       self.assertRegexpMatches(
           stdout[3], r'^runfiles_manifest_file=[a-zA-Z]:[/\\].*MANIFEST$')
@@ -212,6 +188,41 @@
     exit_code, _, stderr = self.RunBazel(['test', '//foo:test'] + launcher_flag)
     self.AssertExitCode(exit_code, 0, stderr)
 
+  def testJavaBinaryLauncher(self):
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('foo/BUILD', [
+        'java_binary(',
+        '  name = "foo",',
+        '  srcs = ["Main.java"],',
+        '  main_class = "Main",',
+        '  data = ["//bar:bar.txt"],',
+        ')',
+    ])
+    self.ScratchFile('foo/Main.java', [
+        'public class Main {',
+        '  public static void main(String[] args) {'
+        '    System.out.println("hello java");',
+        '    System.out.println("java_runfiles=" + ',
+        '        System.getenv("JAVA_RUNFILES"));',
+        '    System.out.println("runfiles_manifest_only=" + ',
+        '        System.getenv("RUNFILES_MANIFEST_ONLY"));',
+        '    System.out.println("runfiles_manifest_file=" + ',
+        '        System.getenv("RUNFILES_MANIFEST_FILE"));',
+        '  }',
+        '}',
+    ])
+    self.ScratchFile('bar/BUILD', ['exports_files(["bar.txt"])'])
+    self.ScratchFile('bar/bar.txt', ['hello'])
+
+    exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    bazel_bin = stdout[0]
+    self._buildJavaTargets(bazel_bin, ['--windows_exe_launcher=0'], '')
+    exit_code, _, stderr = self.RunBazel(['clean'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self._buildJavaTargets(bazel_bin, ['--windows_exe_launcher=1'], '.exe'
+                           if self.IsWindows() else '')
+
   def testShBinaryLauncher(self):
     self.ScratchFile('WORKSPACE')
     self.ScratchFile(
@@ -257,7 +268,9 @@
 
     self._buildShBinaryTargets(bazel_bin, ['--windows_exe_launcher=0'], '.cmd'
                                if self.IsWindows() else '')
-    self._buildShBinaryTargets(bazel_bin, [], '.exe'
+    exit_code, _, stderr = self.RunBazel(['clean'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self._buildShBinaryTargets(bazel_bin, ['--windows_exe_launcher=1'], '.exe'
                                if self.IsWindows() else '')
 
   def testShBinaryArgumentPassing(self):
@@ -286,7 +299,7 @@
     bazel_bin = stdout[0]
 
     exit_code, _, stderr = self.RunBazel(
-        ['build', '--windows_exe_launcher', '//foo:bin'])
+        ['build', '--windows_exe_launcher=1', '//foo:bin'])
     self.AssertExitCode(exit_code, 0, stderr)
 
     bin_suffix = '.exe' if self.IsWindows() else ''
@@ -358,7 +371,134 @@
 
     self._buildPyTargets(bazel_bin, ['--windows_exe_launcher=0'], '.cmd'
                          if self.IsWindows() else '')
-    self._buildPyTargets(bazel_bin, [], '.exe' if self.IsWindows() else '')
+    exit_code, _, stderr = self.RunBazel(['clean'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self._buildPyTargets(bazel_bin, ['--windows_exe_launcher=1'], '.exe'
+                         if self.IsWindows() else '')
+
+  def testWindowsJavaExeLauncher(self):
+    # Skip this test on non-Windows platforms
+    if not self.IsWindows():
+      return
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('foo/BUILD', [
+        'java_binary(',
+        '  name = "foo",',
+        '  srcs = ["Main.java"],',
+        '  main_class = "Main",',
+        '  jvm_flags = ["--flag1", "--flag2"],',
+        ')',
+    ])
+    self.ScratchFile('foo/Main.java', [
+        'public class Main {',
+        '  public static void main(String[] args) {'
+        '    System.out.println("helloworld");',
+        '  }',
+        '}',
+    ])
+
+    exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    bazel_bin = stdout[0]
+
+    exit_code, _, stderr = self.RunBazel(
+        ['build', '--windows_exe_launcher=1', '//foo:foo'])
+    self.AssertExitCode(exit_code, 0, stderr)
+
+    binary = os.path.join(bazel_bin, 'foo', 'foo.exe')
+    self.assertTrue(os.path.exists(binary))
+
+    # Add this flag to make launcher print the command it generated instead of
+    # launching the real program.
+    print_cmd = '--print_launcher_command'
+
+    exit_code, stdout, stderr = self.RunProgram([binary, '--debug', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn(
+        '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005',
+        stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--debug', print_cmd],
+        env_add={'DEFAULT_JVM_DEBUG_PORT': '12345'})
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn(
+        '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=12345',
+        stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--debug=12345', print_cmd],
+        env_add={
+            'DEFAULT_JVM_DEBUG_SUSPEND': 'n',
+            'PERSISTENT_TEST_RUNNER': 'true'
+        })
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn(
+        '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12345'
+        ',quiet=y', stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--main_advice=MyMain', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('MyMain', stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--main_advice_classpath=foo/bar', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('-classpath', stdout)
+    classpath = stdout[stdout.index('-classpath') + 1]
+    self.assertIn('foo/bar', classpath)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--jvm_flag="--some_path="./a b/c""', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('--some_path="./a b/c"', stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--jvm_flags="--path1=a --path2=b"', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('--path1=a', stdout)
+    self.assertIn('--path2=b', stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, print_cmd], env_add={'JVM_FLAGS': '--foo --bar'})
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('--flag1', stdout)
+    self.assertIn('--flag2', stdout)
+    self.assertIn('--foo', stdout)
+    self.assertIn('--bar', stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--singlejar', print_cmd])
+    self.AssertExitCode(exit_code, 1, stderr)
+    self.assertIn('foo_deploy.jar does not exist', ''.join(stderr))
+    exit_code, _, stderr = self.RunBazel(['build', '//foo:foo_deploy.jar'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--singlejar', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('-classpath', stdout)
+    classpath = stdout[stdout.index('-classpath') + 1]
+    self.assertIn('foo_deploy.jar', classpath)
+
+    exit_code, stdout, stderr = self.RunProgram([binary, '--print_javabin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('local_jdk/bin/java.exe', ''.join(stdout))
+
+    my_tmp_dir = self.ScratchDir('my/temp/dir')
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, print_cmd], env_add={'TEST_TMPDIR': my_tmp_dir})
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('-Djava.io.tmpdir=%s' % my_tmp_dir, stdout)
+
+    exit_code, stdout, stderr = self.RunProgram(
+        [binary, '--classpath_limit=0', print_cmd])
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertIn('-classpath', stdout)
+    classpath = stdout[stdout.index('-classpath') + 1]
+    self.assertIn('foo-classpath.jar', classpath)
+    classpath_jar = os.path.join(bazel_bin, 'foo', 'foo-classpath.jar')
+    self.assertTrue(os.path.exists(classpath_jar))
 
   def AssertRunfilesManifestContains(self, manifest, entry):
     with open(manifest, 'r') as f:
diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py
index fe228c9..6a5d1c6 100644
--- a/src/test/py/bazel/test_base.py
+++ b/src/test/py/bazel/test_base.py
@@ -126,15 +126,18 @@
     Raises:
       ArgumentError: if `path` is absolute or contains uplevel references
       IOError: if an I/O error occurs
+    Returns:
+      The absolute path of the directory created.
     """
     if not path:
-      return
+      return None
     abspath = self.Path(path)
     if os.path.exists(abspath):
       if os.path.isdir(abspath):
-        return
+        return abspath
       raise IOError('"%s" (%s) exists and is not a directory' % (path, abspath))
     os.makedirs(abspath)
+    return abspath
 
   def ScratchFile(self, path, lines=None):
     """Creates a file under the test's scratch directory.
diff --git a/src/tools/launcher/bash_launcher.cc b/src/tools/launcher/bash_launcher.cc
index 3e41ac1..01d9511 100644
--- a/src/tools/launcher/bash_launcher.cc
+++ b/src/tools/launcher/bash_launcher.cc
@@ -26,6 +26,8 @@
 using std::string;
 using std::vector;
 
+static constexpr const char* BASH_BIN_PATH = "bash_bin_path";
+
 ExitCode BashBinaryLauncher::Launch() {
   string bash_binary = this->GetLaunchInfoByKey(BASH_BIN_PATH);
   // If specified bash binary path doesn't exist, then fall back to
diff --git a/src/tools/launcher/bash_launcher.h b/src/tools/launcher/bash_launcher.h
index 659c144..7587355 100644
--- a/src/tools/launcher/bash_launcher.h
+++ b/src/tools/launcher/bash_launcher.h
@@ -20,8 +20,6 @@
 namespace bazel {
 namespace launcher {
 
-static constexpr const char* BASH_BIN_PATH = "bash_bin_path";
-
 class BashBinaryLauncher : public BinaryLauncherBase {
  public:
   BashBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info, int argc,
diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc
index ce626d3..b8c0bdb 100644
--- a/src/tools/launcher/java_launcher.cc
+++ b/src/tools/launcher/java_launcher.cc
@@ -12,14 +12,286 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <sstream>
+#include <string>
+#include <vector>
+
 #include "src/tools/launcher/java_launcher.h"
+#include "src/tools/launcher/util/launcher_util.h"
 
 namespace bazel {
 namespace launcher {
 
+using std::getline;
+using std::ofstream;
+using std::ostringstream;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+// The runfile path of java binary, eg. local_jdk/bin/java.exe
+static constexpr const char* JAVA_BIN_PATH = "java_bin_path";
+static constexpr const char* JAR_BIN_PATH = "jar_bin_path";
+static constexpr const char* CLASSPATH = "classpath";
+static constexpr const char* JAVA_START_CLASS = "java_start_class";
+static constexpr const char* JVM_FLAGS = "jvm_flags";
+
+// Check if a string start with a certain prefix.
+// If it's true, store the substring without the prefix in value.
+// If value is quoted, then remove the quotes.
+static bool GetFlagValue(const string& str, const string& prefix,
+                         string* value_ptr) {
+  if (str.compare(0, prefix.length(), prefix)) {
+    return false;
+  }
+  string& value = *value_ptr;
+  value = str.substr(prefix.length());
+  int len = value.length();
+  if (len >= 2 && value[0] == '"' && value[len - 1] == '"') {
+    value = value.substr(1, len - 2);
+  }
+  return true;
+}
+
+// Parses one launcher flag and updates this object's state accordingly.
+//
+// Returns true if the flag is a valid launcher flag; false otherwise.
+bool JavaBinaryLauncher::ProcessWrapperArgument(const string& argument) {
+  string flag_value;
+  if (argument.compare("--debug") == 0) {
+    string default_jvm_debug_port;
+    if (GetEnv("DEFAULT_JVM_DEBUG_PORT", &default_jvm_debug_port)) {
+      this->jvm_debug_port = default_jvm_debug_port;
+    } else {
+      this->jvm_debug_port = "5005";
+    }
+  } else if (GetFlagValue(argument, "--debug=", &flag_value)) {
+    this->jvm_debug_port = flag_value;
+  } else if (GetFlagValue(argument, "--main_advice=", &flag_value)) {
+    this->main_advice = flag_value;
+  } else if (GetFlagValue(argument, "--main_advice_classpath=", &flag_value)) {
+    this->main_advice_classpath = flag_value;
+  } else if (GetFlagValue(argument, "--jvm_flag=", &flag_value)) {
+    this->jvm_flags_cmdline.push_back(flag_value);
+  } else if (GetFlagValue(argument, "--jvm_flags=", &flag_value)) {
+    stringstream flag_value_ss(flag_value);
+    string item;
+    while (getline(flag_value_ss, item, ' ')) {
+      this->jvm_flags_cmdline.push_back(item);
+    }
+  } else if (argument.compare("--singlejar") == 0) {
+    this->singlejar = true;
+  } else if (argument.compare("--print_javabin") == 0) {
+    this->print_javabin = true;
+  } else if (GetFlagValue(argument, "--classpath_limit=", &flag_value)) {
+    this->classpath_limit = std::stoi(flag_value);
+  } else {
+    return false;
+  }
+  return true;
+}
+
+vector<string> JavaBinaryLauncher::ProcessesCommandLine() {
+  vector<string> args;
+  bool first = 1;
+  for (const auto& arg : this->GetCommandlineArguments()) {
+    // Skip the first arugment.
+    if (first) {
+      first = 0;
+      continue;
+    }
+    string flag_value;
+    // TODO(pcloudy): Should rename this flag to --native_launcher_flag.
+    // But keep it as it is for now to be consistent with the shell script
+    // launcher.
+    if (GetFlagValue(arg, "--wrapper_script_flag=", &flag_value)) {
+      if (!ProcessWrapperArgument(flag_value)) {
+        die("invalid wrapper argument '%s'", arg);
+      }
+    } else if (!args.empty() || !ProcessWrapperArgument(arg)) {
+      args.push_back(arg);
+    }
+  }
+  return args;
+}
+
+string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) {
+  static constexpr const char* URI_PREFIX = "file:/";
+  ostringstream manifest_classpath;
+  manifest_classpath << "Class-Path:";
+  stringstream classpath_ss(classpath);
+  string path;
+  while (getline(classpath_ss, path, ';')) {
+    manifest_classpath << ' ';
+    manifest_classpath << URI_PREFIX;
+    for (const auto& x : path) {
+      if (x == ' ') {
+        manifest_classpath << "%20";
+      } else {
+        manifest_classpath << x;
+      }
+    }
+  }
+
+  string binary_base_path =
+      GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]);
+  string jar_manifest_file_path = binary_base_path + ".jar_manifest";
+  ofstream jar_manifest_file(jar_manifest_file_path);
+  jar_manifest_file << "Manifest-Version: 1.0\n";
+  // No line in the MANIFEST.MF file may be longer than 72 bytes.
+  // A space prefix indicates the line is still the content of the last
+  // attribute.
+  string manifest_classpath_str = manifest_classpath.str();
+  for (size_t i = 0; i < manifest_classpath_str.length(); i += 71) {
+    if (i > 0) {
+      jar_manifest_file << " ";
+    }
+    jar_manifest_file << manifest_classpath_str.substr(i, 71) << "\n";
+  }
+  jar_manifest_file.close();
+
+  // Create the command for generating classpath jar.
+  // We pass the command to cmd.exe to use redirection for suppressing output.
+  string manifest_jar_path = binary_base_path + "-classpath.jar";
+  string jar_bin = this->Rlocation(this->GetLaunchInfoByKey(JAR_BIN_PATH),
+                                   /*need_workspace_name =*/false);
+  vector<string> arguments;
+  arguments.push_back("/c");
+
+  ostringstream jar_command;
+  jar_command << jar_bin << " cvfm ";
+  jar_command << manifest_jar_path << " ";
+  jar_command << jar_manifest_file_path << " ";
+  jar_command << "> nul";
+  arguments.push_back(jar_command.str());
+
+  if (this->LaunchProcess("cmd.exe", arguments) != 0) {
+    die("Couldn't create classpath jar: %s", manifest_jar_path.c_str());
+  }
+
+  return manifest_jar_path;
+}
+
 ExitCode JavaBinaryLauncher::Launch() {
-  // TODO(pcloudy): Implement Java launcher
-  return 0;
+  // Parse the original command line.
+  vector<string> remaining_args = this->ProcessesCommandLine();
+
+  // Set JAVA_RUNFILES
+  string java_runfiles;
+  if (!GetEnv("JAVA_RUNFILES", &java_runfiles)) {
+    java_runfiles = this->GetRunfilesPath();
+  }
+  SetEnv("JAVA_RUNFILES", java_runfiles);
+
+  // Print Java binary path if needed
+  string java_bin = this->Rlocation(this->GetLaunchInfoByKey(JAVA_BIN_PATH),
+                                    /*need_workspace_name =*/false);
+  if (this->print_javabin ||
+      this->GetLaunchInfoByKey(JAVA_START_CLASS) == "--print_javabin") {
+    printf("%s\n", java_bin.c_str());
+    return 0;
+  }
+
+  ostringstream classpath;
+
+  // Run deploy jar if needed, otherwise generate the CLASSPATH by rlocation.
+  if (this->singlejar) {
+    string deploy_jar =
+        GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]) +
+        "_deploy.jar";
+    if (!DoesFilePathExist(deploy_jar.c_str())) {
+      die("Option --singlejar was passed, but %s does not exist.\n  (You may "
+          "need to build it explicitly.)",
+          deploy_jar.c_str());
+    }
+    classpath << deploy_jar << ';';
+  } else {
+    // Add main advice classpath if exists
+    if (!this->main_advice_classpath.empty()) {
+      classpath << this->main_advice_classpath << ';';
+    }
+    string path;
+    stringstream classpath_ss(this->GetLaunchInfoByKey(CLASSPATH));
+    while (getline(classpath_ss, path, ';')) {
+      classpath << this->Rlocation(path) << ';';
+    }
+  }
+
+  // Set jvm debug options
+  ostringstream jvm_debug_flags;
+  if (!this->jvm_debug_port.empty()) {
+    string jvm_debug_suspend;
+    if (!GetEnv("DEFAULT_JVM_DEBUG_SUSPEND", &jvm_debug_suspend)) {
+      jvm_debug_suspend = "y";
+    }
+    jvm_debug_flags << "-agentlib:jdwp=transport=dt_socket,server=y";
+    jvm_debug_flags << ",suspend=" << jvm_debug_suspend;
+    jvm_debug_flags << ",address=" << jvm_debug_port;
+
+    string value;
+    if (GetEnv("PERSISTENT_TEST_RUNNER", &value) && value == "true") {
+      jvm_debug_flags << ",quiet=y";
+    }
+  }
+
+  // Get jvm flags from JVM_FLAGS environment variable and JVM_FLAGS launch info
+  vector<string> jvm_flags;
+  string jvm_flags_env;
+  GetEnv("JVM_FLAGS", &jvm_flags_env);
+  string flag;
+  stringstream jvm_flags_env_ss(jvm_flags_env);
+  while (getline(jvm_flags_env_ss, flag, ' ')) {
+    jvm_flags.push_back(flag);
+  }
+  stringstream jvm_flags_launch_info_ss(this->GetLaunchInfoByKey(JVM_FLAGS));
+  while (getline(jvm_flags_launch_info_ss, flag, ' ')) {
+    jvm_flags.push_back(flag);
+  }
+
+  // Check if TEST_TMPDIR is available to use for scratch.
+  string test_tmpdir;
+  if (GetEnv("TEST_TMPDIR", &test_tmpdir) &&
+      DoesDirectoryPathExist(test_tmpdir.c_str())) {
+    jvm_flags.push_back("-Djava.io.tmpdir=" + test_tmpdir);
+  }
+
+  // Construct the final command line arguments
+  vector<string> arguments;
+  // Add classpath flags
+  arguments.push_back("-classpath");
+  // Check if CLASSPATH is over classpath length limit.
+  // If it does, then we create a classpath jar to pass CLASSPATH value.
+  string classpath_str = classpath.str();
+  if (classpath_str.length() > this->classpath_limit) {
+    arguments.push_back(CreateClasspathJar(classpath_str));
+  } else {
+    arguments.push_back(classpath_str);
+  }
+  // Add JVM debug flags
+  string jvm_debug_flags_str = jvm_debug_flags.str();
+  if (!jvm_debug_flags_str.empty()) {
+    arguments.push_back(jvm_debug_flags_str);
+  }
+  // Add JVM flags parsed from env and launch info.
+  for (const auto& arg : jvm_flags) {
+    arguments.push_back(arg);
+  }
+  // Add JVM flags parsed from command line.
+  for (const auto& arg : this->jvm_flags_cmdline) {
+    arguments.push_back(arg);
+  }
+  // Add main advice class
+  if (!this->main_advice.empty()) {
+    arguments.push_back(this->main_advice);
+  }
+  // Add java start class
+  arguments.push_back(this->GetLaunchInfoByKey(JAVA_START_CLASS));
+  // Add the remaininng arguements, they will be passed to the program.
+  for (const auto& arg : remaining_args) {
+    arguments.push_back(arg);
+  }
+
+  return this->LaunchProcess(java_bin, arguments);
 }
 
 }  // namespace launcher
diff --git a/src/tools/launcher/java_launcher.h b/src/tools/launcher/java_launcher.h
index 5ffe588..896aacd 100644
--- a/src/tools/launcher/java_launcher.h
+++ b/src/tools/launcher/java_launcher.h
@@ -15,17 +15,81 @@
 #ifndef BAZEL_SRC_TOOLS_LAUNCHER_JAVA_LAUNCHER_H_
 #define BAZEL_SRC_TOOLS_LAUNCHER_JAVA_LAUNCHER_H_
 
+#include <string>
+#include <vector>
+
 #include "src/tools/launcher/launcher.h"
 
 namespace bazel {
 namespace launcher {
 
+// Windows per-arg limit is MAX_ARG_STRLEN == 8k,
+// here we use a slightly smaller value.
+static const int MAX_ARG_STRLEN = 7000;
+
 class JavaBinaryLauncher : public BinaryLauncherBase {
  public:
   JavaBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info, int argc,
                      char* argv[])
-      : BinaryLauncherBase(launch_info, argc, argv){}
+      : BinaryLauncherBase(launch_info, argc, argv),
+        singlejar(false),
+        print_javabin(false),
+        classpath_limit(MAX_ARG_STRLEN) {}
   ExitCode Launch();
+
+ private:
+  // If present, these flags should either be at the beginning of the command
+  // line, or they should be wrapped in a --wrapper_script_flag=FLAG argument.
+  //
+  // --debug               Launch the JVM in remote debugging mode listening
+  // --debug=<port>        to the specified port or the port set in the
+  //                       DEFAULT_JVM_DEBUG_PORT environment variable (e.g.
+  //                       'export DEFAULT_JVM_DEBUG_PORT=8000') or else the
+  //                       default port of 5005.  The JVM starts suspended
+  //                       unless the DEFAULT_JVM_DEBUG_SUSPEND environment
+  //                       variable is set to 'n'.
+  // --main_advice=<class> Run an alternate main class with the usual main
+  //                       program and arguments appended as arguments.
+  // --main_advice_classpath=<classpath>
+  //                       Prepend additional class path entries.
+  // --jvm_flag=<flag>     Pass <flag> to the "java" command itself.
+  //                       <flag> may contain spaces. Can be used multiple
+  //                       times.
+  // --jvm_flags=<flags>   Pass space-separated flags to the "java" command
+  //                       itself. Can be used multiple times.
+  // --singlejar           Start the program from the packed-up deployment
+  //                       jar rather than from the classpath.
+  // --print_javabin       Print the location of java executable binary and
+  // exit.
+  // --classpath_limit=<length>
+  //                       Specify the maximum classpath length. If the
+  //                       classpath is shorter, this script passes it to Java
+  //                       as a command line flag, otherwise it creates a
+  //                       classpath jar.
+  //
+  // The remainder of the command line is passed to the program.
+  bool ProcessWrapperArgument(const std::string& argument);
+
+  // Parse arguments sequentially until the first unrecognized arg is
+  // encountered. Scan the remaining args for --wrapper_script_flag=X options
+  // and process them.
+  //
+  // Return the remaining arguments that should be passed to the program.
+  std::vector<std::string> ProcessesCommandLine();
+
+  std::string jvm_debug_port;
+  std::string main_advice;
+  std::string main_advice_classpath;
+  std::vector<std::string> jvm_flags_cmdline;
+  bool singlejar;
+  bool print_javabin;
+  int classpath_limit;
+
+  // Create a classpath jar to pass CLASSPATH value when its length is over
+  // limit.
+  //
+  // Return the path of the classpath jar created.
+  std::string CreateClasspathJar(const std::string& classpath);
 };
 
 }  // namespace launcher
diff --git a/src/tools/launcher/launcher.cc b/src/tools/launcher/launcher.cc
index 8e82542..b981761 100644
--- a/src/tools/launcher/launcher.cc
+++ b/src/tools/launcher/launcher.cc
@@ -67,6 +67,13 @@
   die("Couldn't find MANIFEST file under %s.runfiles\\", binary.c_str());
 }
 
+string BinaryLauncherBase::GetRunfilesPath() const {
+  string runfiles_path =
+      GetBinaryPathWithExtension(this->commandline_arguments[0]) + ".runfiles";
+  std::replace(runfiles_path.begin(), runfiles_path.end(), '/', '\\');
+  return runfiles_path;
+}
+
 void BinaryLauncherBase::ParseManifestFile(ManifestFileMap* manifest_file_map,
                                            const string& manifest_path) {
   // TODO(laszlocsomor): prefix manifest_path with the longpath prefix.
@@ -90,11 +97,16 @@
   }
 }
 
-string BinaryLauncherBase::Rlocation(const string& path) const {
-  auto entry = manifest_file_map.find(this->workspace_name + "/" + path);
+string BinaryLauncherBase::Rlocation(const string& path,
+                                     bool need_workspace_name) const {
+  string query_path = path;
+  if (need_workspace_name) {
+    query_path = this->workspace_name + "/" + path;
+  }
+  auto entry = manifest_file_map.find(query_path);
   if (entry == manifest_file_map.end()) {
     die("Rlocation failed on %s, path doesn't exist in MANIFEST file",
-        path.c_str());
+        query_path.c_str());
   }
   return entry->second;
 }
@@ -132,10 +144,28 @@
   result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0;
 }
 
+bool BinaryLauncherBase::PrintLauncherCommandLine(
+    const string& executable, const vector<string>& arguments) const {
+  bool has_print_cmd_flag = false;
+  for (const auto& arg : arguments) {
+    has_print_cmd_flag |= (arg == "--print_launcher_command");
+  }
+  if (has_print_cmd_flag) {
+    printf("%s\n", executable.c_str());
+    for (const auto& arg : arguments) {
+      printf("%s\n", arg.c_str());
+    }
+  }
+  return has_print_cmd_flag;
+}
+
 ExitCode BinaryLauncherBase::LaunchProcess(
     const string& executable, const vector<string>& arguments) const {
-  SetEnvironmentVariableA("RUNFILES_MANIFEST_ONLY", "1");
-  SetEnvironmentVariableA("RUNFILES_MANIFEST_FILE", manifest_file.c_str());
+  if (PrintLauncherCommandLine(executable, arguments)) {
+    return 0;
+  }
+  SetEnv("RUNFILES_MANIFEST_ONLY", "1");
+  SetEnv("RUNFILES_MANIFEST_FILE", manifest_file);
   CmdLine cmdline;
   CreateCommandLine(&cmdline, executable, arguments);
   PROCESS_INFORMATION processInfo = {0};
diff --git a/src/tools/launcher/launcher.h b/src/tools/launcher/launcher.h
index 3ba8ca4..1915b36 100644
--- a/src/tools/launcher/launcher.h
+++ b/src/tools/launcher/launcher.h
@@ -49,9 +49,17 @@
   const std::vector<std::string>& GetCommandlineArguments() const;
 
   // Map a runfile path to its absolute path.
-  std::string Rlocation(const std::string& path) const;
+  //
+  // If need_workspace_name is true, then this method prepend workspace name to
+  // path before doing rlocation.
+  // If need_workspace_name is false, then this method uses path directly.
+  // The default value of need_workspace_name is true.
+  std::string Rlocation(const std::string& path,
+                        bool need_workspace_name = true) const;
 
   // Lauch a process with given executable and command line arguments.
+  // If --print_launcher_command exists in arguments, then we print the full
+  // command line instead of launching the real process.
   //
   // exectuable: the binary to be executed.
   // arguments:  the command line arguments to be passed to the exectuable,
@@ -62,6 +70,12 @@
   // A launch function to be implemented for a specific language.
   virtual ExitCode Launch() = 0;
 
+  // Return the runfiles directory of this binary.
+  //
+  // The method appends ".exe.runfiles" to the first command line argument,
+  // converts forward slashes to back slashes, then returns that.
+  std::string GetRunfilesPath() const;
+
  private:
   // A map to store all the launch information.
   const LaunchDataParser::LaunchInfo& launch_info;
@@ -79,6 +93,14 @@
   // A map to store all entries of the manifest file.
   std::unordered_map<std::string, std::string> manifest_file_map;
 
+  // If --print_launcher_command is presented in arguments,
+  // then print the command line.
+  //
+  // Return true if command line is printed.
+  bool PrintLauncherCommandLine(
+      const std::string& executable,
+      const std::vector<std::string>& arguments) const;
+
   // Create a command line to be passed to Windows CreateProcessA API.
   //
   // exectuable: the binary to be executed.
diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc
index 5131243..b5e65f9 100644
--- a/src/tools/launcher/python_launcher.cc
+++ b/src/tools/launcher/python_launcher.cc
@@ -24,6 +24,8 @@
 using std::string;
 using std::vector;
 
+static constexpr const char* PYTHON_BIN_PATH = "python_bin_path";
+
 ExitCode PythonBinaryLauncher::Launch() {
   string python_binary = this->GetLaunchInfoByKey(PYTHON_BIN_PATH);
   // If specified python binary path doesn't exist, then fall back to
diff --git a/src/tools/launcher/python_launcher.h b/src/tools/launcher/python_launcher.h
index d909ea6..1a21ca3 100644
--- a/src/tools/launcher/python_launcher.h
+++ b/src/tools/launcher/python_launcher.h
@@ -20,8 +20,6 @@
 namespace bazel {
 namespace launcher {
 
-static constexpr const char* PYTHON_BIN_PATH = "python_bin_path";
-
 class PythonBinaryLauncher : public BinaryLauncherBase {
  public:
   PythonBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info,
diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc
index 247c705..bc3eddb 100644
--- a/src/tools/launcher/util/launcher_util.cc
+++ b/src/tools/launcher/util/launcher_util.cc
@@ -76,6 +76,15 @@
           !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
 }
 
+bool DoesDirectoryPathExist(const char* path) {
+  // TODO(laszlocsomor): convert `path` to (const wchar_t*), add longpath-prefix
+  // and use GetFileAttributesW.
+  DWORD dwAttrib = GetFileAttributesA(path);
+
+  return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
+          (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
+}
+
 string GetBinaryPathWithoutExtension(const string& binary) {
   if (binary.find(".exe", binary.size() - 4) != string::npos) {
     return binary.substr(0, binary.length() - 4);
@@ -120,5 +129,22 @@
   return escaped_arg.str();
 }
 
+// An environment variable has a maximum size limit of 32,767 characters
+// https://msdn.microsoft.com/en-us/library/ms683188.aspx
+static const int BUFFER_SIZE = 32767;
+
+bool GetEnv(const string& env_name, string* value) {
+  char buffer[BUFFER_SIZE];
+  if (!GetEnvironmentVariableA(env_name.c_str(), buffer, BUFFER_SIZE)) {
+    return false;
+  }
+  *value = buffer;
+  return true;
+}
+
+bool SetEnv(const string& env_name, const string& value) {
+  return SetEnvironmentVariableA(env_name.c_str(), value.c_str());
+}
+
 }  // namespace launcher
 }  // namespace bazel
diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h
index 669ea1d..e558300 100644
--- a/src/tools/launcher/util/launcher_util.h
+++ b/src/tools/launcher/util/launcher_util.h
@@ -50,6 +50,20 @@
 // Check if a file exists at a given path.
 bool DoesFilePathExist(const char* path);
 
+// Check if a directory exists at a given path.
+bool DoesDirectoryPathExist(const char* path);
+
+// Get the value of a specific environment variable
+//
+// Return true if succeeded and the result is stored in buffer.
+// Return false if the environment variable doesn't exist or the value is empty.
+bool GetEnv(const std::string& env_name, std::string* buffer);
+
+// Set the value of a specific environment variable
+//
+// Return true if succeeded, otherwise false.
+bool SetEnv(const std::string& env_name, const std::string& value);
+
 }  // namespace launcher
 }  // namespace bazel
 
diff --git a/src/tools/launcher/util/launcher_util_test.cc b/src/tools/launcher/util/launcher_util_test.cc
index b895b65..9d03849 100644
--- a/src/tools/launcher/util/launcher_util_test.cc
+++ b/src/tools/launcher/util/launcher_util_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <windows.h>
 #include <cstdlib>
 #include <fstream>
 #include <iostream>
@@ -90,5 +91,22 @@
   ASSERT_FALSE(DoesFilePathExist(file2.c_str()));
 }
 
+TEST_F(LaunchUtilTest, DoesDirectoryPathExistTest) {
+  string dir1 = GetTmpDir() + "/dir1";
+  string dir2 = GetTmpDir() + "/dir2";
+  CreateDirectory(dir1.c_str(), NULL);
+  ASSERT_TRUE(DoesDirectoryPathExist(dir1.c_str()));
+  ASSERT_FALSE(DoesDirectoryPathExist(dir2.c_str()));
+}
+
+TEST_F(LaunchUtilTest, SetAndGetEnvTest) {
+  ASSERT_TRUE(SetEnv("foo", "bar"));
+  string value;
+  ASSERT_TRUE(GetEnv("foo", &value));
+  ASSERT_EQ(value, "bar");
+  SetEnv("FOO", "");
+  ASSERT_FALSE(GetEnv("FOO", &value));
+}
+
 }  // namespace launcher
 }  // namespace bazel