Remove components field from ArtifactRoot. If we guarantee that each component is a plain path segment (not made of multiple segment) then it is reconstructible from the execPath.

Also remove the only-used-in-tests #asDerivedRoot(Path, Path) creation method (woof).

Ninja TargetCompleteEvents are slightly changed, in that the components list may now be a list with one item per segment, versus the previous behavior of one item with all the segments.

PiperOrigin-RevId: 303754015
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
index 3316026..6eba8cf 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
@@ -14,12 +14,10 @@
 
 package com.google.devtools.build.lib.actions;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Interner;
 import com.google.common.collect.Interners;
-import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skylarkbuildapi.FileRootApi;
@@ -29,7 +27,6 @@
 import com.google.devtools.build.lib.vfs.Root;
 import java.io.Serializable;
 import java.util.Objects;
-import javax.annotation.Nullable;
 
 /**
  * A root for an artifact. The roots are the directories containing artifacts, and they are mapped
@@ -61,11 +58,6 @@
     return new ArtifactRoot(root, PathFragment.EMPTY_FRAGMENT, RootType.Source);
   }
 
-  public static ArtifactRoot asExternalSourceRoot(Root root) {
-    return new ArtifactRoot(
-        root, LabelConstants.EXPERIMENTAL_EXTERNAL_PATH_PREFIX, RootType.Source);
-  }
-
   /**
    * Constructs an ArtifactRoot given the output prefixes. (eg, "bin"), and (eg, "testlogs")
    * relative to the execRoot.
@@ -73,29 +65,18 @@
    * <p>Be careful with this method - all derived roots must be registered with the artifact factory
    * before the analysis phase.
    */
