Expose --output_user_base to the Bazel server process

...so that it can use that path to compute other directories in the
output user base, in particular the default location for caches.
The first cache we will add is the hash-index cache for downloads
of external archives, but a spawn cache might be added later in the
output user base as well.

Change-Id: I24b1c33235c8f76ec008ecb1789163de2b2a45be
PiperOrigin-RevId: 187164275
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 7035472..0b6ec47 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -494,6 +494,8 @@
   result.push_back("--connect_timeout_secs=" +
                    ToString(globals->options->connect_timeout_secs));
 
+  result.push_back("--output_user_root=" +
+                   blaze::ConvertPath(globals->options->output_user_root));
   result.push_back("--install_base=" +
                    blaze::ConvertPath(globals->options->install_base));
   result.push_back("--install_md5=" + globals->install_md5);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ServerDirectories.java b/src/main/java/com/google/devtools/build/lib/analysis/ServerDirectories.java
index 0e34cf3..9b25f7a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ServerDirectories.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ServerDirectories.java
@@ -26,17 +26,16 @@
 import javax.annotation.Nullable;
 
 /**
- * Represents the server install directory, which contains the Bazel installation and embedded
- * binaries.
- *
- * <p>The <code>installBase</code> is the directory where the Blaze binary has been installed. The
- * <code>outputBase</code> is the directory below which Blaze puts all its state.
+ * Represents the relevant directories for the server: the location of the embedded binaries
+ * and the output directories.
  */
 @AutoCodec
 @Immutable
 public final class ServerDirectories {
   public static final ObjectCodec<ServerDirectories> CODEC = new ServerDirectories_AutoCodec();
 
+  /** Top-level user output directory; used, e.g., as default location for caches. */
+  private final Path outputUserRoot;
   /** Where Blaze gets unpacked. */
   private final Path installBase;
   /** The content hash of everything in installBase. */
@@ -44,22 +43,25 @@
   /** The root of the temp and output trees. */
   private final Path outputBase;
 
-  public ServerDirectories(Path installBase, Path outputBase, @Nullable String installMD5) {
+  public ServerDirectories(
+      Path installBase, Path outputBase, Path outputUserRoot, @Nullable String installMD5) {
     this(
         installBase,
         outputBase,
+        outputUserRoot,
         Strings.isNullOrEmpty(installMD5) ? null : checkMD5(HashCode.fromString(installMD5)));
   }
 
   @AutoCodec.Instantiator
-  ServerDirectories(Path installBase, Path outputBase, HashCode installMD5) {
+  ServerDirectories(Path installBase, Path outputBase, Path outputUserRoot, HashCode installMD5) {
+    this.outputUserRoot = outputUserRoot;
     this.installBase = installBase;
     this.outputBase = outputBase;
     this.installMD5 = installMD5;
   }
 
-  public ServerDirectories(Path installBase, Path outputBase) {
-    this(installBase, outputBase, (HashCode) null);
+  public ServerDirectories(Path installBase, Path outputBase, Path outputUserRoot) {
+    this(installBase, outputBase, outputUserRoot, (HashCode) null);
   }
 
   private static HashCode checkMD5(HashCode hash) {
@@ -81,6 +83,13 @@
     return outputBase;
   }
 
+  /**
+   * Returns the root directory for user output. In particular default caches will be located here.
+   */
+  public Path getOutputUserRoot() {
+    return outputUserRoot;
+  }
+
   /** Returns the installed embedded binaries directory, under the shared installBase location. */
   public Path getEmbeddedBinariesRoot() {
     return installBase.getChild("_embedded_binaries");
@@ -96,7 +105,7 @@
 
   @Override
   public int hashCode() {
-    return Objects.hash(installBase, installMD5, outputBase);
+    return Objects.hash(installBase, installMD5, outputBase, outputUserRoot);
   }
 
   @Override
@@ -110,6 +119,7 @@
     ServerDirectories that = (ServerDirectories) obj;
     return this.installBase.equals(that.installBase)
         && Objects.equals(this.installMD5, that.installMD5)
-        && this.outputBase.equals(that.outputBase);
+        && this.outputBase.equals(that.outputBase)
+        && this.outputUserRoot.equals(that.outputUserRoot);
   }
 }
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 a27b9b6..df1692f 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
@@ -977,17 +977,22 @@
     String productName = startupOptions.productName.toLowerCase(Locale.US);
 
     PathFragment workspaceDirectory = startupOptions.workspaceDirectory;
+    PathFragment outputUserRoot = startupOptions.outputUserRoot;
     PathFragment installBase = startupOptions.installBase;
     PathFragment outputBase = startupOptions.outputBase;
 
     maybeForceJNIByGettingPid(installBase); // Must be before first use of JNI.
 
-    // From the point of view of the Java program --install_base and --output_base
-    // are mandatory options, despite the comment in their declarations.
+    // From the point of view of the Java program --install_base, --output_base, and
+    // --output_user_root are mandatory options, despite the comment in their declarations.
     if (installBase == null || !installBase.isAbsolute()) { // (includes "" default case)
       throw new IllegalArgumentException(
           "Bad --install_base option specified: '" + installBase + "'");
     }
+    if (outputUserRoot != null && !outputUserRoot.isAbsolute()) { // (includes "" default case)
+      throw new IllegalArgumentException(
+          "Bad --output_user_root option specified: '" + outputUserRoot + "'");
+    }
     if (outputBase != null && !outputBase.isAbsolute()) { // (includes "" default case)
       throw new IllegalArgumentException(
           "Bad --output_base option specified: '" + outputBase + "'");
@@ -1009,6 +1014,7 @@
     Path.setFileSystemForSerialization(fs);
     SubprocessBuilder.setSubprocessFactory(subprocessFactoryImplementation());
 
+    Path outputUserRootPath = fs.getPath(outputUserRoot);
     Path installBasePath = fs.getPath(installBase);
     Path outputBasePath = fs.getPath(outputBase);
     Path workspaceDirectoryPath = null;
@@ -1017,7 +1023,8 @@
     }
 
     ServerDirectories serverDirectories =
-        new ServerDirectories(installBasePath, outputBasePath, startupOptions.installMD5);
+        new ServerDirectories(
+            installBasePath, outputBasePath, outputUserRootPath, startupOptions.installMD5);
     Clock clock = BlazeClock.instance();
     BlazeRuntime.Builder runtimeBuilder =
         new BlazeRuntime.Builder()
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 8cccbac..e8eafbc 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
@@ -128,10 +128,6 @@
   )
   public PathFragment outputBase;
 
-  /* Note: This option is only used by the C++ client, never by the Java server.
-   * It is included here to make sure that the option is documented in the help
-   * output, which is auto-generated by Java code.
-   */
   @Option(
     name = "output_user_root",
     defaultValue = "null", // NOTE: purely decorative!  See class docstring.
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/packages/AbstractPackageLoader.java b/src/main/java/com/google/devtools/build/lib/skyframe/packages/AbstractPackageLoader.java
index 850687d..5613c87 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/packages/AbstractPackageLoader.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/packages/AbstractPackageLoader.java
@@ -143,7 +143,8 @@
       // TODO(nharmata): Refactor WorkspaceFileFunction to make this a non-issue.
       Path devNull = workspaceDir.getFileSystem().getPath("/dev/null");
       directories =
-          new BlazeDirectories(new ServerDirectories(devNull, devNull), workspaceDir, "blaze");
+          new BlazeDirectories(
+              new ServerDirectories(devNull, devNull, devNull), workspaceDir, "blaze");
     }
 
     public Builder setRuleClassProvider(RuleClassProvider ruleClassProvider) {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BlazeDirectoriesTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BlazeDirectoriesTest.java
index 06ffcd0..930a2a5 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/BlazeDirectoriesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BlazeDirectoriesTest.java
@@ -33,20 +33,24 @@
     FileSystem fs = scratch.getFileSystem();
     Path installBase = fs.getPath("/my/install");
     Path outputBase = fs.getPath("/my/output");
+    Path userRoot = fs.getPath("/home/user/root");
     Path workspace = fs.getPath("/my/ws");
     BlazeDirectories directories =
-        new BlazeDirectories(new ServerDirectories(installBase, outputBase), workspace, "foo");
+        new BlazeDirectories(
+            new ServerDirectories(installBase, outputBase, userRoot), workspace, "foo");
     assertThat(outputBase.getRelative("execroot/ws")).isEqualTo(directories.getExecRoot());
 
     workspace = null;
     directories =
-        new BlazeDirectories(new ServerDirectories(installBase, outputBase), workspace, "foo");
+        new BlazeDirectories(
+            new ServerDirectories(installBase, outputBase, userRoot), workspace, "foo");
     assertThat(outputBase.getRelative("execroot/" + BlazeDirectories.DEFAULT_EXEC_ROOT))
         .isEqualTo(directories.getExecRoot());
 
     workspace = fs.getPath("/");
     directories =
-        new BlazeDirectories(new ServerDirectories(installBase, outputBase), workspace, "foo");
+        new BlazeDirectories(
+            new ServerDirectories(installBase, outputBase, userRoot), workspace, "foo");
     assertThat(outputBase.getRelative("execroot/" + BlazeDirectories.DEFAULT_EXEC_ROOT))
         .isEqualTo(directories.getExecRoot());
   }
@@ -58,20 +62,23 @@
             new BlazeDirectories(
                 new ServerDirectories(
                     FsUtils.TEST_FILESYSTEM.getPath("/install_base"),
-                    FsUtils.TEST_FILESYSTEM.getPath("/output_base")),
+                    FsUtils.TEST_FILESYSTEM.getPath("/output_base"),
+                    FsUtils.TEST_FILESYSTEM.getPath("/user_root")),
                 FsUtils.TEST_FILESYSTEM.getPath("/workspace"),
                 "Blaze"),
             new BlazeDirectories(
                 new ServerDirectories(
                     FsUtils.TEST_FILESYSTEM.getPath("/install_base"),
                     FsUtils.TEST_FILESYSTEM.getPath("/output_base"),
+                    FsUtils.TEST_FILESYSTEM.getPath("/user_root"),
                     "1234abcd1234abcd1234abcd1234abcd"),
                 FsUtils.TEST_FILESYSTEM.getPath("/workspace"),
                 "Blaze"),
             new BlazeDirectories(
                 new ServerDirectories(
                     FsUtils.TEST_FILESYSTEM.getPath("/install_base"),
-                    FsUtils.TEST_FILESYSTEM.getPath("/output_base")),
+                    FsUtils.TEST_FILESYSTEM.getPath("/output_base"),
+                    FsUtils.TEST_FILESYSTEM.getPath("/user_root")),
                 FsUtils.TEST_FILESYSTEM.getPath("/workspace"),
                 "Bazel"))
         .addDependency(FileSystem.class, FsUtils.TEST_FILESYSTEM)
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java
index c38437b..470909c 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionActionTest.java
@@ -71,7 +71,10 @@
     substitutions.add(Substitution.of("%value%", "bar"));
     directories =
         new BlazeDirectories(
-            new ServerDirectories(scratch.resolve("/install"), scratch.resolve("/base")),
+            new ServerDirectories(
+                scratch.resolve("/install"),
+                scratch.resolve("/base"),
+                scratch.resolve("/userRoot")),
             scratch.resolve("/workspace"),
             "mock-product-name");
     binTools = BinTools.empty(directories);
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index 279675f..6e1c063 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -154,7 +154,7 @@
             BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY);
     directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     workspaceStatusActionFactory =
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index b749ec4..e3f69f5 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -208,7 +208,7 @@
     analysisMock = getAnalysisMock();
     directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     actionKeyContext = new ActionKeyContext();
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
index b7dd9ba..ab725a9 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
@@ -106,7 +106,7 @@
     final PackageFactory pkgFactory;
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     pkgFactory =
diff --git a/src/test/java/com/google/devtools/build/lib/exec/BlazeExecutorTest.java b/src/test/java/com/google/devtools/build/lib/exec/BlazeExecutorTest.java
index f520cc4..ec724b2 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/BlazeExecutorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/BlazeExecutorTest.java
@@ -50,7 +50,10 @@
     fileSystem = new InMemoryFileSystem();
     directories =
         new BlazeDirectories(
-            new ServerDirectories(fileSystem.getPath("/install"), fileSystem.getPath("/base")),
+            new ServerDirectories(
+                fileSystem.getPath("/install"),
+                fileSystem.getPath("/base"),
+                fileSystem.getPath("/root")),
             fileSystem.getPath("/workspace"),
             "mock-product-name");
     binTools = BinTools.empty(directories);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java b/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
