diff --git a/src/python/pants/backend/python/goals/package_pex_binary.py b/src/python/pants/backend/python/goals/package_pex_binary.py index ec225ad5832..129b636b417 100644 --- a/src/python/pants/backend/python/goals/package_pex_binary.py +++ b/src/python/pants/backend/python/goals/package_pex_binary.py @@ -10,11 +10,13 @@ PexEmitWarningsField, PexEntryPointField, PexIgnoreErrorsField, + PexIncludeToolsField, PexInheritPathField, ) from pants.backend.python.target_types import PexPlatformsField as PythonPlatformsField from pants.backend.python.target_types import ( PexShebangField, + PexUnzipField, PexZipSafeField, ResolvedPexEntryPoint, ResolvePexEntryPointRequest, @@ -50,6 +52,8 @@ class PexBinaryFieldSet(PackageFieldSet, RunFieldSet): shebang: PexShebangField zip_safe: PexZipSafeField platforms: PythonPlatformsField + unzip: PexUnzipField + include_tools: PexIncludeToolsField def generate_additional_args(self, pex_binary_defaults: PexBinaryDefaults) -> Tuple[str, ...]: args = [] @@ -65,6 +69,10 @@ def generate_additional_args(self, pex_binary_defaults: PexBinaryDefaults) -> Tu args.append(f"--python-shebang={self.shebang.value}") if self.zip_safe.value is False: args.append("--not-zip-safe") + if self.unzip.value is True: + args.append("--unzip") + if self.include_tools.value is True: + args.append("--include-tools") return tuple(args) diff --git a/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py b/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py index 1356d00da82..dd6a8905d1f 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py +++ b/src/python/pants/backend/python/goals/run_pex_binary_integration_test.py @@ -1,21 +1,21 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). - +import json from textwrap import dedent import pytest -from pants.testutil.pants_integration_test import run_pants, setup_tmpdir +from pants.testutil.pants_integration_test import PantsResult, run_pants, setup_tmpdir @pytest.mark.parametrize( - "tgt_content", + ("entry_point", "unzip", "include_tools"), [ - "python_library(name='lib')\npex_binary(entry_point='app.py')", - "python_library(name='lib')\npex_binary(entry_point='app.py:main')", + ("app.py", True, True), + ("app.py:main", False, False), ], ) -def test_run_sample_script(tgt_content: str) -> None: +def test_run_sample_script(entry_point: str, unzip: bool, include_tools: bool) -> None: """Test that we properly run a `pex_binary` target. This checks a few things: @@ -39,7 +39,16 @@ def main(): main() """ ), - "src_root1/project/BUILD": tgt_content, + "src_root1/project/BUILD": dedent( + f"""\ + python_library(name='lib') + pex_binary( + entry_point={entry_point!r}, + unzip={unzip!r}, + include_tools={include_tools!r}, + ) + """ + ), "src_root2/utils/strutil.py": dedent( """\ def upper_case(s): @@ -48,18 +57,27 @@ def upper_case(s): ), "src_root2/utils/BUILD": "python_library()", } - with setup_tmpdir(sources) as tmpdir: - result = run_pants( - [ + + def run(*extra_args: str, **extra_env: str) -> PantsResult: + with setup_tmpdir(sources) as tmpdir: + args = [ "--backend-packages=pants.backend.python", f"--source-root-patterns=['/{tmpdir}/src_root1', '/{tmpdir}/src_root2']", "--pants-ignore=__pycache__", "--pants-ignore=/src/python", "run", f"{tmpdir}/src_root1/project/app.py", + *extra_args, ] - ) + return run_pants(args, extra_env=extra_env) + result = run() assert "Hola, mundo.\n" in result.stderr assert result.stdout == "HELLO WORLD.\n" assert result.exit_code == 23 + + if include_tools: + result = run("--", "info", PEX_TOOLS="1") + assert result.exit_code == 0 + pex_info = json.loads(result.stdout) + assert unzip == pex_info["unzip"] diff --git a/src/python/pants/backend/python/target_types.py b/src/python/pants/backend/python/target_types.py index 3c9952867bc..f32c5a560d7 100644 --- a/src/python/pants/backend/python/target_types.py +++ b/src/python/pants/backend/python/target_types.py @@ -254,6 +254,27 @@ def value_or_global_default(self, pex_binary_defaults: PexBinaryDefaults) -> boo return self.value +class PexUnzipField(BoolField): + alias = "unzip" + default = False + value: bool + help = ( + "Whether to have the PEX unzip itself into the PEX_ROOT before running.\n\nEnabling unzip " + "mode can provide lower startup latencies for most PEX files; even on first run." + ) + + +class PexIncludeToolsField(BoolField): + alias = "include_tools" + default = False + value: bool + help = ( + "Whether to include Pex tools in the PEX bootstrap code.\n\nWith tools included, the " + "generated PEX file can be executed with `PEX_TOOLS=1 --help` to gain access " + "to all the available tools." + ) + + class PexBinary(Target): alias = "pex_binary" core_fields = ( @@ -268,6 +289,8 @@ class PexBinary(Target): PexIgnoreErrorsField, PexShebangField, PexEmitWarningsField, + PexUnzipField, + PexIncludeToolsField, ) help = ( "A Python target that can be converted into an executable PEX file.\n\nPEX files are "