diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e76548def35f..ab74dc5b047a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,86 @@ +## Release 0.16.0 (2018-07-31) + +``` +Baseline: 4f64b77a3dd8e4ccdc8077051927985f9578a3a5 + +Cherry picks: + + 4c9a0c82d308d5df5c524e2a26644022ff525f3e: + reduce the size of bazel's embedded jdk + + d3228b61f633cdc5b3f740b641a0836f1bd79abd: + remote: limit number of open tcp connections by default. Fixes + #5491 + + 8ff87c164f48dbabe3b20becd00dde90c50d46f5: + Fix autodetection of linker flags + + c4622ac9205d2f1b42dac8c598e83113d39e7f11: + Fix autodetection of -z linker flags + + 10219659f58622d99034288cf9f491865f818218: + blaze_util_posix.cc: fix order of #define + + ab1f269017171223932e0da9bb539e8a17dd99ed: + blaze_util_freebsd.cc: include path.h explicitly + + 68e92b45a37f2142c768a56eb7ecfa484b8b22df: + openjdk: update macOS openjdk image. Fixes #5532 + + f45c22407e6b00fcba706eb62141cb9036bd38d7: + Set the start time of binary and JSON profiles to zero correctly. + + bca1912853086b8e9a28a85a1b144ec0dc9717cc: + remote: fix race on download error. Fixes #5047 + + 3842bd39e10612c7eef36c6048407e81bcd0a8fb: + jdk: use parallel old gc and disable compact strings +``` + +Incompatible changes: + + - The $(ANDROID_CPU) Make variable is not available anymore. Use + $(TARGET_CPU) after an Android configuration transition instead. + - The $(JAVA_TRANSLATIONS) Make variable is not supported anymore. + - Skylark structs (using struct()) may no longer have to_json and + to_proto overridden. + - The mobile-install --skylark_incremental_res flag is no longer + available, use the --skylark flag instead. + +New features: + + - android_local_test now takes advantage of Robolectric's binary + resource processing which allows for faster tests. + - Allow @ in package names. + +Important changes: + + - Option --glibc is removed, toolchain selection relies solely on + --cpu and --compiler options. + - Build support for enabling cross binary FDO optimization. + - The --distdir option is no longer experimental. This + option allows to specify additional directories to look for + files before trying to fetch them from the network. Files from + any of the distdirs are only used if a checksum for the file + is specified and both, the filename and the checksum, match. + - Java coverage works now with multiple jobs. + - Flip default value of --experimental_shortened_obj_file_path to + true, Bazel now generates short object file path by default. + - New rules for importing Android dependencies: + `aar_import_external` and `aar_maven_import_external`. + `aar_import_external` enables specifying external AAR + dependencies using a list of HTTP URLs for the artifact. + `aar_maven_import_external` enables specifying external AAR + dependencies using the artifact coordinate and a list of server + URLs. + - The BAZEL_JAVAC_OPTS environment variable allows arguments, e.g., + "-J-Xmx2g", may be passed to the javac compiler during bootstrap + build. This is helpful if your system chooses too small of a max + heap size for the Java compiler during the bootstrap build. + - --noexpand_configs_in_place is deprecated. + - A tool to parse the Bazel execution log. + - Support for LIPO has been fully removed. + - Remove support for --discard_actions_after_execution. + - Add --materialize_param_files flag to write parameter files even + when actions are executed remotely. + - Windows default system bazelrc is read from the user's + ProgramData if present. + - --[no]allow_undefined_configs no longer exists, passing undefined + configs is an error. + - In remote caching we limit the number of open + TCP connections to 100 by default. The number can be adjusted + by specifying the --remote_max_connections flag. + ## Release 0.15.0 (2018-06-26) ``` @@ -3001,5 +3084,6 @@ Initial release. + diff --git a/WORKSPACE b/WORKSPACE index 015e37f8e17cab..94bfdabc79d738 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -117,31 +117,31 @@ distdir_tar( # OpenJDK distributions used to create a version of Bazel bundled with the OpenJDK. http_file( name = "openjdk_linux", - sha256 = "45f2dfbee93b91b1468cf81d843fc6d9a47fef1f831c0b7ceff4f1eb6e6851c8", + sha256 = "85b81652b3fe8cfb0a2cfb835988672bde7844f613dae5b9487b5b44921a1afd", urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-linux_x64.tar.gz", - "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-linux_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu9.0.7.1-jdk9.0.7-linux_x64.tar.gz", + "https://mirror.bazel.build/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-linux_x64.tar.gz", + "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-linux_x64.tar.gz", + "https://cdn.azul.com/zulu/bin/zulu8.28.0.1-jdk8.0.163-linux_x64.tar.gz", ], ) http_file( name = "openjdk_macos", - sha256 = "5a5b3225b86d3fdb51e9add5335f43cc19c6b2d9b8b5558e72b52d7b2ce9162e", + sha256 = "292a44695e708a8822b3d0a462faf32887ddb80b56aa91e7598fd4b6f6341f69", urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-macosx_x64.tar.gz", - "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-macosx_x64.tar.gz", - "https://cdn.azul.com/zulu/bin/zulu9.0.7.1-jdk9.0.7-macosx_x64.tar.gz", + "https://mirror.bazel.build/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-macosx_x64.tar.gz", + "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-macosx_x64.tar.gz", + "https://cdn.azul.com/zulu/bin/zulu8.28.0.1-jdk8.0.163-macosx_x64.tar.gz", ], ) http_file( name = "openjdk_win", - sha256 = "75f76c53c6a1f12b1a571b86bd9708ab75adf582d689dddc94fdd77dcc0f3f5c", + sha256 = "0c5ea3634ae7d7851630cf61fdd6344cce110cf9246593345e49fc430bb39442", urls = [ - "https://mirror.bazel.build/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-win_x64.zip", - "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-win_x64.zip", - "https://cdn.azul.com/zulu/bin/zulu9.0.7.1-jdk9.0.7-win_x64.zip", + "https://mirror.bazel.build/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-win_x64.zip", + "https://bazel-mirror.storage.googleapis.com/openjdk/azul-zulu-8.28.0.1-jdk8.0.163/zulu8.28.0.1-jdk8.0.163-win_x64.zip", + "https://cdn.azul.com/zulu/bin/zulu8.28.0.1-jdk8.0.163-win_x64.zip", ], ) diff --git a/compile.sh b/compile.sh index ed506d28c2ba61..48359e37771875 100755 --- a/compile.sh +++ b/compile.sh @@ -92,11 +92,11 @@ display "." log "Building output/bazel" # We set host and target platform directly since the defaults in @bazel_tools # have not yet been generated. -bazel_build "src:bazel_nojdk${EXE_EXT}" \ +bazel_build "src:bazel${EXE_EXT}" \ --host_platform=@bazel_tools//platforms:host_platform \ --platforms=@bazel_tools//platforms:target_platform \ || fail "Could not build Bazel" -bazel_bin_path="$(get_bazel_bin_path)/src/bazel_nojdk${EXE_EXT}" +bazel_bin_path="$(get_bazel_bin_path)/src/bazel${EXE_EXT}" [ -e "$bazel_bin_path" ] \ || fail "Could not find freshly built Bazel binary at '$bazel_bin_path'" cp -f "$bazel_bin_path" "output/bazel${EXE_EXT}" \ diff --git a/scripts/BUILD b/scripts/BUILD index 31e8958461d009..ee6ef86c37968a 100644 --- a/scripts/BUILD +++ b/scripts/BUILD @@ -11,13 +11,18 @@ genrule( cmd = " ".join([ "$(location :generate_bash_completion.sh)", "--bazel=$(location //src:bazel)", + "--javabase=$(JAVABASE)", "--output=$@", "--prepend=$(location bazel-complete-template.bash)", ]), output_to_bindir = 1, + # Bazel 0.8.0 doesn't have this target under @bazel_tools, so we have to + # use it from the main repository + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], tools = [ ":generate_bash_completion.sh", "//src:bazel", + "//tools/defaults:jdk", ], visibility = ["//scripts/packages:__subpackages__"], ) diff --git a/scripts/packages/BUILD b/scripts/packages/BUILD index ace087ce2abf28..4a456191b4c078 100644 --- a/scripts/packages/BUILD +++ b/scripts/packages/BUILD @@ -86,7 +86,7 @@ genrule( genrule( name = "rename-bazel-bin", - srcs = ["//src:bazel"], + srcs = ["//src:bazel_with_jdk"], outs = ["with-jdk/bazel-real"], cmd = "mkdir -p $$(dirname $@); cp $< $@", ) diff --git a/src/BUILD b/src/BUILD index 5c11f7e4ea5b20..cc81bd1fcf98b0 100644 --- a/src/BUILD +++ b/src/BUILD @@ -40,7 +40,7 @@ filegroup( }), ) for suffix, embedded_tools_target in { "": [":embedded_tools"], - "_nojdk": [":embedded_tools_nojdk"], + "_with_jdk": [":embedded_tools_with_jdk"], "_notools": [], }.items()] @@ -174,13 +174,26 @@ py_binary( "//conditions:default": [ "//src/tools/singlejar:singlejar_local", ], - }) + ([":embedded_jdk"] if (suffix == "") else []), + }) + (select({ + "//src/conditions:darwin": [ + "@openjdk_macos//file", + ], + "//src/conditions:darwin_x86_64": [ + "@openjdk_macos//file", + ], + "//src/conditions:windows": [ + "@openjdk_win//file", + ], + "//conditions:default": [ + "@openjdk_linux//file", + ], + }) if (suffix == "_with_jdk") else []), visibility = [ "//src/test:__subpackages__", # For integration tests ], ) for suffix in [ "", - "_nojdk", + "_with_jdk", ]] rule_size_test( @@ -193,32 +206,13 @@ rule_size_test( margin = 5, # percentage ) -filegroup( - name = "embedded_jdk", - srcs = select({ - "//src/conditions:darwin": [ - "@openjdk_macos//file", - ], - "//src/conditions:darwin_x86_64": [ - "@openjdk_macos//file", - ], - "//src/conditions:windows": [ - "@openjdk_win//file", - ], - "//conditions:default": [ - "@openjdk_linux//file", - ], - }), - visibility = ["//src/test/shell/bazel:__pkg__"], -) - [srcsfile( name = "embedded_tools" + suffix + "_params", srcs = [":embedded_tools" + suffix + "_srcs"], out = "embedded_tools" + suffix + ".params", ) for suffix in [ "", - "_nojdk", + "_with_jdk", ]] genrule( @@ -233,13 +227,13 @@ genrule( ) genrule( - name = "embedded_tools_nojdk", + name = "embedded_tools_with_jdk", srcs = [ - ":embedded_tools_nojdk_params", - ":embedded_tools_nojdk_srcs", + ":embedded_tools_with_jdk_params", + ":embedded_tools_with_jdk_srcs", ], - outs = ["embedded_tools_nojdk.zip"], - cmd = "$(location :create_embedded_tools) \"$@\" $(location :embedded_tools_nojdk_params)", + outs = ["embedded_tools_with_jdk.zip"], + cmd = "$(location :create_embedded_tools) \"$@\" $(location :embedded_tools_with_jdk_params)", tools = [":create_embedded_tools"], ) @@ -264,7 +258,7 @@ genrule( ) for suffix, embed in [ ("", True), ("_notools", False), - ("_nojdk", True), + ("_with_jdk", True), ]] [genrule( @@ -289,7 +283,7 @@ genrule( ) for suffix in [ "", "_notools", - "_nojdk", + "_with_jdk", ]] # Build an executable named `bazel.exe`. @@ -310,7 +304,7 @@ genrule( ) for suffix in [ "", "_notools", - "_nojdk", + "_with_jdk", ]] filegroup( diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc index 09760fcba3c7f4..0dbedf0657cb54 100644 --- a/src/main/cpp/blaze.cc +++ b/src/main/cpp/blaze.cc @@ -418,13 +418,6 @@ static vector GetArgumentArray( result.push_back("-XX:HeapDumpPath=" + blaze_util::PathAsJvmFlag(heap_crash_path)); - // TODO(b/109998449): only assume JDK >= 9 for embedded JDKs - if (!globals->options->GetEmbeddedJavabase().empty()) { - // quiet warnings from com.google.protobuf.UnsafeUtil, - // see: https://github.com/google/protobuf/issues/3781 - result.push_back("--add-opens=java.base/java.nio=ALL-UNNAMED"); - } - result.push_back("-Xverify:none"); vector user_options; diff --git a/src/main/cpp/blaze_util_freebsd.cc b/src/main/cpp/blaze_util_freebsd.cc index 0d196827d87a8b..7cca0a0973a9cc 100644 --- a/src/main/cpp/blaze_util_freebsd.cc +++ b/src/main/cpp/blaze_util_freebsd.cc @@ -33,6 +33,7 @@ #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/path.h" #include "src/main/cpp/util/port.h" #include "src/main/cpp/util/strings.h" diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc index 2398e17fcbe5e4..0a3daaab81ef86 100644 --- a/src/main/cpp/blaze_util_posix.cc +++ b/src/main/cpp/blaze_util_posix.cc @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#define _WITH_DPRINTF #include "src/main/cpp/blaze_util_platform.h" -#define _WITH_DPRINTF #include #include #include diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc index 7b5625c2ef32ec..8f028f55c6fe41 100644 --- a/src/main/cpp/startup_options.cc +++ b/src/main/cpp/startup_options.cc @@ -414,24 +414,16 @@ string StartupOptions::GetSystemJavabase() const { return blaze::GetSystemJavabase(); } -string StartupOptions::GetEmbeddedJavabase() { - string bundled_jre_path = blaze_util::JoinPath( - install_base, "_embedded_binaries/embedded_tools/jdk"); - if (blaze_util::CanExecuteFile(blaze_util::JoinPath( - bundled_jre_path, GetJavaBinaryUnderJavabase()))) { - return bundled_jre_path; - } - return ""; -} - string StartupOptions::GetHostJavabase() { // 1) Allow overriding the host_javabase via --host_javabase. if (!host_javabase.empty()) { return host_javabase; } if (default_host_javabase.empty()) { - string bundled_jre_path = GetEmbeddedJavabase(); - if (!bundled_jre_path.empty()) { + string bundled_jre_path = blaze_util::JoinPath( + install_base, "_embedded_binaries/embedded_tools/jdk"); + if (blaze_util::CanExecuteFile(blaze_util::JoinPath( + bundled_jre_path, GetJavaBinaryUnderJavabase()))) { // 2) Use a bundled JVM if we have one. default_host_javabase = bundled_jre_path; } else { diff --git a/src/main/cpp/startup_options.h b/src/main/cpp/startup_options.h index 39981955454c31..84079f1b3fa158 100644 --- a/src/main/cpp/startup_options.h +++ b/src/main/cpp/startup_options.h @@ -266,9 +266,6 @@ class StartupOptions { // from a blazerc file, if a key is not present, it is the default. std::map option_sources; - // Returns the embedded JDK, or an empty string. - std::string GetEmbeddedJavabase(); - // Returns the GetHostJavabase. This should be called after parsing // the --host_javabase option. std::string GetHostJavabase(); diff --git a/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java b/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java index 9309e3b9703ad5..6cd2e4be65a9bc 100644 --- a/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java +++ b/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java @@ -564,10 +564,10 @@ public synchronized void start( FileWriter writer = null; if (stream != null) { if (format == Format.BINARY_BAZEL_FORMAT) { - writer = new BinaryFormatWriter(stream, profileStartTime, comment); + writer = new BinaryFormatWriter(stream, execStartTimeNanos, comment); writer.start(); } else if (format == Format.JSON_TRACE_FILE_FORMAT) { - writer = new JsonTraceFileWriter(stream, profileStartTime); + writer = new JsonTraceFileWriter(stream, execStartTimeNanos); writer.start(); } } diff --git a/src/main/java/com/google/devtools/build/lib/profiler/analysis/ProfileInfo.java b/src/main/java/com/google/devtools/build/lib/profiler/analysis/ProfileInfo.java index 5050eb4436a12b..4eb7f6e115b9c9 100644 --- a/src/main/java/com/google/devtools/build/lib/profiler/analysis/ProfileInfo.java +++ b/src/main/java/com/google/devtools/build/lib/profiler/analysis/ProfileInfo.java @@ -444,6 +444,7 @@ public interface InfoListener { private static final AggregateAttr ZERO = new AggregateAttr(0, 0); public final String comment; + private long minTaskStartTime = Long.MAX_VALUE; private boolean corruptedOrIncomplete = false; // TODO(bazel-team): (2010) In one case, this list took 277MB of heap. Ideally it should be @@ -475,6 +476,7 @@ private ProfileInfo(String comment) { private void addTask(Task task) { allTasksById.add(task); + minTaskStartTime = Math.min(minTaskStartTime, task.startTime); } /** @@ -624,6 +626,16 @@ public AggregateAttr getStatsForType(ProfilerTask type, Collection tasks) return new AggregateAttr(count, totalTime); } + /** + * Returns the minimum task start time, that is, when the profile actually started. + * + *

