Support disabling the repository cache

Support disabling the repository cache in cases where caching is
not necessary, but the additional disk operations are a problem.
This is achieved by interpreting --repository_cache= as a request
to disable the cache; technically, this is an incompatible change,
as currently the empty string is interpreted as "put the cache
top-level into the workspace". However, it is unlikely that this
use of the option is widely used.

Fixes #6229.

Change-Id: I082e814fc36b60b35e6e0ea6c5db8e892f46c4ef
PiperOrigin-RevId: 215365431
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 8aebb29..b6e4167 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -244,16 +244,19 @@
       repositoryCache.setHardlink(repoOptions.useHardlinks);
       skylarkRepositoryFunction.setTimeoutScaling(repoOptions.experimentalScaleTimeouts);
       if (repoOptions.experimentalRepositoryCache != null) {
-        Path repositoryCachePath;
-        if (repoOptions.experimentalRepositoryCache.isAbsolute()) {
-          repositoryCachePath = filesystem.getPath(repoOptions.experimentalRepositoryCache);
-        } else {
-          repositoryCachePath =
-              env.getBlazeWorkspace()
-                  .getWorkspace()
-                  .getRelative(repoOptions.experimentalRepositoryCache);
+        // A set but empty path indicates a request to disable the repository cache.
+        if (!repoOptions.experimentalRepositoryCache.isEmpty()) {
+          Path repositoryCachePath;
+          if (repoOptions.experimentalRepositoryCache.isAbsolute()) {
+            repositoryCachePath = filesystem.getPath(repoOptions.experimentalRepositoryCache);
+          } else {
+            repositoryCachePath =
+                env.getBlazeWorkspace()
+                    .getWorkspace()
+                    .getRelative(repoOptions.experimentalRepositoryCache);
+          }
+          repositoryCache.setRepositoryCachePath(repositoryCachePath);
         }
-        repositoryCache.setRepositoryCachePath(repositoryCachePath);
       } else {
         Path repositoryCachePath =
             env.getDirectories()
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
index 39decbc..fbe2348 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
@@ -34,16 +34,16 @@
 public class RepositoryOptions extends OptionsBase {
 
   @Option(
-    name = "repository_cache",
-    oldName = "experimental_repository_cache",
-    defaultValue = "null",
-    documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
-    effectTags = {OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION},
-    converter = OptionsUtils.PathFragmentConverter.class,
-    help =
-        "Specifies the cache location of the downloaded values obtained "
-            + "during the fetching of external repositories."
-  )
+      name = "repository_cache",
+      oldName = "experimental_repository_cache",
+      defaultValue = "null",
+      documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
+      effectTags = {OptionEffectTag.BAZEL_INTERNAL_CONFIGURATION},
+      converter = OptionsUtils.PathFragmentConverter.class,
+      help =
+          "Specifies the cache location of the downloaded values obtained "
+              + "during the fetching of external repositories. An empty string "
+              + "as argument requests the cache to be disabled.")
   public PathFragment experimentalRepositoryCache;
 
   @Option(
diff --git a/src/test/shell/bazel/external_integration_test.sh b/src/test/shell/bazel/external_integration_test.sh
index 5c93100..3cb8c96 100755
--- a/src/test/shell/bazel/external_integration_test.sh
+++ b/src/test/shell/bazel/external_integration_test.sh
@@ -1242,6 +1242,97 @@
   expect_log '@ext//:foo'
 }
 
+function test_repository_cache_default() {
+  # Verify that the repository cache is enabled by default.
+  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
+  cd "${WRKDIR}"
+  mkdir ext
+  cat > ext/BUILD <<'EOF'
+genrule(
+  name="foo",
+  outs=["foo.txt"],
+  cmd="echo Hello World > $@",
+  visibility = ["//visibility:public"],
+)
+EOF
+  TOPDIR=`pwd`
+  zip ext.zip ext/*
+  rm -rf ext
+  sha256=$(sha256sum ext.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",
+  urls=["file://${TOPDIR}/ext.zip"],
+  sha256="${sha256}",
+)
+EOF
+  # Use the external repository once to make sure it is cached.
+  bazel build '@ext//:foo' || fail "expected sucess"
+
+  # Now "go offline" and clean local resources.
+  rm -f "${TOPDIR}/ext.zip"
+  bazel clean --expunge
+
+  # Still, the file should be cached.
+  bazel build '@ext//:foo' || fail "expected success"
+}
+
+function test_cache_disable {
+  # Verify that the repository cache can be disabled.
+  WRKDIR=$(mktemp -d "${TEST_TMPDIR}/testXXXXXX")
+  cd "${WRKDIR}"
+  mkdir ext
+  cat > ext/BUILD <<'EOF'
+genrule(
+  name="foo",
+  outs=["foo.txt"],
+  cmd="echo Hello World > $@",
+  visibility = ["//visibility:public"],
+)
+EOF
+  TOPDIR=`pwd`
+  zip ext.zip ext/*
+  rm -rf ext
+  sha256=$(sha256sum ext.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",
+  urls=["file://${TOPDIR}/ext.zip"],
+  sha256="${sha256}",
+)
+EOF
+  # Use `--repository_cache` with no path to explicitly disable repository cache
+  bazel build --repository_cache= '@ext//:foo' || fail "expected sucess"
+
+  # make sure, the empty path is not interpreted relative to `pwd`; i.e., we do
+  # not expect any new directories generated in the workspace, in particular
+  # none named conent_adressable, which is the directory where the cache puts
+  # its artifacts into.
+  ls -al | grep content_addressable \
+      && fail "Should not interpret empty path as cache directly in the work space" || :
+
+  # Now "go offline" and clean local resources.
+  rm -f "${TOPDIR}/ext.zip"
+  bazel clean --expunge
+
+  # The build should fail since we are not using the repository cache, but the
+  # original file can no longer be "downloaded".
+  bazel build --repository_cache= '@ext//:foo' \
+      && fail "Should fail for lack of fetchable faile" || :
+}
+
 function test_repository_cache() {
   # Verify that --repository_cache works for query and caches soly
   # based on the predicted hash.