Repository: bazelbuild/bazel
Issue: Crash when specifying multiple platforms
Labels: type: bug
P1
team-Configurability

### Description of the problem / feature request:

When porting `--cpu` and `--compiler` and other command line options to `constraint_setting`, it makes sense to have multiple platforms, for example one for the cpu and one for the compiler.

Specifying multiple `--platforms` will ignore all but the last. Using a comma-separated list makes Bazel crash.

By the way, why is it called platform**s** when only one platform can be specified?

(My other use case is to use platforms to set groups of `constraint_values` at ones. This could be replaced by `build_setting`s, but the command line would be really long unless using configs in bazelrc as a replacement.)

### Feature requests: what underlying problem are you trying to solve with this feature?

Allow multiple platforms to be specified. Conflicting `constraint_values` should produce an error.

It would be beneficial to let `--platforms` accumulate so that some can be set through bazelrc, for example with the automatic linux/windows configs, and others appended manually.

### Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

```
cat > BUILD.bazel <<EOF
constraint_setting(name = "cs_foo")
constraint_value(name = "cv_foo", constraint_setting = ":cs_foo")
platform(name = "foo", constraint_values = [":cv_foo"])
config_setting(name = "is_foo", constraint_values = [":cv_foo"])

constraint_setting(name = "cs_bar")
constraint_value(name = "cv_bar", constraint_setting = ":cs_bar")
platform(name = "bar", constraint_values = [":cv_bar"])
config_setting(name = "is_bar", constraint_values = [":cv_bar"])

filegroup(
    name = "my_filegroup",
    srcs = select({
        ":is_foo": ["foo.txt"],
        ":is_bar": ["bar.txt"],
    }),
)
EOF
# If both :foo and :bar would be used (what I expected), the select() should error out due to multiple matches.
bazel build --platforms=:foo --platform=:bar :my_filegroup
# This will crash. See the stack trace at the end.
bazel build --platforms=:foo,:bar :my_filegroup
```

### What operating system are you running Bazel on?

Linux (Ubuntu)

### What's the output of `bazel info release`?

`release 1.1.0`

###  Have you found anything relevant by searching the web?

- StackOverflow: http://stackoverflow.com/questions/tagged/bazel
Not much about multiple platforms.

- GitHub issues: https://github.com/bazelbuild/bazel/issues
https://github.com/bazelbuild/bazel/issues?page=2&q=is%3Aissue+multiple+platform&utf8=%E2%9C%93 gave nothing.

