Allow `BootClassPathInfo.system` to be a sequence of files

PiperOrigin-RevId: 388718540
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
index cfe3e2a..2507ce0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
@@ -13,6 +13,9 @@
 // limitations under the License.
 package com.google.devtools.build.lib.rules.java;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
@@ -24,7 +27,8 @@
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
 import com.google.devtools.build.lib.starlarkbuildapi.FileApi;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi;
-import javax.annotation.Nullable;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.Optional;
 import net.starlark.java.annot.Param;
 import net.starlark.java.annot.ParamType;
 import net.starlark.java.annot.StarlarkBuiltin;
@@ -66,8 +70,13 @@
               allowedTypes = {
                 @ParamType(type = FileApi.class),
                 @ParamType(type = NoneType.class),
+                @ParamType(type = Sequence.class),
               },
-              defaultValue = "None"),
+              defaultValue = "None",
+              doc =
+                  "The inputs to javac's --system flag, either a directory or a listing of files,"
+                      + " which must contain at least 'release', 'lib/modules', and"
+                      + " 'lib/jrt-fs.jar'"),
         },
         selfCall = true,
         useStarlarkThread = true)
@@ -77,10 +86,13 @@
         Object systemOrNone,
         StarlarkThread thread)
         throws EvalException {
+      NestedSet<Artifact> systemInputs = getSystemInputs(systemOrNone);
+      Optional<PathFragment> systemPath = getSystemPath(systemInputs);
       return new BootClassPathInfo(
           getBootClassPath(bootClassPathList),
           getAuxiliary(auxiliaryList),
-          getSystem(systemOrNone),
+          systemInputs,
+          systemPath,
           thread.getCallerLocation());
     }
 
@@ -96,32 +108,65 @@
           Order.STABLE_ORDER, Sequence.cast(auxiliaryList, Artifact.class, "auxiliary"));
     }
 
-    private static Artifact getSystem(Object systemOrNone) throws EvalException {
+    private static NestedSet<Artifact> getSystemInputs(Object systemOrNone) throws EvalException {
       if (systemOrNone == Starlark.NONE) {
-        return null;
+        return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
       }
       if (systemOrNone instanceof Artifact) {
-        return (Artifact) systemOrNone;
+        return NestedSetBuilder.create(Order.STABLE_ORDER, (Artifact) systemOrNone);
       }
-      throw Starlark.errorf("for system, got %s, want File or None", Starlark.type(systemOrNone));
+      if (systemOrNone instanceof Sequence<?>) {
+        return NestedSetBuilder.wrap(
+            Order.STABLE_ORDER, Sequence.cast(systemOrNone, Artifact.class, "system"));
+      }
+      throw Starlark.errorf(
+          "for system, got %s, want File, sequence, or None", Starlark.type(systemOrNone));
+    }
+
+    private static Optional<PathFragment> getSystemPath(NestedSet<Artifact> systemInputs)
+        throws EvalException {
+      ImmutableList<Artifact> inputs = systemInputs.toList();
+      if (inputs.isEmpty()) {
+        return Optional.empty();
+      }
+      if (inputs.size() == 1) {
+        Artifact input = getOnlyElement(inputs);
+        if (!input.isTreeArtifact()) {
+          throw Starlark.errorf("for system, %s is not a directory", input.getExecPathString());
+        }
+        return Optional.of(input.getExecPath());
+      }
+      Optional<PathFragment> input =
+          inputs.stream()
+              .map(Artifact::getExecPath)
+              .filter(p -> p.getBaseName().equals("release"))
+              .map(PathFragment::getParentDirectory)
+              .findAny();
+      if (!input.isPresent()) {
+        throw Starlark.errorf("for system, expected inputs to contain 'release'");
+      }
+      return input;
     }
   }
 
   private final NestedSet<Artifact> bootclasspath;
   private final NestedSet<Artifact> auxiliary;
-  @Nullable private final Artifact system;
+  private final NestedSet<Artifact> systemInputs;
+  private final Optional<PathFragment> systemPath;
 
   @VisibleForSerialization
   @AutoCodec.Instantiator
   public BootClassPathInfo(
       NestedSet<Artifact> bootclasspath,
       NestedSet<Artifact> auxiliary,
-      Artifact system,
+      NestedSet<Artifact> systemInputs,
+      Optional<PathFragment> systemPath,
       Location creationLocation) {
     super(creationLocation);
     this.bootclasspath = bootclasspath;
     this.auxiliary = auxiliary;
-    this.system = system;
+    this.systemInputs = systemInputs;
+    this.systemPath = systemPath;
   }
 
   @Override
