Move some IO-focused classes that don't have deps on other parts of Bazel to the new lib/io package.

Also, couldn't resist some clean-ups on Abstract(File)ChainUniquenessFunction: was only used for RootedPath, so specialized to that, and streamlined the uniquifier a bit, hopefully without any bugs that our test battery won't catch.

I did debate truly uniquifying the cycle by breaking ties based on the next element, but managed to back away.

PiperOrigin-RevId: 363756543
diff --git a/src/main/java/com/google/devtools/build/lib/io/AbstractFileChainUniquenessFunction.java b/src/main/java/com/google/devtools/build/lib/io/AbstractFileChainUniquenessFunction.java
new file mode 100644
index 0000000..76fc2a9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/io/AbstractFileChainUniquenessFunction.java
@@ -0,0 +1,83 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.io;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.skyframe.EmptySkyValue;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/**
+ * Given a "cycle" of {@link RootedPath} files, emits an error message for this cycle. The keys for
+ * this SkyFunction are assumed to deduplicate cycles that differ only in which element of the cycle
+ * they start at, so multiple paths to the cycle will be reported by a single execution of this
+ * function.
+ *
+ * <p>The cycle need not actually be a cycle -- any iterable exhibiting an error that is independent
+ * of the iterable's starting point can be an argument to this function.
+ */
+abstract class AbstractFileChainUniquenessFunction implements SkyFunction {
+  protected abstract String getConciseDescription();
+
+  protected abstract String getHeaderMessage();
+
+  protected abstract String getFooterMessage();
+
+  protected abstract String elementToString(RootedPath path);
+
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) {
+    StringBuilder errorMessage = new StringBuilder();
+    errorMessage.append(getConciseDescription() + " detected\n");
+    errorMessage.append(getHeaderMessage() + "\n");
+    @SuppressWarnings("unchecked")
+    ImmutableList<RootedPath> chain = (ImmutableList<RootedPath>) skyKey.argument();
+    for (RootedPath elt : chain) {
+      errorMessage.append(elementToString(elt) + "\n");
+    }
+    errorMessage.append(getFooterMessage() + "\n");
+    // The purpose of this SkyFunction is the side effect of emitting an error message exactly
+    // once per build per unique error.
+    env.getListener().handle(Event.error(errorMessage.toString()));
+    return EmptySkyValue.INSTANCE;
+  }
+
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  /**
+   * Creates a canonicalized representation of the cycle specified by {@code chain}. {@code chain}
+   * must be non-empty. The representation may not be unique if cycle has duplicate elements.
+   */
+  static ImmutableList<RootedPath> canonicalize(ImmutableList<RootedPath> cycle) {
+    int minPos = 0;
+    RootedPath min = cycle.get(0);
+    for (int i = 1; i < cycle.size(); i++) {
+      RootedPath cur = cycle.get(i);
+      if (cur.compareTo(min) < 0) {
+        minPos = i;
+        min = cur;
+      }
+    }
+    return ImmutableList.<RootedPath>builderWithExpectedSize(cycle.size())
+        .addAll(cycle.subList(minPos, cycle.size()))
+        .addAll(cycle.subList(0, minPos))
+        .build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/io/BUILD b/src/main/java/com/google/devtools/build/lib/io/BUILD
index 810dec3..fdd02fe 100644
--- a/src/main/java/com/google/devtools/build/lib/io/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/io/BUILD
@@ -10,9 +10,71 @@
     visibility = ["//src:__subpackages__"],
 )
 
-# TODO(janakr): move other general-purpose IO classes here (FileSymlinkException
-#  and friends, for instance).
 java_library(
     name = "inconsistent_filesystem_exception",
     srcs = ["InconsistentFilesystemException.java"],
 )