This should be very close to zero except that some Blaze versions contained a bug that made + * them not subtract the current time from task start times in the profile.

+ */ + public long getMinTaskStartTime() { + return minTaskStartTime; + } + /** * Returns list of all root tasks related to (in other words, started during) * the specified phase task. diff --git a/src/main/java/com/google/devtools/build/lib/profiler/chart/AggregatingChartCreator.java b/src/main/java/com/google/devtools/build/lib/profiler/chart/AggregatingChartCreator.java index 30f0e3a9a547b0..da0b4e2015cc8f 100644 --- a/src/main/java/com/google/devtools/build/lib/profiler/chart/AggregatingChartCreator.java +++ b/src/main/java/com/google/devtools/build/lib/profiler/chart/AggregatingChartCreator.java @@ -112,13 +112,13 @@ public Chart create() { for (ProfileInfo.Task task : info.allTasksById) { if (ACTION_TASKS.contains(task.type)) { - createBar(chart, task, actionType); + createBar(chart, info.getMinTaskStartTime(), task, actionType); } else if (LOCK_TASKS.contains(task.type)) { - createBar(chart, task, lockType); + createBar(chart, info.getMinTaskStartTime(), task, lockType); } else if (BLAZE_TASKS.contains(task.type)) { - createBar(chart, task, blazeType); + createBar(chart, info.getMinTaskStartTime(), task, blazeType); } else if (showVFS && VFS_TASKS.contains(task.type)) { - createBar(chart, task, vfsType); + createBar(chart, info.getMinTaskStartTime(), task, vfsType); } } @@ -132,9 +132,11 @@ public Chart create() { * @param task the profiler task from which the bar is created * @param type the type of the bar */ - private void createBar(Chart chart, Task task, ChartBarType type) { + private void createBar(Chart chart, long minTaskStartTime, Task task, ChartBarType type) { String label = task.type.description + ": " + task.getDescription(); - chart.addBar(task.threadId, task.startTime, task.startTime + task.durationNanos, type, label); + chart.addBar(task.threadId, + task.startTime - minTaskStartTime, + task.startTime - minTaskStartTime + task.durationNanos, type, label); } /** diff --git a/src/main/java/com/google/devtools/build/lib/profiler/chart/CommonChartCreator.java b/src/main/java/com/google/devtools/build/lib/profiler/chart/CommonChartCreator.java index bb681604d3380a..076d143f33f004 100644 --- a/src/main/java/com/google/devtools/build/lib/profiler/chart/CommonChartCreator.java +++ b/src/main/java/com/google/devtools/build/lib/profiler/chart/CommonChartCreator.java @@ -36,8 +36,8 @@ private static void addColumn(Chart chart, ProfileInfo info, ProfilePhase phase) if (task != null) { String label = task.type.description + ": " + task.getDescription(); ChartBarType type = chart.lookUpType(task.getDescription()); - long stop = task.startTime + info.getPhaseDuration(task); - chart.addTimeRange(task.startTime, stop, type, label); + long stop = task.startTime - info.getMinTaskStartTime() + info.getPhaseDuration(task); + chart.addTimeRange(task.startTime - info.getMinTaskStartTime(), stop, type, label); } } diff --git a/src/main/java/com/google/devtools/build/lib/profiler/chart/DetailedChartCreator.java b/src/main/java/com/google/devtools/build/lib/profiler/chart/DetailedChartCreator.java index 27ea8571f55f33..6e1d1965441737 100644 --- a/src/main/java/com/google/devtools/build/lib/profiler/chart/DetailedChartCreator.java +++ b/src/main/java/com/google/devtools/build/lib/profiler/chart/DetailedChartCreator.java @@ -52,7 +52,7 @@ public Chart create() { for (Task task : info.allTasksById) { String label = task.type.description + ": " + task.getDescription(); ChartBarType type = chart.lookUpType(task.type.description); - long stop = task.startTime + task.durationNanos; + long stop = task.startTime - info.getMinTaskStartTime() + task.durationNanos; CriticalPathEntry entry = null; // for top level tasks, check if they are on the critical path @@ -66,12 +66,14 @@ public Chart create() { } if (nextEntry != null) { // time is start and not stop as we traverse the critical back backwards - chart.addVerticalLine(task.threadId, nextEntry.task.threadId, task.startTime); + chart.addVerticalLine(task.threadId, nextEntry.task.threadId, + task.startTime - info.getMinTaskStartTime()); } } } - chart.addBar(task.threadId, task.startTime, stop, type, (entry != null), label); + chart.addBar(task.threadId, task.startTime - info.getMinTaskStartTime(), stop, type, + entry != null, label); } return chart; diff --git a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java index ef90223d838f9f..66f29c5120166d 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java +++ b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java @@ -13,8 +13,7 @@ // limitations under the License. package com.google.devtools.build.lib.remote; -import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture; - +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -27,6 +26,7 @@ import com.google.devtools.build.lib.concurrent.ThreadSafety; import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode; import com.google.devtools.build.lib.remote.util.DigestUtil; +import com.google.devtools.build.lib.remote.util.Utils; import com.google.devtools.build.lib.util.io.FileOutErr; import com.google.devtools.build.lib.vfs.Dirent; import com.google.devtools.build.lib.vfs.FileStatus; @@ -170,68 +170,84 @@ public void onFailure(Throwable t) { // TODO(olaola): will need to amend to include the TreeNodeRepository for updating. public void download(ActionResult result, Path execRoot, FileOutErr outErr) throws ExecException, IOException, InterruptedException { - try { - Context ctx = Context.current(); - List fileDownloads = - Collections.synchronizedList( - new ArrayList<>(result.getOutputFilesCount() + result.getOutputDirectoriesCount())); - for (OutputFile file : result.getOutputFilesList()) { - Path path = execRoot.getRelative(file.getPath()); - ListenableFuture download = - retrier.executeAsync( - () -> ctx.call(() -> downloadFile(path, file.getDigest(), file.getContent()))); - fileDownloads.add(new FuturePathBooleanTuple(download, path, file.getIsExecutable())); - } + Context ctx = Context.current(); + List fileDownloads = + Collections.synchronizedList( + new ArrayList<>(result.getOutputFilesCount() + result.getOutputDirectoriesCount())); + for (OutputFile file : result.getOutputFilesList()) { + Path path = execRoot.getRelative(file.getPath()); + ListenableFuture download = + retrier.executeAsync( + () -> ctx.call(() -> downloadFile(path, file.getDigest(), file.getContent()))); + fileDownloads.add(new FuturePathBooleanTuple(download, path, file.getIsExecutable())); + } - List> dirDownloads = - new ArrayList<>(result.getOutputDirectoriesCount()); - for (OutputDirectory dir : result.getOutputDirectoriesList()) { - SettableFuture dirDownload = SettableFuture.create(); - ListenableFuture protoDownload = - retrier.executeAsync(() -> ctx.call(() -> downloadBlob(dir.getTreeDigest()))); - Futures.addCallback( - protoDownload, - new FutureCallback() { - @Override - public void onSuccess(byte[] b) { - try { - Tree tree = Tree.parseFrom(b); - Map childrenMap = new HashMap<>(); - for (Directory child : tree.getChildrenList()) { - childrenMap.put(digestUtil.compute(child), child); - } - Path path = execRoot.getRelative(dir.getPath()); - fileDownloads.addAll(downloadDirectory(path, tree.getRoot(), childrenMap, ctx)); - dirDownload.set(null); - } catch (IOException e) { - dirDownload.setException(e); + List> dirDownloads = new ArrayList<>(result.getOutputDirectoriesCount()); + for (OutputDirectory dir : result.getOutputDirectoriesList()) { + SettableFuture dirDownload = SettableFuture.create(); + ListenableFuture protoDownload = + retrier.executeAsync(() -> ctx.call(() -> downloadBlob(dir.getTreeDigest()))); + Futures.addCallback( + protoDownload, + new FutureCallback() { + @Override + public void onSuccess(byte[] b) { + try { + Tree tree = Tree.parseFrom(b); + Map childrenMap = new HashMap<>(); + for (Directory child : tree.getChildrenList()) { + childrenMap.put(digestUtil.compute(child), child); } + Path path = execRoot.getRelative(dir.getPath()); + fileDownloads.addAll(downloadDirectory(path, tree.getRoot(), childrenMap, ctx)); + dirDownload.set(null); + } catch (IOException e) { + dirDownload.setException(e); } + } - @Override - public void onFailure(Throwable t) { - dirDownload.setException(t); - } - }, - MoreExecutors.directExecutor()); - dirDownloads.add(dirDownload); - } + @Override + public void onFailure(Throwable t) { + dirDownload.setException(t); + } + }, + MoreExecutors.directExecutor()); + dirDownloads.add(dirDownload); + } - fileDownloads.addAll(downloadOutErr(result, outErr, ctx)); + // Subsequently we need to wait for *every* download to finish, even if we already know that + // one failed. That's so that when exiting this method we can be sure that all downloads have + // finished and don't race with the cleanup routine. + // TODO(buchgr): Look into cancellation. - for (ListenableFuture dirDownload : dirDownloads) { - // Block on all directory download futures, so that we can be sure that we have discovered - // all file downloads and can subsequently safely iterate over the list of file downloads. + IOException downloadException = null; + try { + fileDownloads.addAll(downloadOutErr(result, outErr, ctx)); + } catch (IOException e) { + downloadException = e; + } + for (ListenableFuture dirDownload : dirDownloads) { + // Block on all directory download futures, so that we can be sure that we have discovered + // all file downloads and can subsequently safely iterate over the list of file downloads. + try { getFromFuture(dirDownload); + } catch (IOException e) { + downloadException = downloadException == null ? e : downloadException; } + } - for (FuturePathBooleanTuple download : fileDownloads) { + for (FuturePathBooleanTuple download : fileDownloads) { + try { getFromFuture(download.getFuture()); if (download.getPath() != null) { download.getPath().setExecutable(download.isExecutable()); } + } catch (IOException e) { + downloadException = downloadException == null ? e : downloadException; } - } catch (IOException downloadException) { + } + + if (downloadException != null) { try { // Delete any (partially) downloaded output files, since any subsequent local execution // of this action may expect none of the output files to exist. @@ -261,6 +277,11 @@ public void onFailure(Throwable t) { } } + @VisibleForTesting + protected T getFromFuture(ListenableFuture f) throws IOException, InterruptedException { + return Utils.getFromFuture(f); + } + /** Tuple of {@code ListenableFuture, Path, boolean}. */ private static class FuturePathBooleanTuple { private final ListenableFuture future; diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteOptions.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteOptions.java index f3fa147291be6a..bb61082a3dab1e 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/RemoteOptions.java +++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteOptions.java @@ -37,12 +37,13 @@ public final class RemoteOptions extends OptionsBase { @Option( name = "remote_max_connections", - defaultValue = "0", + defaultValue = "100", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS}, help = "The max. number of concurrent network connections to the remote cache/executor. By " - + "default Bazel selects the ideal number of connections automatically.") + + "default Bazel limits the number of TCP connections to 100. Setting this flag to " + + "0 will make Bazel choose the number of connections automatically.") public int remoteMaxConnections; @Option( diff --git a/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java b/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java index b1b0d604df366e..8e6269a44d8dfa 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java +++ b/src/main/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCache.java @@ -14,8 +14,6 @@ package com.google.devtools.build.lib.remote; -import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture; - import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; diff --git a/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java b/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java index fe4881c4745300..7afe9b84063c25 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java +++ b/src/test/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCacheTests.java @@ -15,19 +15,46 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; +import static org.junit.Assert.fail; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; import com.google.devtools.build.lib.actions.ExecException; import com.google.devtools.build.lib.clock.JavaClock; import com.google.devtools.build.lib.remote.AbstractRemoteActionCache.UploadManifest; +import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode; import com.google.devtools.build.lib.remote.util.DigestUtil; +import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey; +import com.google.devtools.build.lib.remote.util.Utils; +import com.google.devtools.build.lib.util.io.FileOutErr; import com.google.devtools.build.lib.vfs.DigestHashFunction; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import com.google.devtools.remoteexecution.v1test.ActionResult; +import com.google.devtools.remoteexecution.v1test.Command; +import com.google.devtools.remoteexecution.v1test.Digest; +import com.google.devtools.remoteexecution.v1test.OutputFile; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -40,6 +67,13 @@ public class AbstractRemoteActionCacheTests { private Path execRoot; private final DigestUtil digestUtil = new DigestUtil(DigestHashFunction.SHA256); + private static ListeningScheduledExecutorService retryService; + + @BeforeClass + public static void beforeEverything() { + retryService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1)); + } + @Before public void setUp() throws Exception { fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256); @@ -47,6 +81,11 @@ public void setUp() throws Exception { execRoot.createDirectory(); } + @AfterClass + public static void afterEverything() { + retryService.shutdownNow(); + } + @Test public void uploadSymlinkAsFile() throws Exception { ActionResult.Builder result = ActionResult.newBuilder(); @@ -92,4 +131,118 @@ public void uploadSymlinkInDirectory() throws Exception { .hasMessageThat() .contains("Only regular files and directories may be uploaded to a remote cache."); } + + @Test + public void onErrorWaitForRemainingDownloadsToComplete() throws Exception { + // If one or more downloads of output files / directories fail then the code should + // wait for all downloads to have been completed before it tries to clean up partially + // downloaded files. + + Path stdout = fs.getPath("/execroot/stdout"); + Path stderr = fs.getPath("/execroot/stderr"); + + Map> downloadResults = new HashMap<>(); + Path file1 = fs.getPath("/execroot/file1"); + Digest digest1 = digestUtil.compute("file1".getBytes("UTF-8")); + downloadResults.put(digest1, Futures.immediateFuture("file1".getBytes("UTF-8"))); + Path file2 = fs.getPath("/execroot/file2"); + Digest digest2 = digestUtil.compute("file2".getBytes("UTF-8")); + downloadResults.put(digest2, Futures.immediateFailedFuture(new IOException("download failed"))); + Path file3 = fs.getPath("/execroot/file3"); + Digest digest3 = digestUtil.compute("file3".getBytes("UTF-8")); + downloadResults.put(digest3, Futures.immediateFuture("file3".getBytes("UTF-8"))); + + RemoteOptions options = new RemoteOptions(); + RemoteRetrier retrier = new RemoteRetrier(options, (e) -> false, retryService, + Retrier.ALLOW_ALL_CALLS); + List> blockingDownloads = new ArrayList<>(); + AtomicInteger numSuccess = new AtomicInteger(); + AtomicInteger numFailures = new AtomicInteger(); + AbstractRemoteActionCache cache = new DefaultRemoteActionCache(options, digestUtil, retrier) { + @Override + public ListenableFuture downloadBlob(Digest digest, OutputStream out) { + SettableFuture result = SettableFuture.create(); + Futures.addCallback(downloadResults.get(digest), new FutureCallback() { + @Override + public void onSuccess(byte[] bytes) { + numSuccess.incrementAndGet(); + try { + out.write(bytes); + out.close(); + result.set(null); + } catch (IOException e) { + result.setException(e); + } + } + + @Override + public void onFailure(Throwable throwable) { + numFailures.incrementAndGet(); + result.setException(throwable); + } + }, MoreExecutors.directExecutor()); + return result; + } + + @Override + protected T getFromFuture(ListenableFuture f) + throws IOException, InterruptedException { + blockingDownloads.add(f); + return Utils.getFromFuture(f); + } + }; + + ActionResult result = ActionResult.newBuilder() + .setExitCode(0) + .addOutputFiles(OutputFile.newBuilder().setPath(file1.getPathString()).setDigest(digest1)) + .addOutputFiles(OutputFile.newBuilder().setPath(file2.getPathString()).setDigest(digest2)) + .addOutputFiles(OutputFile.newBuilder().setPath(file3.getPathString()).setDigest(digest3)) + .build(); + try { + cache.download(result, execRoot, new FileOutErr(stdout, stderr)); + fail("Expected IOException"); + } catch (IOException e) { + assertThat(numSuccess.get()).isEqualTo(2); + assertThat(numFailures.get()).isEqualTo(1); + assertThat(blockingDownloads).hasSize(3); + assertThat(Throwables.getRootCause(e)).hasMessageThat().isEqualTo("download failed"); + } + } + + private static class DefaultRemoteActionCache extends AbstractRemoteActionCache { + + public DefaultRemoteActionCache(RemoteOptions options, + DigestUtil digestUtil, Retrier retrier) { + super(options, digestUtil, retrier); + } + + @Override + public void ensureInputsPresent(TreeNodeRepository repository, Path execRoot, TreeNode root, + Command command) throws IOException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + ActionResult getCachedActionResult(ActionKey actionKey) + throws IOException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + void upload(ActionKey actionKey, Path execRoot, Collection files, FileOutErr outErr, + boolean uploadAction) throws ExecException, IOException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + protected ListenableFuture downloadBlob(Digest digest, OutputStream out) { + throw new UnsupportedOperationException(); + } + + @Override + public void close() { + throw new UnsupportedOperationException(); + } + } } diff --git a/src/test/py/bazel/BUILD b/src/test/py/bazel/BUILD index 9087fe0bd7acab..3763e6bef9807e 100644 --- a/src/test/py/bazel/BUILD +++ b/src/test/py/bazel/BUILD @@ -9,7 +9,7 @@ filegroup( filegroup( name = "test-deps", testonly = 1, - srcs = ["//src:bazel"], + srcs = ["//src:bazel_with_jdk"], data = ["//src/tools/remote:worker"], ) diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py index 4d5278876cbfd8..acdf340d2117b8 100644 --- a/src/test/py/bazel/test_base.py +++ b/src/test/py/bazel/test_base.py @@ -235,7 +235,7 @@ def RunBazel(self, args, env_remove=None, env_add=None): (int, [string], [string]) tuple: exit code, stdout lines, stderr lines """ return self.RunProgram([ - self.Rlocation('io_bazel/src/bazel'), + self.Rlocation('io_bazel/src/bazel_with_jdk'), '--bazelrc=/dev/null', '--nomaster_bazelrc', ] + args, env_remove, env_add) @@ -357,8 +357,6 @@ def _EnvMap(self, env_remove=None, env_add=None): 'BAZEL_SH': TestBase.GetEnv('BAZEL_SH', 'c:\\tools\\msys64\\usr\\bin\\bash.exe'), - 'JAVA_HOME': - TestBase.GetEnv('JAVA_HOME'), } else: env = {'HOME': os.path.join(self._temp, 'home')} diff --git a/src/test/py/bazel/windows_remote_test.py b/src/test/py/bazel/windows_remote_test.py index e962ce590d31ae..fc9a03a139e4c6 100644 --- a/src/test/py/bazel/windows_remote_test.py +++ b/src/test/py/bazel/windows_remote_test.py @@ -203,7 +203,8 @@ def testJavaTestWithRuntimeRunsRemotely(self): # Test. exit_code, stdout, stderr = self._RunRemoteBazel([ - 'test', '--test_output=all', '--javabase=//foo:jdk8', '//foo:foo_test' + 'test', '--test_output=all', '--host_javabase=//foo:jdk8', + '--javabase=//foo:jdk8', '//foo:foo_test' ]) self.AssertExitCode(exit_code, 0, stderr, stdout) diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD index 7fa364176b58a6..1e0b6ed743b74f 100644 --- a/src/test/shell/bazel/BUILD +++ b/src/test/shell/bazel/BUILD @@ -524,7 +524,7 @@ sh_test( srcs = ["bazel_with_jdk_test.sh"], data = [ ":test-deps", - "//src:bazel", + "//src:bazel_with_jdk", ], tags = ["no_windows"], ) @@ -541,14 +541,10 @@ sh_test( name = "bazel_bootstrap_distfile_test", timeout = "eternal", srcs = ["bazel_bootstrap_distfile_test.sh"], - args = [ - "$(location //:bazel-distfile)", - "$(location //src:embedded_jdk)", - ], + args = ["$(location //:bazel-distfile)"], data = [ ":test-deps", "//:bazel-distfile", - "//src:embedded_jdk", ], tags = ["jdk8"], ) diff --git a/src/test/shell/bazel/bazel_bootstrap_distfile_test.sh b/src/test/shell/bazel/bazel_bootstrap_distfile_test.sh index 273fe8d5cd20df..3f4dd06efabd6a 100755 --- a/src/test/shell/bazel/bazel_bootstrap_distfile_test.sh +++ b/src/test/shell/bazel/bazel_bootstrap_distfile_test.sh @@ -19,7 +19,6 @@ set -u DISTFILE=$(rlocation io_bazel/${1#./}) -EMBEDDED_JDK=$(rlocation io_bazel/${2#./}) shift 1 # Load the test setup defined in the parent directory @@ -42,23 +41,12 @@ function test_bootstrap() { export SOURCE_DATE_EPOCH=1501234567 _log_progress "unzip" unzip -q "${DISTFILE}" - if [[ $EMBEDDED_JDK == *.tar.gz ]]; then - tar xf $EMBEDDED_JDK - elif [[ $EMBEDDED_JDK == *.zip ]]; then - unzip -q $EMBEDDED_JDK - fi - JAVABASE=$(echo zulu*) - _log_progress "bootstrap" env EXTRA_BAZEL_ARGS="--curses=no --strategy=Javac=standalone" ./compile.sh \ || fail "Expected to be able to bootstrap bazel" _log_progress "run" - ./output/bazel \ - --host_javabase=$JAVABASE --host_jvm_args=--add-opens=java.base/java.nio=ALL-UNNAMED \ - version > "${TEST_log}" || fail "Generated bazel not working" - ./output/bazel \ - --host_javabase=$JAVABASE --host_jvm_args=--add-opens=java.base/java.nio=ALL-UNNAMED \ - shutdown + ./output/bazel version > "${TEST_log}" || fail "Generated bazel not working" + ./output/bazel shutdown _log_progress "assert" expect_log "${SOURCE_DATE_EPOCH}" cd "${olddir}" diff --git a/src/test/shell/bazel/bazel_with_jdk_test.sh b/src/test/shell/bazel/bazel_with_jdk_test.sh index cf333452e49e4c..98c022345de88b 100755 --- a/src/test/shell/bazel/bazel_with_jdk_test.sh +++ b/src/test/shell/bazel/bazel_with_jdk_test.sh @@ -21,8 +21,8 @@ source $(rlocation io_bazel/src/test/shell/integration_test_setup.sh) \ || { echo "integration_test_setup.sh not found!" >&2; exit 1; } -function bazel() { - $(rlocation io_bazel/src/bazel) --bazelrc=$TEST_TMPDIR/bazelrc "$@" +function bazel_with_jdk() { + $(rlocation io_bazel/src/bazel_with_jdk) --bazelrc=$TEST_TMPDIR/bazelrc "$@" return $? } @@ -48,32 +48,32 @@ function cleanup() { } function test_bazel_uses_bundled_jdk() { - bazel --batch info &> "$TEST_log" || fail "bazel info failed" - install_base="$(bazel --batch info install_base)" + bazel_with_jdk --batch info &> "$TEST_log" || fail "bazel_with_jdk info failed" + install_base="$(bazel_with_jdk --batch info install_base)" # Case-insensitive match, because Windows paths are case-insensitive. grep -sqi -- "^java-home: ${install_base}/_embedded_binaries/embedded_tools/jdk" $TEST_log || \ - fail "bazel's java-home is not inside the install base" + fail "bazel_with_jdk's java-home is not inside the install base" } -# Tests that "bazel license" prints the license of the bundled JDK by grepping for +# Tests that "bazel_with_jdk license" prints the license of the bundled JDK by grepping for # representative strings from those files. If this test breaks after upgrading the version of the # bundled JDK, the strings may have to be updated. function test_bazel_license_prints_jdk_license() { - bazel --batch license \ - &> "$TEST_log" || fail "running bazel license failed" + bazel_with_jdk --batch license \ + &> "$TEST_log" || fail "running bazel_with_jdk license failed" expect_log "OPENJDK ASSEMBLY EXCEPTION" || \ - fail "'bazel license' did not print an expected string from ASSEMBLY_EXCEPTION" + fail "'bazel_with_jdk license' did not print an expected string from ASSEMBLY_EXCEPTION" expect_log "Provided you have not received the software directly from Azul and have already" || \ - fail "'bazel license' did not print an expected string from DISCLAIMER" + fail "'bazel_with_jdk license' did not print an expected string from DISCLAIMER" expect_log '"CLASSPATH" EXCEPTION TO THE GPL' || \ - fail "'bazel license' did not print an expected string from LICENSE" + fail "'bazel_with_jdk license' did not print an expected string from LICENSE" expect_log "which may be included with JRE [0-9]\+, JDK [0-9]\+, and OpenJDK [0-9]\+" || \ - fail "'bazel license' did not print an expected string from THIRD_PARTY_README" + fail "'bazel_with_jdk license' did not print an expected string from THIRD_PARTY_README" } -run_suite "bazel test suite" +run_suite "bazel_with_jdk test suite" diff --git a/src/test/shell/bin/bazel b/src/test/shell/bin/bazel index 3a77cb3222bdf1..27975618ee62a8 100755 --- a/src/test/shell/bin/bazel +++ b/src/test/shell/bin/bazel @@ -17,4 +17,4 @@ # Wrapper script to run bazel in the tests. Any change to this file will # affect all our integration tests. # -exec $(rlocation io_bazel/src/bazel) --bazelrc=$TEST_TMPDIR/bazelrc "$@" +exec $(rlocation io_bazel/src/bazel) --bazelrc=$TEST_TMPDIR/bazelrc "$@" \ No newline at end of file diff --git a/src/test/shell/integration/test_test.sh b/src/test/shell/integration/test_test.sh index a6d70dde041ccd..c2e599749b70b2 100755 --- a/src/test/shell/integration/test_test.sh +++ b/src/test/shell/integration/test_test.sh @@ -64,7 +64,7 @@ EOF bazel test --nocache_test_results //tests:fail &>$TEST_log \ && fail "expected failure" || true - expect_log "^//tests:fail[[:space:]]\+FAILED in [[:digit:]]\+[\.,][[:digit:]]\+s" + expect_log "^//tests:fail[[:space:]]\+FAILED in [[:digit:]]\+\.[[:digit:]]\+s" expect_log "^Executed 1 out of 1 test: 1 fails" } diff --git a/src/test/shell/testenv.sh b/src/test/shell/testenv.sh index c1a90998195ec6..646d94656e78dc 100755 --- a/src/test/shell/testenv.sh +++ b/src/test/shell/testenv.sh @@ -260,6 +260,8 @@ function setup_bazelrc() { cat >$TEST_TMPDIR/bazelrc <