-  public static ArtifactRoot asDerivedRoot(Path execRoot, PathFragment... prefixes) {
+  public static ArtifactRoot asDerivedRoot(Path execRoot, String... prefixes) {
     Path root = execRoot;
-    for (PathFragment prefix : prefixes) {
-      root = root.getRelative(prefix);
+    for (String prefix : prefixes) {
+      // Tests can have empty segments here, be gentle to them.
+      if (!prefix.isEmpty()) {
+        root = root.getChild(prefix);
+      }
     }
     Preconditions.checkArgument(root.startsWith(execRoot));
     Preconditions.checkArgument(!root.equals(execRoot));
     PathFragment execPath = root.relativeTo(execRoot);
-    return INTERNER.intern(
-        new ArtifactRoot(
-            Root.fromPath(root), execPath, RootType.Output, ImmutableList.copyOf(prefixes)));
-  }
-
-  /**
-   * Returns the given path as a derived root, relative to the given exec root. The root must be a
-   * proper sub-directory of the exec root (i.e. not equal). Neither may be {@code null}.
-   *
-   * <p>Be careful with this method - all derived roots must be registered with the artifact factory
-   * before the analysis phase.
-   */
-  @VisibleForTesting
-  public static ArtifactRoot asDerivedRoot(Path execRoot, Path root) {
-    return asDerivedRoot(execRoot, root.relativeTo(execRoot));
+    return INTERNER.intern(new ArtifactRoot(Root.fromPath(root), execPath, RootType.Output));
   }
 
   public static ArtifactRoot middlemanRoot(Path execRoot, Path outputDir) {
@@ -108,9 +89,8 @@
 
   @AutoCodec.VisibleForSerialization
   @AutoCodec.Instantiator
-  static ArtifactRoot createForSerialization(
-      Root root, PathFragment execPath, RootType rootType, ImmutableList<PathFragment> components) {
-    return INTERNER.intern(new ArtifactRoot(root, execPath, rootType, components));
+  static ArtifactRoot createForSerialization(Root root, PathFragment execPath, RootType rootType) {
+    return INTERNER.intern(new ArtifactRoot(root, execPath, rootType));
   }
 
   @AutoCodec.VisibleForSerialization
@@ -123,18 +103,11 @@
   private final Root root;
   private final PathFragment execPath;
   private final RootType rootType;
-  @Nullable private final ImmutableList<PathFragment> components;
 
-  private ArtifactRoot(
-      Root root, PathFragment execPath, RootType rootType, ImmutableList<PathFragment> components) {
+  private ArtifactRoot(Root root, PathFragment execPath, RootType rootType) {
     this.root = Preconditions.checkNotNull(root);
     this.execPath = execPath;
     this.rootType = rootType;
-    this.components = components;
-  }
-
-  private ArtifactRoot(Root root, PathFragment execPath, RootType rootType) {
-    this(root, execPath, rootType, /* components= */ null);
   }
 
   public Root getRoot() {
@@ -154,8 +127,8 @@
     return getExecPath().getPathString();
   }
 
-  public ImmutableList<PathFragment> getComponents() {
-    return components;
+  public ImmutableList<String> getComponents() {
+    return execPath.getSegments();
   }
 
   public boolean isSourceRoot() {
@@ -173,7 +146,7 @@
 
   @Override
   public int hashCode() {
-    return Objects.hash(root, execPath, rootType, components);
+    return Objects.hash(root, execPath, rootType);
   }
 
   @Override
@@ -185,10 +158,7 @@
       return false;
     }
     ArtifactRoot r = (ArtifactRoot) o;
-    return root.equals(r.root)
-        && execPath.equals(r.execPath)
-        && rootType == r.rootType
-        && Objects.equals(components, r.components);
+    return root.equals(r.root) && execPath.equals(r.execPath) && rootType == r.rootType;
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
index 52ccd0b..3c90397 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
@@ -22,7 +22,6 @@
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.util.StringCanonicalizer;
 import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.Objects;
 import javax.annotation.Nullable;
 
@@ -48,7 +47,7 @@
 @AutoCodec
 @Immutable
 public final class BlazeDirectories {
-  // Include directory name, relative to execRoot/blaze-out/configuration.
+  // Include directory name, relative to execRoot/blaze-out/configuration. Only one segment allowed.
   public static final String RELATIVE_INCLUDE_DIR = StringCanonicalizer.intern("include");
   @VisibleForTesting static final String DEFAULT_EXEC_ROOT = "default-exec-root";
 
@@ -207,7 +206,7 @@
    */
   public ArtifactRoot getBuildDataDirectory(String workspaceName) {
     return ArtifactRoot.asDerivedRoot(
-        getExecRoot(workspaceName), PathFragment.create(getRelativeOutputPath(productName)));
+        getExecRoot(workspaceName), getRelativeOutputPath(productName));
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
index b99b6e6..f14f3ab 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
@@ -333,10 +333,7 @@
                 name == null
                     ? artifact.getRootRelativePath().getRelative(relPath).getPathString()
                     : name);
-    if (artifact.getRoot().getComponents() != null) {
-      builder.addAllPathPrefix(
-          Iterables.transform(artifact.getRoot().getComponents(), PathFragment::getPathString));
-    }
+    builder.addAllPathPrefix(artifact.getRoot().getComponents());
     return builder;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/OutputDirectories.java b/src/main/java/com/google/devtools/build/lib/analysis/config/OutputDirectories.java
index f7418e6..4594476 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/OutputDirectories.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/OutputDirectories.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
@@ -79,7 +80,7 @@
     INCLUDE(BlazeDirectories.RELATIVE_INCLUDE_DIR),
     OUTPUT(false);
 
-    private final PathFragment nameFragment;
+    private final String nameFragment;
     private final boolean middleman;
 
     /**
@@ -89,12 +90,14 @@
      * @param isMiddleman whether the root should be a middleman root or a "normal" derived root.
      */
     OutputDirectory(boolean isMiddleman) {
-      this.nameFragment = PathFragment.EMPTY_FRAGMENT;
+      this.nameFragment = "";
       this.middleman = isMiddleman;
     }
 
     OutputDirectory(String name) {
-      this.nameFragment = PathFragment.create(name);
+      this.nameFragment = name;
+      // Must be a legal basename for root: no segments allowed.
+      FileSystemUtils.checkBaseName(nameFragment);
       this.middleman = false;
     }
 
@@ -111,10 +114,7 @@
       }
       // e.g., [[execroot/repo1]/bazel-out/config/bin]
       return ArtifactRoot.asDerivedRoot(
-          execRoot,
-          PathFragment.create(directories.getRelativeOutputPath()),
-          PathFragment.create(outputDirName),
-          nameFragment);
+          execRoot, directories.getRelativeOutputPath(), outputDirName, nameFragment);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
index 3f581f9..105d15c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
@@ -70,7 +70,8 @@
         Preconditions.checkNotNull(ruleContext.getConfiguration())
             .getDirectories()
             .getExecRoot(ruleContext.getWorkspaceName());
-    this.derivedOutputRoot = ArtifactRoot.asDerivedRoot(execRoot, outputRootPath);
+    this.derivedOutputRoot =
+        ArtifactRoot.asDerivedRoot(execRoot, outputRootPath.getSegments().toArray(new String[0]));
   }
 
   DerivedArtifact createOutputArtifact(PathFragment pathRelativeToWorkingDirectory)
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
index 2be4d90..c5f2e7f 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
@@ -75,7 +75,7 @@
     clientRoot = Root.fromPath(scratch.dir("/client/workspace"));
     clientRoRoot = Root.fromPath(scratch.dir("/client/RO/workspace"));
     alienRoot = Root.fromPath(scratch.dir("/client/workspace"));
-    outRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out-root/x/bin"));
+    outRoot = ArtifactRoot.asDerivedRoot(execRoot, "out-root", "x", "bin");
 
     fooPath = PathFragment.create("foo");
     fooPackage = PackageIdentifier.createInMainRepo(fooPath);
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
index 2c19585..6ecb16c 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
@@ -50,7 +50,7 @@
   public void testAsDerivedRoot() throws IOException {
     Path execRoot = scratch.dir("/exec");
     Path rootDir = scratch.dir("/exec/root");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, rootDir);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, "root");
     assertThat(root.isSourceRoot()).isFalse();
     assertThat(root.getExecPath()).isEqualTo(PathFragment.create("root"));
     assertThat(root.getRoot()).isEqualTo(Root.fromPath(rootDir));
@@ -58,18 +58,23 @@
   }
 
   @Test
-  public void testBadAsDerivedRoot() throws IOException {
+  public void emptyExecPathNotOk() throws IOException {
     Path execRoot = scratch.dir("/exec");
-    Path outsideDir = scratch.dir("/not_exec");
-    assertThrows(
-        IllegalArgumentException.class, () -> ArtifactRoot.asDerivedRoot(execRoot, outsideDir));
+    assertThrows(IllegalArgumentException.class, () -> ArtifactRoot.asDerivedRoot(execRoot, ""));
   }
 
   @Test
-  public void testBadAsDerivedRootSameForBoth() throws IOException {
+  public void emptySegmentOk() throws IOException {
+    Path execRoot = scratch.dir("/exec");
+    assertThat(ArtifactRoot.asDerivedRoot(execRoot, "", "suffix", ""))
+        .isEqualTo(ArtifactRoot.asDerivedRoot(execRoot, "suffix"));
+  }
+
+  @Test
+  public void segmentsAreSingles() throws IOException {
     Path execRoot = scratch.dir("/exec");
     assertThrows(
-        IllegalArgumentException.class, () -> ArtifactRoot.asDerivedRoot(execRoot, execRoot));
+        IllegalArgumentException.class, () -> ArtifactRoot.asDerivedRoot(execRoot, "suffix/"));
   }
 
   @Test
@@ -79,22 +84,24 @@
   }
 
   @Test
-  public void testBadAsDerivedRootNullExecRoot() throws IOException {
-    Path execRoot = scratch.dir("/exec");
-    assertThrows(NullPointerException.class, () -> ArtifactRoot.asDerivedRoot(null, execRoot));
+  public void testBadAsDerivedRootNullExecRoot() {
+    assertThrows(NullPointerException.class, () -> ArtifactRoot.asDerivedRoot(null, "exec"));
   }
 
   @Test
   public void testEquals() throws IOException {
     Path execRoot = scratch.dir("/exec");
-    Path rootDir = scratch.dir("/exec/root");
+    String rootSegment = "root";
+    Path rootDir = execRoot.getChild(rootSegment);
+    rootDir.createDirectoryAndParents();
     Path otherRootDir = scratch.dir("/");
     Path sourceDir = scratch.dir("/source");
-    ArtifactRoot rootA = ArtifactRoot.asDerivedRoot(execRoot, rootDir);
-    assertEqualsAndHashCode(true, rootA, ArtifactRoot.asDerivedRoot(execRoot, rootDir));
+    ArtifactRoot rootA = ArtifactRoot.asDerivedRoot(execRoot, rootSegment);
+    assertEqualsAndHashCode(true, rootA, ArtifactRoot.asDerivedRoot(execRoot, rootSegment));
     assertEqualsAndHashCode(false, rootA, ArtifactRoot.asSourceRoot(Root.fromPath(sourceDir)));
     assertEqualsAndHashCode(false, rootA, ArtifactRoot.asSourceRoot(Root.fromPath(rootDir)));
-    assertEqualsAndHashCode(false, rootA, ArtifactRoot.asDerivedRoot(otherRootDir, rootDir));
+    assertEqualsAndHashCode(
+        false, rootA, ArtifactRoot.asDerivedRoot(otherRootDir, "exec", rootSegment));
   }
 
   public void assertEqualsAndHashCode(boolean expected, Object a, Object b) {
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index da96ea8..070896f 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -58,18 +58,17 @@
   public final void setRootDir() throws Exception  {
     scratch = new Scratch();
     execDir = scratch.dir("/exec");
-    rootDir = ArtifactRoot.asDerivedRoot(execDir, scratch.dir("/exec/root"));
+    rootDir = ArtifactRoot.asDerivedRoot(execDir, "root");
   }
 
   @Test
   public void testConstruction_badRootDir() throws IOException {
     Path f1 = scratch.file("/exec/dir/file.ext");
-    Path bogusDir = scratch.file("/exec/dir/bogus");
     assertThrows(
         IllegalArgumentException.class,
         () ->
             ActionsTestUtil.createArtifactWithExecPath(
-                    ArtifactRoot.asDerivedRoot(execDir, bogusDir), f1.relativeTo(execDir))
+                    ArtifactRoot.asDerivedRoot(execDir, "bogus"), f1.relativeTo(execDir))
                 .getRootRelativePath());
   }
 
@@ -230,9 +229,7 @@
   @Test
   public void testToDetailString() throws Exception {
     Path execRoot = scratch.getFileSystem().getPath("/execroot/workspace");
-    Artifact a =
-        ActionsTestUtil.createArtifact(
-            ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/execroot/workspace/b")), "c");
+    Artifact a = ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, "b"), "c");
     assertThat(a.toDetailString()).isEqualTo("[[<execution_root>]b]c");
   }
 
@@ -243,8 +240,7 @@
         IllegalArgumentException.class,
         () ->
             ActionsTestUtil.createArtifactWithExecPath(
-                    ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/a")),
-                    PathFragment.create("c"))
+                    ArtifactRoot.asDerivedRoot(execRoot, "a"), PathFragment.create("c"))
                 .getRootRelativePath());
   }
 
