Initial implementation of java_runtime and java_runtime_suite

These rules can be used to configure JDKs, as an alternative to
the filegroups and filegroups-of-filegroups used currently.

--
PiperOrigin-RevId: 144655277
MOS_MIGRATED_REVID=144655277
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 9f97081..b174fd4 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -753,8 +753,12 @@
         "rules/java/JavaPlugin.java",
         "rules/java/JavaPrimaryClassProvider.java",
         "rules/java/JavaProvider.java",
+        "rules/java/JavaRuntime.java",
         "rules/java/JavaRuntimeClasspathProvider.java",
         "rules/java/JavaRuntimeJarProvider.java",
+        "rules/java/JavaRuntimeRule.java",
+        "rules/java/JavaRuntimeSuite.java",
+        "rules/java/JavaRuntimeSuiteRule.java",
         "rules/java/JavaSkylarkCommon.java",
         "rules/java/JavaSourceInfoProvider.java",
         "rules/java/JavaToolchain.java",
@@ -842,6 +846,7 @@
         "rules/java/JavaPluginInfoProvider.java",
         "rules/java/JavaRuleOutputJarsProvider.java",
         "rules/java/JavaRunfilesProvider.java",
+        "rules/java/JavaRuntimeProvider.java",
         "rules/java/JavaSemantics.java",
         "rules/java/JavaSkylarkApiProvider.java",
         "rules/java/JavaSourceJarsProvider.java",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index 362fd27..87dc0d5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -116,6 +116,8 @@
 import com.google.devtools.build.lib.rules.java.JavaConfigurationLoader;
 import com.google.devtools.build.lib.rules.java.JavaImportBaseRule;
 import com.google.devtools.build.lib.rules.java.JavaOptions;
+import com.google.devtools.build.lib.rules.java.JavaRuntimeRule;
+import com.google.devtools.build.lib.rules.java.JavaRuntimeSuiteRule;
 import com.google.devtools.build.lib.rules.java.JavaSkylarkCommon;
 import com.google.devtools.build.lib.rules.java.JavaToolchainRule;
 import com.google.devtools.build.lib.rules.java.JvmConfigurationLoader;
@@ -484,6 +486,8 @@
           builder.addRuleDefinition(new BazelJavaTestRule());
           builder.addRuleDefinition(new BazelJavaPluginRule());
           builder.addRuleDefinition(new JavaToolchainRule());
+          builder.addRuleDefinition(new JavaRuntimeRule());
+          builder.addRuleDefinition(new JavaRuntimeSuiteRule());
 
           builder.addRuleDefinition(new ExtraActionRule());
           builder.addRuleDefinition(new ActionListenerRule());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntime.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntime.java
