Skip to content

Commit

Permalink
Windows: Implement Java native launcher
Browse files Browse the repository at this point in the history
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
  • Loading branch information
meteorcloudy authored and damienmg committed Aug 25, 2017
1 parent 5371d13 commit 54c5c5c
Show file tree
Hide file tree
Showing 20 changed files with 858 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -41,6 +47,30 @@ public static void writeLaunchInfo(ByteArrayOutputStream launchInfo, String key,
launchInfo.write('\0');
}

/**
* 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.
Expand All @@ -64,15 +94,17 @@ public static void writeDataSize(ByteArrayOutputStream launchInfo) throws IOExce
*/
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
Expand Down Expand Up @@ -318,6 +319,10 @@ public static final class BaseJavaBinaryRule implements RuleDefinition {

@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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteSource;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
Expand All @@ -35,6 +36,7 @@
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.bazel.rules.BazelConfiguration;
import com.google.devtools.build.lib.bazel.rules.NativeLauncherUtil;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
Expand Down Expand Up @@ -63,7 +65,11 @@
import com.google.devtools.build.lib.util.ShellEscaper;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -239,6 +245,15 @@ public String getValue() {
}
}

/**
* 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,
Expand All @@ -260,7 +275,10 @@ public Artifact createStubAction(
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"));

Expand Down Expand Up @@ -312,7 +330,20 @@ public Artifact createStubAction(

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));
Expand Down Expand Up @@ -345,6 +376,84 @@ public Artifact createStubAction(
}
}

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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import com.google.devtools.build.lib.rules.java.SingleJarActionBuilder;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -127,7 +128,14 @@ public ConfiguredTarget create(RuleContext ruleContext)
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);

Expand Down Expand Up @@ -181,13 +189,20 @@ public ConfiguredTarget create(RuleContext ruleContext)

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -154,7 +155,14 @@ public ConfiguredTarget create(RuleContext ruleContext)
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()) {
Expand Down Expand Up @@ -238,6 +246,12 @@ public ConfiguredTarget create(RuleContext ruleContext)

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(
Expand All @@ -246,7 +260,7 @@ public ConfiguredTarget create(RuleContext ruleContext)
jvmFlags,
executableForRunfiles,
mainClass,
JavaCommon.getJavaBinSubstitution(ruleContext, launcher));
javaExecutable);
if (!executableToRun.equals(executableForRunfiles)) {
filesBuilder.add(executableToRun);
runfilesBuilder.addArtifact(executableToRun);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,10 +480,11 @@ public static PathFragment getJavaExecutable(RuleContext ruleContext) {
}

/**
* 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;
Expand All @@ -501,8 +502,16 @@ public static String getJavaBinSubstitution(
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()) {
Expand All @@ -514,6 +523,13 @@ public static String getJavaBinSubstitution(
}
}

/** 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.
Expand Down
Loading

0 comments on commit 54c5c5c

Please sign in to comment.