Add support for packaging JDK files into deploy jars

PiperOrigin-RevId: 447514841
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
index c2869ac..432a5dc 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
@@ -568,7 +568,10 @@
       OneVersionEnforcementLevel oneVersionEnforcementLevel,
       Artifact oneVersionAllowlistArtifact,
       Artifact sharedArchive,
-      boolean multiReleaseDeployJars) {
+      boolean multiReleaseDeployJars,
+      PathFragment javaHome,
+      Artifact libModules,
+      NestedSet<Artifact> hermeticInputs) {
     return DeployArchiveBuilder.defaultSingleJarCommandLineWithoutOneVersion(
             output,
             mainClass,
@@ -579,7 +582,10 @@
             includeBuildData,
             compression,
             launcher,
-            /* multiReleaseDeployJars= */ multiReleaseDeployJars)
+            /* multiReleaseDeployJars= */ multiReleaseDeployJars,
+            javaHome,
+            libModules,
+            hermeticInputs)
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
index 5d4eb9b..b3519fe 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/DeployArchiveBuilder.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.rules.java;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
@@ -30,10 +31,12 @@
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.rules.cpp.CppHelper;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.HashSet;
 import java.util.Set;
 import javax.annotation.Nullable;
@@ -70,6 +73,9 @@
   @Nullable private Artifact oneVersionAllowlistArtifact;
   @Nullable private Artifact sharedArchive;
   private boolean multiReleaseDeployJars;
+  @Nullable private PathFragment javaHome;
+  @Nullable private Artifact libModules;
+  private NestedSet<Artifact> hermeticInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
 
   /** Type of compression to apply to output archive. */
   public enum Compression {
@@ -180,6 +186,21 @@
     return this;
   }
 
+  public DeployArchiveBuilder setJavaHome(PathFragment javaHome) {
+    this.javaHome = requireNonNull(javaHome);
+    return this;
+  }
+
+  public DeployArchiveBuilder setLibModules(@Nullable Artifact libModules) {
+    this.libModules = libModules;
+    return this;
+  }
+
+  public DeployArchiveBuilder setHermeticInputs(NestedSet<Artifact> hermeticInputs) {
+    this.hermeticInputs = requireNonNull(hermeticInputs);
+    return this;
+  }
+
   public static CustomCommandLine.Builder defaultSingleJarCommandLineWithoutOneVersion(
       Artifact outputJar,
       String javaMainClass,
@@ -190,7 +211,10 @@
       boolean includeBuildData,
       Compression compress,
       Artifact launcher,
-      boolean multiReleaseDeployJars) {
+      boolean multiReleaseDeployJars,
+      PathFragment javaHome,
+      Artifact libModules,
+      NestedSet<Artifact> hermeticInputs) {
     return defaultSingleJarCommandLine(
         outputJar,
         javaMainClass,
@@ -203,7 +227,10 @@
         launcher,
         OneVersionEnforcementLevel.OFF,
         null,
-        /* multiReleaseDeployJars= */ multiReleaseDeployJars);
+        /* multiReleaseDeployJars= */ multiReleaseDeployJars,
+        javaHome,
+        libModules,
+        hermeticInputs);
   }
 
   public static CustomCommandLine.Builder defaultSingleJarCommandLine(
@@ -218,7 +245,10 @@
       Artifact launcher,
       OneVersionEnforcementLevel oneVersionEnforcementLevel,
       @Nullable Artifact oneVersionAllowlistArtifact,
-      boolean multiReleaseDeployJars) {
+      boolean multiReleaseDeployJars,
+      PathFragment javaHome,
+      Artifact libModules,
+      NestedSet<Artifact> hermeticInputs) {
 
     CustomCommandLine.Builder args = CustomCommandLine.builder();
     args.addExecPath("--output", outputJar);
@@ -265,6 +295,9 @@
     if (multiReleaseDeployJars) {
       args.add("--multi_release");
     }
+    args.addPath("--hermetic_java_home", javaHome);
+    args.addExecPath("--jdk_lib_modules", libModules);
+    args.addExecPaths("--resources", hermeticInputs);
     return args;
   }
 
