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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

58.06
/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
4✔
5
import logging
4✔
6
import os
4✔
7
from dataclasses import dataclass
4✔
8

9
from pants.backend.python.target_types import (
4✔
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
4✔
53
from pants.backend.python.util_rules.pex import create_pex, digest_complete_platforms
4✔
54
from pants.backend.python.util_rules.pex_from_targets import (
4✔
55
    PexFromTargetsRequest,
56
    create_pex_from_targets,
57
)
58
from pants.core.environments.target_types import EnvironmentField
4✔
59
from pants.core.goals.package import (
4✔
60
    BuiltPackage,
61
    BuiltPackageArtifact,
62
    OutputPathField,
63
    PackageFieldSet,
64
)
65
from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior
4✔
66
from pants.engine.intrinsics import digest_to_snapshot
4✔
67
from pants.engine.rules import collect_rules, implicitly, rule
4✔
68
from pants.engine.unions import UnionRule
4✔
69
from pants.util.frozendict import FrozenDict
4✔
70
from pants.util.logging import LogLevel
4✔
71

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

74

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

79
    required_fields = (PexEntryPointField,)
4✔
80

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

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

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

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

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

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

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

UNCOV
211
        return tuple(args)
×
212

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

216

217
@dataclass(frozen=True)
4✔
218
class PexFromTargetsRequestForBuiltPackage:
4✔
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
227

228

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

238
    output_filename = field_set.output_pex_filename()
3✔
239

240
    complete_platforms = await digest_complete_platforms(field_set.complete_platforms)
3✔
241

242
    request = PexFromTargetsRequest(
3✔
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)
3✔
259

260

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

267
    base_pex_request = await create_pex_from_targets(**implicitly(pft_request.request))
2✔
268
    if field_set.builds_pex_and_scie():
2✔
UNCOV
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:
274
        pex_request = base_pex_request
2✔
275

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

279
    # "The" PEX, and not scie, hashes, or future auxiliary files must be first
280
    artifacts = [BuiltPackageArtifact(pft_request.request.output_filename)]
2✔
281
    if PexLayout.ZIPAPP == pft_request.request.layout:
2✔
282
        artifacts.extend(
2✔
283
            BuiltPackageArtifact(artifact)
284
            for artifact in snapshot.files
285
            if artifact != pft_request.request.output_filename
286
        )
287
    else:
UNCOV
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, (
2✔
298
        "PEX must be first BuiltPackageArtifact"
299
    )
300
    return BuiltPackage(pex.digest, tuple(artifacts))
2✔
301

302

303
def rules():
4✔
304
    return [*collect_rules(), UnionRule(PackageFieldSet, PexBinaryFieldSet)]
4✔
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