@@ -254,7 +250,7 @@
         (Artifact.DerivedArtifact) ActionsTestUtil.createArtifact(rootDir, "src/a");
     artifact.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA);
     ArtifactRoot anotherRoot =
-        ArtifactRoot.asDerivedRoot(scratch.getFileSystem().getPath("/"), scratch.dir("/src"));
+        ArtifactRoot.asDerivedRoot(scratch.getFileSystem().getPath("/"), "src");
     Artifact.DerivedArtifact anotherArtifact =
         new Artifact.DerivedArtifact(
             anotherRoot,
@@ -345,8 +341,7 @@
         .isTrue();
     assertThat(
             ActionsTestUtil.createArtifact(
-                    ArtifactRoot.asDerivedRoot(
-                        scratch.dir("/genfiles"), scratch.dir("/genfiles/aaa")),
+                    ArtifactRoot.asDerivedRoot(scratch.dir("/genfiles"), "aaa"),
                     scratch.file("/genfiles/aaa/bar.out"))
                 .isSourceArtifact())
         .isFalse();
@@ -355,7 +350,7 @@
   @Test
   public void testGetRoot() throws Exception {
     Path execRoot = scratch.getFileSystem().getPath("/");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/newRoot"));
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, "newRoot");
     assertThat(ActionsTestUtil.createArtifact(root, scratch.file("/newRoot/foo")).getRoot())
         .isEqualTo(root);
   }
@@ -363,7 +358,7 @@
   @Test
   public void hashCodeAndEquals() throws IOException {
     Path execRoot = scratch.getFileSystem().getPath("/");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/newRoot"));
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, "newRoot");
     ActionLookupValue.ActionLookupKey firstOwner =
         new ActionLookupValue.ActionLookupKey() {
           @Override
@@ -417,9 +412,9 @@
   }
 
   @Test
-  public void canDeclareContentBasedOutput() throws Exception {
+  public void canDeclareContentBasedOutput() {
     Path execRoot = scratch.getFileSystem().getPath("/");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/newRoot"));
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, "newRoot");
     assertThat(
             new Artifact.DerivedArtifact(
                     root,
diff --git a/src/test/java/com/google/devtools/build/lib/actions/CompositeRunfilesSupplierTest.java b/src/test/java/com/google/devtools/build/lib/actions/CompositeRunfilesSupplierTest.java
index c55d455..cd41a8c 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/CompositeRunfilesSupplierTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/CompositeRunfilesSupplierTest.java
@@ -48,7 +48,7 @@
   public final void createMocks() throws IOException {
     Scratch scratch = new Scratch();
     execRoot = scratch.getFileSystem().getPath("/");
-    rootDir = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/fake/root/dont/matter"));
+    rootDir = ArtifactRoot.asDerivedRoot(execRoot, "fake", "root", "dont", "matter");
 
     mockFirst = mock(RunfilesSupplier.class);
     mockSecond = mock(RunfilesSupplier.class);
diff --git a/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
index a09b8b1..620ac76 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
@@ -49,16 +49,14 @@
  */
 @RunWith(JUnit4.class)
 public class CustomCommandLineTest {
-
-  private Scratch scratch;
   private ArtifactRoot rootDir;
   private Artifact artifact1;
   private Artifact artifact2;
 
   @Before
   public void createArtifacts() throws Exception  {
-    scratch = new Scratch();
-    rootDir = ArtifactRoot.asDerivedRoot(scratch.dir("/exec/root"), scratch.dir("/exec/root/dir"));
+    Scratch scratch = new Scratch();
+    rootDir = ArtifactRoot.asDerivedRoot(scratch.dir("/exec/root"), "dir");
     artifact1 = ActionsTestUtil.createArtifact(rootDir, scratch.file("/exec/root/dir/file1.txt"));
     artifact2 = ActionsTestUtil.createArtifact(rootDir, scratch.file("/exec/root/dir/file2.txt"));
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
index da31fad..f64993b 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
@@ -52,8 +52,10 @@
   public final void createExecutor() throws Exception  {
     final Path inputDir = scratch.dir("/in");
     execRoot = scratch.getFileSystem().getPath("/");
-    inputRoot = ArtifactRoot.asDerivedRoot(execRoot, inputDir);
-    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/out"));
+    inputRoot = ArtifactRoot.asDerivedRoot(execRoot, inputDir.relativeTo(execRoot).getPathString());
+    String outSegment = "out";
+    execRoot.getChild(outSegment).createDirectoryAndParents();
+    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
     outErr = new TestFileOutErr();
     executor = new DummyExecutor(scratch.getFileSystem(), inputDir);
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
index 8eb9f18..601a412 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
@@ -44,8 +44,7 @@
     errorMessage = "An error just happened.";
     anOutput =
         ActionsTestUtil.createArtifact(
-            ArtifactRoot.asDerivedRoot(scratch.dir("/"), scratch.dir("/out")),
-            scratch.file("/out/foo"));
+            ArtifactRoot.asDerivedRoot(scratch.dir("/"), "out"), scratch.file("/out/foo"));
     outputs = ImmutableList.of(anOutput);
     failAction = new FailAction(NULL_ACTION_OWNER, outputs, errorMessage);
     actionGraph.registerAction(failAction);
diff --git a/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java b/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
index 4207588..fac6aa9 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
@@ -46,10 +46,11 @@
   public void testSmoke() throws Exception {
     MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
     Path execRoot = fileSystem.getPath("/");
-    Path root = fileSystem.getPath("/root");
+    String outSegment = "root";
+    Path root = execRoot.getChild(outSegment);
     Path path = root.getRelative("foo");
     Artifact output =
-        ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, root), path);
+        ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, outSegment), path);
     Action action =
         new TestAction(
             TestAction.NO_EFFECT,
@@ -58,7 +59,7 @@
     actionGraph.registerAction(action);
     actionGraph.unregisterAction(action);
     path = root.getRelative("bar");
-    output = ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, root), path);
+    output = ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, outSegment), path);
     Action action2 =
         new TestAction(
             TestAction.NO_EFFECT,
@@ -74,9 +75,10 @@
     MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
     Path execRoot = fileSystem.getPath("/");
     Path root = fileSystem.getPath("/root");
-    Path path = root.getRelative("/root/foo");
+    Path path = root.getRelative("foo");
     Artifact output =
-        ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, root), path);
+        ActionsTestUtil.createArtifact(
+            ArtifactRoot.asDerivedRoot(execRoot, root.relativeTo(execRoot).getPathString()), path);
     Action action =
         new TestAction(
             TestAction.NO_EFFECT,
@@ -108,9 +110,11 @@
           "action-graph-test",
           ErrorClassifier.DEFAULT);
       Path execRoot = fileSystem.getPath("/");
