• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 21042790249

15 Jan 2026 06:57PM UTC coverage: 43.263% (-35.4%) from 78.666%
21042790249

Pull #23021

github

web-flow
Merge cc03ad8de into d250c80fe
Pull Request #23021: WIP gh workflow scie pex

23 of 33 new or added lines in 3 files covered. (69.7%)

16147 existing lines in 521 files now uncovered.

26164 of 60477 relevant lines covered (43.26%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.73
/src/python/pants/backend/python/goals/package_pex_binary.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
import dataclasses
2✔
5
import logging
2✔
6
import os
2✔
7
from dataclasses import dataclass
2✔
8

9
from pants.backend.python.target_types import (
2✔
10
    PexArgsField,
11
    PexBinaryDefaults,
12
    PexCheckField,
13
    PexCompletePlatformsField,
14
    PexEmitWarningsField,
15
    PexEntryPointField,
16
    PexEnvField,
17
    PexExecutableField,
18
    PexExecutionMode,
19
    PexExecutionModeField,
20
    PexExtraBuildArgsField,
21
    PexIgnoreErrorsField,
22
    PexIncludeRequirementsField,
23
    PexIncludeSourcesField,
24
    PexIncludeToolsField,
25
    PexInheritPathField,
26
    PexLayout,
27
    PexLayoutField,
28
    PexScieArgsField,
29
    PexScieBindResourcePathField,
30
    PexScieBusyBox,
31
    PexScieEnvField,
32
    PexScieExeField,
33
    PexScieField,
34
    PexScieHashAlgField,
35
    PexScieLoadDotenvField,
36
    PexScieNameStyleField,
37
    PexSciePbsDebug,
38
    PexSciePbsFreeThreaded,
39
    PexSciePbsReleaseField,
40
    PexSciePbsStripped,
41
    PexSciePexEntrypointEnvPassthrough,
42
    PexSciePlatformField,
43
    PexSciePythonVersion,
44
    PexScriptField,
45
    PexShBootField,
46
    PexShebangField,
47
    PexStripEnvField,
48
    PexVenvHermeticScripts,
49
    PexVenvSitePackagesCopies,
50
    ResolvePexEntryPointRequest,
51
)
52
from pants.backend.python.target_types_rules import resolve_pex_entry_point
2✔
53
from pants.backend.python.util_rules.pex import create_pex, digest_complete_platforms
2✔
54
from pants.backend.python.util_rules.pex_from_targets import (
2✔
55
    PexFromTargetsRequest,
56
    create_pex_from_targets,
57
)
58
from pants.core.environments.target_types import EnvironmentField
2✔
59
from pants.core.goals.package import (
2✔
60
    BuiltPackage,
61
    BuiltPackageArtifact,
62
    OutputPathField,
63
    PackageFieldSet,
64
)
65
from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior
2✔
66
from pants.engine.intrinsics import digest_to_snapshot
2✔
67
from pants.engine.rules import collect_rules, implicitly, rule
2✔
68
from pants.engine.unions import UnionRule
2✔
69
from pants.util.frozendict import FrozenDict
2✔
70
from pants.util.logging import LogLevel
2✔
71

72
logger = logging.getLogger(__name__)
2✔
73

74

75
@dataclass(frozen=True)
2✔
76
class PexBinaryFieldSet(PackageFieldSet, RunFieldSet):
2✔
77
    run_in_sandbox_behavior = RunInSandboxBehavior.RUN_REQUEST_HERMETIC
2✔
78

79
    required_fields = (PexEntryPointField,)
2✔
80

81
    entry_point: PexEntryPointField
2✔
82
    script: PexScriptField
2✔
83
    executable: PexExecutableField
2✔
84
    args: PexArgsField
2✔
85
    env: PexEnvField
2✔
86

87
    output_path: OutputPathField
2✔
88
    emit_warnings: PexEmitWarningsField
2✔
89
    ignore_errors: PexIgnoreErrorsField
2✔
90
    inherit_path: PexInheritPathField
2✔
91
    sh_boot: PexShBootField
2✔
92
    shebang: PexShebangField
2✔
93
    strip_env: PexStripEnvField
2✔
94
    complete_platforms: PexCompletePlatformsField
2✔
95
    layout: PexLayoutField
2✔
96
    execution_mode: PexExecutionModeField
2✔
97
    include_requirements: PexIncludeRequirementsField
2✔
98
    include_sources: PexIncludeSourcesField
2✔
99
    include_tools: PexIncludeToolsField
2✔
100
    venv_site_packages_copies: PexVenvSitePackagesCopies
2✔
101
    venv_hermetic_scripts: PexVenvHermeticScripts
2✔
102
    environment: EnvironmentField
2✔
103
    check: PexCheckField
2✔
104
    extra_build_args: PexExtraBuildArgsField
2✔
105

106
    scie: PexScieField
2✔
107
    scie_bind_resource_path: PexScieBindResourcePathField
2✔
108
    scie_exe: PexScieExeField
2✔
109
    scie_args: PexScieArgsField
2✔
110
    scie_env: PexScieEnvField
2✔
111
    scie_load_dotenv: PexScieLoadDotenvField
2✔
112
    scie_name_style: PexScieNameStyleField
2✔
113
    scie_busybox: PexScieBusyBox
2✔
114
    scie_pex_entrypoint_env_passthrough: PexSciePexEntrypointEnvPassthrough
2✔
115
    scie_platform: PexSciePlatformField
2✔
116
    scie_pbs_release: PexSciePbsReleaseField
2✔
117
    scie_python_version: PexSciePythonVersion
2✔
118
    scie_hash_alg: PexScieHashAlgField
2✔
119
    scie_pbs_free_threaded: PexSciePbsFreeThreaded
2✔
120
    scie_pbs_debug: PexSciePbsDebug
2✔
121
    scie_pbs_stripped: PexSciePbsStripped
2✔
122

123
    def builds_pex_and_scie(self) -> bool:
2✔
124
        return self.scie.value is not None
×
125

126
    @property
2✔
127
    def _execution_mode(self) -> PexExecutionMode:
2✔
128
        return PexExecutionMode(self.execution_mode.value)
×
129

130
    def generate_additional_args(self, pex_binary_defaults: PexBinaryDefaults) -> tuple[str, ...]:
2✔
131
        args = []
×
132
        if self.emit_warnings.value_or_global_default(pex_binary_defaults) is False:
×
133
            args.append("--no-emit-warnings")
×
134
        elif self.emit_warnings.value_or_global_default(pex_binary_defaults) is True:
×
135
            args.append("--emit-warnings")
×
136
        if self.ignore_errors.value is True:
×
137
            args.append("--ignore-errors")
×
138
        if self.inherit_path.value is not None:
×
139
            args.append(f"--inherit-path={self.inherit_path.value}")
×
140
        if self.sh_boot.value is True:
×
141
            args.append("--sh-boot")
×
142
        if self.check.value is not None:
×
143
            args.append(f"--check={self.check.value}")
×
144
        if self.shebang.value is not None:
×
145
            args.append(f"--python-shebang={self.shebang.value}")
×
146
        if self.strip_env.value is False:
×
147
            args.append("--no-strip-pex-env")
×
148
        if self._execution_mode is PexExecutionMode.VENV:
×
149
            args.extend(("--venv", "prepend"))
×
150
        if self.include_tools.value is True:
×
151
            args.append("--include-tools")
×
152
        if self.venv_site_packages_copies.value is True:
×
153
            args.append("--venv-site-packages-copies")
×
154
        if self.venv_hermetic_scripts.value is False:
×
155
            args.append("--non-hermetic-venv-scripts")
×
156
        if self.extra_build_args.value:
×
157
            args.extend(self.extra_build_args.value)
×
158
        return tuple(args)
×
159

160
    def generate_scie_args(
2✔
161
        self,
162
    ) -> tuple[str, ...]:
163
        args = []
×
164
        if self.scie.value is not None:
×
165
            args.append(f"--scie={self.scie.value}")
×
NEW
166
        if self.scie_bind_resource_path.value is not None:
×
NEW
167
            args.extend(
×
168
                [
169
                    f"--scie-bind-resource-path={resource_path}"
170
                    for resource_path in self.scie_bind_resource_path.value
171
                ]
172
            )
NEW
173
        if self.scie_exe.value is not None:
×
NEW
174
            args.extend([f"--scie-exe={exe}" for exe in self.scie_exe.value])
×
NEW
175
        if self.scie_args.value is not None:
×
NEW
176
            args.extend([f"--scie-args={arg}" for arg in self.scie_args.value])
×
NEW
177
        if self.scie_env.value is not None:
×
NEW
178
            args.extend([f"--scie-env={e}" for e in self.scie_env.value])
×
179
        if self.scie_load_dotenv.value is not None:
×
180
            if self.scie_load_dotenv.value:
×
181
                args.append("--scie-load-dotenv")
×
182
            else:
183
                args.append("--no-scie-load-dotenv")
×
184
        if self.scie_name_style.value is not None:
×
185
            args.append(f"--scie-name-style={self.scie_name_style.value}")
×
186
        if self.scie_busybox.value is not None:
×
187
            args.append(f"--scie-busybox={self.scie_busybox.value}")
×
188
        if self.scie_pex_entrypoint_env_passthrough.value is True:
×
189
            args.append("--scie-busybox-pex-entrypoint-env-passthrough")
×
190
        if self.scie_platform.value is not None:
×
191
            args.extend([f"--scie-platform={platform}" for platform in self.scie_platform.value])
×
192
        if self.scie_pbs_release.value is not None:
×
193
            args.append(f"--scie-pbs-release={self.scie_pbs_release.value}")
×
194
        if self.scie_python_version.value is not None:
×
195
            args.append(f"--scie-python-version={self.scie_python_version.value}")
×
196
        if self.scie_hash_alg.value is not None:
×
197
            args.append(f"--scie-hash-alg={self.scie_hash_alg.value}")
×
198
        if self.scie_pbs_debug.value is not None:
×
199
            if self.scie_pbs_debug.value:
×
200
                args.append("--scie-pbs-debug")
×
201
            else:
202
                args.append("--no-scie-pbs-debug")
×
203
        if self.scie_pbs_free_threaded.value is not None:
×
204
            if self.scie_pbs_free_threaded.value:
×
205
                args.append("--scie-pbs-free-threaded")
×
206
            else:
207
                args.append("--no-scie-pbs-free-threaded")
×
208
        if self.scie_pbs_stripped.value is True:
×
209
            args.append("--scie-pbs-stripped")
×
210

211
        return tuple(args)
×
212

213
    def output_pex_filename(self) -> str:
2✔
214
        return self.output_path.value_or_default(file_ending="pex")
×
215

216

217
@dataclass(frozen=True)
2✔
218
class PexFromTargetsRequestForBuiltPackage:
2✔
219
    """An intermediate class that gives consumers access to the data used to create a
220
    `PexFromTargetsRequest` to fulfil a `BuiltPackage` request.
221

222
    This class is used directly by `run_pex_binary`, but should be handled transparently by direct
223
    `BuiltPackage` requests.
224
    """
225

226
    request: PexFromTargetsRequest
2✔
227

228

229
@rule(level=LogLevel.DEBUG)
2✔
230
async def package_pex_binary(
2✔
231
    field_set: PexBinaryFieldSet,
232
    pex_binary_defaults: PexBinaryDefaults,
233
) -> PexFromTargetsRequestForBuiltPackage:
234
    resolved_entry_point = await resolve_pex_entry_point(
×
235
        ResolvePexEntryPointRequest(field_set.entry_point)
236
    )
237

238
    output_filename = field_set.output_pex_filename()
×
239

240
    complete_platforms = await digest_complete_platforms(field_set.complete_platforms)
×
241

242
    request = PexFromTargetsRequest(
×
243
        addresses=[field_set.address],
244
        internal_only=False,
245
        main=resolved_entry_point.val or field_set.script.value or field_set.executable.value,
246
        inject_args=field_set.args.value or [],
247
        inject_env=field_set.env.value or FrozenDict[str, str](),
248
        complete_platforms=complete_platforms,
249
        output_filename=output_filename,
250
        layout=PexLayout(field_set.layout.value),
251
        additional_args=field_set.generate_additional_args(pex_binary_defaults),
252
        include_requirements=field_set.include_requirements.value,
253
        include_source_files=field_set.include_sources.value,
254
        include_local_dists=True,
255
        warn_for_transitive_files_targets=True,
256
    )
257

258
    return PexFromTargetsRequestForBuiltPackage(request)
×
259

260

261
@rule
2✔
262
async def built_package_for_pex_from_targets_request(
2✔
263
    field_set: PexBinaryFieldSet,
264
) -> BuiltPackage:
265
    pft_request = await package_pex_binary(field_set, **implicitly())
×
266

NEW
267
    base_pex_request = await create_pex_from_targets(**implicitly(pft_request.request))
×
268
    if field_set.builds_pex_and_scie():
×
269
        pex_request = dataclasses.replace(
×
270
            base_pex_request,
271
            additional_args=(*base_pex_request.additional_args, *field_set.generate_scie_args()),
272
        )
273
    else:
NEW
274
        pex_request = base_pex_request
×
275

276
    pex = await create_pex(**implicitly(pex_request))
×
277
    snapshot = await digest_to_snapshot(pex.digest)
×
278

279
    # "The" PEX, and not scie, hashes, or future auxiliary files must be first
280
    artifacts = [BuiltPackageArtifact(pft_request.request.output_filename)]
×
281
    if PexLayout.ZIPAPP == pft_request.request.layout:
×
282
        artifacts.extend(
×
283
            BuiltPackageArtifact(artifact)
284
            for artifact in snapshot.files
285
            if artifact != pft_request.request.output_filename
286
        )
287
    else:
288
        artifacts.extend(
×
289
            BuiltPackageArtifact(artifact)
290
            for artifact in snapshot.files
291
            if (
292
                pft_request.request.output_filename
293
                != os.path.commonpath((pft_request.request.output_filename, artifact))
294
            )
295
        )
296
    # Make sure the "regular PEX first" invariant explained above is true
297
    assert artifacts[0].relpath in snapshot.files or artifacts[0].relpath in snapshot.dirs, (
×
298
        "PEX must be first BuiltPackageArtifact"
299
    )
300
    return BuiltPackage(pex.digest, tuple(artifacts))
×
301

302

303
def rules():
2✔
304
    return [*collect_rules(), UnionRule(PackageFieldSet, PexBinaryFieldSet)]
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc