diff options
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 |