-      Path root = fileSystem.getPath("/root");
-      Path path = root.getRelative("foo");
-      output = ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, root), path);
+      String rootSegment = "root";
+      Path root = execRoot.getChild(rootSegment);
+      Path path = root.getChild("foo");
+      output =
+          ActionsTestUtil.createArtifact(ArtifactRoot.asDerivedRoot(execRoot, rootSegment), path);
       allActions.add(
           new TestAction(
               TestAction.NO_EFFECT,
diff --git a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
index b440f10..1f73d08 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
@@ -257,6 +257,11 @@
     }
   }
 
+  public static ArtifactRoot createArtifactRootFromTwoPaths(Path root, Path execPath) {
+    return ArtifactRoot.asDerivedRoot(
+        root, execPath.relativeTo(root).getSegments().toArray(new String[0]));
+  }
+
   /**
    * {@link SkyFunction.Environment} that internally makes a full Skyframe evaluate call for the
    * requested keys, blocking until the values are ready.
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java
index 1ab6835..12a72a1 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java
@@ -192,8 +192,7 @@
     FileSystem fs = new InMemoryFileSystem();
     if (path.startsWith("/exec/out")) {
       return ActionsTestUtil.createArtifact(
-          ArtifactRoot.asDerivedRoot(fs.getPath("/exec"), fs.getPath("/exec/out")),
-          fs.getPath(path));
+          ArtifactRoot.asDerivedRoot(fs.getPath("/exec"), "out"), fs.getPath(path));
     } else {
       return ActionsTestUtil.createArtifact(
           ArtifactRoot.asSourceRoot(Root.fromPath(fs.getPath("/exec"))), fs.getPath(path));
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/RunfilesSupplierImplTest.java b/src/test/java/com/google/devtools/build/lib/analysis/RunfilesSupplierImplTest.java
index 85aab12..b05d8f1 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/RunfilesSupplierImplTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/RunfilesSupplierImplTest.java
@@ -24,7 +24,6 @@
 import com.google.devtools.build.lib.testutil.Scratch;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-import java.io.IOException;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,10 +36,10 @@
   private ArtifactRoot rootDir;
 
   @Before
-  public final void setRoot() throws IOException {
+  public final void setRoot() {
     Scratch scratch = new Scratch();
     Path execRoot = scratch.getFileSystem().getPath("/");
-    rootDir = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/fake/root/dont/matter"));
+    rootDir = ArtifactRoot.asDerivedRoot(execRoot, "fake", "root", "dont", "matter");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/SourceManifestActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/SourceManifestActionTest.java
index 02919d7..d1b75bb 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/SourceManifestActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/SourceManifestActionTest.java
@@ -76,8 +76,7 @@
     pythonSourceFile = ActionsTestUtil.createArtifact(trivialRoot, pythonSourcePath);
     fakeManifest.put(buildFilePath.relativeTo(rootDirectory), buildFile);
     fakeManifest.put(pythonSourcePath.relativeTo(rootDirectory), pythonSourceFile);
-    ArtifactRoot outputDir =
-        ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("blaze-output"));
+    ArtifactRoot outputDir = ArtifactRoot.asDerivedRoot(rootDirectory, "blaze-output");
     manifestOutputPath = rootDirectory.getRelative("blaze-output/trivial.runfiles_manifest");
     manifestOutputFile = ActionsTestUtil.createArtifact(outputDir, manifestOutputPath);
   }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
index aed1bc1..6cf1dbd 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
@@ -53,7 +53,7 @@
   public final void setRootDir() throws Exception {
     Scratch scratch = new Scratch();
     Path execRoot = scratch.getFileSystem().getPath("/");
-    root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/blaze-out"));
+    root = ArtifactRoot.asDerivedRoot(execRoot, "blaze-out");
     path = scratch.dir("/blaze-out/foo");
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
index 00aaaa2..8b3fd57 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
@@ -64,7 +64,7 @@
   @Before
   public void createArtifacts() throws Exception  {
     Path execRoot = scratch.getFileSystem().getPath("/exec");
-    rootDir = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/exec/out"));
+    rootDir = ArtifactRoot.asDerivedRoot(execRoot, "out");
     outputArtifact = getBinArtifactWithNoOwner("destination.txt");
     FileSystemUtils.createDirectoryAndParents(outputArtifact.getPath().getParentDirectory());
     treeArtifact = createTreeArtifact("artifact/myTreeFileArtifact");
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
index 61b30ba..da2483a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
@@ -54,7 +54,7 @@
   public void setRootDir() throws Exception  {
     Scratch scratch = new Scratch();
     Path execRoot = scratch.getFileSystem().getPath("/");
-    root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/exec/root"));
+    root = ArtifactRoot.asDerivedRoot(execRoot, "root");
   }
 
   @Test
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 c00cf5f..ba35d5d 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
@@ -86,8 +86,8 @@
 
   private void createArtifacts(String template) throws Exception {
     ArtifactRoot workspace = ArtifactRoot.asSourceRoot(Root.fromPath(scratch.dir("/workspace")));
-    outputRoot =
-        ArtifactRoot.asDerivedRoot(scratch.dir("/workspace"), scratch.dir("/workspace/out"));
+    scratch.dir("/workspace/out");
+    outputRoot = ArtifactRoot.asDerivedRoot(scratch.dir("/workspace"), "out");
     Path input = scratch.overwriteFile("/workspace/input.txt", StandardCharsets.UTF_8, template);
     inputArtifact = ActionsTestUtil.createArtifact(workspace, input);
     output = scratch.resolve("/workspace/out/destination.txt");
diff --git a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
index a1dd8c3..062ea75 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
@@ -66,8 +66,7 @@
 
   private final FileSystem fs = new InMemoryFileSystem();
   private final Path execRoot = fs.getPath("/root");
-  private final ArtifactRoot rootDir =
-      ArtifactRoot.asDerivedRoot(execRoot, fs.getPath("/root/out"));
+  private final ArtifactRoot rootDir = ArtifactRoot.asDerivedRoot(execRoot, "out");
 
   private SpawnInputExpander expander = new SpawnInputExpander(execRoot, /*strict=*/ true);
   private Map<PathFragment, ActionInput> inputMappings = new HashMap<>();