@@ -347,6 +380,10 @@
     if (sharedArchive != null) {
       inputs.add(sharedArchive);
     }
+    inputs.addTransitive(hermeticInputs);
+    if (libModules != null) {
+      inputs.add(libModules);
+    }
 
     Artifact singlejar = JavaToolchainProvider.from(ruleContext).getSingleJar();
 
@@ -374,7 +411,10 @@
             oneVersionEnforcementLevel,
             oneVersionAllowlistArtifact,
             sharedArchive,
-            /* multiReleaseDeployJars= */ multiReleaseDeployJars);
+            /* multiReleaseDeployJars= */ multiReleaseDeployJars,
+            javaHome,
+            libModules,
+            hermeticInputs);
     if (checkDesugarDeps) {
       commandLine = CommandLine.concat(commandLine, ImmutableList.of("--check_desugar_deps"));
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
index 836ba59..707cb09 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
@@ -15,6 +15,7 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
 import static com.google.devtools.build.lib.rules.cpp.CppRuleClasses.JAVA_LAUNCHER_LINK;
 import static com.google.devtools.build.lib.rules.cpp.CppRuleClasses.STATIC_LINKING_MODE;
 import static com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression.COMPRESSED;
@@ -386,6 +387,22 @@
 
     Artifact jsa = createSharedArchive(ruleContext, javaArtifacts, attributes);
 
+    if (ruleContext.isAttrDefined("hermetic", BOOLEAN)
+        && ruleContext.attributes().get("hermetic", BOOLEAN)) {
+      if (!createExecutable) {
+        ruleContext.ruleError("hermetic specified but create_executable is false");
+      }
+      JavaRuntimeInfo javaRuntime = JavaRuntimeInfo.from(ruleContext);
+      if (javaRuntime.libModules() == null) {
+        ruleContext.attributeError(
+            "hermetic", "hermetic specified but java_runtime.lib_modules is absent");
+      }
+      deployArchiveBuilder
+          .setJavaHome(javaRuntime.javaHomePathFragment())
+          .setLibModules(javaRuntime.libModules())
+          .setHermeticInputs(javaRuntime.hermeticInputs());
+    }
+
     deployArchiveBuilder
         .setOutputJar(deployJar)
         .setJavaStartClass(mainClass)
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
index cd09c46..f25dedf 100644
--- 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
@@ -86,6 +86,12 @@
     PathFragment javaHomeRunfilesPath =
         javaBinaryRunfilesPath.getParentDirectory().getParentDirectory();
 
+    NestedSet<Artifact> hermeticInputs =
+        PrerequisiteArtifacts.nestedSet(ruleContext, "hermetic_srcs");
+    filesBuilder.addTransitive(hermeticInputs);
+
+    Artifact libModules = ruleContext.getPrerequisiteArtifact("lib_modules");
+
     NestedSet<Artifact> filesToBuild = filesBuilder.build();
 
     // TODO(cushon): clean up uses of java_runtime in data deps and remove this
@@ -100,7 +106,9 @@
             javaHome,
             javaBinaryExecPath,
             javaHomeRunfilesPath,
-            javaBinaryRunfilesPath);
+            javaBinaryRunfilesPath,
+            hermeticInputs,
+            libModules);
 
     TemplateVariableInfo templateVariableInfo =
         new TemplateVariableInfo(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfo.java
index 27be255..b911ed6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfo.java
@@ -43,13 +43,17 @@
       PathFragment javaHome,
       PathFragment javaBinaryExecPath,
       PathFragment javaHomeRunfilesPath,
-      PathFragment javaBinaryRunfilesPath) {
+      PathFragment javaBinaryRunfilesPath,
+      NestedSet<Artifact> hermeticInputs,
+      @Nullable Artifact libModules) {
     return new JavaRuntimeInfo(
         javaBaseInputs,
         javaHome,
         javaBinaryExecPath,
         javaHomeRunfilesPath,
-        javaBinaryRunfilesPath);
+        javaBinaryRunfilesPath,
+        hermeticInputs,
+        libModules);
   }
 
   @Override
