aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java40
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaRuleClasses.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java113
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java19
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java18
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java22
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java10
-rw-r--r--src/test/py/bazel/launcher_test.py220
-rw-r--r--src/test/py/bazel/test_base.py7
-rw-r--r--src/tools/launcher/bash_launcher.cc2
-rw-r--r--src/tools/launcher/bash_launcher.h2
-rw-r--r--src/tools/launcher/java_launcher.cc276
-rw-r--r--src/tools/launcher/java_launcher.h66
-rw-r--r--src/tools/launcher/launcher.cc40
-rw-r--r--src/tools/launcher/launcher.h24
-rw-r--r--src/tools/launcher/python_launcher.cc2
-rw-r--r--src/tools/launcher/python_launcher.h2
-rw-r--r--src/tools/launcher/util/launcher_util.cc26
-rw-r--r--src/tools/launcher/util/launcher_util.h14
-rw-r--r--src/tools/launcher/util/launcher_util_test.cc18
20 files changed, 858 insertions, 68 deletions
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 6323772b58..1e07ab52fb 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 @@ public final class NativeLauncherUtil {
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 @@ public final class NativeLauncherUtil {
}
/**
+ * 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 final class NativeLauncherUtil {
*/
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 1b363de058..73fbf27975 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.RuleDefinition;
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 @@ public class BazelJavaRuleClasses {
@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 845f48102b..65956414df 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.base.Optional;
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.actions.TemplateExpansionAction.Te
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.Preconditions;
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 @@ public class BazelJavaSemantics implements JavaSemantics {
}
}
+ /**
+ * 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 @@ public class BazelJavaSemantics implements JavaSemantics {
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 @@ public class BazelJavaSemantics implements JavaSemantics {
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 @@ public class BazelJavaSemantics implements JavaSemantics {
}
}
+ 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 c28b32e0ae..cc6aff0791 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.OneVersionCheckActionBuilder;
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 @@ public abstract class AndroidLocalTestBase implements RuleConfiguredTargetFactor
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 @@ public abstract class AndroidLocalTestBase implements RuleConfiguredTargetFactor
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 9a093d163c..0672fe4942 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.JavaConfiguration.OneVersionEnfo
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 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
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 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
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 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
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 3490db77d7..8e7fdf2395 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 @@ public class JavaCommon {
}
/**
- * 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 @@ public class JavaCommon {
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 @@ public class JavaCommon {
}
}
+ /** 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 e6f3b9b37e..9e2e719532 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 @@ public interface JavaSemantics {
* <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 @@ public interface JavaSemantics {
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 d009ded6f8..7f81864cc9 100644
--- a/src/test/py/bazel/launcher_test.py
+++ b/src/test/py/bazel/launcher_test.py
@@ -21,48 +21,23 @@ from src.test.py.bazel import test_base
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'])
- self.AssertExitCode(exit_code, 0, stderr)
- bazel_bin = stdout[0]
-
- exit_code, _, stderr = self.RunBazel(['build', '//foo'])
+ def _buildJavaTargets(self, bazel_bin, launcher_flag, binary_suffix):
+ exit_code, _, stderr = self.RunBazel(['build', '//foo'] + launcher_flag)
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 @@ class LauncherTest(test_base.TestBase):
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 @@ class LauncherTest(test_base.TestBase):
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 @@ class LauncherTest(test_base.TestBase):
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 @@ class LauncherTest(test_base.TestBase):
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 @@ class LauncherTest(test_base.TestBase):
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 fe228c9d15..6a5d1c670c 100644
--- a/src/test/py/bazel/test_base.py
+++ b/src/test/py/bazel/test_base.py
@@ -126,15 +126,18 @@ class TestBase(unittest.TestCase):
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 3e41ac152a..01d9511c74 100644
--- a/src/tools/launcher/bash_launcher.cc
+++ b/src/tools/launcher/bash_launcher.cc
@@ -26,6 +26,8 @@ using std::ostringstream;
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 659c144c39..758735561e 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 ce626d347e..b8c0bdbd43 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 5ffe5881ab..896aacd74a 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 8e82542fb4..b981761da6 100644
--- a/src/tools/launcher/launcher.cc
+++ b/src/tools/launcher/launcher.cc
@@ -67,6 +67,13 @@ string BinaryLauncherBase::FindManifestFile(const char* argv0) {
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 @@ void BinaryLauncherBase::ParseManifestFile(ManifestFileMap* manifest_file_map,
}
}
-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 @@ void BinaryLauncherBase::CreateCommandLine(
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 3ba8ca4723..1915b36455 100644
--- a/src/tools/launcher/launcher.h
+++ b/src/tools/launcher/launcher.h
@@ -49,9 +49,17 @@ class BinaryLauncherBase {
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 @@ class BinaryLauncherBase {
// 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 @@ class BinaryLauncherBase {
// 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 5131243a69..b5e65f9187 100644
--- a/src/tools/launcher/python_launcher.cc
+++ b/src/tools/launcher/python_launcher.cc
@@ -24,6 +24,8 @@ namespace launcher {
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 d909ea6bcd..1a21ca308e 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 247c7050c5..bc3eddb284 100644
--- a/src/tools/launcher/util/launcher_util.cc
+++ b/src/tools/launcher/util/launcher_util.cc
@@ -76,6 +76,15 @@ bool DoesFilePathExist(const char* path) {
!(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 @@ string GetEscapedArgument(const string& argument) {
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 669ea1dbfc..e5583006c0 100644
--- a/src/tools/launcher/util/launcher_util.h
+++ b/src/tools/launcher/util/launcher_util.h
@@ -50,6 +50,20 @@ std::string GetEscapedArgument(const std::string& argument);
// 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 b895b65c9c..9d03849a83 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 @@ TEST_F(LaunchUtilTest, DoesFilePathExistTest) {
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