@@ -373,10 +372,11 @@
 
   private SpecialArtifact createSpecialArtifact(String relPath, SpecialArtifactType type)
       throws IOException {
-    Path outputDir = execRoot.getRelative("out");
+    String outputSegment = "out";
+    Path outputDir = execRoot.getRelative(outputSegment);
     Path outputPath = outputDir.getRelative(relPath);
     outputPath.createDirectoryAndParents();
-    ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(execRoot, outputDir);
+    ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(execRoot, outputSegment);
     return new SpecialArtifact(
         derivedRoot,
         derivedRoot.getExecPath().getRelative(derivedRoot.getRoot().relativize(outputPath)),
diff --git a/src/test/java/com/google/devtools/build/lib/remote/ByteStreamBuildEventArtifactUploaderTest.java b/src/test/java/com/google/devtools/build/lib/remote/ByteStreamBuildEventArtifactUploaderTest.java
index 02d0413..7fc5469 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/ByteStreamBuildEventArtifactUploaderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/ByteStreamBuildEventArtifactUploaderTest.java
@@ -117,7 +117,7 @@
     // on different threads than the setUp.
     prevContext = withEmptyMetadata.attach();
 
-    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out"));
+    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, "out");
     outputRoot.getRoot().asPath().createDirectoryAndParents();
 
     retryService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java
index 2e66e63..cdc5946 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionFileSystemTest.java
@@ -59,7 +59,7 @@
     MockitoAnnotations.initMocks(this);
     fs = new InMemoryFileSystem(new JavaClock(), HASH_FUNCTION);
     execRoot = fs.getPath("/exec");
-    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out"));
+    outputRoot = ArtifactRoot.asDerivedRoot(execRoot, "out");
     outputRoot.getRoot().asPath().createDirectoryAndParents();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcherTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcherTest.java
index c08f74c..2453cfe 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcherTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcherTest.java
@@ -69,7 +69,7 @@
     FileSystem fs = new InMemoryFileSystem(new JavaClock(), HASH_FUNCTION);
     execRoot = fs.getPath("/exec");
     execRoot.createDirectoryAndParents();
-    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("root"));
+    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, "root");
     artifactRoot.getRoot().asPath().createDirectoryAndParents();
     options = Options.getDefaults(RemoteOptions.class);
     digestUtil = new DigestUtil(HASH_FUNCTION);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteCacheTests.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteCacheTests.java
index ca29a75..716944f 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteCacheTests.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteCacheTests.java
@@ -111,7 +111,7 @@
     execRoot = fs.getPath("/execroot");
     execRoot.createDirectoryAndParents();
     fakeFileCache = new FakeActionInputFileCache(execRoot);
-    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getChild("outputs"));
+    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, "outputs");
     artifactRoot.getRoot().asPath().createDirectoryAndParents();
     retryService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));
   }
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index ebcf429..4082565 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -941,7 +941,7 @@
     RemoteOptions options = Options.getDefaults(RemoteOptions.class);
     options.remoteOutputsMode = RemoteOutputsMode.TOPLEVEL;
 
-    ArtifactRoot outputRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("outs"));
+    ArtifactRoot outputRoot = ArtifactRoot.asDerivedRoot(execRoot, "outs");
     Artifact topLevelOutput =
         ActionsTestUtil.createArtifact(outputRoot, outputRoot.getRoot().getRelative("foo.bin"));
 
diff --git a/src/test/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeTest.java b/src/test/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeTest.java
index 13ec28d..bd478f4 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/merkletree/DirectoryTreeTest.java
@@ -61,7 +61,7 @@
   public void setup() {
     FileSystem fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
     execRoot = fs.getPath("/exec");
-    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("srcs"));
+    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, "srcs");
     digestUtil = new DigestUtil(fs.getDigestFunction());
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/remote/merkletree/MerkleTreeTest.java b/src/test/java/com/google/devtools/build/lib/remote/merkletree/MerkleTreeTest.java
index d66b936..754a95c 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/merkletree/MerkleTreeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/merkletree/MerkleTreeTest.java
@@ -58,7 +58,7 @@
   public void setup() {
     FileSystem fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
     execRoot = fs.getPath("/exec");
-    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("srcs"));
+    artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, "srcs");
     digestUtil = new DigestUtil(fs.getDigestFunction());
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
index bff0031..fda9faf 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainFeaturesTest.java
@@ -134,8 +134,9 @@
 
   private Artifact scratchArtifact(String s) {
     Path execRoot = outputBase.getRelative("exec");
-    Path outputRoot = execRoot.getRelative("out");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot);
+    String outSegment = "out";
+    Path outputRoot = execRoot.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
     try {
       return ActionsTestUtil.createArtifact(
           root, scratch.overwriteFile(outputRoot.getRelative(s).toString()));
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLineTest.java
index 7f6c4b7..58d4db3 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CompileCommandLineTest.java
@@ -51,8 +51,9 @@
 
   private Artifact scratchArtifact(String s) {
     Path execRoot = outputBase.getRelative("exec");
-    Path outputRoot = execRoot.getRelative("root");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot);
+    String outSegment = "root";
+    Path outputRoot = execRoot.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
     try {
       return ActionsTestUtil.createArtifact(
           root, scratch.overwriteFile(outputRoot.getRelative(s).toString()));
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index 38061ed..e2ba662 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -729,8 +729,9 @@
 
   private Artifact scratchArtifact(String s) {
     Path execRoot = outputBase.getRelative("exec");
-    Path outputRoot = execRoot.getRelative("out");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot);
+    String outSegment = "out";
+    Path outputRoot = execRoot.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
     try {
       return ActionsTestUtil.createArtifact(
           root, scratch.overwriteFile(outputRoot.getRelative(s).toString()));
@@ -888,7 +889,7 @@
     Path execRoot = fs.getPath(TestUtils.tmpDir());
     PathFragment execPath = PathFragment.create("out").getRelative(name);
     return new SpecialArtifact(
-        ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
+        ArtifactRoot.asDerivedRoot(execRoot, "out"),
         execPath,
         ActionsTestUtil.NULL_ARTIFACT_OWNER,
         SpecialArtifactType.TREE);
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CreateIncSymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CreateIncSymlinkActionTest.java
index ae4d1d6..bcdb9c6 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CreateIncSymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CreateIncSymlinkActionTest.java
@@ -45,9 +45,10 @@
   private final ActionKeyContext actionKeyContext = new ActionKeyContext();
 
   @Test
-  public void testDifferentOrderSameActionKey() throws Exception {
-    Path includePath = rootDirectory.getRelative("out");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, includePath);
+  public void testDifferentOrderSameActionKey() {
+    String outSegment = "out";
+    Path includePath = rootDirectory.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outSegment);
     Artifact a = ActionsTestUtil.createArtifact(root, "a");
     Artifact b = ActionsTestUtil.createArtifact(root, "b");
     Artifact c = ActionsTestUtil.createArtifact(root, "c");
@@ -66,9 +67,10 @@
   }
 
   @Test
-  public void testDifferentTargetsDifferentActionKey() throws Exception {
-    Path includePath = rootDirectory.getRelative("out");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, includePath);
+  public void testDifferentTargetsDifferentActionKey() {
+    String outSegment = "out";
+    Path includePath = rootDirectory.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outSegment);
     Artifact a = ActionsTestUtil.createArtifact(root, "a");
     Artifact b = ActionsTestUtil.createArtifact(root, "b");
     CreateIncSymlinkAction action1 =
@@ -83,9 +85,10 @@
   }
 
   @Test
-  public void testDifferentSymlinksDifferentActionKey() throws Exception {
-    Path includePath = rootDirectory.getRelative("out");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, includePath);
+  public void testDifferentSymlinksDifferentActionKey() {
+    String outSegment = "out";
+    Path includePath = rootDirectory.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outSegment);
     Artifact a = ActionsTestUtil.createArtifact(root, "a");
     Artifact b = ActionsTestUtil.createArtifact(root, "b");
     CreateIncSymlinkAction action1 =
@@ -101,9 +104,10 @@
 
   @Test
   public void testExecute() throws Exception {
-    Path outputDir = rootDirectory.getRelative("out");
+    String outSegment = "out";
+    Path outputDir = rootDirectory.getRelative(outSegment);
     outputDir.createDirectory();
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outputDir);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outSegment);
     Path symlink = rootDirectory.getRelative("out/a");
     Artifact a = ActionsTestUtil.createArtifact(root, symlink);
     Artifact b = ActionsTestUtil.createArtifact(root, "b");