index eb35dd6..90578ed 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/PackageLoadingTestCase.java
@@ -92,7 +92,7 @@
     }
     directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             loadingMock.getProductName());
     packageFactory =
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/BuildFileModificationTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/BuildFileModificationTest.java
index aaf0c5f..1f3354e 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/BuildFileModificationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/BuildFileModificationTest.java
@@ -75,7 +75,7 @@
     ruleClassProvider = analysisMock.createRuleClassProvider();
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     skyframeExecutor =
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/IncrementalLoadingTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/IncrementalLoadingTest.java
index d4373b4..5692301 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/IncrementalLoadingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/IncrementalLoadingTest.java
@@ -466,7 +466,8 @@
       LoadingMock loadingMock = LoadingMock.get();
       BlazeDirectories directories =
           new BlazeDirectories(
-              new ServerDirectories(fs.getPath("/install"), fs.getPath("/output")),
+              new ServerDirectories(
+                  fs.getPath("/install"), fs.getPath("/output"), fs.getPath("/userRoot")),
               workspace,
               loadingMock.getProductName());
       skyframeExecutor =
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
index 6814b89..cf7c61b 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/LoadingPhaseRunnerTest.java
@@ -661,7 +661,8 @@
       analysisMock.setupMockClient(mockToolsConfig);
       directories =
           new BlazeDirectories(
-              new ServerDirectories(fs.getPath("/install"), fs.getPath("/output")),
+              new ServerDirectories(
+                  fs.getPath("/install"), fs.getPath("/output"), fs.getPath("/userRoot")),
               workspace,
               analysisMock.getProductName());
       FileSystemUtils.deleteTree(workspace.getRelative("base"));
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/PackageCacheTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/PackageCacheTest.java
index 9134e4d..b934efe 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/PackageCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/PackageCacheTest.java
@@ -81,7 +81,7 @@
     ruleClassProvider = analysisMock.createRuleClassProvider();
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase),
+            new ServerDirectories(outputBase, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     PackageFactory.BuilderForTesting packageFactoryBuilder =
diff --git a/src/test/java/com/google/devtools/build/lib/repository/ExternalPackageUtilTest.java b/src/test/java/com/google/devtools/build/lib/repository/ExternalPackageUtilTest.java
index 02df4c6..44808de 100644
--- a/src/test/java/com/google/devtools/build/lib/repository/ExternalPackageUtilTest.java
+++ b/src/test/java/com/google/devtools/build/lib/repository/ExternalPackageUtilTest.java
@@ -93,7 +93,7 @@
         new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, rootDirectory),
             rootDirectory,
             analysisMock.getProductName());
     ExternalFilesHelper externalFilesHelper =
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
index 0547078..f3ab343 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
@@ -75,7 +75,8 @@
   public void setupDelegator() throws Exception {
     Path root = scratch.dir("/outputbase");
     BlazeDirectories directories =
-        new BlazeDirectories(new ServerDirectories(root, root), root, TestConstants.PRODUCT_NAME);
+        new BlazeDirectories(
+            new ServerDirectories(root, root, root), root, TestConstants.PRODUCT_NAME);
     delegatorFunction =
         new RepositoryDelegatorFunction(
             ImmutableMap.of(), null, new AtomicBoolean(true), ImmutableMap::of, directories);
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
index 0d9e7ba..32a0879 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherRcoptionsTest.java
@@ -132,7 +132,10 @@
   public final void initializeRuntime() throws Exception {
     String productName = TestConstants.PRODUCT_NAME;
     ServerDirectories serverDirectories =
-        new ServerDirectories(scratch.dir("install_base"), scratch.dir("output_base"));
+        new ServerDirectories(
+            scratch.dir("install_base"),
+            scratch.dir("output_base"),
+            scratch.dir("user_output_root"));
     this.runtime =
         new BlazeRuntime.Builder()
             .setFileSystem(scratch.getFileSystem())
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
index ab601c2..2efebed 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
@@ -64,7 +64,8 @@
     parser.setAllowResidue(true);
     String productName = TestConstants.PRODUCT_NAME;
     ServerDirectories serverDirectories =
-        new ServerDirectories(scratch.dir("install_base"), scratch.dir("output_base"));
+        new ServerDirectories(
+            scratch.dir("install_base"), scratch.dir("output_base"), scratch.dir("user_root"));
     this.runtime =
         new BlazeRuntime.Builder()
             .setFileSystem(scratch.getFileSystem())
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
index 023ce84..3c0af6c 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
@@ -627,7 +627,9 @@
     BuildConfiguration configuration =
         new BuildConfiguration(
             new BlazeDirectories(
-                new ServerDirectories(outputBase, outputBase), rootDirectory, "productName"),
+                new ServerDirectories(outputBase, outputBase, outputBase),
+                rootDirectory,
+                "productName"),
             ImmutableMap
                 .<Class<? extends BuildConfiguration.Fragment>, BuildConfiguration.Fragment>of(),
             BuildOptions.of(
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/CommandInterruptionTest.java b/src/test/java/com/google/devtools/build/lib/runtime/CommandInterruptionTest.java
index adf8a56..de38e15 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/CommandInterruptionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/CommandInterruptionTest.java
@@ -348,7 +348,8 @@
     isTestShuttingDown = new AtomicBoolean(false);
     String productName = TestConstants.PRODUCT_NAME;
     ServerDirectories serverDirectories =
-        new ServerDirectories(scratch.dir("install"), scratch.dir("output"));
+        new ServerDirectories(
+            scratch.dir("install"), scratch.dir("output"), scratch.dir("user_root"));
     BlazeRuntime runtime =
         new BlazeRuntime.Builder()
             .setFileSystem(scratch.getFileSystem())
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/commands/CleanCommandRecommendsAsyncTest.java b/src/test/java/com/google/devtools/build/lib/runtime/commands/CleanCommandRecommendsAsyncTest.java
index 7c0006c..4a45e7d 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/commands/CleanCommandRecommendsAsyncTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/commands/CleanCommandRecommendsAsyncTest.java
@@ -79,7 +79,8 @@
     String productName = TestConstants.PRODUCT_NAME;
     Scratch scratch = new Scratch();
     ServerDirectories serverDirectories =
-        new ServerDirectories(scratch.dir("install"), scratch.dir("output"));
+        new ServerDirectories(
+            scratch.dir("install"), scratch.dir("output"), scratch.dir("user_root"));
     BlazeRuntime runtime =
         new BlazeRuntime.Builder()
             .setFileSystem(scratch.getFileSystem())
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
index 036becf..3b83fa8 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
@@ -77,7 +77,8 @@
                 ImmutableList.of(Root.fromPath(root)),
                 BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
     BlazeDirectories directories =
-        new BlazeDirectories(new ServerDirectories(root, root), root, TestConstants.PRODUCT_NAME);
+        new BlazeDirectories(
+            new ServerDirectories(root, root, root), root, TestConstants.PRODUCT_NAME);
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
         pkgLocator,
         ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
index b693360..7563137 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
@@ -83,7 +83,7 @@
     deletedPackages = new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, outputBase),
             rootDirectory,
             analysisMock.getProductName());
     ExternalFilesHelper externalFilesHelper =
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
index c78eab0..6a8543f 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
@@ -129,7 +129,7 @@
     AtomicReference<PathPackageLocator> pkgLocatorRef = new AtomicReference<>(pkgLocator);
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(pkgRoot.asPath(), outputBase),
+            new ServerDirectories(pkgRoot.asPath(), outputBase, outputBase),
             pkgRoot.asPath(),
             TestConstants.PRODUCT_NAME);
     ExternalFilesHelper externalFilesHelper =
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
index 6fb3cd8..bd1a1a0 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
@@ -94,7 +94,7 @@
             pkgLocator,
             ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
             new BlazeDirectories(
-                new ServerDirectories(outputBase, outputBase),
+                new ServerDirectories(outputBase, outputBase, outputBase),
                 rootDirectory,
                 TestConstants.PRODUCT_NAME));
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index af372ae..27a8fd0 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -109,7 +109,7 @@
                 BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(pkgRoot, pkgRoot), pkgRoot, TestConstants.PRODUCT_NAME);
