Upon noticing an error in an external repository, also report the reason

Users sometimes find it hard (issue #7850) to identify the root
cause of a failure, if it is a fetch in an external repository.
As we print a message anyway on observing a failure in a fetch,
make this message more helpful by
- upgrading it to a proper error message, and
- including the message of the actual fetch error.
Also ensure that in case of a repository fetch failing due to
the execution of `fail` in the implementation function, the
message is reported as well.

Fixes #7850
Fixes #3683

Change-Id: I70eea4f966c98949e0f57462ac73b2a078552073
PiperOrigin-RevId: 243988092
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
index 5da88d6..b5ec7f5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
@@ -185,8 +185,11 @@
       }
       env.getListener()
           .handle(
-              Event.info(
-                  "An error occurred during the fetch of repository '" + rule.getName() + "'"));
+              Event.error(
+                  "An error occurred during the fetch of repository '"
+                      + rule.getName()
+                      + "':\n   "
+                      + e.getMessage()));
       if (!Strings.isNullOrEmpty(rule.getDefinitionInformation())) {
         env.getListener().handle(Event.info(rule.getDefinitionInformation()));
       }
diff --git a/src/test/shell/bazel/skylark_repository_test.sh b/src/test/shell/bazel/skylark_repository_test.sh
index ef3c2b0..72fc395 100755
--- a/src/test/shell/bazel/skylark_repository_test.sh
+++ b/src/test/shell/bazel/skylark_repository_test.sh
@@ -1260,6 +1260,114 @@
       || fail "expected success after successful sync"
 }
 
+function test_download_failure_message() {
+  # Regression test for #7850
+  # Verify that the for a failed downlaod, it is clearly indicated
+  # what was attempted to download and how it fails.
+  cat > BUILD <<'EOF'
+genrule(
+  name = "it",
+  outs = ["it.txt"],
+  srcs = ["@ext_foo//:data.txt"],
+  cmd = "cp $< $@",
+)
+EOF
+  cat > repo.bzl <<'EOF'
+def _impl(ctx):
+  ctx.file("BUILD", "exports_files(['data.txt'])")
+  ctx.symlink(ctx.attr.data, "data.txt")
+
+trivial_repo = repository_rule(
+  implementation = _impl,
+  attrs = { "data" : attr.label() },
+)
+EOF
+  cat > root.bzl <<'EOF'
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+def root_cause():
+  http_archive(
+    name = "this_is_the_root_cause",
+    urls = ["http://does.not.exist.example.com/some/file.tar"],
+  )
+
+EOF
+  cat > WORKSPACE <<'EOF'
+load("//:root.bzl", "root_cause")
+load("//:repo.bzl", "trivial_repo")
+
+root_cause()
+
+trivial_repo(
+  name = "ext_baz",
+  data = "@this_is_the_root_cause//:data.txt",
+)
+trivial_repo(
+  name = "ext_bar",
+  data = "@ext_baz//:data.txt",
+)
+
+trivial_repo(
+  name = "ext_foo",
+  data = "@ext_bar//:data.txt",
+)
+EOF
+
+  bazel build //:it > "${TEST_log}" 2>&1 \
+      && fail "Expected failure" || :
+
+  # Extract the first error message printed
+  ed "${TEST_log}" <<'EOF'
+1
+/^ERROR
+.,/^[^ ]/-1w firsterror.log
+Q
+EOF
+  echo; echo "first error message which should focus on the root cause";
+  echo "=========="; cat firsterror.log; echo "=========="
+  # We expect it to contain the root cause, and the failure ...
+  grep -q 'this_is_the_root_cause' firsterror.log \
+      || fail "Root-cause repository not mentioned"
+  grep -q '[uU]nknown host.*does.not.exist.example.com' firsterror.log \
+      || fail "Failure reason not mentioned"
+  # ...but not be cluttered with information not related to the root cause
+  grep 'ext_foo' firsterror.log && fail "unrelated repo mentioned" || :
+  grep 'ext_bar' firsterror.log && fail "unrelated repo mentioned" || :
+  grep 'ext_baz' firsterror.log && fail "unrelated repo mentioned" || :
+  grep '//:it' firsterror.log && fail "unrelated target mentioned" || :
+
+  # Verify that the same is true, if the error is caused by a fail statement.
+  cat > root.bzl <<'EOF'
+def _impl(ctx):
+  fail("Here be dragons")
+
+repo = repository_rule(implementation=_impl, attrs = {})
+
+def root_cause():
+  repo(name = "this_is_the_root_cause")
+EOF
+  bazel build //:it > "${TEST_log}" 2>&1 \
+      && fail "Expected failure" || :
+
+  # Extract the first error message printed
+  ed "${TEST_log}" <<'EOF'
+1
+/^ERROR
+.,/^[^ ]/-1w firsterror.log
+Q
+EOF
+  echo; echo "first error message which should focus on the root cause";
+  echo "=========="; cat firsterror.log; echo "=========="
+  grep -q 'this_is_the_root_cause' firsterror.log \
+      || fail "Root-cause repository not mentioned"
+  grep -q 'Here be dragons' firsterror.log \
+      || fail "fail error message not shown"
+  grep 'ext_foo' firsterror.log && fail "unrelated repo mentioned" || :
+  grep 'ext_bar' firsterror.log && fail "unrelated repo mentioned" || :
+  grep 'ext_baz' firsterror.log && fail "unrelated repo mentioned" || :
+  grep '//:it' firsterror.log && fail "unrelated target mentioned" || :
+}
+
 function test_circular_load_error_message() {
   cat > WORKSPACE <<'EOF'
 load("//:a.bzl", "total")