Add py3-bin symlink, rename experimental flag

This adds <prefix>py3-bin, to match <prefix>py2-bin. The flag is renamed to --experimental_create_py_bin_symlinks and controls both links.

Also added an explicit direct dep to BUILD that was introduced in a previous change but already satisfied transitively.

RELNOTES: None
PiperOrigin-RevId: 282604328
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 06ae68d..1f68f9b 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -1144,6 +1144,7 @@
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/protobuf:crosstool_config_java_proto",
         "//src/main/protobuf:extra_actions_base_java_proto",
+        "//third_party:error_prone_annotations",
         "//third_party:guava",
         "//third_party:jsr305",
         "//third_party/protobuf:protobuf_java",
@@ -1292,6 +1293,7 @@
         ":out-err",
         ":packages-internal",
         ":process_util",
+        ":python-rules",
         ":shared-base-rules",
         ":skylark_semantics",
         ":unix",
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequestOptions.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequestOptions.java
index 247d463..061e138 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequestOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequestOptions.java
@@ -292,18 +292,19 @@
   }
 
   @Option(
-      name = "experimental_create_py2_bin_symlink",
+      name = "experimental_create_py_bin_symlinks",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
       effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
       help =
-          "If enabled, a py2-bin symlink (with the appropriate prefix) will be created. This acts"
-              + " just like the bin symlink, except that it is guaranteed to point to a directory"
-              + " containing outputs built for Python 2 targets, whereas the bin symlink could"
-              + " point to either Python 2 or Python 3 outputs depending on the values of"
-              + " --python_version and --use_top_level_targets_for_symlinks. IMPORTANT: This"
-              + " flag is not planned to be enabled by default, and should not be relied on.")
-  public boolean experimentalCreatePy2BinSymlink;
+          "If enabled, two symlinks, `py2-bin` and `py3-bin`, will be created (with the"
+              + " appropriate prefix). These act just like the `bin` symlink, except that they are"
+              + " guaranteed to point to directories containing outputs built for Python 2 and"
+              + " Python 3 targets respectively. Note that the `bin` symlink (if it exists) always"
+              + " overlaps with one of these; which one depends on the values of --python_version"
+              + " and --use_top_level_targets_for_symlinks. IMPORTANT: This flag is not planned to "
+              + "be enabled by default, and should not be relied on.")
+  public boolean experimentalCreatePyBinSymlinks;
 
   @Option(
       name = "print_workspace_in_output_paths_if_needed",
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index 580d4c6..8fbd4ad 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -511,7 +511,7 @@
           buildRequestOptions.getSymlinkPrefix(productName),
           productName,
           !buildRequestOptions.incompatibleSkipGenfilesSymlink,
-          buildRequestOptions.experimentalCreatePy2BinSymlink);
+          buildRequestOptions.experimentalCreatePyBinSymlinks);
     }
   }
 
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 9e2bd79..cb84069 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
@@ -15,6 +15,7 @@
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 
+import com.google.common.base.Ascii;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -151,15 +152,21 @@
     }
   }
 
-  private enum Py2BinSymlink implements SymlinkDefinition {
-    INSTANCE;
+  private enum PyBinSymlink implements SymlinkDefinition {
+    PY2(PythonVersion.PY2),
+    PY3(PythonVersion.PY3);
 
-    private static final PythonVersionTransition py2Transition =
-        PythonVersionTransition.toConstant(PythonVersion.PY2);
+    private final String versionString;
+    private final PythonVersionTransition transition;
+
+    private PyBinSymlink(PythonVersion version) {
+      this.versionString = Ascii.toLowerCase(version.toString());
+      this.transition = PythonVersionTransition.toConstant(version);
+    }
 
     @Override
     public String getLinkName(String symlinkPrefix, String productName, String workspaceBaseName) {
-      return symlinkPrefix + "py2-bin";
+      return symlinkPrefix + versionString + "-bin";
     }
 
     @Override
@@ -170,7 +177,7 @@
         Path outputPath,
         Path execRoot) {
       return targetConfigs.stream()
-          .map(config -> configGetter.apply(py2Transition.patch(config.getOptions())))
+          .map(config -> configGetter.apply(transition.patch(config.getOptions())))
           .map(config -> config.getBinDirectory(repositoryName).getRoot().asPath())
           .distinct()
           .collect(toImmutableSet());
@@ -183,12 +190,13 @@
    * <p>The result is always a subset of {@link #getAllLinkDefinitions}.
    */
   private static ImmutableList<SymlinkDefinition> getLinkDefinitions(
-      boolean includeGenfiles, boolean includePy2Bin) {
+      boolean includeGenfiles, boolean includePyBin) {
     // The order of this list controls priority for PathPrettyPrinter#getPrettyPath.
     ImmutableList.Builder<SymlinkDefinition> builder = ImmutableList.builder();
     builder.add(new ConfigSymlink("bin", BuildConfiguration::getBinDirectory));
-    if (includePy2Bin) {
-      builder.add(Py2BinSymlink.INSTANCE);
+    if (includePyBin) {
+      builder.add(PyBinSymlink.PY2);
+      builder.add(PyBinSymlink.PY3);
     }
     builder.add(new ConfigSymlink("testlogs", BuildConfiguration::getTestLogsDirectory));
     if (includeGenfiles) {
@@ -205,7 +213,7 @@
    * actually requested by the build options.
    */
   private static final ImmutableList<SymlinkDefinition> getAllLinkDefinitions() {
-    return getLinkDefinitions(/*includeGenfiles=*/ true, /*includePy2Bin=*/ true);
+    return getLinkDefinitions(/*includeGenfiles=*/ true, /*includePyBin=*/ true);
   }
 
   private static final String NO_CREATE_SYMLINKS_PREFIX = "/";
@@ -240,7 +248,7 @@
       String symlinkPrefix,
       String productName,
       boolean createGenfilesSymlink,
-      boolean createPy2BinSymlink) {
+      boolean createPyBinSymlinks) {
     if (NO_CREATE_SYMLINKS_PREFIX.equals(symlinkPrefix)) {
       return;
     }
@@ -253,7 +261,7 @@
 
     List<SymlinkDefinition> defs =
         getLinkDefinitions(
-            /*includeGenfiles=*/ createGenfilesSymlink, /*includePy2Bin=*/ createPy2BinSymlink);
+            /*includeGenfiles=*/ createGenfilesSymlink, /*includePyBin=*/ createPyBinSymlinks);
     for (SymlinkDefinition definition : defs) {
       String symlinkName = definition.getLinkName(symlinkPrefix, productName, workspaceBaseName);
       if (!createdLinks.add(symlinkName)) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonVersionTransition.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonVersionTransition.java
index 0248bc9..8279679 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonVersionTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonVersionTransition.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
+import com.google.errorprone.annotations.Immutable;
 import java.util.Objects;
 
 /**
@@ -29,6 +30,7 @@
  * <p>Subclasses should override {@link #determineNewVersion}, as well as {@link #equals} and {@link
  * #hashCode}.
  */
+@Immutable
 public abstract class PythonVersionTransition implements PatchTransition {
 
   /** Returns a transition that sets the version to {@code newVersion}. */