+
+java_library(
+    name = "file_symlink_exception",
+    srcs = ["FileSymlinkException.java"],
+)
+
+java_library(
+    name = "file_symlink_cycle_exception",
+    srcs = ["FileSymlinkCycleException.java"],
+    deps = [
+        ":file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/vfs",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
+    name = "abstract_chain_uniqueness_function",
+    srcs = ["AbstractFileChainUniquenessFunction.java"],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/vfs",
+        "//src/main/java/com/google/devtools/build/skyframe",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
+    name = "file_symlink_cycle_uniqueness_function",
+    srcs = ["FileSymlinkCycleUniquenessFunction.java"],
+    deps = [
+        ":abstract_chain_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
+        "//src/main/java/com/google/devtools/build/lib/vfs",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
+    name = "file_symlink_infinite_expansion_exception",
+    srcs = ["FileSymlinkInfiniteExpansionException.java"],
+    deps = [
+        ":file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
+        "//src/main/java/com/google/devtools/build/lib/vfs",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
+    name = "file_symlink_infinite_expansion_uniqueness_function",
+    srcs = ["FileSymlinkInfiniteExpansionUniquenessFunction.java"],
+    deps = [
+        ":abstract_chain_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
+        "//src/main/java/com/google/devtools/build/lib/vfs",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+        "//third_party:guava",
+    ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleException.java b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleException.java
similarity index 97%
rename from src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleException.java
rename to src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleException.java
index 5fd93a1..8821aec 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleException.java
+++ b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleException.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunction.java b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunction.java
similarity index 84%
rename from src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunction.java
rename to src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunction.java
index d9b3705..56f746b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunction.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Interner;
@@ -28,11 +28,12 @@
  * (e.g. {@code ['a' -> 'b' -> 'c' -> 'a']} and {@code ['b' -> 'c' -> 'a' -> 'b']}), and letting
  * Skyframe do its magic.
  */
-public class FileSymlinkCycleUniquenessFunction
-    extends AbstractChainUniquenessFunction<RootedPath> {
+public class FileSymlinkCycleUniquenessFunction extends AbstractFileChainUniquenessFunction {
+  public static final SkyFunctionName NAME =
+      SkyFunctionName.createHermetic("FILE_SYMLINK_CYCLE_UNIQUENESS");
 
-  static SkyKey key(ImmutableList<RootedPath> cycle) {
-    return Key.create(ChainUniquenessUtils.canonicalize(cycle));
+  public static SkyKey key(ImmutableList<RootedPath> cycle) {
+    return Key.create(AbstractFileChainUniquenessFunction.canonicalize(cycle));
   }
 
   @AutoCodec.VisibleForSerialization
@@ -52,7 +53,7 @@
 
     @Override
     public SkyFunctionName functionName() {
-      return SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS;
+      return NAME;
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkException.java b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkException.java
similarity index 88%
rename from src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkException.java
rename to src/main/java/com/google/devtools/build/lib/io/FileSymlinkException.java
index 50610d5..e8da68d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkException.java
+++ b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkException.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import java.io.IOException;
 
@@ -22,6 +22,6 @@
   }
 
   /** Returns a description of the problem that is suitable for printing to users. */
-  // TODO(nharmata): Consider unifying this with AbstractChainUniquenessFunction.
+  // TODO(nharmata): Consider unifying this with AbstractFileChainUniquenessFunction.
   public abstract String getUserFriendlyMessage();
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionException.java b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionException.java
similarity index 97%
rename from src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionException.java
rename to src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionException.java
index 0020a6b..cc2a02d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionException.java
+++ b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionException.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionUniquenessFunction.java b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionUniquenessFunction.java
similarity index 72%
rename from src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionUniquenessFunction.java
rename to src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionUniquenessFunction.java
index 56f6801..eb4f47c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionUniquenessFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/io/FileSymlinkInfiniteExpansionUniquenessFunction.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Interner;
@@ -23,18 +23,18 @@
 import com.google.devtools.build.skyframe.SkyKey;
 
 /**
- * A {@link
- * com/google/devtools/build/lib/skyframe/FileSymlinkInfiniteExpansionUniquenessFunction.java used
- * only in javadoc: com.google.devtools.build.skyframe.SkyFunction} that has the side effect of
- * reporting a file symlink expansion error exactly once. This is achieved by forcing the same value
- * key for two logically equivalent expansion errors (e.g. ['a' -> 'b' -> 'c' -> 'a/nope'] and ['b'
- * -> 'c' -> 'a' -> 'a/nope']), and letting Skyframe do its magic.
+ * A {@link com.google.devtools.build.skyframe.SkyFunction} that has the side effect of reporting a
+ * file symlink expansion error exactly once. This is achieved by forcing the same value key for two
+ * logically equivalent expansion errors (e.g. ['a' -> 'b' -> 'c' -> 'a/nope'] and ['b' -> 'c' ->
+ * 'a' -> 'a/nope']), and letting Skyframe do its magic.
  */
 public class FileSymlinkInfiniteExpansionUniquenessFunction
-    extends AbstractChainUniquenessFunction<RootedPath> {
+    extends AbstractFileChainUniquenessFunction {
+  public static final SkyFunctionName NAME =
+      SkyFunctionName.createHermetic("FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS");
 
-  static SkyKey key(ImmutableList<RootedPath> cycle) {
-    return Key.create(ChainUniquenessUtils.canonicalize(cycle));
+  public static SkyKey key(ImmutableList<RootedPath> cycle) {
+    return Key.create(AbstractFileChainUniquenessFunction.canonicalize(cycle));
   }
 
   @AutoCodec.VisibleForSerialization
@@ -54,7 +54,7 @@
 
     @Override
     public SkyFunctionName functionName() {
-      return SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS;
+      return NAME;
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AbstractChainUniquenessFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AbstractChainUniquenessFunction.java
deleted file mode 100644
index 489cc0e..0000000
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AbstractChainUniquenessFunction.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.skyframe;
-
-import com.google.common.collect.ImmutableList;
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.skyframe.EmptySkyValue;
-import com.google.devtools.build.skyframe.SkyFunction;
-import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
-
-/**
- * Given a "cycle" of objects of type {@param S}, emits an error message for this cycle.
- * The keys for this SkyFunction are assumed to deduplicate cycles that differ only in which element
- * of the cycle they start at, so multiple paths to the cycle will be reported by a single execution
- * of this function.
- *
- * <p>The cycle need not actually be a cycle -- any iterable exhibiting an error that is independent
- * of the iterable's starting point can be an argument to this function.
- */
-abstract class AbstractChainUniquenessFunction<S> implements SkyFunction {
-  protected abstract String getConciseDescription();
-
-  protected abstract String getHeaderMessage();
-
-  protected abstract String getFooterMessage();
-
-  protected abstract String elementToString(S elt);
-
-  @Override
-  public SkyValue compute(SkyKey skyKey, Environment env) {
-    StringBuilder errorMessage = new StringBuilder();
-    errorMessage.append(getConciseDescription() + " detected\n");
-    errorMessage.append(getHeaderMessage() + "\n");
-    @SuppressWarnings("unchecked")
-    ImmutableList<S> chain = (ImmutableList<S>) skyKey.argument();
-    for (S elt : chain) {
-      errorMessage.append(elementToString(elt) + "\n");
-    }
-    errorMessage.append(getFooterMessage() + "\n");
-    // The purpose of this SkyFunction is the side effect of emitting an error message exactly
-    // once per build per unique error.
-    env.getListener().handle(Event.error(errorMessage.toString()));
-    return EmptySkyValue.INSTANCE;
-  }
-
-  @Override
-  public String extractTag(SkyKey skyKey) {
-    return null;
-  }
-}
-
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 3e49f95..0d9388a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -142,9 +142,6 @@
         ":directory_listing_state_value",
         ":execution_finished_event",
         ":file_function",
-        ":file_symlink_cycle_uniqueness_function",
-        ":file_symlink_exception",
-        ":file_symlink_infinite_expansion_uniqueness_function",
         ":fileset_entry_function",
         ":filesystem_value_checker",
         ":glob_descriptor",
@@ -275,6 +272,9 @@
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
@@ -323,17 +323,6 @@
 )
 
 java_library(
-    name = "abstract_chain_uniqueness_function",
-    srcs = ["AbstractChainUniquenessFunction.java"],
-    deps = [
-        "//src/main/java/com/google/devtools/build/lib/events",
-        "//src/main/java/com/google/devtools/build/skyframe",
-        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
-        "//third_party:guava",
-    ],
-)
-
-java_library(
     name = "detailed_exceptions",
     srcs = [
         "DetailedException.java",
@@ -1040,12 +1029,6 @@
 )
 
 java_library(
-    name = "chain_uniqueness_utils",
-    srcs = ["ChainUniquenessUtils.java"],
-    deps = ["//third_party:guava"],
-)
-
-java_library(
     name = "client_environment_function",
     srcs = ["ClientEnvironmentFunction.java"],
     deps = [
@@ -1388,12 +1371,12 @@
     srcs = ["FileFunction.java"],
     deps = [
         ":cycle_utils",
-        ":file_symlink_cycle_exception",
-        ":file_symlink_cycle_uniqueness_function",
-        ":file_symlink_exception",
-        ":file_symlink_infinite_expansion_exception",
-        ":file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/pkgcache",
         "//src/main/java/com/google/devtools/build/lib/util",
         "//src/main/java/com/google/devtools/build/lib/vfs",
@@ -1406,62 +1389,6 @@
 )
 
 java_library(
-    name = "file_symlink_cycle_exception",
-    srcs = ["FileSymlinkCycleException.java"],
-    deps = [
-        ":file_symlink_exception",
-        "//src/main/java/com/google/devtools/build/lib/vfs",
-        "//third_party:guava",
-    ],
-)
-
-java_library(
-    name = "file_symlink_cycle_uniqueness_function",
-    srcs = ["FileSymlinkCycleUniquenessFunction.java"],
-    deps = [
-        ":abstract_chain_uniqueness_function",
-        ":chain_uniqueness_utils",
-        ":sky_functions",
-        "//src/main/java/com/google/devtools/build/lib/concurrent",
-        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
-        "//src/main/java/com/google/devtools/build/lib/vfs",
-        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
-        "//third_party:guava",
-    ],
-)
-
-java_library(
-    name = "file_symlink_exception",
-    srcs = ["FileSymlinkException.java"],
-)
-
-java_library(
-    name = "file_symlink_infinite_expansion_exception",
-    srcs = ["FileSymlinkInfiniteExpansionException.java"],
-    deps = [
-        ":file_symlink_exception",
-        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
-        "//src/main/java/com/google/devtools/build/lib/vfs",
-        "//third_party:guava",
-    ],
-)
-
-java_library(
-    name = "file_symlink_infinite_expansion_uniqueness_function",
-    srcs = ["FileSymlinkInfiniteExpansionUniquenessFunction.java"],
-    deps = [
-        ":abstract_chain_uniqueness_function",
-        ":chain_uniqueness_utils",
-        ":sky_functions",
-        "//src/main/java/com/google/devtools/build/lib/concurrent",
-        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
-        "//src/main/java/com/google/devtools/build/lib/vfs",
-        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
-        "//third_party:guava",
-    ],
-)
-
-java_library(
     name = "fileset_entry_function",
     srcs = ["FilesetEntryFunction.java"],
     deps = [
@@ -1548,8 +1475,6 @@
     srcs = ["GlobFunction.java"],
     deps = [
         ":directory_listing_value",
-        ":file_symlink_infinite_expansion_exception",
-        ":file_symlink_infinite_expansion_uniqueness_function",
         ":glob_descriptor",
         ":glob_value",
         ":ignored_package_prefixes_value",
@@ -1557,6 +1482,8 @@
         "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
@@ -1781,7 +1708,6 @@
     srcs = ["PackageLookupFunction.java"],
     deps = [
         ":already_reported_exception",
-        ":file_symlink_exception",
         ":ignored_package_prefixes_value",
         ":local_repository_lookup_value",
         ":package_lookup_value",
@@ -1789,6 +1715,7 @@
         "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/cmdline:LabelValidator",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_exception",
         "//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
@@ -2064,9 +1991,6 @@
     deps = [
         ":directory_listing_value",
         ":dirents",
-        ":file_symlink_exception",
-        ":file_symlink_infinite_expansion_exception",
-        ":file_symlink_infinite_expansion_uniqueness_function",
         ":package_lookup_value",
         ":precomputed_value",
         ":process_package_directory_result",
@@ -2074,6 +1998,9 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
@@ -2147,9 +2074,6 @@
         ":action_execution_value",
         ":detailed_exceptions",
         ":directory_listing_value",
-        ":file_symlink_exception",
-        ":file_symlink_infinite_expansion_exception",
-        ":file_symlink_infinite_expansion_uniqueness_function",
         ":package_lookup_value",
         ":sky_functions",
         ":tree_artifact_value",
@@ -2159,6 +2083,9 @@
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/profiler",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ChainUniquenessUtils.java b/src/main/java/com/google/devtools/build/lib/skyframe/ChainUniquenessUtils.java
deleted file mode 100644
index 0ab6f96..0000000
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ChainUniquenessUtils.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.skyframe;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * A value for ensuring that an error for a cycle/chain is reported exactly once. This is achieved
- * by forcing the same value key for two logically equivalent errors and letting Skyframe do its
- * magic.
- */
-class ChainUniquenessUtils {
-
-  private ChainUniquenessUtils() {}
-
-  /**
-   * Create a canonicalized representation of the cycle specified by {@code chain}. {@code chain}
-   * must be non-empty.
-   */
-  static <T> ImmutableList<T> canonicalize(ImmutableList<T> cycle) {
-    int minPos = 0;
-    String minString = cycle.get(0).toString();
-    for (int i = 1; i < cycle.size(); i++) {
-      // TODO(bazel-team): Is the toString representation stable enough?
-      String candidateString = cycle.get(i).toString();
-      if (candidateString.compareTo(minString) < 0) {
-        minPos = i;
-        minString = candidateString;
-      }
-    }
-    ImmutableList.Builder<T> builder = ImmutableList.builder();
-    for (int i = 0; i < cycle.size(); i++) {
-      int pos = (minPos + i) % cycle.size();
-      builder.add(cycle.get(pos));
-    }
-    return builder.build();
-  }
-}
-
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
index 7e0668a..8feb81d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
@@ -21,6 +21,11 @@
 import com.google.devtools.build.lib.actions.FileStateType;
 import com.google.devtools.build.lib.actions.FileStateValue;
 import com.google.devtools.build.lib.actions.FileValue;
+import com.google.devtools.build.lib.io.FileSymlinkCycleException;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
index 4fd7369..9f3c88e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
@@ -22,6 +22,8 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.vfs.Dirent;
 import com.google.devtools.build.lib.vfs.PathFragment;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
index 60df010..5d273c9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.lib.actions.FileValue;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.io.FileSymlinkException;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
 import com.google.devtools.build.lib.packages.ErrorDeterminingRepositoryException;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 150ef47..40d20b5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -36,6 +36,7 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
+import com.google.devtools.build.lib.io.FileSymlinkException;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
index ec22abe..e4a2f0e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.cmdline.LabelValidator;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.io.FileSymlinkException;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.packages.BuildFileName;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ProcessPackageDirectory.java b/src/main/java/com/google/devtools/build/lib/skyframe/ProcessPackageDirectory.java
index 36ba24f..d5f4ede 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ProcessPackageDirectory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ProcessPackageDirectory.java
@@ -26,6 +26,9 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
index 457394e..3b62ec3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
@@ -32,6 +32,9 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.io.FileSymlinkException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
 import com.google.devtools.build.lib.profiler.Profiler;
 import com.google.devtools.build.lib.profiler.ProfilerTask;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
index 0321227..65f3c74 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -31,10 +31,6 @@
       SkyFunctionName.createHermetic("ACTION_ENVIRONMENT_VARIABLE");
   public static final SkyFunctionName DIRECTORY_LISTING_STATE =
       SkyFunctionName.createNonHermetic("DIRECTORY_LISTING_STATE");
-  public static final SkyFunctionName FILE_SYMLINK_CYCLE_UNIQUENESS =
-      SkyFunctionName.createHermetic("FILE_SYMLINK_CYCLE_UNIQUENESS");
-  public static final SkyFunctionName FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS =
-      SkyFunctionName.createHermetic("FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS");
   public static final SkyFunctionName DIRECTORY_LISTING =
       SkyFunctionName.createHermetic("DIRECTORY_LISTING");
   // Hermetic even though package lookups secretly access the set of deleted packages, because
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 9043195..5b84fe3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -114,6 +114,8 @@
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.BuildFileName;
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
@@ -495,9 +497,9 @@
     map.put(SkyFunctions.ACTION_ENVIRONMENT_VARIABLE, new ActionEnvironmentFunction());
     map.put(FileStateValue.FILE_STATE, newFileStateFunction());
     map.put(SkyFunctions.DIRECTORY_LISTING_STATE, newDirectoryListingStateFunction());
-    map.put(SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+    map.put(FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
     map.put(
-        SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+        FileSymlinkInfiniteExpansionUniquenessFunction.NAME,
         new FileSymlinkInfiniteExpansionUniquenessFunction());
     map.put(FileValue.FILE, new FileFunction(pkgLocator, nonexistentFileReceiver));
     map.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
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 471b98b..f2830e4 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
@@ -37,6 +37,8 @@
 import com.google.devtools.build.lib.concurrent.NamedForkJoinPool;
 import com.google.devtools.build.lib.events.Reporter;
 import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.BuildFileName;
 import com.google.devtools.build.lib.packages.CachingPackageLocator;
@@ -61,8 +63,6 @@
 import com.google.devtools.build.lib.skyframe.ExternalPackageFunction;
 import com.google.devtools.build.lib.skyframe.FileFunction;
 import com.google.devtools.build.lib.skyframe.FileStateFunction;
-import com.google.devtools.build.lib.skyframe.FileSymlinkCycleUniquenessFunction;
-import com.google.devtools.build.lib.skyframe.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.skyframe.IgnoredPackagePrefixesFunction;
 import com.google.devtools.build.lib.skyframe.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.skyframe.PackageFunction;
@@ -465,9 +465,9 @@
         .put(
             FileStateValue.FILE_STATE,
             new FileStateFunction(tsgm, syscallCacheRef, externalFilesHelper))
-        .put(SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction())
+        .put(FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction())
         .put(
-            SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+            FileSymlinkInfiniteExpansionUniquenessFunction.NAME,
             new FileSymlinkInfiniteExpansionUniquenessFunction())
         .put(FileValue.FILE, new FileFunction(pkgLocatorRef))
         .put(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/packages/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/packages/BUILD
index f0acc83..ccb25d4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/packages/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/packages/BUILD
@@ -36,14 +36,14 @@
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/pkgcache",
         "//src/main/java/com/google/devtools/build/lib/repository:external_package_helper",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_compile",
         "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:file_function",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_cycle_uniqueness_function",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:ignored_package_prefixes_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:managed_directories_knowledge",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_function",
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java b/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
index 2f97b3c..1f2ebfc 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
@@ -16,6 +16,7 @@
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import java.io.Serializable;
+import java.util.Comparator;
 import java.util.Objects;
 import javax.annotation.Nullable;
 
@@ -30,7 +31,7 @@
  * clients via #asPath or #getRoot.
  */
 @AutoCodec
-public class RootedPath implements Serializable {
+public class RootedPath implements Serializable, Comparable<RootedPath> {
   private final Root root;
   private final PathFragment rootRelativePath;
 
@@ -128,9 +129,16 @@
     return result;
   }
 
-
   @Override
   public String toString() {
     return "[" + root + "]/[" + rootRelativePath + "]";
   }
+
+  @Override
+  public int compareTo(RootedPath o) {
+    return COMPARATOR.compare(this, o);
+  }
+
+  private static final Comparator<RootedPath> COMPARATOR =
+      Comparator.comparing(RootedPath::getRoot).thenComparing(RootedPath::getRootRelativePath);
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunctionTest.java b/src/test/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunctionTest.java
similarity index 94%
rename from src/test/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunctionTest.java
rename to src/test/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunctionTest.java
index 24835fe..678445b 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileSymlinkCycleUniquenessFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/io/FileSymlinkCycleUniquenessFunctionTest.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.skyframe;
+package com.google.devtools.build.lib.io;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.testing.EqualsTester;
@@ -26,9 +26,8 @@
 
 @RunWith(JUnit4.class)
 public class FileSymlinkCycleUniquenessFunctionTest {
-
   @Test
-  public void testHashCodeAndEqualsContract() throws Exception {
+  public void testHashCodeAndEqualsContract() {
     Root root = Root.fromPath(new InMemoryFileSystem(DigestHashFunction.SHA256).getPath("/root"));
     RootedPath p1 = RootedPath.toRootedPath(root, PathFragment.create("p1"));
     RootedPath p2 = RootedPath.toRootedPath(root, PathFragment.create("p2"));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index 8667c03..2c858f9 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -196,10 +196,10 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:directory_listing_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:directory_listing_state_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:file_function",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_cycle_exception",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_cycle_uniqueness_function",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_infinite_expansion_exception",
-        "//src/main/java/com/google/devtools/build/lib/skyframe:file_symlink_infinite_expansion_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_cycle_uniqueness_function",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_exception",
+        "//src/main/java/com/google/devtools/build/lib/io:file_symlink_infinite_expansion_uniqueness_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:fileset_entry_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:fileset_entry_key",
         "//src/main/java/com/google/devtools/build/lib/skyframe:fileset_entry_value",
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 c687dbb..b68a2f3 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
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
@@ -144,7 +145,7 @@
         SkyFunctions.LOCAL_REPOSITORY_LOOKUP,
         new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER));
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+        FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
     ImmutableMap<String, RepositoryFunction> repositoryHandlers =
         ImmutableMap.of(
             LocalRepositoryRule.NAME, (RepositoryFunction) new LocalRepositoryFunction());
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 92c7dcc..725e5af 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
@@ -43,6 +43,10 @@
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.events.NullEventHandler;
 import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleException;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.io.InconsistentFilesystemException;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
@@ -162,10 +166,10 @@
                         new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS),
                         externalFilesHelper))
                 .put(
-                    SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS,
+                    FileSymlinkCycleUniquenessFunction.NAME,
                     new FileSymlinkCycleUniquenessFunction())
                 .put(
-                    SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+                    FileSymlinkInfiniteExpansionUniquenessFunction.NAME,
                     new FileSymlinkInfiniteExpansionUniquenessFunction())
                 .put(FileValue.FILE, new FileFunction(pkgLocatorRef))
                 .put(
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 e6c792e..8b54594 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
@@ -41,6 +41,8 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.BasicFilesystemDirtinessChecker;
@@ -138,9 +140,9 @@
             externalFilesHelper));
     skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator));
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+        FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+        FileSymlinkInfiniteExpansionUniquenessFunction.NAME,
         new FileSymlinkInfiniteExpansionUniquenessFunction());
     skyFunctions.put(
         SkyFunctions.PACKAGE, new PackageFunction(null, null, null, null, null, null, null));
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 3a88497..eb6c31d 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
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
@@ -124,7 +125,7 @@
         SkyFunctions.LOCAL_REPOSITORY_LOOKUP,
         new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER));
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+        FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
 
     differencer = new SequencedRecordingDifferencer();
     evaluator = new InMemoryMemoizingEvaluator(skyFunctions, differencer);
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 73aeb14..31932ec 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
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
 import com.google.devtools.build.lib.packages.BuildFileName;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
@@ -152,7 +153,7 @@
         SkyFunctions.LOCAL_REPOSITORY_LOOKUP,
         new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER));
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+        FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
 
     ImmutableMap<String, RepositoryFunction> repositoryHandlers =
         ImmutableMap.of(
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 0d0a332..6778af8 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
@@ -50,6 +50,8 @@
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction;
+import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
@@ -181,10 +183,10 @@
         SkyFunctions.LOCAL_REPOSITORY_LOOKUP,
         new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER));
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+        FileSymlinkInfiniteExpansionUniquenessFunction.NAME,
         new FileSymlinkInfiniteExpansionUniquenessFunction());
     skyFunctions.put(
-        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
+        FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction());
     // We use a non-hermetic key to allow us to invalidate the proper artifacts on rebuilds. We
     // could have the artifact depend on the corresponding FileValue, but that would not cover the
     // case of a generated directory, which we have test coverage for.