Windows: add --incompatible_windows_style_arg_escaping
Add the --incompatible_windows_style_arg_escaping
flag (default: false). This flag has no effect on
platforms other than Windows.
Semantics:
- When true: subprocess arguments are escaped with
ShellUtils.windowsEscapeArg. This is the correct
behavior.
- When false: subprocess arguments are escaped
with ShellUtils.quoteCommandLine. This is the
default behavior on all platforms, but it is
incorrect on Windows.
Incompatible flag: https://github.com/bazelbuild/bazel/issues/7454
Related: https://github.com/bazelbuild/bazel/issues/7122
RELNOTES[NEW]: Added --incompatible_windows_style_arg_escaping flag: enables correct subprocess argument escaping on Windows. (No-op on other platforms.)
PiperOrigin-RevId: 234581069
diff --git a/src/main/cpp/bazel_startup_options.cc b/src/main/cpp/bazel_startup_options.cc
index 2ec8c8c..f4d494f 100644
--- a/src/main/cpp/bazel_startup_options.cc
+++ b/src/main/cpp/bazel_startup_options.cc
@@ -28,8 +28,10 @@
use_system_rc(true),
use_workspace_rc(true),
use_home_rc(true),
- use_master_bazelrc_(true) {
+ use_master_bazelrc_(true),
+ incompatible_windows_style_arg_escaping(false) {
RegisterNullaryStartupFlag("home_rc");
+ RegisterNullaryStartupFlag("incompatible_windows_style_arg_escaping");
RegisterNullaryStartupFlag("master_bazelrc");
RegisterNullaryStartupFlag("system_rc");
RegisterNullaryStartupFlag("workspace_rc");
@@ -104,6 +106,14 @@
}
use_master_bazelrc_ = false;
option_sources["blazerc"] = rcfile;
+ } else if (GetNullaryOption(arg,
+ "--incompatible_windows_style_arg_escaping")) {
+ incompatible_windows_style_arg_escaping = true;
+ option_sources["incompatible_windows_style_arg_escaping"] = rcfile;
+ } else if (GetNullaryOption(arg,
+ "--noincompatible_windows_style_arg_escaping")) {
+ incompatible_windows_style_arg_escaping = false;
+ option_sources["incompatible_windows_style_arg_escaping"] = rcfile;
} else {
*is_processed = false;
return blaze_exit_code::SUCCESS;
@@ -137,4 +147,13 @@
}
}
+void BazelStartupOptions::AddExtraOptions(
+ std::vector<std::string> *result) const {
+ if (incompatible_windows_style_arg_escaping) {
+ result->push_back("--incompatible_windows_style_arg_escaping");
+ } else {
+ result->push_back("--noincompatible_windows_style_arg_escaping");
+ }
+}
+
} // namespace blaze
diff --git a/src/main/cpp/bazel_startup_options.h b/src/main/cpp/bazel_startup_options.h
index d84cfd2..5eba030 100644
--- a/src/main/cpp/bazel_startup_options.h
+++ b/src/main/cpp/bazel_startup_options.h
@@ -26,6 +26,8 @@
public:
explicit BazelStartupOptions(const WorkspaceLayout *workspace_layout);
+ void AddExtraOptions(std::vector<std::string> *result) const override;
+
blaze_exit_code::ExitCode ProcessArgExtra(
const char *arg, const char *next_arg, const std::string &rcfile,
const char **value, bool *is_processed, std::string *error) override;
@@ -39,6 +41,13 @@
bool use_home_rc;
// TODO(b/36168162): Remove the master rc flag.
bool use_master_bazelrc_;
+
+ // Whether Windows-style subprocess argument escaping is enabled on Windows,
+ // or the (buggy) Bash-style is used.
+ // This flag only affects builds on Windows, and it's a no-op on other
+ // platforms.
+ // See https://github.com/bazelbuild/bazel/issues/7122
+ bool incompatible_windows_style_arg_escaping;
};
} // namespace blaze
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index e6e30fa..b500053 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -1046,9 +1046,10 @@
return OS.getCurrent() == OS.WINDOWS ? new WindowsFileSystem() : new UnixFileSystem();
}
- private static SubprocessFactory subprocessFactoryImplementation() {
+ private static SubprocessFactory subprocessFactoryImplementation(
+ BlazeServerStartupOptions startupOptions) {
if (!"0".equals(System.getProperty("io.bazel.EnableJni")) && OS.getCurrent() == OS.WINDOWS) {
- return new WindowsSubprocessFactory(false);
+ return new WindowsSubprocessFactory(startupOptions.windowsStyleArgEscaping);
} else {
return JavaSubprocessFactory.INSTANCE;
}
@@ -1155,7 +1156,7 @@
"No module set the default hash function.", ExitCode.BLAZE_INTERNAL_ERROR, e);
}
Path.setFileSystemForSerialization(fs);
- SubprocessBuilder.setDefaultSubprocessFactory(subprocessFactoryImplementation());
+ SubprocessBuilder.setDefaultSubprocessFactory(subprocessFactoryImplementation(startupOptions));
Path outputUserRootPath = fs.getPath(outputUserRoot);
Path installBasePath = fs.getPath(installBase);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
index bd0d705..1c60aa7 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java
@@ -464,4 +464,23 @@
+ " flag in your bazelrc once and forget about it so that you get coredumps when you"
+ " actually encounter a condition that triggers them.")
public boolean unlimitCoredumps;
+
+ @Option(
+ name = "incompatible_windows_style_arg_escaping",
+ defaultValue = "false", // NOTE: purely decorative, rc files are read by the client.
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {
+ OptionEffectTag.ACTION_COMMAND_LINES,
+ OptionEffectTag.EXECUTION,
+ },
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES,
+ },
+ help =
+ "On Linux/macOS/non-Windows: no-op. On Windows: if true, then subprocess arguments are"
+ + " escaped Windows-style. When false, the arguments are escaped Bash-style. The"
+ + " Bash-style is buggy, the Windows-style is correct. See"
+ + " https://github.com/bazelbuild/bazel/issues/7122")
+ public boolean windowsStyleArgEscaping;
}
diff --git a/src/test/cpp/bazel_startup_options_test.cc b/src/test/cpp/bazel_startup_options_test.cc
index 1e91902..18467a3 100644
--- a/src/test/cpp/bazel_startup_options_test.cc
+++ b/src/test/cpp/bazel_startup_options_test.cc
@@ -87,6 +87,7 @@
ExpectIsNullaryOption(options, "fatal_event_bus_exceptions");
ExpectIsNullaryOption(options, "home_rc");
ExpectIsNullaryOption(options, "host_jvm_debug");
+ ExpectIsNullaryOption(options, "incompatible_windows_style_arg_escaping");
ExpectIsNullaryOption(options, "shutdown_on_low_sys_mem");
ExpectIsNullaryOption(options, "ignore_all_rc_files");
ExpectIsNullaryOption(options, "master_bazelrc");
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index b491f11..dc9f303 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -799,3 +799,10 @@
],
deps = ["@bazel_tools//tools/bash/runfiles"],
)
+
+sh_test(
+ name = "windows_arg_esc_test",
+ srcs = ["windows_arg_esc_test.sh"],
+ data = [":test-deps"],
+ deps = ["@bazel_tools//tools/bash/runfiles"],
+)
diff --git a/src/test/shell/bazel/windows_arg_esc_test.sh b/src/test/shell/bazel/windows_arg_esc_test.sh
new file mode 100755
index 0000000..2667a8d
--- /dev/null
+++ b/src/test/shell/bazel/windows_arg_esc_test.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
+ || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+# `uname` returns the current platform, e.g "MSYS_NT-10.0" or "Linux".
+# `tr` converts all upper case letters to lower case.
+# `case` matches the result if the `uname | tr` expression to string prefixes
+# that use the same wildcards as names do in Bash, i.e. "msys*" matches strings
+# starting with "msys", and "*" matches everything (it's the default case).
+case "$(uname -s | tr [:upper:] [:lower:])" in
+msys*)
+ # As of 2019-02-18, Bazel on Windows only supports MSYS Bash.
+ declare -r is_windows=true
+ ;;
+*)
+ declare -r is_windows=false
+ ;;
+esac
+
+if "$is_windows"; then
+ # Disable MSYS path conversion that converts path-looking command arguments to
+ # Windows paths (even if they arguments are not in fact paths).
+ export MSYS_NO_PATHCONV=1
+ export MSYS2_ARG_CONV_EXCL="*"
+fi
+
+
+function create_pkg() {
+ if [[ -d foo ]]; then
+ return
+ fi
+
+ mkdir foo || fail "mkdir foo"
+
+ cat >foo/BUILD <<'EOF'
+load(":rules.bzl", "args1_test", "args2_test")
+args1_test(name = "x")
+args2_test(name = "y")
+EOF
+
+ cat >foo/rules.bzl <<'EOF'
+def _impl(ctx, cmd, out):
+ output = ctx.actions.declare_file(out)
+ ctx.actions.run_shell(
+ outputs = [output],
+ command = cmd,
+ arguments = ['echo', 'a', 'b', 'c'],
+ )
+ return [DefaultInfo(executable = output)]
+
+def _impl1(ctx):
+ return _impl(ctx, '${1+"$@"}', "foo1")
+
+def _impl2(ctx):
+ return _impl(ctx, '${1+"$@"} ', "foo2")
+
+args1_test = rule(implementation = _impl1, test = True)
+
+args2_test = rule(implementation = _impl2, test = True)
+EOF
+}
+
+function assert_foo1_failed() {
+ # The command fails because Bazel invokes it incorrectly.
+ expect_log "error executing shell command"
+ expect_log "\$: command not found"
+ expect_not_log "output .*foo/foo1.* was not created"
+}
+
+function assert_command_succeeded() {
+ local -r output=$1
+
+ # The command succeeds, though the build fails because the command does not
+ # create any outputs. This is expected.
+ expect_log "From.* foo/${output}"
+ expect_log "output .*foo/${output}.* was not created"
+ expect_log "not all outputs were created"
+}
+
+function assert_build() {
+ local -r enable_windows_style_arg_escaping=$1
+
+ if $enable_windows_style_arg_escaping; then
+ local -r flag="--incompatible_windows_style_arg_escaping"
+ local -r expect_foo1_fails="false"
+ else
+ local -r flag="--noincompatible_windows_style_arg_escaping"
+ local -r expect_foo1_fails="$is_windows"
+ fi
+
+ create_pkg
+
+ bazel $flag build foo:x --verbose_failures 2>$TEST_log \
+ && fail "expected failure" || true
+ if $expect_foo1_fails; then
+ # foo1's command does not work on Windows without the Windows-style argument
+ # escaping, see https://github.com/bazelbuild/bazel/issues/7122
+ assert_foo1_failed
+ else
+ assert_command_succeeded foo1
+ fi
+
+ # foo2's command works on Windows even with the incorrect escaping semantics,
+ # see https://github.com/bazelbuild/bazel/issues/7122
+ bazel $flag build foo:y --verbose_failures 2>$TEST_log \
+ && fail "expected failure" || true
+ assert_command_succeeded foo2
+}
+
+function test_nowindows_style_arg_escaping() {
+ # On Windows: assert that with --noincompatible_windows_style_arg_escaping,
+ # Bazel incorrectly escapes the shell command arguments of "//foo:x" and
+ # reports a bogus error ("/usr/bin/bash: $: command not found"), but it
+ # correctly invokes the command for "//foo:y".
+ #
+ # On other platforms: assert that Bazel correctly invokes the commands for
+ # both "//foo:x" and "//foo:y". This is not affected by
+ # --noincompatible_windows_style_arg_escaping.
+ # The test should behave the same as test_windows_style_arg_escaping.
+ assert_build false
+}
+
+function test_windows_style_arg_escaping() {
+ # On Windows: assert that with --incompatible_windows_style_arg_escaping,
+ # Bazel correctly invokes the commands for both "//foo:x" and "//foo:y".
+ #
+ # On other platforms: assert that Bazel correctly invokes the commands for
+ # both "//foo:x" and "//foo:y". This is not affected by
+ # --incompatible_windows_style_arg_escaping.
+ # The test should behave the same as test_nowindows_style_arg_escaping.
+ assert_build true
+}
+
+run_suite "Windows argument escaping test"