@@ -137,9 +141,10 @@
 
   @Test
   public void testFileRemoved() throws Exception {
-    Path outputDir = rootDirectory.getRelative("out");
+    String outSegment = "out";
+    Path outputDir = rootDirectory.getRelative(outSegment);
     outputDir.createDirectory();
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outputDir);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(rootDirectory, outSegment);
     Path symlink = rootDirectory.getRelative("out/subdir/a");
     Artifact a = ActionsTestUtil.createArtifact(root, symlink);
     Artifact b = ActionsTestUtil.createArtifact(root, "b");
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/HeaderDiscoveryTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/HeaderDiscoveryTest.java
index 93f4799..6f35ce0 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/HeaderDiscoveryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/HeaderDiscoveryTest.java
@@ -37,11 +37,12 @@
 /** Test. */
 @RunWith(JUnit4.class)
 public class HeaderDiscoveryTest {
+  private static final String DERIVED_SEGMENT = "derived";
 
   private final FileSystem fs = new InMemoryFileSystem();
   private final Path execRoot = fs.getPath("/execroot");
-  private final Path derivedRoot = execRoot.getRelative("derived");
-  private final ArtifactRoot artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, derivedRoot);
+  private final Path derivedRoot = execRoot.getChild(DERIVED_SEGMENT);
+  private final ArtifactRoot artifactRoot = ArtifactRoot.asDerivedRoot(execRoot, DERIVED_SEGMENT);
 
   @Test
   public void errorsWhenMissingHeaders() {
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java
index e19f182..820bd9e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java
@@ -57,8 +57,9 @@
 
   private Artifact scratchArtifact(String s) {
     Path execRoot = outputBase.getRelative("exec");
-    Path outputRoot = execRoot.getRelative("root");
-    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot);
+    String outSegment = "root";
+    Path outputRoot = execRoot.getRelative(outSegment);
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
     try {
       return ActionsTestUtil.createArtifact(
           root, scratch.overwriteFile(outputRoot.getRelative(s).toString()));
@@ -392,7 +393,7 @@
     Path execRoot = fs.getPath(TestUtils.tmpDir());
     PathFragment execPath = PathFragment.create("out").getRelative(name);
     return new SpecialArtifact(
-        ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
+        ArtifactRoot.asDerivedRoot(execRoot, "out"),
         execPath,
         ActionsTestUtil.NULL_ARTIFACT_OWNER,
         SpecialArtifactType.TREE);
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java
index 86df9eb..3b4d95d 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java
@@ -51,7 +51,7 @@
   public void setup() {
     fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
     execRoot = fs.getPath("/exec/root");
-    ar = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getChild("out"));
+    ar = ArtifactRoot.asDerivedRoot(execRoot, "out");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/J2ObjcSourceTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/J2ObjcSourceTest.java
index ff38b55..0ab3954 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/J2ObjcSourceTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/J2ObjcSourceTest.java
@@ -40,7 +40,9 @@
   public final void setRootDir() throws Exception  {
     Scratch scratch = new Scratch();
     Path execRoot = scratch.getFileSystem().getPath("/exec");
-    rootDir = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/exec/root"));
+    String outSegment = "root";
+    execRoot.getChild(outSegment).createDirectoryAndParents();
+    rootDir = ArtifactRoot.asDerivedRoot(execRoot, outSegment);
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java b/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
index c10186d..1753de3 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
@@ -54,7 +54,7 @@
   private final ArtifactRoot root =
       ArtifactRoot.asSourceRoot(Root.fromPath(FILE_SYSTEM.getPath("/")));
   private final ArtifactRoot derivedRoot =
-      ArtifactRoot.asDerivedRoot(FILE_SYSTEM.getPath("/"), FILE_SYSTEM.getPath("/out"));
+      ArtifactRoot.asDerivedRoot(FILE_SYSTEM.getPath("/"), "out");
 
   private ProtoInfo protoInfo(
       ImmutableList<Artifact> directProtos,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
index fc43e95..a5c43a1 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
@@ -56,7 +56,8 @@
   public final void setRootDir() throws Exception  {
     scratch = new Scratch();
     sourceRoot = ArtifactRoot.asSourceRoot(Root.fromPath(scratch.dir("/workspace")));
-    outputRoot = ArtifactRoot.asDerivedRoot(scratch.dir("/output"), scratch.dir("/output/bin"));
+    scratch.dir("/output/bin");
+    outputRoot = ArtifactRoot.asDerivedRoot(scratch.dir("/output"), "bin");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
index e152c7b..720090b 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
@@ -219,7 +219,7 @@
   private SpecialArtifact createTreeArtifact(String path) {
     PathFragment execPath = PathFragment.create("out").getRelative(path);
     return new SpecialArtifact(
-        ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")),
+        ArtifactRoot.asDerivedRoot(rootDirectory, "out"),
         execPath,
         CTKEY,
         SpecialArtifactType.TREE);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index 0d38158..913d827 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -308,8 +308,7 @@
   private Artifact.DerivedArtifact createDerivedArtifact(String path) {
     PathFragment execPath = PathFragment.create("out").getRelative(path);
     Artifact.DerivedArtifact output =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, ALL_OWNER);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, ALL_OWNER);
     actions.add(new DummyAction(NestedSetBuilder.emptySet(Order.STABLE_ORDER), output));
     output.setGeneratingActionKey(ActionLookupData.create(ALL_OWNER, actions.size() - 1));
     return output;
@@ -331,10 +330,7 @@
   private SpecialArtifact createDerivedTreeArtifactOnly(String path) {
     PathFragment execPath = PathFragment.create("out").getRelative(path);
     return new SpecialArtifact(
-        ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-        execPath,
-        ALL_OWNER,
-        SpecialArtifactType.TREE);
+        ArtifactRoot.asDerivedRoot(root, "out"), execPath, ALL_OWNER, SpecialArtifactType.TREE);
   }
 
   private TreeFileArtifact createFakeTreeFileArtifact(
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 c04a600..12da7a1 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
@@ -711,17 +711,19 @@
   }
 
   private Artifact createDerivedArtifact(String relPath) throws IOException {
-    Path outputPath = fs.getPath("/bin");
+    String outSegment = "bin";
+    Path outputPath = fs.getPath("/" + outSegment);
     outputPath.createDirectory();
     return ActionsTestUtil.createArtifact(
-        ArtifactRoot.asDerivedRoot(fs.getPath("/"), outputPath), outputPath.getRelative(relPath));
+        ArtifactRoot.asDerivedRoot(fs.getPath("/"), outSegment), outputPath.getRelative(relPath));
   }
 
   private SpecialArtifact createTreeArtifact(String relPath) throws IOException {
-    Path outputDir = fs.getPath("/bin");
+    String outSegment = "bin";
+    Path outputDir = fs.getPath("/" + outSegment);
     Path outputPath = outputDir.getRelative(relPath);
     outputDir.createDirectory();
-    ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(fs.getPath("/"), outputDir);
+    ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(fs.getPath("/"), outSegment);
     return new SpecialArtifact(
         derivedRoot,
         derivedRoot.getExecPath().getRelative(derivedRoot.getRoot().relativize(outputPath)),
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 f9302a8..c4043f2 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
@@ -211,7 +211,7 @@
   private SpecialArtifact treeArtifact(String path) {
     SpecialArtifact treeArtifact =
         new SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(rootDirectory, "out"),
             PathFragment.create("out/" + path),
             ActionsTestUtil.NULL_ARTIFACT_OWNER,
             SpecialArtifactType.TREE);
@@ -231,8 +231,7 @@
     Artifact.DerivedArtifact result =
         (Artifact.DerivedArtifact)
             ActionsTestUtil.createArtifactWithExecPath(
-                ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")),
-                execPath);
+                ArtifactRoot.asDerivedRoot(rootDirectory, "out"), execPath);
     result.setGeneratingActionKey(
         ActionLookupData.create(ActionsTestUtil.NULL_ARTIFACT_OWNER, artifacts.size()));
     artifacts.add(result);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
index 60ed4ba..a4453c4 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
@@ -639,16 +639,14 @@
     // an action from its respective configured target.
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact output1 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc1);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc1);
     Action action1 =
         new MissingOutputAction(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER), output1, MiddlemanType.NORMAL);
     ConfiguredTargetValue ctValue1 = createConfiguredTargetValue(action1, lc1);
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact output2 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc2);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc2);
     Action action2 =
         new MissingOutputAction(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER), output2, MiddlemanType.NORMAL);
