aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2017-10-05 08:56:41 +0200
committerGravatar Klaus Aehlig <aehlig@google.com>2017-10-06 19:46:25 +0200
commit8a2f09c0290fd39d612390d2ccec1cc2d770f0ea (patch)
tree3cb1fa1a1a221ad357ea96b90a73b672f75459a2 /src
parent072646951fb99021b50e496530c7b1060fb49041 (diff)
Windows,launcher: add LauncherFileWriteAction
Introduce LauncherFileWriteAction, a FileWriteAction that can create a native {java,sh,py}_binary launcher from the launcher stub and the user-specified launch information. The LauncherFileWriteAction class does not use the "copy" command of cmd.exe so it's not restricted with path lengths. The class stores a LaunchInfo object, which describes the launch data that the launcher stub reads to identify its payload. The LaunchInfo is a lazy object, similar to CustomCommandLine. LaunchInfo won't fully construct the launch data until it is about to write the data to the output. Thus LaunchInfo is memory efficient because it won't create any String objects in the analysis phase that it would only read in the execution phase. Fixes https://github.com/bazelbuild/bazel/issues/3802 Change-Id: I4ddd83369e7131d42e2e9b35f105ad2dc60bcc52 PiperOrigin-RevId: 171115105
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteAction.java279
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java128
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java94
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java32
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteActionTest.java190
6 files changed, 514 insertions, 236 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteAction.java
new file mode 100644
index 0000000000..c6be7181e1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteAction.java
@@ -0,0 +1,279 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.analysis.actions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import javax.annotation.Nullable;
+
+/** Action that writes a native {@code .exe} launcher for {java,sh,py}_binary rules on Windows. */
+public final class LauncherFileWriteAction extends AbstractFileWriteAction {
+
+ // Generated with /usr/bin/uuidgen.
+ // This GUID doesn't have to be anything specific, we only use it to salt action cache keys so it
+ // just has to be unique among other actions.
+ private static final String GUID = "1f57afe7-f6f8-487c-9a8a-0a0286172fef";
+
+ private final LaunchInfo launchInfo;
+ private final Artifact launcher;
+
+ /** Creates a new {@link LauncherFileWriteAction}, registering it with the {@code ruleContext}. */
+ public static void createAndRegister(
+ RuleContext ruleContext, Artifact output, LaunchInfo launchInfo) {
+ ruleContext.registerAction(
+ new LauncherFileWriteAction(
+ ruleContext.getActionOwner(),
+ output,
+ ruleContext.getPrerequisiteArtifact("$launcher", Mode.HOST),
+ launchInfo));
+ }
+
+ /** Creates a new {@code LauncherFileWriteAction}. */
+ private LauncherFileWriteAction(
+ ActionOwner owner, Artifact output, Artifact launcher, LaunchInfo launchInfo) {
+ super(
+ owner,
+ ImmutableList.of(Preconditions.checkNotNull(launcher)),
+ output,
+ /*makeExecutable=*/ true);
+ this.launcher = launcher; // already null-checked in the superclass c'tor
+ this.launchInfo = Preconditions.checkNotNull(launchInfo);
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) {
+ // TODO(laszlocsomor): make this code check for the execution platform, not the host platform,
+ // once Bazel supports distinguishing between the two.
+ // OS.getCurrent() returns the host platform, not the execution platform, which is fine in a
+ // single-machine execution environment, but problematic with remote execution.
+ Preconditions.checkState(OS.getCurrent() == OS.WINDOWS);
+ return out -> {
+ InputStream in = this.launcher.getPath().getInputStream();
+ ByteStreams.copy(in, out);
+ long dataLength = this.launchInfo.write(out);
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN); // All Windows versions are little endian.
+ buffer.putLong(dataLength);
+ out.write(buffer.array());
+ out.flush();
+ };
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ try {
+ f.addBytes(this.launcher.getPath().getDigest());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ f.addString(this.launchInfo.fingerPrint);
+ return f.hexDigestAndReset();
+ }
+
+ /**
+ * Metadata that describes the payload of the native launcher binary.
+ *
+ * <p>This object constructs the binary metadata lazily, to save memory.
+ */
+ public static final class LaunchInfo {
+
+ /** Precomputed fingerprint of this object. */
+ public final String fingerPrint;
+
+ private final ImmutableList<Entry> entries;
+
+ private LaunchInfo(ImmutableList<Entry> entries) {
+ this.entries = entries;
+ this.fingerPrint = computeKey(entries);
+ }
+
+ /** Creates a new {@link Builder}. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Writes this object's entries to {@code out}, returns the total written amount in bytes. */
+ @VisibleForTesting
+ long write(OutputStream out) throws IOException {
+ long len = 0;
+ for (Entry e : entries) {
+ len += e.write(out);
+ out.write('\0');
+ ++len;
+ }
+ return len;
+ }
+
+ /** Computes the fingerprint of the {@code entries}. */
+ private static String computeKey(ImmutableList<Entry> entries) {
+ Fingerprint f = new Fingerprint();
+ for (Entry e : entries) {
+ e.addToFingerprint(f);
+ }
+ return f.hexDigestAndReset();
+ }
+
+ /** Writes {@code s} to {@code out} encoded as UTF-8, returns the written length in bytes. */
+ private static long writeString(String s, OutputStream out) throws IOException {
+ byte[] b = s.getBytes(StandardCharsets.UTF_8);
+ out.write(b);
+ return b.length;
+ }
+
+ /** Represents one entry in {@link LaunchInfo.entries}. */
+ private static interface Entry {
+ /** Writes this entry to {@code out}, returns the written length in bytes. */
+ long write(OutputStream out) throws IOException;
+
+ /** Adds this entry to the fingerprint computer {@code f}. */
+ void addToFingerprint(Fingerprint f);
+ }
+
+ /** A key-value pair entry. */
+ private static final class KeyValuePair implements Entry {
+ private final String key;
+ @Nullable private final String value;
+
+ public KeyValuePair(String key, @Nullable String value) {
+ this.key = Preconditions.checkNotNull(key);
+ this.value = value;
+ }
+
+ @Override
+ public long write(OutputStream out) throws IOException {
+ long len = writeString(key, out);
+ len += writeString("=", out);
+ if (value != null && !value.isEmpty()) {
+ len += writeString(value, out);
+ }
+ return len;
+ }
+
+ @Override
+ public void addToFingerprint(Fingerprint f) {
+ f.addString(key);
+ f.addString(value != null ? value : "");
+ }
+ }
+
+ /** A pair of a key and a delimiter-joined list of values. */
+ private static final class JoinedValues implements Entry {
+ private final String key;
+ private final String delimiter;
+ @Nullable private final Iterable<String> values;
+
+ public JoinedValues(String key, String delimiter, @Nullable Iterable<String> values) {
+ this.key = Preconditions.checkNotNull(key);
+ this.delimiter = Preconditions.checkNotNull(delimiter);
+ this.values = values;
+ }
+
+ @Override
+ public long write(OutputStream out) throws IOException {
+ long len = writeString(key, out);
+ len += writeString("=", out);
+ if (values != null) {
+ boolean first = true;
+ for (String v : values) {
+ if (first) {
+ first = false;
+ } else {
+ len += writeString(delimiter, out);
+ }
+ len += writeString(v, out);
+ }
+ }
+ return len;
+ }
+
+ @Override
+ public void addToFingerprint(Fingerprint f) {
+ f.addString(key);
+ if (values != null) {
+ for (String v : values) {
+ f.addString(v != null ? v : "");
+ }
+ }
+ }
+ }
+
+ /** Builder for {@link LaunchInfo} instances. */
+ public static final class Builder {
+ private ImmutableList.Builder<Entry> entries = ImmutableList.builder();
+
+ /** Builds a {@link LaunchInfo} from this builder. This builder may be reused. */
+ public LaunchInfo build() {
+ return new LaunchInfo(entries.build());
+ }
+
+ /**
+ * Adds a key-value pair entry.
+ *
+ * <p>Examples:
+ *
+ * <ul>
+ * <li>{@code key} is "foo" and {@code value} is "bar", the written value is "foo=bar\0"
+ * <li>{@code key} is "foo" and {@code value} is null or empty, the written value is
+ * "foo=\0"
+ * </ul>
+ */
+ public Builder addKeyValuePair(String key, @Nullable String value) {
+ Preconditions.checkNotNull(key);
+ if (!key.isEmpty()) {
+ entries.add(new KeyValuePair(key, value));
+ }
+ return this;
+ }
+
+ /**
+ * Adds a key and list of lazily-joined values.
+ *
+ * <p>Examples:
+ *
+ * <ul>
+ * <li>{@code key} is "foo", {@code delimiter} is ";", {@code values} is ["bar", "baz",
+ * "qux"], the written value is "foo=bar;baz;qux\0"
+ * <li>{@code key} is "foo", {@code delimiter} is irrelevant, {@code value} is null or
+ * empty, the written value is "foo=\0"
+ * </ul>
+ */
+ public Builder addJoinedValues(
+ String key, String delimiter, @Nullable Iterable<String> values) {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(delimiter);
+ if (!key.isEmpty()) {
+ entries.add(new JoinedValues(key, delimiter, values));
+ }
+ return this;
+ }
+ }
+ }
+}
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
deleted file mode 100644
index c416267e15..0000000000
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/NativeLauncherUtil.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2017 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.bazel.rules;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.io.ByteSource;
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.analysis.RuleContext;
-import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction;
-import com.google.devtools.build.lib.analysis.actions.SpawnAction;
-import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/** Utility class to create Windows native launcher */
-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 {
- launchInfo.write(key.getBytes(UTF_8));
- launchInfo.write('=');
- launchInfo.write(value.getBytes(UTF_8));
- 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.
- */
- public static void writeDataSize(ByteArrayOutputStream launchInfo) throws IOException {
- long dataSize = launchInfo.size();
- ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
- // All Windows versions are little endian.
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putLong(dataSize);
- launchInfo.write(buffer.array());
- }
-
- /**
- * The launcher file consists of a base launcher binary and the launch information appended to the
- * binary.
- *
- * @param ruleContext The rule context.
- * @param launcher The exe launcher we are going to build.
- * @param launchInfo The launch info to be appended.
- */
- 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, launchInfo, /*makeExecutable=*/ false));
-
- Artifact baseLauncherBinary = ruleContext.getPrerequisiteArtifact("$launcher", Mode.HOST);
-
- ruleContext.registerAction(
- new SpawnAction.Builder()
- .addInput(baseLauncherBinary)
- .addInput(launchInfoFile)
- .addOutput(launcher)
- .setShellCommand(
- "cmd.exe /c \"copy /Y /B "
- + baseLauncherBinary.getExecPathString().replace('/', '\\')
- + "+"
- + launchInfoFile.getExecPathString().replace('/', '\\')
- + " "
- + launcher.getExecPathString().replace('/', '\\')
- + " > nul\"")
- .useDefaultShellEnvironment()
- .setMnemonic("BuildNativeLauncher")
- .build(ruleContext));
- }
-}
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 de646ac546..c4e97d82da 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,13 +21,14 @@ 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.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.ComputedSubstitution;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
@@ -36,7 +37,6 @@ import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
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;
@@ -65,11 +65,7 @@ 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;
@@ -376,59 +372,6 @@ 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,
@@ -437,19 +380,26 @@ public class BazelJavaSemantics implements JavaSemantics {
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);
+ LaunchInfo launchInfo =
+ LaunchInfo.builder()
+ .addKeyValuePair("binary_type", "Java")
+ .addKeyValuePair("workspace_name", ruleContext.getWorkspaceName())
+ .addKeyValuePair("java_bin_path", javaExecutable)
+ .addKeyValuePair(
+ "jar_bin_path",
+ JavaCommon.getJavaExecutable(ruleContext)
+ .getParentDirectory()
+ .getRelative("jar.exe")
+ .getPathString())
+ .addKeyValuePair("java_start_class", javaStartClass)
+ .addJoinedValues(
+ "classpath",
+ ";",
+ Iterables.transform(classpath, Artifact.ROOT_RELATIVE_PATH_STRING))
+ .addJoinedValues("jvm_flags", " ", jvmFlags)
+ .build();
+
+ LauncherFileWriteAction.createAndRegister(ruleContext, javaLauncher, launchInfo);
return javaLauncher;
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
index 328f553691..52d839c4d9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
@@ -26,6 +26,8 @@ import com.google.devtools.build.lib.analysis.Runfiles.Builder;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
@@ -33,7 +35,6 @@ import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Su
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector.InstrumentationSpec;
-import com.google.devtools.build.lib.bazel.rules.NativeLauncherUtil;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -44,8 +45,6 @@ import com.google.devtools.build.lib.rules.python.PythonSemantics;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.PathFragment;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -203,21 +202,13 @@ public class BazelPythonSemantics implements PythonSemantics {
private static Artifact createWindowsExeLauncher(
RuleContext ruleContext, String pythonBinary, Artifact pythonLauncher)
throws InterruptedException {
- ByteArrayOutputStream launchInfo = new ByteArrayOutputStream();
- try {
- NativeLauncherUtil.writeLaunchInfo(launchInfo, "binary_type", "Python");
- NativeLauncherUtil.writeLaunchInfo(
- launchInfo, "workspace_name", ruleContext.getWorkspaceName());
- NativeLauncherUtil.writeLaunchInfo(launchInfo, "python_bin_path", pythonBinary);
-
- NativeLauncherUtil.writeDataSize(launchInfo);
- } catch (IOException e) {
- ruleContext.ruleError(e.getMessage());
- throw new InterruptedException();
- }
-
- NativeLauncherUtil.createNativeLauncherActions(ruleContext, pythonLauncher, launchInfo);
-
+ LaunchInfo launchInfo =
+ LaunchInfo.builder()
+ .addKeyValuePair("binary_type", "Python")
+ .addKeyValuePair("workspace_name", ruleContext.getWorkspaceName())
+ .addKeyValuePair("python_bin_path", pythonBinary)
+ .build();
+ LauncherFileWriteAction.createAndRegister(ruleContext, pythonLauncher, launchInfo);
return pythonLauncher;
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java
index b531f8e1e9..8150feaed9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java
@@ -23,17 +23,16 @@ import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.RunfilesSupport;
import com.google.devtools.build.lib.analysis.actions.ExecutableSymlinkAction;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
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.util.OS;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
/**
* Implementation for the sh_binary rule.
@@ -105,22 +104,19 @@ public class ShBinary implements RuleConfiguredTargetFactory {
Artifact bashLauncher =
ruleContext.getImplicitOutputArtifact(ruleContext.getTarget().getName() + ".exe");
- ByteArrayOutputStream launchInfo = new ByteArrayOutputStream();
- try {
- NativeLauncherUtil.writeLaunchInfo(launchInfo, "binary_type", "Bash");
- NativeLauncherUtil.writeLaunchInfo(
- launchInfo, "workspace_name", ruleContext.getWorkspaceName());
- NativeLauncherUtil.writeLaunchInfo(
- launchInfo,
- "bash_bin_path",
- ruleContext.getFragment(BazelConfiguration.class).getShellExecutable().getPathString());
- NativeLauncherUtil.writeDataSize(launchInfo);
- } catch (IOException e) {
- ruleContext.ruleError(e.getMessage());
- throw new RuleErrorException();
- }
+ LaunchInfo launchInfo =
+ LaunchInfo.builder()
+ .addKeyValuePair("binary_type", "Bash")
+ .addKeyValuePair("workspace_name", ruleContext.getWorkspaceName())
+ .addKeyValuePair(
+ "bash_bin_path",
+ ruleContext
+ .getFragment(BazelConfiguration.class)
+ .getShellExecutable()
+ .getPathString())
+ .build();
- NativeLauncherUtil.createNativeLauncherActions(ruleContext, bashLauncher, launchInfo);
+ LauncherFileWriteAction.createAndRegister(ruleContext, bashLauncher, launchInfo);
return bashLauncher;
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteActionTest.java
new file mode 100644
index 0000000000..e71a3952f4
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/LauncherFileWriteActionTest.java
@@ -0,0 +1,190 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.analysis.actions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.testing.NullPointerTester;
+import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LauncherFileWriteAction}. */
+@RunWith(JUnit4.class)
+public class LauncherFileWriteActionTest {
+
+ @Test
+ public void testAddKeyValuePair() throws Exception {
+ LaunchInfo actual =
+ LaunchInfo.builder()
+ .addKeyValuePair("", "won't show up")
+ .addKeyValuePair("foo", "bar")
+ .addKeyValuePair("baz", null)
+ .build();
+ ByteArrayOutputStream expected = new ByteArrayOutputStream();
+ expected.write("foo=bar\0".getBytes(StandardCharsets.UTF_8));
+ expected.write("baz=\0".getBytes(StandardCharsets.UTF_8));
+ assertOutput(actual, expected.toByteArray());
+ }
+
+ @Test
+ public void testKeyValueFingerprint() throws Exception {
+ // LaunchInfos with different entries should have different fingerprints.
+ assertThat(LaunchInfo.builder().addKeyValuePair("foo", "bar").build().fingerPrint)
+ .isNotEqualTo(LaunchInfo.builder().addKeyValuePair("bar", "foo").build().fingerPrint);
+
+ // LaunchInfos with the same entries but in different order should have different fingerprints.
+ assertThat(
+ LaunchInfo.builder()
+ .addKeyValuePair("foo", "bar")
+ .addKeyValuePair("bar", "foo")
+ .build()
+ .fingerPrint)
+ .isNotEqualTo(
+ LaunchInfo.builder()
+ .addKeyValuePair("bar", "foo")
+ .addKeyValuePair("foo", "bar")
+ .build()
+ .fingerPrint);
+
+ // Two identically-constructed LaunchInfos should have the same fingerprint.
+ assertThat(
+ LaunchInfo.builder()
+ .addKeyValuePair("foo", "bar")
+ .addKeyValuePair("bar", "foo")
+ .build()
+ .fingerPrint)
+ .isEqualTo(
+ LaunchInfo.builder()
+ .addKeyValuePair("foo", "bar")
+ .addKeyValuePair("bar", "foo")
+ .build()
+ .fingerPrint);
+ }
+
+ @Test
+ public void testAddJoinedValues() throws Exception {
+ LaunchInfo actual =
+ LaunchInfo.builder()
+ .addJoinedValues("foo", "", ImmutableList.of())
+ .addJoinedValues("bar", "x", ImmutableList.of())
+ .addJoinedValues("baz", ";", ImmutableList.of("aa"))
+ .addJoinedValues("qux", ":", ImmutableList.of("aa", "bb", "cc"))
+ .addJoinedValues("mex", "--", ImmutableList.of("aa", "bb", "cc"))
+ .build();
+ ByteArrayOutputStream expected = new ByteArrayOutputStream();
+ expected.write("foo=\0".getBytes(StandardCharsets.UTF_8));
+ expected.write("bar=\0".getBytes(StandardCharsets.UTF_8));
+ expected.write("baz=aa\0".getBytes(StandardCharsets.UTF_8));
+ expected.write("qux=aa:bb:cc\0".getBytes(StandardCharsets.UTF_8));
+ expected.write("mex=aa--bb--cc\0".getBytes(StandardCharsets.UTF_8));
+ assertOutput(actual, expected.toByteArray());
+ }
+
+ @Test
+ public void testJoinedValuesFingerprint() throws Exception {
+ // LaunchInfos with different entries should have different fingerprints.
+ assertThat(
+ LaunchInfo.builder()
+ .addJoinedValues("foo", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint)
+ .isNotEqualTo(
+ LaunchInfo.builder()
+ .addJoinedValues("bar", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint);
+
+ // LaunchInfos with the same entries but in different order should have different fingerprints.
+ assertThat(
+ LaunchInfo.builder()
+ .addJoinedValues("foo", ";", ImmutableList.of("aa", "bb"))
+ .addJoinedValues("bar", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint)
+ .isNotEqualTo(
+ LaunchInfo.builder()
+ .addJoinedValues("bar", ";", ImmutableList.of("aa", "bb"))
+ .addJoinedValues("foo", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint);
+
+ // Two identically-constructed LaunchInfos should have the same fingerprint.
+ assertThat(
+ LaunchInfo.builder()
+ .addJoinedValues("foo", ";", ImmutableList.of("aa", "bb"))
+ .addJoinedValues("bar", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint)
+ .isEqualTo(
+ LaunchInfo.builder()
+ .addJoinedValues("foo", ";", ImmutableList.of("aa", "bb"))
+ .addJoinedValues("bar", ";", ImmutableList.of("aa", "bb"))
+ .build()
+ .fingerPrint);
+ }
+
+ @Test
+ public void testFingerprintDependsOnEntryType() throws Exception {
+ // Although these LaunchInfo objects render to the same octet stream, their fingerprint is
+ // different because we construct them differently.
+ LaunchInfo actual1 = LaunchInfo.builder().addKeyValuePair("foo", "bar;baz").build();
+ LaunchInfo actual2 =
+ LaunchInfo.builder().addJoinedValues("foo", ";", ImmutableList.of("bar", "baz")).build();
+ try (ByteArrayOutputStream out1 = new ByteArrayOutputStream();
+ ByteArrayOutputStream out2 = new ByteArrayOutputStream()) {
+ actual1.write(out1);
+ actual2.write(out2);
+ assertThat(out1.toByteArray()).isEqualTo(out2.toByteArray());
+ }
+ assertThat(actual1.fingerPrint).isNotEqualTo(actual2.fingerPrint);
+ }
+
+ @Test
+ public void testNulls() throws Exception {
+ assertOutput(LaunchInfo.builder().build(), new byte[0]);
+
+ assertOutput(
+ LaunchInfo.builder().addKeyValuePair("", null).addKeyValuePair("", "").build(),
+ new byte[0]);
+
+ assertOutput(
+ LaunchInfo.builder()
+ .addJoinedValues("", "", null)
+ .addJoinedValues("", "delimiter", null)
+ .addJoinedValues("", "", ImmutableList.of())
+ .addJoinedValues("", "delimiter", ImmutableList.of())
+ .build(),
+ new byte[0]);
+
+ LaunchInfo.Builder obj = LaunchInfo.builder();
+ Class<LaunchInfo.Builder> clazz = LaunchInfo.Builder.class;
+ NullPointerTester npt = new NullPointerTester().setDefault(String.class, "foo");
+
+ npt.testMethod(obj, clazz.getMethod("addKeyValuePair", String.class, String.class));
+ npt.testMethod(
+ obj, clazz.getMethod("addJoinedValues", String.class, String.class, Iterable.class));
+ }
+
+ private static void assertOutput(LaunchInfo actual, byte[] expected) throws Exception {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ assertThat(actual.write(out)).isEqualTo(expected.length);
+ assertThat(out.toByteArray()).isEqualTo(expected);
+ }
+ }
+}