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

pantsbuild / pants / 22740642519

05 Mar 2026 11:00PM UTC coverage: 52.677% (-40.3%) from 92.931%
22740642519

Pull #23157

github

web-flow
Merge 2aa18e6d4 into f0030f5e7
Pull Request #23157: [pants ng] Partition source files by config.

31678 of 60136 relevant lines covered (52.68%)

0.53 hits per line

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

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

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

73
logger = logging.getLogger(__name__)
1✔
74

75

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

80
    required_fields = (PexEntryPointField,)
1✔
81

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

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

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

125
    def builds_pex_and_scie(self) -> bool:
1✔
126
        return self.scie.value is not None
×
127

128
    @property
1✔
129
    def _execution_mode(self) -> PexExecutionMode:
1✔
130
        return PexExecutionMode(self.execution_mode.value)
×
131

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

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

215
        return tuple(args)
×
216

217
    def output_pex_filename(self) -> str:
1✔
218
        return self.output_path.value_or_default(file_ending="pex")
×
219

220

221
@dataclass(frozen=True)
1✔
222
class PexFromTargetsRequestForBuiltPackage:
1✔
223
    """An intermediate class that gives consumers access to the data used to create a
224
    `PexFromTargetsRequest` to fulfil a `BuiltPackage` request.
225

226
    This class is used directly by `run_pex_binary`, but should be handled transparently by direct
227
    `BuiltPackage` requests.
228
    """
229

230
    request: PexFromTargetsRequest
231

232

233
@rule(level=LogLevel.DEBUG)
1✔
234
async def package_pex_binary(
1✔
235
    field_set: PexBinaryFieldSet,
236
    pex_binary_defaults: PexBinaryDefaults,
237
) -> PexFromTargetsRequestForBuiltPackage:
238
    resolved_entry_point = await resolve_pex_entry_point(
×
239
        ResolvePexEntryPointRequest(field_set.entry_point)
240
    )
241

242
    output_filename = field_set.output_pex_filename()
×
243

244
    complete_platforms = await digest_complete_platforms(field_set.complete_platforms)
×
245

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

262
    return PexFromTargetsRequestForBuiltPackage(request)
×
263

264

265
@rule
1✔
266
async def built_package_for_pex_from_targets_request(
1✔
267
    field_set: PexBinaryFieldSet,
268
) -> BuiltPackage:
269
    pft_request = await package_pex_binary(field_set, **implicitly())
×
270

271
    base_pex_request = await create_pex_from_targets(**implicitly(pft_request.request))
×
272
    if field_set.builds_pex_and_scie():
×
273
        pex_request = dataclasses.replace(
×
274
            base_pex_request,
275
            additional_args=(*base_pex_request.additional_args, *field_set.generate_scie_args()),
276
        )
277
    else:
278
        pex_request = base_pex_request
×
279

280
    pex = await create_pex(**implicitly(pex_request))
×
281
    snapshot = await digest_to_snapshot(pex.digest)
×
282

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

306

307
def rules():
1✔
308
    return [*collect_rules(), UnionRule(PackageFieldSet, PexBinaryFieldSet)]
1✔
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