@@ -703,7 +701,7 @@
     ActionLookupValue.ActionLookupKey inputKey = new InjectedActionLookupKey("input");
     Artifact input =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             PathFragment.create("out").getRelative("input"),
             inputKey);
     Action baseAction =
@@ -711,16 +709,14 @@
     ConfiguredTargetValue ctBase = createConfiguredTargetValue(baseAction, inputKey);
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact output1 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc1);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc1);
     Action action1 =
         new DummyAction(
             NestedSetBuilder.create(Order.STABLE_ORDER, input), output1, MiddlemanType.NORMAL);
     ConfiguredTargetValue ctValue1 = createConfiguredTargetValue(action1, lc1);
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact output2 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc2);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc2);
     Action action2 =
         new DummyAction(
             NestedSetBuilder.create(Order.STABLE_ORDER, input), output2, MiddlemanType.NORMAL);
@@ -802,8 +798,7 @@
     // thing if they executed, but they look the same to our execution engine.
     ActionLookupValue.ActionLookupKey lcA = new InjectedActionLookupKey("lcA");
     Artifact outputA =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lcA);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lcA);
     CountDownLatch actionAStartedSoOthersCanProceed = new CountDownLatch(1);
     CountDownLatch actionCFinishedSoACanFinish = new CountDownLatch(1);
     Action actionA =
@@ -827,16 +822,14 @@
     // Shared actions: they look the same from the point of view of Blaze data.
     ActionLookupValue.ActionLookupKey lcB = new InjectedActionLookupKey("lcB");
     Artifact outputB =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lcB);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lcB);
     Action actionB =
         new DummyAction(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER), outputB, MiddlemanType.NORMAL);
     ConfiguredTargetValue ctB = createConfiguredTargetValue(actionB, lcB);
     ActionLookupValue.ActionLookupKey lcC = new InjectedActionLookupKey("lcC");
     Artifact outputC =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lcC);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lcC);
     Action actionC =
         new DummyAction(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER), outputC, MiddlemanType.NORMAL);
@@ -960,7 +953,7 @@
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact.SpecialArtifact output1 =
         new Artifact.SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath,
             lc1,
             Artifact.SpecialArtifactType.TREE);
@@ -971,7 +964,7 @@
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact.SpecialArtifact output2 =
         new Artifact.SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath,
             lc2,
             Artifact.SpecialArtifactType.TREE);
@@ -1053,7 +1046,7 @@
     ActionLookupValue.ActionLookupKey baseKey = new InjectedActionLookupKey("base");
     Artifact.SpecialArtifact baseOutput =
         new Artifact.SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath,
             baseKey,
             Artifact.SpecialArtifactType.TREE);
@@ -1065,7 +1058,7 @@
     PathFragment execPath2 = PathFragment.create("out").getRelative("treesShared");
     Artifact.SpecialArtifact sharedOutput1 =
         new Artifact.SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath2,
             shared1,
             Artifact.SpecialArtifactType.TREE);
@@ -1075,7 +1068,7 @@
     ActionLookupValue.ActionLookupKey shared2 = new InjectedActionLookupKey("shared2");
     Artifact.SpecialArtifact sharedOutput2 =
         new Artifact.SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath2,
             shared2,
             Artifact.SpecialArtifactType.TREE);
@@ -1257,16 +1250,14 @@
     // an action from its respective configured target.
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact output1 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc1);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc1);
     Action action1 =
         new DummyAction(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER), output1, MiddlemanType.NORMAL);
     ConfiguredTargetValue ctValue1 = createConfiguredTargetValue(action1, lc1);
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact output2 =
-        new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")), execPath, lc2);
+        new Artifact.DerivedArtifact(ArtifactRoot.asDerivedRoot(root, "out"), execPath, lc2);
     CountDownLatch action2Running = new CountDownLatch(1);
     CountDownLatch topActionTestedOutput = new CountDownLatch(1);
     Action action2 =
@@ -1285,9 +1276,7 @@
     ActionLookupValue.ActionLookupKey topLc = new InjectedActionLookupKey("top");
     Artifact top =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            relativeOut.getChild("top"),
