Minor refactor of convenience symlink logic

- Eliminate ConfigGroup value object, instead push the Python transition into the SymlinkDefinition that needs it.

- Revert List of configs back to Set of configs (seems more natural and avoids changing or copying the collection in the caller).

- Tweak docstrings a little.

RELNOTES: None
PiperOrigin-RevId: 282594829
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java b/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
index 271378e..9e2bd79 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/OutputDirectoryLinksUtils.java
@@ -13,7 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.buildtool;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
@@ -36,7 +36,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
-import javax.annotation.Nullable;
 
 /** Static utilities for managing output directory symlinks. */
 public final class OutputDirectoryLinksUtils {
@@ -44,45 +43,28 @@
   // Static utilities class.
   private OutputDirectoryLinksUtils() {}
 
-  /**
-   * A grouping of a target configuration and its derived configurations, which, as a unit,
-   * determine the candidate destinations for symlinks.
-   */
-  private static final class ConfigGroup {
-
-    private static final PythonVersionTransition py2Transition =
-        PythonVersionTransition.toConstant(PythonVersion.PY2);
-
-    final BuildConfiguration targetConfig;
-
-    @Nullable final BuildConfiguration py2Config;
-
-    /**
-     * Constructs from a given target configuration, using {@code configGetter} as a factory to make
-     * configurations from options.
-     */
-    ConfigGroup(
-        BuildConfiguration targetConfig, Function<BuildOptions, BuildConfiguration> configGetter) {
-      this.targetConfig = targetConfig;
-      this.py2Config = configGetter.apply(py2Transition.patch(targetConfig.getOptions()));
-    }
-  }
-
   /** Represents a single kind of convenience symlink ({@code bazel-bin}, etc.). */
   interface SymlinkDefinition {
     /**
-     * Returns the name for this symlink in the workspace. This is independent of the target
-     * configuration(s).
+     * Returns the name for this symlink in the workspace.
+     *
+     * <p>Note that this is independent of the target configuration(s) that may help determine the
+     * symlink's destination.
      */
     String getLinkName(String symlinkPrefix, String productName, String workspaceBaseName);
 
     /**
-     * Returns a list of candidate target paths for the symlink.
+     * Returns a list of candidate destination paths for the symlink.
      *
      * <p>The symlink should only be created if there is exactly one candidate.
+     *
+     * <p>{@code configGetter} is used to compute derived configurations, if needed. It is used for
+     * symlinks that link to the output directories of configs that are related to, but not included
+     * in, {@code targetConfigs}.
      */
-    List<Path> getLinkPaths(
-        List<ConfigGroup> configGroups,
+    Set<Path> getLinkPaths(
+        Set<BuildConfiguration> targetConfigs,
+        Function<BuildOptions, BuildConfiguration> configGetter,
         RepositoryName repositoryName,
         Path outputPath,
         Path execRoot);
@@ -108,16 +90,16 @@
     }
 
     @Override
-    public List<Path> getLinkPaths(
-        List<ConfigGroup> configGroups,
+    public Set<Path> getLinkPaths(
+        Set<BuildConfiguration> targetConfigs,
+        Function<BuildOptions, BuildConfiguration> configGetter,
         RepositoryName repositoryName,
         Path outputPath,
         Path execRoot) {
-      return configGroups.stream()
-          .map(group -> group.targetConfig)
+      return targetConfigs.stream()
           .map(config -> configToRoot.apply(config, repositoryName).getRoot().asPath())
           .distinct()
-          .collect(toImmutableList());
+          .collect(toImmutableSet());
     }
   }
 
@@ -130,12 +112,13 @@
     }
 
     @Override
-    public List<Path> getLinkPaths(
-        List<ConfigGroup> configGroups,
+    public Set<Path> getLinkPaths(
+        Set<BuildConfiguration> targetConfigs,
+        Function<BuildOptions, BuildConfiguration> configGetter,
         RepositoryName repositoryName,
         Path outputPath,
         Path execRoot) {
-      return ImmutableList.of(execRoot);
+      return ImmutableSet.of(execRoot);
     }
   }
 
@@ -158,34 +141,39 @@
     };
 
     @Override
-    public List<Path> getLinkPaths(
-        List<ConfigGroup> configGroups,
+    public Set<Path> getLinkPaths(
+        Set<BuildConfiguration> targetConfigs,
+        Function<BuildOptions, BuildConfiguration> configGetter,
         RepositoryName repositoryName,
         Path outputPath,
         Path execRoot) {
-      return ImmutableList.of(outputPath);
+      return ImmutableSet.of(outputPath);
     }
   }
 
   private enum Py2BinSymlink implements SymlinkDefinition {
     INSTANCE;
 
+    private static final PythonVersionTransition py2Transition =
+        PythonVersionTransition.toConstant(PythonVersion.PY2);
+
     @Override
     public String getLinkName(String symlinkPrefix, String productName, String workspaceBaseName) {
       return symlinkPrefix + "py2-bin";
     }
 
     @Override
-    public List<Path> getLinkPaths(
-        List<ConfigGroup> configGroups,
+    public Set<Path> getLinkPaths(
+        Set<BuildConfiguration> targetConfigs,
+        Function<BuildOptions, BuildConfiguration> configGetter,
         RepositoryName repositoryName,
         Path outputPath,
         Path execRoot) {
-      return configGroups.stream()
-          .map(group -> group.py2Config)
+      return targetConfigs.stream()
+          .map(config -> configGetter.apply(py2Transition.patch(config.getOptions())))
           .map(config -> config.getBinDirectory(repositoryName).getRoot().asPath())
           .distinct()
-          .collect(toImmutableList());
+          .collect(toImmutableSet());
     }
   }
 
@@ -263,11 +251,6 @@
     String workspaceBaseName = workspace.getBaseName();
     RepositoryName repositoryName = RepositoryName.createFromValidStrippedName(workspaceName);
 
-    List<ConfigGroup> configGroups =
-        targetConfigs.stream()
-            .map(targetConfig -> new ConfigGroup(targetConfig, configGetter))
-            .collect(toImmutableList());
-
     List<SymlinkDefinition> defs =
         getLinkDefinitions(
             /*includeGenfiles=*/ createGenfilesSymlink, /*includePy2Bin=*/ createPy2BinSymlink);
@@ -277,8 +260,9 @@
         // already created a link by this name
         continue;
       }
-      List<Path> candidatePaths =
-          definition.getLinkPaths(configGroups, repositoryName, outputPath, execRoot);
+      Set<Path> candidatePaths =
+          definition.getLinkPaths(
+              targetConfigs, configGetter, repositoryName, outputPath, execRoot);
       if (candidatePaths.size() == 1) {
         createLink(workspace, symlinkName, Iterables.getOnlyElement(candidatePaths), failures);
       } else {