From 8ac46c157c5da63e97a5825316ce6f0c3290e189 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Tue, 26 Feb 2019 04:51:31 -0800 Subject: [PATCH] Windows: add --incompatible_windows_escape_jvm_flags 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). - Result: 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. - 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: https://github.com/bazelbuild/bazel/issues/7486 Related: https://github.com/bazelbuild/bazel/issues/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 Closes #7490. PiperOrigin-RevId: 235700143 --- .bazelci/postsubmit.yml | 1 + .bazelci/presubmit.yml | 1 + .../java/com/google/devtools/build/lib/BUILD | 1 + .../bazel/rules/java/BazelJavaSemantics.java | 32 +- .../lib/rules/java/JavaConfiguration.java | 6 + .../build/lib/rules/java/JavaOptions.java | 23 ++ src/test/shell/integration/BUILD | 7 + .../integration/jvm_flags_escaping_test.sh | 336 ++++++++++++++++++ src/tools/launcher/java_launcher.cc | 8 +- 9 files changed, 409 insertions(+), 6 deletions(-) create mode 100755 src/test/shell/integration/jvm_flags_escaping_test.sh diff --git a/.bazelci/postsubmit.yml b/.bazelci/postsubmit.yml index 84bb31f68d6644..680f78158d4f01 100644 --- a/.bazelci/postsubmit.yml +++ b/.bazelci/postsubmit.yml @@ -105,6 +105,7 @@ platforms: - "-//src/test/shell/integration:discard_analysis_cache_test" - "-//src/test/shell/integration:discard_graph_edges_test" - "-//src/test/shell/integration:java_integration_test" + - "-//src/test/shell/integration:jvm_flags_escaping_test" - "-//src/test/shell/integration:minimal_jdk_test" - "-//src/test/shell/integration:nonincremental_builds_test" - "-//src/test/shell/integration:output_filter_test" diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 42fa9176961e43..2c9acd4ed5ead1 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -127,6 +127,7 @@ platforms: - "-//src/test/shell/integration:discard_analysis_cache_test" - "-//src/test/shell/integration:discard_graph_edges_test" - "-//src/test/shell/integration:java_integration_test" + - "-//src/test/shell/integration:jvm_flags_escaping_test" - "-//src/test/shell/integration:minimal_jdk_test" - "-//src/test/shell/integration:nonincremental_builds_test" - "-//src/test/shell/integration:output_filter_test" diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 6a92c8b5e107b3..b36522d2917150 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -762,6 +762,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/rules/java:java-rules", "//src/main/java/com/google/devtools/build/lib/rules/objc", "//src/main/java/com/google/devtools/build/lib/rules/platform", + "//src/main/java/com/google/devtools/build/lib/shell", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/apple", 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 399f7afb5c2a3e..d2cf3a15bbdd3a 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 @@ -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; @@ -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 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( @@ -426,9 +447,9 @@ private static Artifact createWindowsExeLauncher( String javaExecutable, NestedSet classpath, String javaStartClass, - ImmutableList jvmFlags, - Artifact javaLauncher) { - + List jvmFlags, + Artifact javaLauncher, + boolean windowsEscapeJvmFlags) { LaunchInfo launchInfo = LaunchInfo.builder() .addKeyValuePair("binary_type", "Java") @@ -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 diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java index cc30c415e66e92..550ba51e03ecea 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java @@ -178,6 +178,7 @@ public boolean alwaysGenerateOutputMapping() { private final ImmutableList