Add a new flag, `--experimental_platform_in_output_dir`, which causes the output dir name to use a non-default platform name instead of the CPU.

Fixes #13818.

PiperOrigin-RevId: 402633072
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 6baac5f..d2aadad 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1527,6 +1527,7 @@
         ":config/invalid_configuration_exception",
         ":config/run_under",
         ":config/transitive_option_details",
+        ":platform_options",
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
         "//src/main/java/com/google/devtools/build/lib/buildeventstream",
@@ -1543,6 +1544,7 @@
         "//src/main/java/net/starlark/java/annot",
         "//src/main/protobuf:failure_details_java_proto",
         "//third_party:guava",
+        "//third_party:jsr305",
     ],
 )
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
index 51fb0ef..f19f77d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
@@ -15,6 +15,7 @@
 package com.google.devtools.build.lib.analysis;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.config.CoreOptionConverters;
 import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.EmptyToNullLabelConverter;
@@ -42,6 +43,8 @@
       Label.parseAbsoluteUnchecked("@bazel_tools//platforms:host_platform");
   public static final Label DEFAULT_HOST_PLATFORM =
       Label.parseAbsoluteUnchecked("@local_config_platform//:host");
+  public static final String DEFAULT_TARGET_PLATFORM_FALLBACK =
+      "@bazel_tools//platforms:target_platform";
 
   /**
    * Main workspace-relative location to use when the user does not explicitly set {@code
@@ -50,6 +53,13 @@
   public static final PathFragment DEFAULT_PLATFORM_MAPPINGS =
       PathFragment.create("platform_mappings");
 
+  private static final ImmutableSet<String> DEFAULT_PLATFORM_NAMES =
+      ImmutableSet.of("host", "host_platform", "target_platform", "default_host", "default_target");
+
+  public static boolean platformIsDefault(Label platform) {
+    return DEFAULT_PLATFORM_NAMES.contains(platform.getName());
+  }
+
   @Option(
       name = "host_platform",
       oldName = "experimental_host_platform",
@@ -97,7 +107,7 @@
   @Option(
       name = "target_platform_fallback",
       converter = EmptyToNullLabelConverter.class,
-      defaultValue = "@bazel_tools//platforms:target_platform",
+      defaultValue = DEFAULT_TARGET_PLATFORM_FALLBACK,
       documentationCategory = OptionDocumentationCategory.TOOLCHAIN,
       effectTags = {
         OptionEffectTag.AFFECTS_OUTPUTS,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index 648f545..578af01 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.actions.BuildConfigurationEvent;
 import com.google.devtools.build.lib.actions.CommandLines.CommandLineLimits;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
 import com.google.devtools.build.lib.analysis.config.OutputDirectories.InvalidMnemonicException;
 import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
@@ -184,9 +185,18 @@
     this.starlarkVisibleFragments = buildIndexOfStarlarkVisibleFragments();
     this.buildOptions = buildOptions;
     this.options = buildOptions.get(CoreOptions.class);
+    PlatformOptions platformOptions = null;
+    if (buildOptions.contains(PlatformOptions.class)) {
+      platformOptions = buildOptions.get(PlatformOptions.class);
+    }
     this.outputDirectories =
         new OutputDirectories(
-            directories, options, this.fragments, mainRepositoryName, siblingRepositoryLayout);
+            directories,
+            options,
+            platformOptions,
+            this.fragments,
+            mainRepositoryName,
+            siblingRepositoryLayout);
     this.mainRepositoryName = mainRepositoryName;
     this.siblingRepositoryLayout = siblingRepositoryLayout;
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java
index 4ec09ca..4fd757b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java
@@ -69,6 +69,16 @@
   public boolean mergeGenfilesDirectory;
 
   @Option(
+      name = "experimental_platform_in_output_dir",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
+      effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
+      metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+      help =
+          "If true, the target platform is used in the output directory name instead of the CPU.")
+  public boolean platformInOutputDir;
+
+  @Option(
       name = "incompatible_use_platforms_repo_for_constraints",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
@@ -840,6 +850,7 @@
     host.commandLineBuildVariables = commandLineBuildVariables;
     host.enforceConstraints = enforceConstraints;
     host.mergeGenfilesDirectory = mergeGenfilesDirectory;
+    host.platformInOutputDir = platformInOutputDir;
     host.cpu = hostCpu;
     host.includeRequiredConfigFragmentsProvider = includeRequiredConfigFragmentsProvider;
 
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 56a9aa3..ddd7fec 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,8 @@
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.PlatformOptions;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
 import com.google.devtools.build.lib.util.OS;
@@ -32,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 
 /**
  * Logic for figuring out what base directories to place outputs generated from a given
@@ -141,12 +144,13 @@
   OutputDirectories(
       BlazeDirectories directories,
       CoreOptions options,
+      @Nullable PlatformOptions platformOptions,
       ImmutableSortedMap<Class<? extends Fragment>, Fragment> fragments,
       RepositoryName mainRepositoryName,
       boolean siblingRepositoryLayout)
       throws InvalidMnemonicException {
     this.directories = directories;
-    this.mnemonic = buildMnemonic(options, fragments);
+    this.mnemonic = buildMnemonic(options, platformOptions, fragments);
     this.outputDirName = options.isHost ? "host" : mnemonic;
 
     this.outputDirectory =
@@ -201,7 +205,9 @@
   }
 
   private static String buildMnemonic(
-      CoreOptions options, ImmutableSortedMap<Class<? extends Fragment>, Fragment> fragments)
+      CoreOptions options,
+      @Nullable PlatformOptions platformOptions,
+      ImmutableSortedMap<Class<? extends Fragment>, Fragment> fragments)
       throws InvalidMnemonicException {
     // See explanation at declaration for outputRoots.
     List<String> nameParts = new ArrayList<>();
@@ -232,12 +238,27 @@
     String mnemonic = nameParts.stream().filter(not(Strings::isNullOrEmpty)).collect(joining("-"));
 
     // Replace the CPU idenfitier.
-    validateMnemonicPart(options.cpu, "CPU name '%s'");
-    mnemonic = mnemonic.replace("{CPU}", options.cpu);
+    String cpuIdentifier = buildCpuIdentifier(options, platformOptions);
+    validateMnemonicPart(cpuIdentifier, "CPU name '%s'");
+    mnemonic = mnemonic.replace("{CPU}", cpuIdentifier);
 
     return mnemonic;
   }
 
+  private static String buildCpuIdentifier(
+      CoreOptions options, @Nullable PlatformOptions platformOptions) {
+    if (options.platformInOutputDir && platformOptions != null) {
+      Label targetPlatform = platformOptions.computeTargetPlatform();
+      // Only use non-default platforms.
+      if (!PlatformOptions.platformIsDefault(targetPlatform)) {
+        return targetPlatform.getName();
+      }
+    }
+
+    // Fall back to using the CPU.
+    return options.cpu;
+  }
+
   private ArtifactRoot buildDerivedRoot(
       String nameFragment, RepositoryName repository, boolean isMiddleman) {
     // e.g., execroot/mainRepoName/bazel-out/[repoName/]config/bin
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationTest.java
index c95432a..53cc4fb 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationTest.java
@@ -367,6 +367,23 @@
         .runTests();
   }
 
+  @Test
+  public void testPlatformInOutputDir_defaultPlatform() throws Exception {
+    BuildConfiguration config = create("--experimental_platform_in_output_dir", "--cpu=k8");
+
+    assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString())
+        .matches(".*/[^/]+-out/k8-fastbuild");
+  }
+
+  @Test
+  public void testPlatformInOutputDir() throws Exception {
+    BuildConfiguration config =
+        create("--experimental_platform_in_output_dir", "--platforms=//platform:alpha");
+
+    assertThat(config.getOutputDirectory(RepositoryName.MAIN).getRoot().toString())
+        .matches(".*/[^/]+-out/alpha-fastbuild");
+  }
+
   /**
    * Partial verification of deserialized BuildConfiguration.
    *