@@ -98,18 +102,24 @@
   private final PathFragment javaBinaryExecPath;
   private final PathFragment javaHomeRunfilesPath;
   private final PathFragment javaBinaryRunfilesPath;
+  private final NestedSet<Artifact> hermeticInputs;
+  @Nullable private final Artifact libModules;
 
   private JavaRuntimeInfo(
       NestedSet<Artifact> javaBaseInputs,
       PathFragment javaHome,
       PathFragment javaBinaryExecPath,
       PathFragment javaHomeRunfilesPath,
-      PathFragment javaBinaryRunfilesPath) {
+      PathFragment javaBinaryRunfilesPath,
+      NestedSet<Artifact> hermeticInputs,
+      @Nullable Artifact libModules) {
     this.javaBaseInputs = javaBaseInputs;
     this.javaHome = javaHome;
     this.javaBinaryExecPath = javaBinaryExecPath;
     this.javaHomeRunfilesPath = javaHomeRunfilesPath;
     this.javaBinaryRunfilesPath = javaBinaryRunfilesPath;
+    this.hermeticInputs = hermeticInputs;
+    this.libModules = libModules;
   }
 
   /** All input artifacts in the javabase. */
@@ -153,6 +163,22 @@
     return javaBinaryRunfilesPath;
   }
 
+  /** Input artifacts required for hermetic deployments. */
+  public NestedSet<Artifact> hermeticInputs() {
+    return hermeticInputs;
+  }
+
+  @Override
+  public Depset starlarkHermeticInputs() {
+    return Depset.of(Artifact.TYPE, hermeticInputs());
+  }
+
+  @Override
+  @Nullable
+  public Artifact libModules() {
+    return libModules;
+  }
+
   @Override
   public Depset starlarkJavaBaseInputs() {
     return Depset.of(Artifact.TYPE, 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
index ddefe4a..f9be8a2 100644
--- 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
@@ -42,6 +42,18 @@
         All files in the runtime.
         <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
         .add(attr("srcs", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE))
+        /* <!-- #BLAZE_RULE(java_runtime).ATTRIBUTE(hermetic_srcs) -->
+        Files in the runtime needed for hermetic deployments.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("hermetic_srcs", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE))
+        /* <!-- #BLAZE_RULE(java_runtime).ATTRIBUTE(lib_modules) -->
+        The lib/modules file needed for hermetic deployments.
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(
+            attr("lib_modules", LABEL)
+                .singleArtifact()
+                .allowedFileTypes(FileTypeSet.ANY_FILE)
+                .exec())
         /* <!-- #BLAZE_RULE(java_runtime).ATTRIBUTE(java) -->
         The path to the java executable.
         <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
index 2598032..02ec7a0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
@@ -280,7 +280,10 @@
       OneVersionEnforcementLevel oneVersionEnforcementLevel,
       Artifact oneVersionAllowlistArtifact,
       Artifact sharedArchive,
-      boolean multiReleaseDeployJars);
+      boolean multiReleaseDeployJars,
+      PathFragment javaHome,
+      Artifact libModules,
+      NestedSet<Artifact> hermeticInputs);
 
   /**
    * Creates the action that writes the Java executable stub script.
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaRuntimeInfoApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaRuntimeInfoApi.java
index e9f8a28..9a35939 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaRuntimeInfoApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaRuntimeInfoApi.java
@@ -16,7 +16,9 @@
 
 import com.google.devtools.build.docgen.annot.DocCategory;
 import com.google.devtools.build.lib.collect.nestedset.Depset;
+import com.google.devtools.build.lib.starlarkbuildapi.FileApi;
 import com.google.devtools.build.lib.starlarkbuildapi.core.StructApi;
+import javax.annotation.Nullable;
 import net.starlark.java.annot.StarlarkBuiltin;
 import net.starlark.java.annot.StarlarkMethod;
 
@@ -68,4 +70,20 @@
       doc = "Returns the files in the Java runtime.",
       structField = true)
   Depset starlarkJavaBaseInputs();
+
+  /** The files in the Java runtime needed for hermetic deployments. */
+  @StarlarkMethod(
+      name = "hermetic_files",
+      doc = "Returns the files in the Java runtime needed for hermetic deployments.",
+      structField = true)
+  Depset starlarkHermeticInputs();
+
+  /** The lib/modules file. */
+  @StarlarkMethod(
+      name = "lib_modules",
+      doc = "Returns the lib/modules file.",
+      structField = true,
+      allowReturnNones = true)
+  @Nullable
+  FileApi libModules();
 }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfoTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfoTest.java
index 27bc0d3..d6fd39d 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfoTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaRuntimeInfoTest.java
@@ -33,14 +33,18 @@
             PathFragment.create(""),
             PathFragment.create(""),
             PathFragment.create(""),
-            PathFragment.create(""));
+            PathFragment.create(""),
+            NestedSetBuilder.emptySet(Order.STABLE_ORDER),
+            null);
     JavaRuntimeInfo b =
         JavaRuntimeInfo.create(
             NestedSetBuilder.emptySet(Order.STABLE_ORDER),
             PathFragment.create(""),
             PathFragment.create(""),
             PathFragment.create(""),
-            PathFragment.create(""));
+            PathFragment.create(""),
+            NestedSetBuilder.emptySet(Order.STABLE_ORDER),
+            null);
 
     new EqualsTester().addEqualityGroup(a).addEqualityGroup(b).testEquals();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/view/java/BUILD b/src/test/java/com/google/devtools/build/lib/view/java/BUILD