-            topLc);
+            ArtifactRoot.asDerivedRoot(root, "out"), relativeOut.getChild("top"), topLc);
     Action topAction =
         new TestAction(
             (Callable<Void> & Serializable)
@@ -1359,9 +1348,7 @@
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact output =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("foo"),
-            lc1);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("foo"), lc1);
     Action action1 = new WarningAction(ImmutableList.of(), output, "action 1");
     SkyValue ctValue1 =
         ValueWithMetadata.normal(
@@ -1374,9 +1361,7 @@
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact output2 =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("bar"),
-            lc2);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("bar"), lc2);
     Action action2 = new WarningAction(ImmutableList.of(output), output2, "action 2");
     SkyValue ctValue2 =
         ValueWithMetadata.normal(
@@ -1528,17 +1513,13 @@
     ActionLookupValue.ActionLookupKey lc1 = new InjectedActionLookupKey("lc1");
     Artifact output =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("foo"),
-            lc1);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("foo"), lc1);
     Action action1 = new CatastrophicAction(output);
     ConfiguredTargetValue ctValue1 = createConfiguredTargetValue(action1, lc1);
     ActionLookupValue.ActionLookupKey lc2 = new InjectedActionLookupKey("lc2");
     Artifact output2 =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("bar"),
-            lc2);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("bar"), lc2);
     AtomicBoolean markerRan = new AtomicBoolean(false);
     Action action2 = new MarkerAction(output2, markerRan);
     ConfiguredTargetValue ctValue2 = createConfiguredTargetValue(action2, lc2);
@@ -1629,7 +1610,7 @@
     ActionLookupValue.ActionLookupKey catastropheCTK = new InjectedActionLookupKey("catastrophe");
     Artifact catastropheArtifact =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath.getRelative("zcatas"),
             catastropheCTK);
     CountDownLatch failureHappened = new CountDownLatch(1);
@@ -1648,17 +1629,13 @@
     ActionLookupValue.ActionLookupKey failureCTK = new InjectedActionLookupKey("failure");
     Artifact failureArtifact =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("fail"),
-            failureCTK);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("fail"), failureCTK);
     Action failureAction = new FailedExecAction(failureArtifact, ExitCode.RESERVED);
     ConfiguredTargetValue failureCTV = createConfiguredTargetValue(failureAction, failureCTK);
     ActionLookupValue.ActionLookupKey topCTK = new InjectedActionLookupKey("top");
     Artifact topArtifact =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("top"),
-            topCTK);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("top"), topCTK);
     Action topAction =
         new DummyAction(
             NestedSetBuilder.create(Order.STABLE_ORDER, failureArtifact, catastropheArtifact),
@@ -1743,7 +1720,7 @@
     ActionLookupValue.ActionLookupKey configuredTargetKey = new InjectedActionLookupKey("key");
     Artifact catastropheArtifact =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath.getRelative("catas"),
             configuredTargetKey);
     int failedSize = 100;
@@ -1767,7 +1744,7 @@
       String failString = HashCode.fromBytes(("fail" + i).getBytes(UTF_8)).toString();
       Artifact failureArtifact =
           new Artifact.DerivedArtifact(
-              ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+              ArtifactRoot.asDerivedRoot(root, "out"),
               execPath.getRelative(failString),
               configuredTargetKey);
       failedArtifacts.add(failureArtifact);
@@ -1885,9 +1862,7 @@
     ActionLookupValue.ActionLookupKey failedKey = new InjectedActionLookupKey("failed");
     Artifact failedOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("failed"),
-            failedKey);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("failed"), failedKey);
     final AtomicReference<Action> failedActionReference = new AtomicReference<>();
     final Action failedAction =
         new TestAction(
@@ -1906,7 +1881,7 @@
     ActionLookupValue.ActionLookupKey catastrophicKey = new InjectedActionLookupKey("catastrophic");
     Artifact catastrophicOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath.getRelative("catastrophic"),
             catastrophicKey);
     Action catastrophicAction = new CatastrophicAction(catastrophicOutput);
@@ -2011,16 +1986,14 @@
     ActionLookupValue.ActionLookupKey succeededKey = new InjectedActionLookupKey("succeeded");
     Artifact succeededOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath.getRelative("succeeded"),
             succeededKey);
 
     ActionLookupValue.ActionLookupKey failedKey = new InjectedActionLookupKey("failed");
     Artifact failedOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("failed"),
-            failedKey);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("failed"), failedKey);
 
     // Create 1 succeeded key and 1 failed key with user error
     Action succeededAction =
@@ -2097,23 +2070,19 @@
     ActionLookupValue.ActionLookupKey succeededKey = new InjectedActionLookupKey("succeeded");
     Artifact succeededOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
+            ArtifactRoot.asDerivedRoot(root, "out"),
             execPath.getRelative("succeeded"),
             succeededKey);
 
     ActionLookupValue.ActionLookupKey failedKey1 = new InjectedActionLookupKey("failed1");
     Artifact failedOutput1 =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("failed1"),
-            failedKey1);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("failed1"), failedKey1);
 
     ActionLookupValue.ActionLookupKey failedKey2 = new InjectedActionLookupKey("failed2");
     Artifact failedOutput2 =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("failed2"),
-            failedKey2);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("failed2"), failedKey2);
 
     // Create 1 succeeded key, 1 failed key with infrastructure error and another failed key with
     // user error.
@@ -2198,9 +2167,7 @@
     ActionLookupValue.ActionLookupKey topKey = new InjectedActionLookupKey("top");
     Artifact topOutput =
         new Artifact.DerivedArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath.getRelative("top"),
-            topKey);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath.getRelative("top"), topKey);
 
     Artifact sourceInput =
         new Artifact.SourceArtifact(
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 1177827..ea124ae 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
@@ -404,9 +404,7 @@
     Path execRoot = fs.getPath(TestUtils.tmpDir());
     PathFragment execPath = PathFragment.create("out").getRelative(name);
     return new Artifact.DerivedArtifact(
-        ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
-        execPath,
-        ACTION_LOOKUP_KEY);
+        ArtifactRoot.asDerivedRoot(execRoot, "out"), execPath, ACTION_LOOKUP_KEY);
   }
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index b80e76e..876a9e6 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -1265,7 +1265,7 @@
         fs.getPath(TestUtils.tmpDir()).getRelative("execroot").getRelative("default-exec-root");
     PathFragment execPath = PathFragment.create("out").getRelative(name);
     return new SpecialArtifact(
-        ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
+        ArtifactRoot.asDerivedRoot(execRoot, "out"),
         execPath,
         ACTION_LOOKUP_KEY,
         SpecialArtifactType.TREE);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
index 8474328..e3dd1d7 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
@@ -232,10 +232,7 @@
     Path fullPath = root.getRelative(execPath);
     SpecialArtifact output =
         new SpecialArtifact(
-            ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
-            execPath,
-            ALL_OWNER,
-            SpecialArtifactType.TREE);
+            ArtifactRoot.asDerivedRoot(root, "out"), execPath, ALL_OWNER, SpecialArtifactType.TREE);
     actions.add(new DummyAction(NestedSetBuilder.emptySet(Order.STABLE_ORDER), output));
     FileSystemUtils.createDirectoryAndParents(fullPath);
     return output;