+            new ServerDirectories(pkgRoot, pkgRoot, pkgRoot), pkgRoot, TestConstants.PRODUCT_NAME);
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
         pkgLocator, ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS, directories);
     skyFunctions.put(SkyFunctions.FILE_STATE, new FileStateFunction(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
index 546ef61..02d5a0e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
@@ -132,7 +132,8 @@
     AtomicReference<ImmutableSet<PackageIdentifier>> deletedPackages =
         new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
-        new BlazeDirectories(new ServerDirectories(root, root), root, TestConstants.PRODUCT_NAME);
+        new BlazeDirectories(
+            new ServerDirectories(root, root, root), root, TestConstants.PRODUCT_NAME);
     ExternalFilesHelper externalFilesHelper =
         ExternalFilesHelper.createForTesting(
             pkgLocator,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunctionTest.java
index e2c8bb5..b6eec17 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunctionTest.java
@@ -75,7 +75,7 @@
     deletedPackages = new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, rootDirectory),
             rootDirectory,
             analysisMock.getProductName());
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
index cccb3ab..0613eed 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
@@ -100,7 +100,7 @@
     deletedPackages = new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, rootDirectory),
             rootDirectory,
             analysisMock.getProductName());
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
index fa745c6..4c56419 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
@@ -67,7 +67,10 @@
   public void setUp() throws Exception {
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(getScratch().dir("/install"), getScratch().dir("/output")),
+            new ServerDirectories(
+                getScratch().dir("/install"),
+                getScratch().dir("/output"),
+                getScratch().dir("/user_root")),
             rootDirectory,
             AnalysisMock.get().getProductName());
     skyframeExecutor =
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
index ce40ea7..0da51f8 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
@@ -101,7 +101,7 @@
         new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, rootDirectory),
             rootDirectory,
             analysisMock.getProductName());
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index d2aca56..6fc7684 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -169,7 +169,7 @@
     AtomicReference<TimestampGranularityMonitor> tsgmRef = new AtomicReference<>(tsgm);
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(rootDirectory, outputBase),
+            new ServerDirectories(rootDirectory, outputBase, outputBase),
             rootDirectory,
             TestConstants.PRODUCT_NAME);
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
diff --git a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
index d2502f8..37fd188 100644
--- a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
@@ -110,7 +110,9 @@
 
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(outputBase, outputBase), workspaceDir, "mock-product-name");
+            new ServerDirectories(outputBase, outputBase, outputBase),
+            workspaceDir,
+            "mock-product-name");
     // This call implicitly symlinks the integration bin tools into the exec root.
     IntegrationMock.get()
         .getIntegrationBinTools(fileSystem, directories, TestConstants.WORKSPACE_NAME);