Repository: pantsbuild/pants

Issue:
`--layout=packed` should always be used for the `repository.pex`
**Describe the bug**
When building a `pex_binary` when `requirements_constraints` is set, the `repository.pex` that is built for the binary will not set `--layout=packed`, because the request that is triggering the constraints build is not `internal_only`.

But it _should_ use `--layout=packed`, since it is only the source of the artifacts for the pex, rather than the exposed artifact. All cases of `repository.pex` builds should (since they are never exposed verbatim, and always consumed by other builds.)

**Pants version**
`2.7.1`


Discussion:

Resolving patch:
diff --git a/src/python/pants/backend/python/util_rules/pex.py b/src/python/pants/backend/python/util_rules/pex.py
--- a/src/python/pants/backend/python/util_rules/pex.py
+++ b/src/python/pants/backend/python/util_rules/pex.py
@@ -99,10 +99,19 @@ class ToolCustomLockfile(Lockfile, _ToolLockfileMixin):
 @dataclass(unsafe_hash=True)
 class PexRequirements:
     req_strings: FrozenOrderedSet[str]
+    # TODO: The constraints.txt resolve for `resolve_all_constraints` will be removed as part of
+    # #12314, but in the meantime, it "acts like" a lockfile, but isn't actually typed as a Lockfile
+    # because the constraints are modified in memory first. This flag marks a `PexRequirements`
+    # resolve as being a request for the entire constraints file.
+    is_all_constraints_resolve: bool
     repository_pex: Pex | None
 
     def __init__(
-        self, req_strings: Iterable[str] = (), *, repository_pex: Pex | None = None
+        self,
+        req_strings: Iterable[str] = (),
+        *,
+        is_all_constraints_resolve: bool = False,
+        repository_pex: Pex | None = None,
     ) -> None:
         """
         :param req_strings: The requirement strings to resolve.
@@ -110,6 +119,7 @@ def __init__(
             `--pex-repository` option.
         """
         self.req_strings = FrozenOrderedSet(sorted(req_strings))
+        self.is_all_constraints_resolve = is_all_constraints_resolve
         self.repository_pex = repository_pex
 
     @classmethod
@@ -432,6 +442,7 @@ async def build_pex(
     )
 
     if isinstance(request.requirements, Lockfile):
+        is_monolithic_resolve = True
         argv.extend(["--requirement", request.requirements.file_path])
 
         globs = PathGlobs(
@@ -453,6 +464,7 @@ async def build_pex(
         requirements_file_digest = await Get(Digest, PathGlobs, globs)
 
     elif isinstance(request.requirements, LockfileContent):
+        is_monolithic_resolve = True
         file_content = request.requirements.file_content
         argv.extend(["--requirement", file_content.path])
 
@@ -466,6 +478,7 @@ async def build_pex(
             _validate_metadata(metadata, request, request.requirements, python_setup)
         requirements_file_digest = await Get(Digest, CreateDigest([file_content]))
     else:
+        is_monolithic_resolve = request.requirements.is_all_constraints_resolve
         argv.extend(request.requirements.req_strings)
 
     merged_digest = await Get(
@@ -484,7 +497,7 @@ async def build_pex(
 
     output_files: Iterable[str] | None = None
     output_directories: Iterable[str] | None = None
-    if request.internal_only:
+    if request.internal_only or is_monolithic_resolve:
         # This is a much friendlier layout for the CAS than the default zipapp.
         argv.extend(["--layout", "packed"])
         output_directories = [request.output_filename]
diff --git a/src/python/pants/backend/python/util_rules/pex_from_targets.py b/src/python/pants/backend/python/util_rules/pex_from_targets.py
--- a/src/python/pants/backend/python/util_rules/pex_from_targets.py
+++ b/src/python/pants/backend/python/util_rules/pex_from_targets.py
@@ -354,7 +354,11 @@ async def _setup_constraints_repository_pex(
             description=f"Resolving {constraints_path}",
             output_filename="repository.pex",
             internal_only=request.internal_only,
-            requirements=PexRequirements(all_constraints),
+            requirements=PexRequirements(
+                all_constraints,
+                # TODO: See PexRequirements docs.
+                is_all_constraints_resolve=True,
+            ),
             interpreter_constraints=request.interpreter_constraints,
             platforms=request.platforms,
             additional_args=request.additional_lockfile_args,


Test patch:
diff --git a/src/python/pants/backend/python/util_rules/pex_test.py b/src/python/pants/backend/python/util_rules/pex_test.py
--- a/src/python/pants/backend/python/util_rules/pex_test.py
+++ b/src/python/pants/backend/python/util_rules/pex_test.py
@@ -412,6 +412,22 @@ def test_resolves_dependencies(rule_runner: RuleRunner) -> None:
     )
 
 
+@pytest.mark.parametrize("is_all_constraints_resolve", [True, False])
+@pytest.mark.parametrize("internal_only", [True, False])
+def test_use_packed_pex_requirements(
+    rule_runner: RuleRunner, is_all_constraints_resolve: bool, internal_only: bool
+) -> None:
+    requirements = PexRequirements(
+        ["six==1.12.0"], is_all_constraints_resolve=is_all_constraints_resolve
+    )
+    pex_data = create_pex_and_get_all_data(
+        rule_runner, requirements=requirements, internal_only=internal_only
+    )
+    # If this is either internal_only, or an all_constraints resolve, we should use packed.
+    should_use_packed = is_all_constraints_resolve or internal_only
+    assert (not pex_data.is_zipapp) == should_use_packed
+
+
 def test_requirement_constraints(rule_runner: RuleRunner) -> None:
     direct_deps = ["requests>=1.0.0,<=2.23.0"]