Repository: pantsbuild/pants

Issue:
Use native `InteractiveProcess` `{chroot}` support to avoid re-downloading JDKs for `run`
#16093 adds support for `immutable_inputs` and `append_only_caches` to `InteractiveProcess`. That support should be used to avoid downloading a separate JDK for the `deploy_jar` `run` implementation.


Discussion:
Great news! It'll be a joy to remove the `RuntimeJvm` hack!

Resolving patch:
diff --git a/src/python/pants/core/goals/run.py b/src/python/pants/core/goals/run.py
--- a/src/python/pants/core/goals/run.py
+++ b/src/python/pants/core/goals/run.py
@@ -1,5 +1,8 @@
 # Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
 # Licensed under the Apache License, Version 2.0 (see LICENSE).
+
+from __future__ import annotations
+
 import logging
 from abc import ABCMeta
 from dataclasses import dataclass
@@ -55,6 +58,8 @@ class RunRequest:
     # be substituted with the (absolute) chroot path.
     args: Tuple[str, ...]
     extra_env: FrozenDict[str, str]
+    immutable_input_digests: Mapping[str, Digest] | None = None
+    append_only_caches: Mapping[str, str] | None = None
 
     def __init__(
         self,
@@ -62,10 +67,14 @@ def __init__(
         digest: Digest,
         args: Iterable[str],
         extra_env: Optional[Mapping[str, str]] = None,
+        immutable_input_digests: Mapping[str, Digest] | None = None,
+        append_only_caches: Mapping[str, str] | None = None,
     ) -> None:
         self.digest = digest
         self.args = tuple(args)
         self.extra_env = FrozenDict(extra_env or {})
+        self.immutable_input_digests = immutable_input_digests
+        self.append_only_caches = append_only_caches
 
 
 class RunDebugAdapterRequest(RunRequest):
@@ -183,6 +192,8 @@ async def run(
             run_in_workspace=True,
             restartable=restartable,
             cleanup=cleanup,
+            immutable_input_digests=request.immutable_input_digests,
+            append_only_caches=request.append_only_caches,
         ),
     )
 
diff --git a/src/python/pants/jvm/run_deploy_jar.py b/src/python/pants/jvm/run_deploy_jar.py
--- a/src/python/pants/jvm/run_deploy_jar.py
+++ b/src/python/pants/jvm/run_deploy_jar.py
@@ -1,38 +1,21 @@
 # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
 # Licensed under the Apache License, Version 2.0 (see LICENSE).
 
-import dataclasses
 import logging
-from dataclasses import dataclass
 from typing import Iterable
 
 from pants.core.goals.package import BuiltPackage
 from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest
-from pants.engine.fs import EMPTY_DIGEST, Digest, MergeDigests
-from pants.engine.internals.native_engine import AddPrefix
-from pants.engine.process import Process, ProcessResult
-from pants.engine.rules import Get, MultiGet, collect_rules, rule
+from pants.engine.process import Process
+from pants.engine.rules import Get, collect_rules, rule
 from pants.engine.unions import UnionRule
 from pants.jvm.jdk_rules import JdkEnvironment, JdkRequest, JvmProcess
 from pants.jvm.package.deploy_jar import DeployJarFieldSet
-from pants.util.frozendict import FrozenDict
 from pants.util.logging import LogLevel
 
 logger = logging.getLogger(__name__)
 
 
-@dataclass(frozen=True)
-class __RuntimeJvm:
-    """Allows Coursier to download a JDK into a Digest, rather than an append-only cache for use
-    with `pants run`.
-
-    This is a hideous stop-gap, which will no longer be necessary once `InteractiveProcess` supports
-    append-only caches. (See #13852 for details on how to do this.)
-    """
-
-    digest: Digest
-
-
 @rule(level=LogLevel.DEBUG)
 async def create_deploy_jar_run_request(
     field_set: DeployJarFieldSet,
@@ -60,14 +43,6 @@ async def create_deploy_jar_run_request(
         ),
     )
 
-    support_digests = await MultiGet(
-        Get(Digest, AddPrefix(digest, prefix))
-        for prefix, digest in proc.immutable_input_digests.items()
-    )
-
-    runtime_jvm = await Get(__RuntimeJvm, JdkEnvironment, jdk)
-    support_digests += (runtime_jvm.digest,)
-
     # TODO(#16104) This argument re-writing code should use the native {chroot} support.
     # See also `jdk_rules.py` for other argument re-writing code.
     def prefixed(arg: str, prefixes: Iterable[str]) -> str:
@@ -89,20 +64,12 @@ def prefixed(arg: str, prefixes: Iterable[str]) -> str:
         if key.startswith("COURSIER"):
             env[key] = prefixed(env[key], (jdk.coursier.cache_dir,))
 
-    request_digest = await Get(
-        Digest,
-        MergeDigests(
-            [
-                proc.input_digest,
-                *support_digests,
-            ]
-        ),
-    )
-
     return RunRequest(
-        digest=request_digest,
+        digest=proc.input_digest,
         args=args,
         extra_env=env,
+        immutable_input_digests=proc.immutable_input_digests,
+        append_only_caches=proc.append_only_caches,
     )
 
 
@@ -115,34 +82,5 @@ async def run_deploy_jar_debug_adapter_binary(
     )
 
 
-@rule
-async def ensure_jdk_for_pants_run(jdk: JdkEnvironment) -> __RuntimeJvm:
-    # `tools.jar` is distributed with the JDK, so we can rely on it existing.
-    ensure_jvm_process = await Get(
-        Process,
-        JvmProcess(
-            jdk=jdk,
-            classpath_entries=[f"{jdk.java_home}/lib/tools.jar"],
-            argv=["com.sun.tools.javac.Main", "--version"],
-            input_digest=EMPTY_DIGEST,
-            description="Ensure download of JDK for `pants run` use",
-        ),
-    )
-
-    # Do not treat the coursier JDK locations as an append-only cache, so that we can capture the
-    # downloaded JDK in a `Digest`
-
-    ensure_jvm_process = dataclasses.replace(
-        ensure_jvm_process,
-        append_only_caches=FrozenDict(),
-        output_directories=(".cache/jdk", ".cache/arc"),
-        use_nailgun=(),
-    )
-
-    ensure_jvm = await Get(ProcessResult, Process, ensure_jvm_process)
-
-    return __RuntimeJvm(ensure_jvm.output_digest)
-
-
 def rules():
     return [*collect_rules(), UnionRule(RunFieldSet, DeployJarFieldSet)]


Test patch: