Skip to content

Commit

Permalink
Windows: add --incompatible_windows_escape_jvm_flags
Browse files Browse the repository at this point in the history
Add --incompatible_windows_escape_jvm_flags flag
(default: false). This flag has no effect on
platforms other than Windows.

This flag affects how Bazel builds the launcher of
java_binary and java_test targets. (The launcher
is the .exe file that sets up the environment for
the Java program and launches the JVM.)

In particular, the flag controls whether or not
Bazel will Bash-tokenize the jvm_flags that were
declared in the BUILD file, and whether or not
these jvm_flags are escaped by the launcher when
it invokes the JVM.

When the flag is enabled:

- Bazel will Bash-tokenize java_binary.jvm_flags
  and java_test.jvm_flags (as documented by the
  Build Encyclopedia) before embedding them into
  the launcher. The build fails if an entry cannot
  be Bash-tokenized, e.g. if it has unterminated
  quotes.

- The launcher will properly escape these flags
  when it runs the JVM as a subprocess (using
  launcher_util::WindowsEscapeArg2).

- The result is that the jvm_flags declared in the
  BUILD file will arrive to the Java program as
  intended. However, due to the extra
  Bash-tokenization step, some ill-formed flags
  are no longer accepted, e.g.
  `jvm_flags=["-Dfoo='a"]` now results in a build
  error.

When the flag is disabled:

- Bazel does not Bash-tokenize the jvm_flags
  before embedding them in the launcher. This
  preserves quoting meant to be stripped away, and
  it also means Bazel won't check whether the
  argument is properly quoted.

- The launcher escapes the flags with a Bash-like
  escaping logic (launcher_util::WindowsEscapeArg)
  which cannot properly quote and escape
  everything.

- The result: the jvm_flags declared in the BUILD
  file might get messed up as they are passed to
  the JVM, or the launcher may not even be able to
  run the JVM. However, due to the lack of
  Bash-tokenization, Bazel propagates some flags
  to the Java binary that it would no longer
  accept if the new
  `--incompatible_windows_escape_jvm_flags` were
  enabled, e.g. `jvm_flags=["'a"]` is fine.

Incompatible flag: bazelbuild#7486

Related: bazelbuild#7072

RELNOTES[NEW]: Added --incompatible_windows_escape_jvm_flags flag: enables correct java_binary.jvm_flags and java_test.jvm_flags tokenization and escaping on Windows. (No-op on other platforms.)

Change-Id: I531cc63cdfeccbe4b6d48876cb82870c1726a723
  • Loading branch information
laszlocsomor committed Feb 21, 2019
1 parent d19d4ba commit d49ae09
Show file tree
Hide file tree
Showing 7 changed files with 344 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/proto",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python",
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
import com.google.devtools.build.lib.rules.java.JavaUtil;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.OS;
Expand Down Expand Up @@ -407,13 +409,32 @@ public Artifact createStubAction(
arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", jvmFlagsList));

if (OS.getCurrent() == OS.WINDOWS) {
boolean windowsEscapeJvmFlags =
ruleContext
.getConfiguration()
.getFragment(JavaConfiguration.class)
.windowsEscapeJvmFlags();

List<String> jvmFlagsForLauncher = jvmFlagsList;
if (windowsEscapeJvmFlags) {
try {
jvmFlagsForLauncher = new ArrayList<>(jvmFlagsList.size());
for (String f : jvmFlagsList) {
ShellUtils.tokenize(jvmFlagsForLauncher, f);
}
} catch (TokenizationException e) {
ruleContext.attributeError("jvm_flags", "could not Bash-tokenize flag: " + e);
}
}

return createWindowsExeLauncher(
ruleContext,
javaExecutable,
classpath,
javaStartClass,
jvmFlagsList,
executable);
jvmFlagsForLauncher,
executable,
windowsEscapeJvmFlags);
}