index 222f0ad..f23c3b9 100644
--- a/src/test/java/com/google/devtools/build/lib/view/java/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/view/java/BUILD
@@ -113,6 +113,7 @@
     deps = [
         "//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/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/rules/java:java-compilation",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
diff --git a/src/test/java/com/google/devtools/build/lib/view/java/SingleJarCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/view/java/SingleJarCommandLineTest.java
index a60dfcc..0481318 100644
--- a/src/test/java/com/google/devtools/build/lib/view/java/SingleJarCommandLineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/view/java/SingleJarCommandLineTest.java
@@ -25,6 +25,8 @@
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.CommandLine;
 import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel;
 import com.google.devtools.build.lib.testutil.FoundationTestCase;
 import com.google.devtools.build.lib.testutil.MoreAsserts;
@@ -55,7 +57,10 @@
                 null,
                 OneVersionEnforcementLevel.OFF,
                 null,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
 
     assertThat(command.arguments()).doesNotContain("--exclude_build_data");
@@ -79,7 +84,10 @@
                 null,
                 OneVersionEnforcementLevel.OFF,
                 null,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
 
     assertThat(command.arguments()).contains("--exclude_build_data");
@@ -103,7 +111,10 @@
                 dummy,
                 OneVersionEnforcementLevel.OFF,
                 null,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
     assertThat(command.arguments()).contains("--java_launcher");
   }
@@ -126,7 +137,10 @@
                 dummy,
                 OneVersionEnforcementLevel.OFF,
                 null,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
     assertThat(command.arguments()).contains("--compression");
   }
@@ -149,7 +163,10 @@
                 dummy,
                 OneVersionEnforcementLevel.OFF,
                 null,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
     assertThat(command.arguments()).doesNotContain("--compression");
   }
@@ -176,7 +193,10 @@
                 dummy,
                 OneVersionEnforcementLevel.WARNING,
                 dummyOneVersion,
-                /* multiReleaseDeployJars= */ false)
+                /* multiReleaseDeployJars= */ false,
+                /* javaHome= */ null,
+                /* libModules= */ null,
+                /* hermeticInputs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER))
             .build();
     assertThat(command.arguments())
         .containsAtLeast("--enforce_one_version", "--succeed_on_found_violations");