new file mode 100644
index 0000000..e8ff3a8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntime.java
@@ -0,0 +1,47 @@
+// Copyright 2017 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.rules.java;
+
+import com.google.devtools.build.lib.actions.Actions;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.CompilationHelper;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.MiddlemanProvider;
+import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/** Implementation for the {@code java_runtime} rule. */
+public class JavaRuntime implements RuleConfiguredTargetFactory {
+  @Override
+  public ConfiguredTarget create(RuleContext ruleContext)
+      throws InterruptedException, RuleErrorException {
+    NestedSet<Artifact> filesToBuild =
+        PrerequisiteArtifacts.nestedSet(ruleContext, "srcs", Mode.TARGET);
+    NestedSet<Artifact> middleman =
+        CompilationHelper.getAggregatingMiddleman(
+            ruleContext, Actions.escapeLabel(ruleContext.getLabel()), filesToBuild);
+    return new RuleConfiguredTargetBuilder(ruleContext)
+        .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
+        .setFilesToBuild(filesToBuild)
+        .addProvider(JavaRuntimeProvider.class, JavaRuntimeProvider.create(filesToBuild))
+        .addProvider(MiddlemanProvider.class, new MiddlemanProvider(middleman))
+        .build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeProvider.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeProvider.java
new file mode 100644
index 0000000..7ecd58c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeProvider.java
@@ -0,0 +1,34 @@
+// Copyright 2017 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.rules.java;
+
+import com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/** Information about the Java runtime used by the <code>java_*</code> rules. */
+@AutoValue
+@Immutable
+public abstract class JavaRuntimeProvider implements TransitiveInfoProvider {
+
+  public static JavaRuntimeProvider create(NestedSet<Artifact> javaBaseInputs) {
+    return new AutoValue_JavaRuntimeProvider(javaBaseInputs);
+  }
+
+  /** All input artifacts in the javabase. */
+  public abstract NestedSet<Artifact> javaBaseInputs();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeRule.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeRule.java
new file mode 100644
index 0000000..ef52686
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeRule.java
@@ -0,0 +1,71 @@
+// Copyright 2017 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.rules.java;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
+import static com.google.devtools.build.lib.packages.BuildType.LICENSE;
+import static com.google.devtools.build.lib.syntax.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+/** Rule definition for {@code java_runtime} */
+public final class JavaRuntimeRule implements RuleDefinition {
+  @Override
+  public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+    return builder
+        /* <!-- #BLAZE_RULE(java_runtime).ATTRIBUTE(srcs) -->
+        All files in the runtime.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("srcs", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE).mandatory())
+        /* <!-- #BLAZE_RULE(java_runtime).ATTRIBUTE(java_home) -->
+        The relative path to the root of the runtime.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("java_home", STRING))
+        .add(attr("output_licenses", LICENSE))
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name("java_runtime")
+        .ancestors(BaseRuleClasses.BaseRule.class)
+        .factoryClass(JavaRuntime.class)
+        .build();
+  }
+}
+/*<!-- #BLAZE_RULE (NAME = java_runtime, TYPE = OTHER, FAMILY = Java) -->
+
+<p>
+Specifies the configuration for a Java runtime.
+</p>
+
+<h4 id="java_runtime">Example:</h4>
+
+<pre class="code">
+java_runtime(
+    name = "jdk-9-ea+153",
+    srcs = glob(["jdk9-ea+153/**"]),
+    java_home = "jdk9-ea+153",
+)
+</pre>
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuite.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuite.java
new file mode 100644
index 0000000..2a42e5e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuite.java
@@ -0,0 +1,46 @@
+// Copyright 2017 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.rules.java;
+
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/** Implementation for the {@code java_runtime_suite} rule. */
+public class JavaRuntimeSuite implements RuleConfiguredTargetFactory {
+  @Override
+  public ConfiguredTarget create(RuleContext ruleContext)
+      throws InterruptedException, RuleErrorException {
+    TransitiveInfoCollection runtime =
+        ruleContext.getPrerequisiteMap("runtimes").get(ruleContext.getConfiguration().getCpu());
+    if (runtime == null) {
+      runtime = ruleContext.getPrerequisite("default", Mode.TARGET);
+    }
+    if (runtime == null) {
+      ruleContext.throwWithRuleError(
+          "could not resolve runtime for cpu " + ruleContext.getConfiguration().getCpu());
+    }
+    return new RuleConfiguredTargetBuilder(ruleContext)
+        .addProvider(JavaRuntimeProvider.class, runtime.getProvider(JavaRuntimeProvider.class))
+        .addProvider(RunfilesProvider.class, runtime.getProvider(RunfilesProvider.class))
+        .setFilesToBuild(runtime.getProvider(FileProvider.class).getFilesToBuild())
+        .build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuiteRule.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuiteRule.java
new file mode 100644
index 0000000..63aa268
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeSuiteRule.java
@@ -0,0 +1,80 @@
+// Copyright 2017 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.rules.java;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LICENSE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+/** Rule definition for {@code java_runtime_suite} */
+public final class JavaRuntimeSuiteRule implements RuleDefinition {
+  @Override
+  public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+    return builder
+        /* <!-- #BLAZE_RULE(java_runtime_suite).ATTRIBUTE(runtimes) -->
+        A map from each supported architecture to the corresponding <code>java_runtime</code>.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("runtimes", BuildType.LABEL_DICT_UNARY))
+        /* <!-- #BLAZE_RULE(java_runtime_suite).ATTRIBUTE(default) -->
+        A map from each supported architecture to the corresponding <code>java_runtime</code>.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(
+            attr("default", BuildType.LABEL)
+                .mandatoryNativeProviders(
+                    ImmutableList.<Class<? extends TransitiveInfoProvider>>of(
+                        JavaRuntimeProvider.class))
+                .allowedFileTypes(FileTypeSet.NO_FILE))
+        .add(attr("output_licenses", LICENSE))
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name("java_runtime_suite")
+        .ancestors(BaseRuleClasses.BaseRule.class)
+        .factoryClass(JavaRuntimeSuite.class)
+        .build();
+  }
+}
+/*<!-- #BLAZE_RULE (NAME = java_runtime_suite, TYPE = OTHER, FAMILY = Java) -->
+
+<p>
+Specifies the configuration for the Java runtimes for each architecture.
+</p>
+
+<h4 id="java_runtime_suite">Example:</h4>
+
+<pre class="code">
+java_runtime_suite(
+   name = "jdk9",
+   runtimes = {
+     "k8": ":jdk9-k8",
+     "ppc": ":jdk9-ppc",
+     "arm": ":jdk9-arm",
+   },
+)
+</pre>
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JvmConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/rules/java/JvmConfigurationLoader.java
index 942d2ab..a8fb7aa 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JvmConfigurationLoader.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JvmConfigurationLoader.java
@@ -29,23 +29,30 @@
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.packages.RawAttributeMapper;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.List;
+import java.util.Map;
 import javax.annotation.Nullable;
 
 /**
  * A provider to load jvm configurations from the package path.
  *
- * <p>If the given {@code javaHome} is a label, i.e. starts with {@code "//"},
- * then the loader will look at the target it refers to. If the target is a
- * filegroup, then the loader will look in it's srcs for a filegroup that ends
- * with {@code -<cpu>}. It will use that filegroup to construct the actual
- * {@link Jvm} instance, using the filegroups {@code path} attribute to
- * construct the new {@code javaHome} path.
+ * <p>If the given {@code javaHome} is a label, i.e. starts with {@code "//"}, then the loader will
+ * look at the target it refers to.
+ *
+ * <ul>
+ *   <li>If the target is a {@code java_runtime_suite}, the loader will select the runtime from the
+ *       entry in {@code java_runtime_suite.runtimes} for {@code cpu}.
+ *   <li>If the target is a filegroup, then the loader will look in it's srcs for a filegroup that
+ *       ends with {@code -<cpu>}. It will use that filegroup to construct the actual {@link Jvm}
+ *       instance, using the filegroups {@code path} attribute to construct the new {@code javaHome}
+ *       path.
+ * </ul>
  *
  * <p>The loader also supports legacy mode, where the JVM can be defined with an abolute path.
  */
@@ -90,88 +97,151 @@
         return null;
       }
       Target javaHomeTarget = lookup.getTarget(label);
-      if ((javaHomeTarget instanceof Rule) &&
-          "filegroup".equals(((Rule) javaHomeTarget).getRuleClass())) {
-        RawAttributeMapper javaHomeAttributes = RawAttributeMapper.of((Rule) javaHomeTarget);
-        if (javaHomeAttributes.isConfigurable("srcs", BuildType.LABEL_LIST)) {
-          throw new InvalidConfigurationException("\"srcs\" in " + javaHome
-              + " is configurable. JAVABASE targets don't support configurable attributes");
-        }
-        List<Label> labels = javaHomeAttributes.get("srcs", BuildType.LABEL_LIST);
-        Label selectedJvmLabel = null;
-        Label defaultJvmLabel = null;
-        for (Label jvmLabel : labels) {
-          if (jvmLabel.getName().endsWith("-" + cpu)) {
-            selectedJvmLabel = jvmLabel;
-            break;
-          }
-          // When we open sourced Bazel, we used the string "default" to look up the Jvm. This is
-          // incorrect for cross-platform builds, but works for purely local builds. Since we now
-          // need to support cross-platform builds, we need to look up by the CPU, rather than the
-          // hard-coded string "default". However, for local builds the Jvm is setup with a
-          // mechanism where we don't currently have access to the CPU value (this is different from
-          // C++, where we infer the CPU from the local machine). As such, looking up only by CPU
-          // breaks builds that currently work, unless we add alias rules for all possible CPU
-          // values (but this is problematic if Bazel is ported to more platforms). For now, we're
-          // working around this problem by falling back to -default if we can't find a Jvm ending
-          // in -<cpu>. This is backwards compatible, but still allows cross-platform builds. In the
-          // medium term, we should rewrite Jvm setup to use a Skylark remote repository, and also
-          // remove the necessity of having a Jvm defined for all platforms even if there's no Java
-          // code.
-          if (jvmLabel.getName().endsWith("-default")) {
-            defaultJvmLabel = jvmLabel;
-          }
-        }
-        if (selectedJvmLabel == null) {
-          selectedJvmLabel = defaultJvmLabel;
-        }
-        if (selectedJvmLabel != null) {
-          selectedJvmLabel = RedirectChaser.followRedirects(
-              lookup, selectedJvmLabel, "Architecture-specific JDK");
-          if (selectedJvmLabel == null) {
-            return null;
-          }
-
-          Target jvmTarget = lookup.getTarget(selectedJvmLabel);
-          if (jvmTarget == null) {
-            return null;
-          }
-
-          PathFragment javaHomePath;
-          if (jvmTarget.getLabel().getPackageIdentifier().getRepository().isDefault()) {
-            javaHomePath = selectedJvmLabel.getPackageFragment();
-          } else {
-            javaHomePath = jvmTarget.getLabel().getPackageIdentifier().getSourceRoot();
-          }
-
-          if ((jvmTarget instanceof Rule) &&
-              "filegroup".equals(((Rule) jvmTarget).getRuleClass())) {
-            RawAttributeMapper jvmTargetAttributes = RawAttributeMapper.of((Rule) jvmTarget);
-            if (jvmTargetAttributes.isConfigurable("path", Type.STRING)) {
-              throw new InvalidConfigurationException("\"path\" in " + jvmTarget
-                  + " is configurable. JVM targets don't support configurable attributes");
-            }
-            String path = jvmTargetAttributes.get("path", Type.STRING);
-            if (path != null) {
-              javaHomePath = javaHomePath.getRelative(path);
-            }
-          }
-          return new Jvm(javaHomePath, selectedJvmLabel);
+      if (javaHomeTarget instanceof Rule) {
+        switch (((Rule) javaHomeTarget).getRuleClass()) {
+          case "filegroup":
+            return createFromFilegroup(lookup, javaHomeTarget, cpu);
+          case "java_runtime_suite":
+            return createFromRuntimeSuite(lookup, (Rule) javaHomeTarget, cpu);
+          default:
+            throw new InvalidConfigurationException(
+                "Unexpected javabase rule kind '" + ((Rule) javaHomeTarget).getRuleClass() + "'");
         }
       }
-      throw new InvalidConfigurationException("No JVM target found under " + javaHome
-          + " that would work for " + cpu);
-    } catch (NoSuchPackageException | NoSuchTargetException e) {
+      throw new InvalidConfigurationException(
+          "No JVM target found under " + javaHome + " that would work for " + cpu);
+    } catch (NoSuchThingException e) {
       lookup.getEventHandler().handle(Event.error(e.getMessage()));
       throw new InvalidConfigurationException(e.getMessage(), e);
     }
   }
 
-  private Jvm createLegacy(String javaHome)
+  private static Jvm createFromFilegroup(
+      ConfigurationEnvironment lookup, Target javaHomeTarget, String cpu)
+      throws InvalidConfigurationException, InterruptedException, NoSuchPackageException,
+          NoSuchTargetException {
+
+    RawAttributeMapper javaHomeAttributes = RawAttributeMapper.of((Rule) javaHomeTarget);
+    if (javaHomeAttributes.isConfigurable("srcs", BuildType.LABEL_LIST)) {
+      throw new InvalidConfigurationException(
+          String.format(
+              "\"srcs\" in %s is configurable. JAVABASE targets don't support configurable"
+                  + " attributes",
+              javaHomeTarget));
+    }
+    List<Label> labels = javaHomeAttributes.get("srcs", BuildType.LABEL_LIST);
+    Label selectedJvmLabel = null;
+    Label defaultJvmLabel = null;
+    for (Label jvmLabel : labels) {
+      if (jvmLabel.getName().endsWith("-" + cpu)) {
+        selectedJvmLabel = jvmLabel;
+        break;
+      }
+      // When we open sourced Bazel, we used the string "default" to look up the Jvm. This is
+      // incorrect for cross-platform builds, but works for purely local builds. Since we now
+      // need to support cross-platform builds, we need to look up by the CPU, rather than the
+      // hard-coded string "default". However, for local builds the Jvm is setup with a
+      // mechanism where we don't currently have access to the CPU value (this is different from
+      // C++, where we infer the CPU from the local machine). As such, looking up only by CPU
+      // breaks builds that currently work, unless we add alias rules for all possible CPU
+      // values (but this is problematic if Bazel is ported to more platforms). For now, we're
+      // working around this problem by falling back to -default if we can't find a Jvm ending
+      // in -<cpu>. This is backwards compatible, but still allows cross-platform builds. In the
+      // medium term, we should rewrite Jvm setup to use a Skylark remote repository, and also
+      // remove the necessity of having a Jvm defined for all platforms even if there's no Java
+      // code.
+      if (jvmLabel.getName().endsWith("-default")) {
+        defaultJvmLabel = jvmLabel;
+      }
+    }
+    if (selectedJvmLabel == null) {
+      selectedJvmLabel = defaultJvmLabel;
+    }
+    if (selectedJvmLabel != null) {
+      selectedJvmLabel =
+          RedirectChaser.followRedirects(lookup, selectedJvmLabel, "Architecture-specific JDK");
+      if (selectedJvmLabel == null) {
+        return null;
+      }
+
+      Target jvmTarget = lookup.getTarget(selectedJvmLabel);
+
+      PathFragment javaHomePath = defaultJavaHome(jvmTarget.getLabel());
+
+      if ((jvmTarget instanceof Rule) && "filegroup".equals(((Rule) jvmTarget).getRuleClass())) {
+        RawAttributeMapper jvmTargetAttributes = RawAttributeMapper.of((Rule) jvmTarget);
+        if (jvmTargetAttributes.isConfigurable("path", Type.STRING)) {
+          throw new InvalidConfigurationException(
+              String.format(
+                  "\"path\" in %s is configurable. JVM targets don't support configurable"
+                      + " attributes",
+                  jvmTarget));
+        }
+        String path = jvmTargetAttributes.get("path", Type.STRING);
+        if (path != null) {
+          javaHomePath = javaHomePath.getRelative(path);
+        }
+      }
+      return new Jvm(javaHomePath, selectedJvmLabel);
+    }
+    throw new InvalidConfigurationException(
+        String.format("No JVM target found under %s that would work for %s", javaHomeTarget, cpu));
+  }
+
+  // TODO(b/34175492): this is temporary until support for filegroup-based javabases is removed.
+  // Eventually the Jvm fragement will containg only the label of a java_runtime rule, and all of
+  // the configuration will be accessed using JavaRuntimeProvider.
+  private static Jvm createFromRuntimeSuite(
+      ConfigurationEnvironment lookup, Rule javaRuntimeSuite, String cpu)
+      throws InvalidConfigurationException, InterruptedException, NoSuchTargetException,
+          NoSuchPackageException {
+    Label javaRuntimeLabel = selectRuntime(javaRuntimeSuite, cpu);
+    Target javaRuntimeTarget = lookup.getTarget(javaRuntimeLabel);
+    if (javaRuntimeTarget == null) {
+      return null;
+    }
+    if (!(javaRuntimeTarget instanceof Rule)) {
+      throw new InvalidConfigurationException(
+          String.format("Invalid java_runtime '%s'", javaRuntimeLabel));
+    }
+    Rule javaRuntimeRule = (Rule) javaRuntimeTarget;
+    if (!javaRuntimeRule.getRuleClass().equals("java_runtime")) {
+      throw new InvalidConfigurationException(
+          String.format("Expected a java_runtime rule, was '%s'", javaRuntimeRule.getRuleClass()));
+    }
+    RawAttributeMapper attributes = RawAttributeMapper.of(javaRuntimeRule);
+    PathFragment javaHomePath = defaultJavaHome(javaRuntimeLabel);
+    if (attributes.isAttributeValueExplicitlySpecified("java_home")) {
+      javaHomePath = javaHomePath.getRelative(attributes.get("java_home", Type.STRING));
+    }
+    return new Jvm(javaHomePath, javaRuntimeLabel);
+  }
+
+  private static Label selectRuntime(Rule javaRuntimeSuite, String cpu)
       throws InvalidConfigurationException {
+    RawAttributeMapper suiteAttributes = RawAttributeMapper.of(javaRuntimeSuite);
+    Map<String, Label> runtimes = suiteAttributes.get("runtimes", BuildType.LABEL_DICT_UNARY);
+    if (runtimes.containsKey(cpu)) {
+      return runtimes.get(cpu);
+    }
+    if (suiteAttributes.isAttributeValueExplicitlySpecified("default")) {
+      return suiteAttributes.get("default", BuildType.LABEL);
+    }
+    throw new InvalidConfigurationException(
+        "No JVM target found under " + javaRuntimeSuite + " that would work for " + cpu);
+  }
+
+  private static PathFragment defaultJavaHome(Label javaBase) {
+    if (javaBase.getPackageIdentifier().getRepository().isDefault()) {
+      return javaBase.getPackageFragment();
+    }
+    return javaBase.getPackageIdentifier().getSourceRoot();
+  }
+
+  private static Jvm createLegacy(String javaHome) throws InvalidConfigurationException {
     if (!javaHome.startsWith("/")) {
-      throw new InvalidConfigurationException("Illegal javabase value '" + javaHome +
-          "', javabase must be an absolute path or label");
+      throw new InvalidConfigurationException(
+          "Illegal javabase value '" + javaHome + "', javabase must be an absolute path or label");
     }
     return new Jvm(new PathFragment(javaHome), null);
   }