ruleContext.registerAction(new TemplateExpansionAction(
Expand All @@ -426,9 +447,9 @@ private static Artifact createWindowsExeLauncher(
String javaExecutable,
NestedSet<Artifact> classpath,
String javaStartClass,
ImmutableList<String> jvmFlags,
Artifact javaLauncher) {

List<String> jvmFlags,
Artifact javaLauncher,
boolean windowsEscapeJvmFlags) {
LaunchInfo launchInfo =
LaunchInfo.builder()
.addKeyValuePair("binary_type", "Java")
Expand All @@ -448,6 +469,7 @@ private static Artifact createWindowsExeLauncher(
"classpath",
";",
Iterables.transform(classpath, Artifact.ROOT_RELATIVE_PATH_STRING))
.addKeyValuePair("escape_jvmflags", windowsEscapeJvmFlags ? "1" : "0")
// TODO(laszlocsomor): Change the Launcher to accept multiple jvm_flags entries. As of
// 2019-02-13 the Launcher accepts just one jvm_flags entry, which contains all the
// flags, joined by TAB characters. The Launcher splits up the string to get the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public boolean alwaysGenerateOutputMapping() {
private final ImmutableList<Label> pluginList;
private final boolean requireJavaToolchainHeaderCompilerDirect;
private final boolean disallowResourceJars;
private final boolean windowsEscapeJvmFlags;

// TODO(dmarting): remove once we have a proper solution for #2539
private final boolean useLegacyBazelJavaTest;
Expand Down Expand Up @@ -213,6 +214,7 @@ public boolean alwaysGenerateOutputMapping() {
this.isJlplStrictDepsEnforced = javaOptions.isJlplStrictDepsEnforced;
this.disallowResourceJars = javaOptions.disallowResourceJars;
this.addTestSupportToCompileTimeDeps = javaOptions.addTestSupportToCompileTimeDeps;
this.windowsEscapeJvmFlags = javaOptions.windowsEscapeJvmFlags;

ImmutableList.Builder<Label> translationsBuilder = ImmutableList.builder();
for (String s : javaOptions.translationTargets) {
Expand Down Expand Up @@ -455,4 +457,8 @@ public boolean requireJavaToolchainHeaderCompilerDirect() {
public boolean disallowResourceJars() {
return disallowResourceJars;
}

public boolean windowsEscapeJvmFlags() {
return windowsEscapeJvmFlags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,27 @@ public ImportDepsCheckingLevelConverter() {
"Disables the resource_jars attribute; use java_import and deps or runtime_deps instead.")
public boolean disallowResourceJars;

@Option(
name = "incompatible_windows_escape_jvm_flags",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
effectTags = {
OptionEffectTag.ACTION_COMMAND_LINES,
OptionEffectTag.AFFECTS_OUTPUTS,
},
metadataTags = {
OptionMetadataTag.INCOMPATIBLE_CHANGE,
OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
},
help =
"On Linux/macOS/non-Windows: no-op. On Windows: this flag affects how java_binary and"
+ " java_test targets are built; in particular, how the launcher of these targets"
+ " escapes flags at the time of running the java_binary/java_test. When the flag is"
+ " true, the launcher escapes the JVM flags using Windows-style escaping (correct"
+ " behavior). When the flag is false, the launcher uses Bash-style escaping"
+ " (buggy behavior). See https://github.com/bazelbuild/bazel/issues/7072")
public boolean windowsEscapeJvmFlags;

private Label getHostJavaBase() {
if (hostJavaBase == null) {
if (useJDK11AsHostJavaBase) {
Expand Down Expand Up @@ -698,6 +719,8 @@ public FragmentOptions getHost() {

host.disallowResourceJars = disallowResourceJars;

host.windowsEscapeJvmFlags = windowsEscapeJvmFlags;

return host;
}

Expand Down
7 changes: 7 additions & 0 deletions src/test/shell/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,13 @@ sh_test(
tags = ["no_windows"],
)

sh_test(
name = "jvm_flags_escaping_test",
srcs = ["jvm_flags_escaping_test.sh"],
data = [":test-deps"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)

########################################################################
# Test suites.

Expand Down
Loading

0 comments on commit d49ae09

Please sign in to comment.