@@ -131,14 +176,19 @@
 
   public static BootClassPathInfo create(NestedSet<Artifact> bootclasspath) {
     return new BootClassPathInfo(
-        bootclasspath, NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), null, null);
+        bootclasspath,
+        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
+        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
+        Optional.empty(),
+        null);
   }
 
   public static BootClassPathInfo empty() {
     return new BootClassPathInfo(
         NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
         NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
-        null,
+        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
+        Optional.empty(),
         null);
   }
 
@@ -156,9 +206,13 @@
   }
 
   /** An argument to the javac >= 9 {@code --system} flag. */
-  @Nullable
-  public Artifact system() {
-    return system;
+  public Optional<PathFragment> systemPath() {
+    return systemPath;
+  }
+
+  /** Contents of the directory that is passed to the javac >= 9 {@code --system} flag. */
+  public NestedSet<Artifact> systemInputs() {
+    return systemInputs;
   }
 
   public boolean isEmpty() {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java
index 11de681..cc34c32 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java
@@ -45,7 +45,9 @@
 import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.util.StringCanonicalizer;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 
@@ -80,7 +82,7 @@
     private final NestedSet<Artifact> bootclasspathEntries;
 
     /** An argument to the javac >= 9 {@code --system} flag. */
-    @Nullable private final Artifact system;
+    @Nullable private final Optional<PathFragment> system;
 
     /** The list of classpath entries to search for annotation processors. */
     private final NestedSet<Artifact> processorPath;
@@ -101,7 +103,7 @@
         Artifact outputJar,
         NestedSet<Artifact> classpathEntries,
         NestedSet<Artifact> bootclasspathEntries,
-        @Nullable Artifact system,
+        Optional<PathFragment> system,
         NestedSet<Artifact> processorPath,
         NestedSet<String> processorNames,
         ImmutableList<Artifact> sourceJars,
@@ -130,8 +132,8 @@
               .addAllProcessor(processorNames.toList())
               .addAllProcessorpath(Artifact.toExecPaths(processorPath.toList()))
               .setOutputjar(outputJar.getExecPathString());
-      if (system != null) {
-        info.setSystem(system.getExecPathString());
+      if (system.isPresent()) {
+        info.setSystem(system.get().toString());
       }
       info.addAllArgument(arguments);
       builder.setExtension(JavaCompileInfo.javaCompileInfo, info.build());
@@ -215,17 +217,18 @@
         .addTransitive(toolchain.getJavaRuntime().javaBaseInputs())
         .addTransitive(bootClassPath.bootclasspath())
         .addAll(sourcePathEntries)
-        .addAll(additionalInputs);
-    Stream.of(coverageArtifact, bootClassPath.system())
-        .filter(x -> x != null)
-        .forEachOrdered(mandatoryInputs::add);
+        .addAll(additionalInputs)
+        .addTransitive(bootClassPath.systemInputs());
+    if (coverageArtifact != null) {
+      mandatoryInputs.add(coverageArtifact);
+    }
 
     JavaCompileExtraActionInfoSupplier extraActionInfoSupplier =
         new JavaCompileExtraActionInfoSupplier(
             outputs.output(),
             classpathEntries,
             bootClassPath.bootclasspath(),
-            bootClassPath.system(),
+            bootClassPath.systemPath(),
             plugins.processorClasspath(),
             plugins.processorClasses(),
             sourceJars,
@@ -303,7 +306,9 @@
     }
     result.addExecPath("--output_deps_proto", outputs.depsProto());
     result.addExecPaths("--bootclasspath", bootClassPath.bootclasspath());
-    result.addExecPath("--system", bootClassPath.system());
+    if (bootClassPath.systemPath().isPresent()) {
+      result.addPath("--system", bootClassPath.systemPath().get());
+    }
     result.addExecPaths("--sourcepath", sourcePathEntries);
     result.addExecPaths("--processorpath", plugins.processorClasspath());
     result.addAll("--processors", plugins.processorClasses());