- email threads on https://groups.google.com/forum/#!forum/bazel-discuss
The closest I get to potentially handle multiple platforms is [Cross compiling with Bazel](https://groups.google.com/forum/#!searchin/bazel-discuss/platforms$20multiple|sort:date/bazel-discuss/yTRS5h2o7mI/zzv-faZJEQAJ).

### Any other information, logs, or outputs that you want to share?

The Bazel crash stack trace when specifying multiple platforms. The same stack trace in both Bazel 0.26.1 as in Bazel 1.1.0. Bazel 0.25.2 does not crash, it just chooses the first of the specified platforms.
```
$ bin/bazel build --platforms=:foo,:bar :some_target
INFO: Writing tracer profile to '/.../command.profile.gz'
Internal error thrown during build. Printing stack trace: java.lang.IllegalArgumentException: Platform mapping only supports a single target platform but found [//:foo, //:bar]
        at com.google.common.base.Preconditions.checkArgument(Preconditions.java:216)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.computeMapping(PlatformMappingValue.java:213)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.lambda$map$0(PlatformMappingValue.java:191)
        at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4875)
        at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2276)
        at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2154)
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2044)
        at com.google.common.cache.LocalCache.get(LocalCache.java:3951)
        at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4870)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.map(PlatformMappingValue.java:191)
        at com.google.devtools.build.lib.skyframe.BuildConfigurationValue.keyWithPlatformMapping(BuildConfigurationValue.java:90)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.toConfigurationKey(SkyframeExecutor.java:2154)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.getConfigurations(SkyframeExecutor.java:1923)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.createConfigurations(SkyframeExecutor.java:1493)
        at com.google.devtools.build.lib.analysis.BuildView.update(BuildView.java:246)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.runAnalysisPhase(AnalysisPhaseRunner.java:212)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.execute(AnalysisPhaseRunner.java:124)
        at com.google.devtools.build.lib.buildtool.BuildTool.buildTargets(BuildTool.java:141)
        at com.google.devtools.build.lib.buildtool.BuildTool.processRequest(BuildTool.java:268)
        at com.google.devtools.build.lib.runtime.commands.BuildCommand.exec(BuildCommand.java:99)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:540)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:204)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:596)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:647)
        at java.base/java.lang.Thread.run(Unknown Source)

INFO: Elapsed time: 0.111s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (0 packages loaded)
Internal error thrown during build. Printing stack trace: java.lang.IllegalArgumentException: Platform mapping only supports a single target platform but found [//:foo, //:bar]
        at com.google.common.base.Preconditions.checkArgument(Preconditions.java:216)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.computeMapping(PlatformMappingValue.java:213)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.lambda$map$0(PlatformMappingValue.java:191)
        at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4875)
        at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2276)
        at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2154)
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2044)
        at com.google.common.cache.LocalCache.get(LocalCache.java:3951)
        at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4870)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.map(PlatformMappingValue.java:191)
        at com.google.devtools.build.lib.skyframe.BuildConfigurationValue.keyWithPlatformMapping(BuildConfigurationValue.java:90)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.toConfigurationKey(SkyframeExecutor.java:2154)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.getConfigurations(SkyframeExecutor.java:1923)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.createConfigurations(SkyframeExecutor.java:1493)
        at com.google.devtools.build.lib.analysis.BuildView.update(BuildView.java:246)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.runAnalysisPhase(AnalysisPhaseRunner.java:212)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.execute(AnalysisPhaseRunner.java:124)
        at com.google.devtools.build.lib.buildtool.BuildTool.buildTargets(BuildTool.java:141)
        at com.google.devtools.build.lib.buildtool.BuildTool.processRequest(BuildTool.java:268)
        at com.google.devtools.build.lib.runtime.commands.BuildCommand.exec(BuildCommand.java:99)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:540)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:204)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:596)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:647)
        at java.base/java.lang.Thread.run(Unknown Source)
java.lang.IllegalArgumentException: Platform mapping only supports a single target platform but found [//:foo, //:bar]
        at com.google.common.base.Preconditions.checkArgument(Preconditions.java:216)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.computeMapping(PlatformMappingValue.java:213)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.lambda$map$0(PlatformMappingValue.java:191)
        at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4875)
        at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2276)
        at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2154)
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2044)
        at com.google.common.cache.LocalCache.get(LocalCache.java:3951)
        at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4870)
        at com.google.devtools.build.lib.skyframe.PlatformMappingValue.map(PlatformMappingValue.java:191)
        at com.google.devtools.build.lib.skyframe.BuildConfigurationValue.keyWithPlatformMapping(BuildConfigurationValue.java:90)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.toConfigurationKey(SkyframeExecutor.java:2154)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.getConfigurations(SkyframeExecutor.java:1923)
        at com.google.devtools.build.lib.skyframe.SkyframeExecutor.createConfigurations(SkyframeExecutor.java:1493)
        at com.google.devtools.build.lib.analysis.BuildView.update(BuildView.java:246)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.runAnalysisPhase(AnalysisPhaseRunner.java:212)
        at com.google.devtools.build.lib.buildtool.AnalysisPhaseRunner.execute(AnalysisPhaseRunner.java:124)
        at com.google.devtools.build.lib.buildtool.BuildTool.buildTargets(BuildTool.java:141)
        at com.google.devtools.build.lib.buildtool.BuildTool.processRequest(BuildTool.java:268)
        at com.google.devtools.build.lib.runtime.commands.BuildCommand.exec(BuildCommand.java:99)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:540)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:204)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:596)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$2(GrpcServerImpl.java:647)
FAILED: Build did NOT complete successfully (0 packages loaded)
```

Issue comments:
Easiest note first: As the error reports, we currently only support a single argument to `--platforms`. The flag is plural in order to allow for a future migration to supporting multiplatform builds without requiring everyone to change their flag settings, but we are not there currently. However, this should not be a crash, so I will fix that ASAP to instead gracefully report an error.

Second note: You will need to discuss with the CPP rules team how to migrate the `--compiler` flag, but trying to do a multiplatform build is not the appropriate way. In general, our view is that when we implement multiplatform builds, the specified targets will be build for all platforms, ie:

```py
bazel build --platforms=//:x86,//:gcc //:foo
```

Will build `foo` twice, once for platform `//:x86` and once for platform `//:gcc`. This is almost certainly not what you want.

You should create a new issue for the migration of the `--compiler` flag, or ask on the [bazel-discuss](https://groups.google.com/forum/#!forum/bazel-discuss) mailing list.
That explains the use case of `--platforms` and the misunderstanding from my side. I will write about my thoughts on bazel-discuss one day soon, so this issue can be closed after the crash fix.

Pull request review comments:
Is this commented out code intentional? If yes please include a TODO explaining its purpose.
I initially added it, then realized it changes semantics and broke tests, and forgot to completely remove. Now it's really gone.

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
index 756fb44b1e1e1c..12344564ede28c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfiguration.java
@@ -16,8 +16,11 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.skylarkbuildapi.platform.PlatformConfigurationApi;
 import com.google.devtools.build.lib.util.RegexFilter;
 import java.util.List;
@@ -49,6 +52,19 @@ public class PlatformConfiguration extends BuildConfiguration.Fragment
     this.targetFilterToAdditionalExecConstraints = targetFilterToAdditionalExecConstraints;
   }
 
+  @Override
+  public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) {
+    PlatformOptions platformOptions = buildOptions.get(PlatformOptions.class);
+    // TODO(https://github.com/bazelbuild/bazel/issues/6519): Implement true multiplatform builds.
+    if (platformOptions.platforms.size() > 1) {
+      reporter.handle(
+          Event.warn(
+              String.format(
+                  "--platforms only supports a single target platform: using the first option %s",
+                  this.targetPlatform)));
+    }
+  }
+
   @Override
   public Label getHostPlatform() {
     return hostPlatform;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
index 151068e997b4d7..d4d6de299c64e2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
@@ -210,12 +210,8 @@ private BuildConfigurationValue.Key computeMapping(
     if (!originalOptions.get(PlatformOptions.class).platforms.isEmpty()) {
       List<Label> platforms = originalOptions.get(PlatformOptions.class).platforms;
 
-      Preconditions.checkArgument(
-          platforms.size() == 1,
-          "Platform mapping only supports a single target platform but found %s",
-          platforms);
-
-      Label targetPlatform = Iterables.getOnlyElement(platforms);
+      // Platform mapping only supports a single target platform, others are ignored.
+      Label targetPlatform = Iterables.getFirst(platforms, null);
       if (!platformsToFlags.containsKey(targetPlatform)) {
         // This can happen if the user has set the platform and any other flags that would normally
         // be mapped from it on the command line instead of relying on the mapping.


Test patch: