Internal change
PiperOrigin-RevId: 234954493
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index b4dc6e1..211fa48 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -947,6 +947,7 @@
"//src/main/java/com/google/devtools/build/lib/actions",
"//src/main/java/com/google/devtools/build/lib/bazel/debug:workspace-rule-event",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/cache",
+ "//src/main/java/com/google/devtools/build/lib/bazel/repository/cache:events",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/shell",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
index ce795c6..f2829af 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
@@ -47,6 +47,7 @@
com.google.devtools.build.lib.bazel.coverage.BazelCoverageReportModule.class,
com.google.devtools.build.lib.skylarkdebug.module.SkylarkDebuggerModule.class,
com.google.devtools.build.lib.bazel.repository.RepositoryResolvedModule.class,
+ com.google.devtools.build.lib.bazel.repository.CacheHitReportingModule.class,
com.google.devtools.build.lib.bazel.SpawnLogModule.class,
com.google.devtools.build.lib.ssd.SsdModule.class,
com.google.devtools.build.lib.worker.WorkerModule.class,
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/CacheHitReportingModule.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/CacheHitReportingModule.java
new file mode 100644
index 0000000..bda4c58
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/CacheHitReportingModule.java
@@ -0,0 +1,81 @@
+// 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.
+package com.google.devtools.build.lib.bazel.repository;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCacheHitEvent;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.repository.RepositoryFailedEvent;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.util.Pair;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Module reporting about cache hits in external repositories in case of failures */
+public final class CacheHitReportingModule extends BlazeModule {
+ private Reporter reporter;
+ private Map<String, Set<Pair<String, URL>>> cacheHitsByRepo;
+
+ @Override
+ public void beforeCommand(CommandEnvironment env) {
+ env.getEventBus().register(this);
+ this.reporter = env.getReporter();
+ this.cacheHitsByRepo = new HashMap<String, Set<Pair<String, URL>>>();
+ }
+
+ @Override
+ public void afterCommand() {
+ this.reporter = null;
+ this.cacheHitsByRepo = null;
+ }
+
+ @Subscribe
+ public synchronized void cacheHit(RepositoryCacheHitEvent event) {
+ String repo = event.getRepo();
+ if (cacheHitsByRepo.get(repo) == null) {
+ cacheHitsByRepo.put(repo, new HashSet<Pair<String, URL>>());
+ }
+ cacheHitsByRepo.get(repo).add(Pair.of(event.getFileHash(), event.getUrl()));
+ }
+
+ @Subscribe
+ public void failed(RepositoryFailedEvent event) {
+ String repo = event.getRepo();
+ Set<Pair<String, URL>> cacheHits = cacheHitsByRepo.get(repo);
+ if (cacheHits != null && !cacheHits.isEmpty()) {
+ StringBuilder info = new StringBuilder();
+
+ info.append("Repository '")
+ .append(repo)
+ .append(
+ "' used the following cache hits instead of downloading the corresponding file.\n");
+ for (Pair<String, URL> hit : cacheHits) {
+ info.append(" * Hash '")
+ .append(hit.getFirst())
+ .append("' for ")
+ .append(hit.getSecond().toString())
+ .append("\n");
+ }
+ info.append("If the definition of '")
+ .append(repo)
+ .append("' was updated, verify that the hashes were also updated.");
+ reporter.handle(Event.info(info.toString()));
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/BUILD
index 388d708..0abca72 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/BUILD
@@ -15,3 +15,12 @@
"//third_party:jsr305",
],
)
+
+java_library(
+ name = "events",
+ srcs = ["RepositoryCacheHitEvent.java"],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:events",
+ "//third_party:guava",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/RepositoryCacheHitEvent.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/RepositoryCacheHitEvent.java
new file mode 100644
index 0000000..009b294
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/RepositoryCacheHitEvent.java
@@ -0,0 +1,43 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.lib.bazel.repository.cache;
+
+import com.google.devtools.build.lib.events.ExtendedEventHandler.ProgressLike;
+import java.net.URL;
+
+/** Event reporting about cache hits for download requests. */
+public class RepositoryCacheHitEvent implements ProgressLike {
+ private final String repo;
+ private final String hash;
+ private final URL url;
+
+ public RepositoryCacheHitEvent(String repo, String hash, URL url) {
+ this.repo = repo;
+ this.hash = hash;
+ this.url = url;
+ }
+
+ public String getRepo() {
+ return repo;
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+ public String getFileHash() {
+ return hash;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
index de0562f..e1e9f21 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/BUILD
@@ -16,6 +16,7 @@
"//src/main/java/com/google/devtools/build/lib:syntax",
"//src/main/java/com/google/devtools/build/lib:util",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/cache",
+ "//src/main/java/com/google/devtools/build/lib/bazel/repository/cache:events",
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/vfs",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
index d039a8d..93842a9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java
@@ -21,6 +21,7 @@
import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache;
import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCache.KeyType;
+import com.google.devtools.build.lib.bazel.repository.cache.RepositoryCacheHitEvent;
import com.google.devtools.build.lib.buildeventstream.FetchEvent;
import com.google.devtools.build.lib.clock.Clock;
import com.google.devtools.build.lib.clock.JavaClock;
@@ -75,6 +76,8 @@
* @param output destination filename if {@code type} is <i>absent</i>, otherwise output directory
* @param eventHandler CLI progress reporter
* @param clientEnv environment variables in shell issuing this command
+ * @param repo the name of the external repository for which the file was fetched; used only for
+ * reporting
* @throws IllegalArgumentException on parameter badness, which should be checked beforehand
* @throws IOException if download was attempted and ended up failing
* @throws InterruptedException if this thread is being cast into oblivion
@@ -85,7 +88,8 @@
Optional<String> type,
Path output,
ExtendedEventHandler eventHandler,
- Map<String, String> clientEnv)
+ Map<String, String> clientEnv,
+ String repo)
throws IOException, InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
@@ -115,6 +119,7 @@
Path cachedDestination = repositoryCache.get(sha256, destination, KeyType.SHA256);
if (cachedDestination != null) {
// Cache hit!
+ eventHandler.post(new RepositoryCacheHitEvent(repo, sha256, urls.get(0)));
return cachedDestination;
}
} catch (IOException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
index 2457712..077f222 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
@@ -399,7 +399,8 @@
Optional.<String>absent(),
outputPath.getPath(),
env.getListener(),
- osObject.getEnvironmentVariables());
+ osObject.getEnvironmentVariables(),
+ getName());
if (executable) {
outputPath.getPath().setExecutable(true);
}
@@ -485,7 +486,8 @@
Optional.of(type),
outputPath.getPath(),
env.getListener(),
- osObject.getEnvironmentVariables());
+ osObject.getEnvironmentVariables(),
+ getName());
} catch (InterruptedException e) {
env.getListener().post(w);
throw new RepositoryFunctionException(
diff --git a/src/main/java/com/google/devtools/build/lib/repository/RepositoryFailedEvent.java b/src/main/java/com/google/devtools/build/lib/repository/RepositoryFailedEvent.java
new file mode 100644
index 0000000..61aec63
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/repository/RepositoryFailedEvent.java
@@ -0,0 +1,32 @@
+// 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.
+package com.google.devtools.build.lib.repository;
+
+import com.google.devtools.build.lib.events.ExtendedEventHandler.ProgressLike;
+
+/**
+ * Event indicating that a failure is related to a given external repository; this is in particular
+ * the case, if fetching that repository failed.
+ */
+public class RepositoryFailedEvent implements ProgressLike {
+ private final String repo;
+
+ public RepositoryFailedEvent(String repo) {
+ this.repo = repo;
+ }
+
+ public String getRepo() {
+ return repo;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
index c0b99c5..fd1e8e0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
@@ -27,6 +27,7 @@
import com.google.devtools.build.lib.repository.ExternalPackageException;
import com.google.devtools.build.lib.repository.ExternalPackageUtil;
import com.google.devtools.build.lib.repository.ExternalRuleNotFoundException;
+import com.google.devtools.build.lib.repository.RepositoryFailedEvent;
import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue.Precomputed;
@@ -219,6 +220,7 @@
} catch (SkyFunctionException e) {
// Upon an exceptional exit, the fetching of that repository is over as well.
env.getListener().post(new RepositoryFetching(repositoryName.getName(), true));
+ env.getListener().post(new RepositoryFailedEvent(repositoryName.strippedName()));
throw e;
}
if (env.valuesMissing()) {
diff --git a/src/test/shell/bazel/external_integration_test.sh b/src/test/shell/bazel/external_integration_test.sh
index 9dc16ee..52ab719 100755
--- a/src/test/shell/bazel/external_integration_test.sh
+++ b/src/test/shell/bazel/external_integration_test.sh
@@ -1410,6 +1410,99 @@
expect_log '@ext//:foo'
}
+function test_cache_hit_reported() {
+ # Verify that information about a chache hit is reported
+ # if an error happend in that repository. This information
+ # is useful as users sometimes change the URL but do not
+ # update the hash.
+ WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
+ cd "${WRKDIR}"
+ mkdir ext-1.1
+ cat > ext-1.1/BUILD <<'EOF'
+genrule(
+ name="foo",
+ outs=["foo.txt"],
+ cmd="echo Hello World > $@",
+ visibility = ["//visibility:public"],
+)
+EOF
+ zip ext-1.1.zip ext-1.1/*
+ rm -rf ext-1.1
+ sha256=$(sha256sum ext-1.1.zip | head -c 64)
+
+ rm -rf main
+ mkdir main
+ cd main
+ cat > WORKSPACE <<EOF
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+http_archive(
+ name="ext",
+ strip_prefix="ext-1.1",
+ urls=["file://${WRKDIR}/ext-1.1.zip"],
+ sha256="${sha256}",
+)
+EOF
+ cat > BUILD <<'EOF'
+genrule(
+ name = "it",
+ srcs = ["@ext//:foo"],
+ outs = ["it.txt"],
+ cmd = "cp $< $@"
+)
+EOF
+ # build to fill the cache
+ bazel build //:it || fail "Expected success"
+
+ # go offline and clean everything
+ bazel clean --expunge
+ rm "${WRKDIR}/ext-1.1.zip"
+
+ # We still should be able to build, as the file is in cache
+ bazel build //:it > "${TEST_log}" 2>&1 || fail "Expected success"
+ # As a cache hit is a perfectly normal thing, we don't expect it to be
+ # reported.
+ expect_not_log 'cache hit'
+ expect_not_log "${sha256}"
+ expect_not_log 'file:.*/ext-1.1.zip'
+
+ # Now update ext-1.1 to ext-1.2, while forgetting to update the checksum
+ ed WORKSPACE <<EOI
+%s/ext-1\.1/ext-1\.2/g
+w
+q
+EOI
+ # The build should fail, as the prefix is not found. The available prefix
+ # should be reported as well as the information that the file was taken
+ # from cache.
+ bazel build //:it > "${TEST_log}" 2>&1 && fail "Should not succeed" || :
+ expect_log 'ext-1.2.*not found'
+ expect_log 'prefixes.*ext-1.1'
+ expect_log 'cache hit'
+ expect_log "${sha256}"
+ expect_log 'file:.*/ext-1.2.zip'
+
+ # Now consider the case where no prefix is specified (and hence, the
+ # download_and_extract call will succeed), but a patch command has
+ # an assumption on a wrong path. As the fetching of the external
+ # repository will fail, we still expect being hinted at the
+ # cache hit.
+ ed WORKSPACE <<'EOI'
+/strip_prefix
+d
+a
+patch_cmds = ["cp ext-1.2/foo.txt ext-1.2/BUILD ."],
+.
+w
+q
+EOI
+ bazel build //:it > "${TEST_log}" 2>&1 && fail "Should not succeed" || :
+ expect_not_log 'prefix'
+ expect_log 'cp ext-1.2/foo.txt ext-1.2/BUILD'
+ expect_log 'cache hit'
+ expect_log "${sha256}"
+ expect_log 'file:.*/ext-1.2.zip'
+}
+
function test_distdir() {
WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
cd "${WRKDIR}"