diff options
author | 2017-08-24 17:40:22 +0200 | |
---|---|---|
committer | 2017-08-25 12:53:08 +0200 | |
commit | 54c5c5cf41bd47d11ea309f37697d83ae65fe9e7 (patch) | |
tree | 181e37a31a457de9989f79199bd98d046e85830c /src/main | |
parent | 5371d134cfaf3c258f9ebb606a5cb061f419613e (diff) |
Windows: Implement Java native launcher
Now Bazel build a Windows exe binary to launch JVM for java_binary and
java_test.
The Java native launcher is implemented with the same logic and
functionalities as the original java shell stub script.
Change-Id: Ida40579bce82425f3506f9376b7256aa3edc265e
PiperOrigin-RevId: 166346445
Diffstat (limited to 'src/main')
7 files changed, 214 insertions, 13 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. */ |