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

pantsbuild / pants / 20438429929

22 Dec 2025 04:55PM UTC coverage: 80.287% (+0.003%) from 80.284%
20438429929

Pull #22934

github

web-flow
Merge b49c09e21 into 06f105be8
Pull Request #22934: feat(go): add multi-module support to golangci-lint plugin and upgrade to v2

37 of 62 new or added lines in 3 files covered. (59.68%)

183 existing lines in 9 files now uncovered.

78528 of 97809 relevant lines covered (80.29%)

3.36 hits per line

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

86.09
/src/python/pants/backend/python/subsystems/setup.py
1
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
12✔
5

6
import enum
12✔
7
import logging
12✔
8
import os
12✔
9
from collections.abc import Iterable
12✔
10
from typing import TypeVar, cast
12✔
11

12
from packaging.utils import canonicalize_name
12✔
13

14
from pants.core.goals.generate_lockfiles import UnrecognizedResolveNamesError
12✔
15
from pants.option.errors import OptionsError
12✔
16
from pants.option.option_types import (
12✔
17
    BoolOption,
18
    DictOption,
19
    EnumOption,
20
    FileOption,
21
    StrListOption,
22
    StrOption,
23
)
24
from pants.option.subsystem import Subsystem
12✔
25
from pants.util.docutil import bin_name, doc_url
12✔
26
from pants.util.memo import memoized_method, memoized_property
12✔
27
from pants.util.strutil import softwrap
12✔
28

29
logger = logging.getLogger(__name__)
12✔
30

31

32
@enum.unique
12✔
33
class InvalidLockfileBehavior(enum.Enum):
12✔
34
    error = "error"
12✔
35
    ignore = "ignore"
12✔
36
    warn = "warn"
12✔
37

38

39
@enum.unique
12✔
40
class LockfileGenerator(enum.Enum):
12✔
41
    PEX = "pex"
12✔
42
    POETRY = "poetry"
12✔
43

44

45
RESOLVE_OPTION_KEY__DEFAULT = "__default__"
12✔
46

47
_T = TypeVar("_T")
12✔
48

49

50
class PythonSetup(Subsystem):
12✔
51
    options_scope = "python"
12✔
52
    help = "Options for Pants's Python backend."
12✔
53

54
    default_interpreter_universe = [
12✔
55
        "2.7",
56
        "3.5",
57
        "3.6",
58
        "3.7",
59
        "3.8",
60
        "3.9",
61
        "3.10",
62
        "3.11",
63
        "3.12",
64
        "3.13",
65
    ]
66

67
    _interpreter_constraints = StrListOption(
12✔
68
        default=None,
69
        help=softwrap(
70
            """
71
            The Python interpreters your codebase is compatible with.
72

73
            These constraints are used as the default value for the `interpreter_constraints`
74
            field of Python targets.
75

76
            Specify with requirement syntax, e.g. `'CPython>=2.7,<3'` (A CPython interpreter with
77
            version >=2.7 AND version <3) or `'PyPy'` (A pypy interpreter of any version). Multiple
78
            constraint strings will be ORed together.
79
            """
80
        ),
81
        metavar="<requirement>",
82
    )
83

84
    warn_on_python2_usage = BoolOption(
12✔
85
        default=True,
86
        advanced=True,
87
        help=softwrap(
88
            """\
89
            True if Pants should generate a deprecation warning when Python 2.x is used in interpreter constraints.
90

91
            As of Pants v2.24.x and later, Pants will no longer be tested regularly with Python 2.7.x. As such, going
92
            forward, Pants may or may not work with Python 2.7. This option allows disabling the deprecation
93
            warning announcing this policy change.
94
            """
95
        ),
96
    )
97

98
    @memoized_property
12✔
99
    def interpreter_constraints(self) -> tuple[str, ...]:
12✔
100
        if not self._interpreter_constraints:
1✔
101
            # TODO: This is a hacky affordance for Pants's own tests, dozens of which were
102
            #  written when Pants provided default ICs, and implicitly rely on that assumption.
103
            #  We'll probably want to find and modify all those tests to set an explicit IC, but
104
            #  that will take time.
105
            if "PYTEST_CURRENT_TEST" in os.environ:
×
106
                return (">=3.9,<3.15",)
×
107
            raise OptionsError(
×
108
                softwrap(
109
                    f"""\
110
                    You must explicitly specify the default Python interpreter versions your code
111
                    is intended to run against.
112

113
                    You specify these interpreter constraints using the `interpreter_constraints`
114
                    option in the `[python]` section of pants.toml.
115

116
                    We recommend constraining to a single interpreter minor version if you can,
117
                    e.g., `interpreter_constraints = ['==3.11.*']`, or at least a small number of
118
                    interpreter minor versions, e.g., `interpreter_constraints = ['>=3.10,<3.12']`.
119

120
                    Individual targets can override these default interpreter constraints,
121
                    if different parts of your codebase run against different python interpreter
122
                    versions in a single repo.
123

124
                    See {doc_url("docs/python/overview/interpreter-compatibility")} for details.
125
                    """
126
                ),
127
            )
128

129
        # Warn if Python 2.x is still in use. This warning should only be displayed once since this
130
        # function is memoized.
131
        if self.warn_on_python2_usage:
1✔
132
            # Side-step import cycle.
133
            from pants.backend.python.util_rules.interpreter_constraints import (
×
134
                warn_on_python2_usage_in_interpreter_constraints,
135
            )
136

137
            warn_on_python2_usage_in_interpreter_constraints(
×
138
                self._interpreter_constraints,
139
                description_of_origin="the `[python].interpreter_constraints` option",
140
            )
141

142
        return self._interpreter_constraints
1✔
143

144
    interpreter_versions_universe = StrListOption(
12✔
145
        default=default_interpreter_universe,
146
        help=softwrap(
147
            f"""
148
            All known Python major/minor interpreter versions that may be used by either
149
            your code or tools used by your code.
150

151
            This is used by Pants to robustly handle interpreter constraints, such as knowing
152
            when generating lockfiles which Python versions to check if your code is using.
153

154
            This does not control which interpreter your code will use. Instead, to set your
155
            interpreter constraints, update `[python].interpreter_constraints`, the
156
            `interpreter_constraints` field, and relevant tool options like
157
            `[isort].interpreter_constraints` to tell Pants which interpreters your code
158
            actually uses. See {doc_url("docs/python/overview/interpreter-compatibility")}.
159

160
            All elements must be the minor and major Python version, e.g. `'2.7'` or `'3.10'`. Do
161
            not include the patch version.
162
            """
163
        ),
164
        advanced=True,
165
    )
166
    enable_resolves = BoolOption(
12✔
167
        default=False,
168
        help=softwrap(
169
            """
170
            Set to true to enable lockfiles for user code. See `[python].resolves` for an
171
            explanation of this feature.
172

173
            This option is mutually exclusive with `[python].requirement_constraints`. We strongly
174
            recommend using this option because it:
175

176
              1. Uses `--hash` to validate that all downloaded files are expected, which reduces\
177
                the risk of supply chain attacks.
178
              2. Enforces that all transitive dependencies are in the lockfile, whereas\
179
                constraints allow you to leave off dependencies. This ensures your build is more\
180
                stable and reduces the risk of supply chain attacks.
181
              3. Allows you to have multiple lockfiles in your repository.
182
            """
183
        ),
184
        advanced=True,
185
        mutually_exclusive_group="lockfile",
186
    )
187
    resolves = DictOption[str](
12✔
188
        default={"python-default": "3rdparty/python/default.lock"},
189
        help=softwrap(
190
            f"""
191
            A mapping of logical names to lockfile paths used in your project.
192

193
            Many organizations only need a single resolve for their whole project, which is
194
            a good default and often the simplest thing to do. However, you may need multiple
195
            resolves, such as if you use two conflicting versions of a requirement in
196
            your repository.
197

198
            If you only need a single resolve, run `{bin_name()} generate-lockfiles` to
199
            generate the lockfile.
200

201
            If you need multiple resolves:
202

203
              1. Via this option, define multiple resolve names and their lockfile paths.\
204
                The names should be meaningful to your repository, such as `data-science` or\
205
                `pants-plugins`.
206
              2. Set the default with `[python].default_resolve`.
207
              3. Update your `python_requirement` targets with the `resolve` field to declare which\
208
                resolve they should be available in. They default to `[python].default_resolve`,\
209
                so you only need to update targets that you want in non-default resolves.\
210
                (Often you'll set this via the `python_requirements` or `poetry_requirements`\
211
                target generators)
212
              4. Run `{bin_name()} generate-lockfiles` to generate the lockfiles. If the results\
213
                aren't what you'd expect, adjust the prior step.
214
              5. Update any targets like `python_source` / `python_sources`,\
215
                `python_test` / `python_tests`, and `pex_binary` which need to set a non-default\
216
                resolve with the `resolve` field.
217

218
            If a target can work with multiple resolves, you can either use the `parametrize`
219
            mechanism or manually create a distinct target per resolve. See {doc_url("docs/using-pants/key-concepts/targets-and-build-files")}
220
            for information about `parametrize`.
221

222
            For example:
223

224
                python_sources(
225
                    resolve=parametrize("data-science", "web-app"),
226
                )
227

228
            You can name the lockfile paths what you would like; Pants does not expect a
229
            certain file extension or location.
230

231
            Only applies if `[python].enable_resolves` is true.
232
            """
233
        ),
234
        advanced=True,
235
    )
236
    default_resolve = StrOption(
12✔
237
        default="python-default",
238
        help=softwrap(
239
            """
240
            The default value used for the `resolve` field.
241

242
            The name must be defined as a resolve in `[python].resolves`.
243
            """
244
        ),
245
        advanced=True,
246
    )
247

248
    _default_to_resolve_interpreter_constraints = BoolOption(
12✔
249
        default=False,
250
        help=softwrap(
251
            """
252
            For Python targets with both `resolve` and `interpreter_constraints` fields, default to using the `interpreter_constraints` field of the resolve if `interpreter_constraints` is not set on the target itself.
253

254
            `[python].enable_resolves` must be `True` for this option to also be enabled. This will become True by default in a future version of Pants and eventually be deprecated and then removed.
255
            """
256
        ),
257
        advanced=True,
258
    )
259

260
    @memoized_property
12✔
261
    def default_to_resolve_interpreter_constraints(self) -> bool:
12✔
262
        if self._default_to_resolve_interpreter_constraints and not self.enable_resolves:
1✔
263
            raise OptionsError(
×
264
                softwrap(
265
                    """
266
                You cannot set `[python].default_to_resolve_interpreter_constraints = true` without setting `[python].enable_resolves = true`.
267

268
                Please either enable resolves or set `[python].default_to_resolve_interpreter_constraints = false` (the default setting).
269
                """
270
                )
271
            )
272
        return self._default_to_resolve_interpreter_constraints
1✔
273

274
    separate_lockfile_metadata_file = BoolOption(
12✔
275
        advanced=True,
276
        default=False,
277
        help=softwrap(
278
            """
279
            If set, lockfile metadata will be written to a separate sibling file, rather than
280
            prepended as a header to the lockfile (which has various disadvantages).
281
            This will soon become True by default and eventually the header option will be
282
            deprecated and then removed.
283
            """
284
        ),
285
    )
286
    default_run_goal_use_sandbox = BoolOption(
12✔
287
        default=True,
288
        help=softwrap(
289
            """
290
            The default value used for the `run_goal_use_sandbox` field of Python targets. See the
291
            relevant field for more details.
292
            """
293
        ),
294
    )
295
    pip_version = StrOption(
12✔
296
        default="24.2",
297
        help=softwrap(
298
            f"""
299
            Use this version of Pip for resolving requirements and generating lockfiles.
300

301
            The value used here must be one of the Pip versions supported by the underlying PEX
302
            version. See {doc_url("docs/python/overview/pex")} for details.
303

304
            N.B.: The `latest` value selects the latest of the choices listed by PEX which is not
305
            necessarily the latest Pip version released on PyPI.
306
            """
307
        ),
308
        advanced=True,
309
    )
310
    _resolves_to_interpreter_constraints = DictOption[list[str]](
12✔
311
        help=softwrap(
312
            """
313
            Override the interpreter constraints to use when generating a resolve's lockfile
314
            with the `generate-lockfiles` goal.
315

316
            By default, each resolve from `[python].resolves` will use your
317
            global interpreter constraints set in `[python].interpreter_constraints`. With
318
            this option, you can override each resolve to use certain interpreter
319
            constraints, such as `{'data-science': ['==3.8.*']}`.
320

321
            Warning: this does NOT impact the interpreter constraints used by targets within the
322
            resolve, which is instead set by the option `[python].interpreter_constraints` and the
323
            `interpreter_constraints` field. It only impacts how the lockfile is generated.
324

325
            Pants will validate that the interpreter constraints of your code using a
326
            resolve are compatible with that resolve's own constraints. For example, if your
327
            code is set to use `['==3.9.*']` via the `interpreter_constraints` field, but it's
328
            using a resolve whose interpreter constraints are set to `['==3.7.*']`, then
329
            Pants will error explaining the incompatibility.
330

331
            The keys must be defined as resolves in `[python].resolves`.
332
            """
333
        ),
334
        advanced=True,
335
    )
336
    _resolves_to_constraints_file = DictOption[str](
12✔
337
        help=softwrap(
338
            f"""
339
            When generating a resolve's lockfile, use a constraints file to pin the version of
340
            certain requirements. This is particularly useful to pin the versions of transitive
341
            dependencies of your direct requirements.
342

343
            See https://pip.pypa.io/en/stable/user_guide/#constraints-files for more information on
344
            the format of constraint files and how constraints are applied in Pex and pip.
345

346
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
347
            `black` and `pytest`) to file paths for
348
            constraints files. For example,
349
            `{{'data-science': '3rdparty/data-science-constraints.txt'}}`.
350
            If a resolve is not set in the dictionary, it will not use a constraints file.
351

352
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
353
            resolves.
354
            """
355
        ),
356
        advanced=True,
357
    )
358
    _resolves_to_no_binary = DictOption[list[str]](
12✔
359
        help=softwrap(
360
            f"""
361
            When generating a resolve's lockfile, do not use binary packages (i.e. wheels) for
362
            these 3rdparty project names.
363

364
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
365
            `black` and `pytest`) to lists of project names. For example,
366
            `{{'data-science': ['requests', 'numpy']}}`. If a resolve is not set in the dictionary,
367
            it will have no restrictions on binary packages.
368

369
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
370
            resolves.
371

372
            For each resolve, you can also use the value `:all:` to disable all binary packages:
373
            `{{'data-science': [':all:']}}`.
374

375
            Note that some packages are tricky to compile and may fail to install when this option
376
            is used on them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-no-binary
377
            for details.
378
            """
379
        ),
380
        advanced=True,
381
    )
382
    _resolves_to_only_binary = DictOption[list[str]](
12✔
383
        help=softwrap(
384
            f"""
385
            When generating a resolve's lockfile, do not use source packages (i.e. sdists) for
386
            these 3rdparty project names, e.g `['django', 'requests']`.
387

388
            Expects a dictionary of resolve names from `[python].resolves` and Python tools (e.g.
389
            `black` and `pytest`) to lists of project names. For example,
390
            `{{'data-science': ['requests', 'numpy']}}`. If a resolve is not set in the dictionary,
391
            it will have no restrictions on source packages.
392

393
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
394
            resolves.
395

396
            For each resolve you can use the value `:all:` to disable all source packages:
397
            `{{'data-science': [':all:']}}`.
398

399
            Packages without binary distributions will fail to install when this option is used on
400
            them. See https://pip.pypa.io/en/stable/cli/pip_install/#install-only-binary for
401
            details.
402
            """
403
        ),
404
        advanced=True,
405
    )
406
    _resolves_to_excludes = DictOption[list[str]](
12✔
407
        help=softwrap(
408
            """ Specifies requirements to exclude from a resolve and its
409
            lockfile.  Any distribution included in the PEX's resolve that
410
            matches the requirement is excluded from the built PEX along with
411
            all of its transitive dependencies that are not also required by
412
            other non-excluded distributions.  At runtime, the PEX will boot
413
            without checking the excluded dependencies are available.
414
            """
415
        ),
416
        advanced=True,
417
    )
418
    _resolves_to_overrides = DictOption[list[str]](
12✔
419
        help=softwrap(
420
            """ Specifies a transitive requirement to override in a resolve
421
            and its lockfile.  Overrides can either modify an existing
422
            dependency on a project name by changing extras, version
423
            constraints or markers or else they can completely swap out the
424
            dependency for a dependency on another project altogether. For the
425
            former, simply supply the requirement you wish. For example,
426
            specifying `--override cowsay==5.0` will override any transitive
427
            dependency on cowsay that has any combination of extras, version
428
            constraints or markers with the requirement `cowsay==5.0`. To
429
            completely replace cowsay with another library altogether, you can
430
            specify an override like `--override cowsay=my-cowsay>2`. This
431
            will replace any transitive dependency on cowsay that has any
432
            combination of extras, version constraints or markers with the
433
            requirement `my-cowsay>2`."""
434
        ),
435
        advanced=True,
436
    )
437

438
    _resolves_to_sources = DictOption[list[str]](
12✔
439
        help=softwrap(""" Defines a limited scope to use a named find links repo or
440
            index for specific dependencies in a resolve and its lockfile.
441
            Sources take the form `<name>=<scope>` where the name must match
442
            a find links repo or index defined via `[python-repos].indexes` or
443
            `[python-repos].find_links`. The scope can be a project name
444
            (e.g., `internal=torch` to resolve the `torch` project from the
445
            `internal` repo), a project name with a marker (e.g.,
446
            `internal=torch; sys_platform != 'darwin'` to resolve `torch` from
447
            the `internal` repo except on macOS), or just a marker (e.g.,
448
            `piwheels=platform_machine == 'armv7l'` to resolve from the
449
            `piwheels` repo when targeting 32bit ARM machines)."""),
450
        advanced=True,
451
    )
452

453
    _resolves_to_lock_style = DictOption[str](
12✔
454
        help=softwrap(
455
            f"""
456
            The style of lock to generate. Valid values are 'strict', 'sources', or 'universal'
457
            (additional styles may be supported in future PEX versions).
458

459
            The 'strict' style generates a lock file that contains exactly the
460
            distributions that would be used in a local PEX build. If an sdist would be used, the sdist is included, but if a
461
            wheel would be used, an accompanying sdist will not be included. The 'sources' style includes locks containing both
462
            wheels and the associated sdists when available. The 'universal' style generates a universal lock for all possible
463
            target interpreters and platforms, although the scope can be constrained via `[python].resolves_to_interpreter_constraints`. Of
464
            the three lock styles, only 'strict' can give you full confidence in the lock since it includes exactly the artifacts
465
            that are included in the local PEX you'll build to test the lock result with before checking in the lock. With the
466
            other two styles you lock un-vetted artifacts in addition to the 'strict' ones; so, even though you can be sure to
467
            reproducibly resolve those same un-vetted artifacts in the future, they're still un-vetted and could be innocently or
468
            maliciously different from the 'strict' artifacts you can locally vet before committing the lock to version control.
469
            The effects of the differences could range from failing a resolve using the lock when the un-vetted artifacts have
470
            different dependencies from their sibling artifacts, to your application crashing due to different code in the sibling
471
            artifacts to being compromised by differing code in the sibling artifacts. So, although the more permissive lock
472
            styles will allow the lock to work on a wider range of machines /are apparently more convenient, the convenience comes
473
            with a potential price and using these styles should be considered carefully.
474

475
            Expects a dictionary of resolve names from `[python].resolves` to style values.
476
            If a resolve is not set in the dictionary, it will default to 'universal'.
477

478
            Examples:
479
            - `{{'data-science': 'strict', 'web-app': 'universal'}}` - use strict style for data-science resolve, universal for web-app
480
            - `{{'python-default': 'sources'}}` - use sources style for the default resolve
481

482
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
483
            resolves.
484

485
            See https://docs.pex-tool.org/api/pex.html for more information on lockfile styles.
486
            """
487
        ),
488
        advanced=True,
489
    )
490

491
    _resolves_to_complete_platforms = DictOption[list[str]](
12✔
492
        help=softwrap(
493
            f"""
494
            The platforms the built PEX should be compatible with when generating lockfiles.
495

496
            Complete platforms allow you to create lockfiles for specific target platforms
497
            (e.g., different CPU architectures or operating systems) rather than the default
498
            universal platforms. This is particularly useful for cross-platform builds or
499
            when you need strict platform-specific dependencies.
500

501
            You can give a list of multiple complete platforms to create a multiplatform lockfile,
502
            meaning that the lockfile will include wheels for all of the supported environments.
503

504
            Expects a dictionary of resolve names from `[python].resolves` to lists of addresses of
505
            `file` or `resource` targets that point to files containing complete platform JSON as
506
            described by Pex (https://pex.readthedocs.io/en/latest/buildingpex.html#complete-platform).
507

508
            For example:
509
            `{{'python-default': ['3rdparty/platforms:linux_aarch64', '3rdparty/platforms:macos_arm64']}}`.
510

511
            You can use the key `{RESOLVE_OPTION_KEY__DEFAULT}` to set a default value for all
512
            resolves.
513

514
            Complete platform JSON files can be generated using PEX's interpreter inspect command on
515
            the target platform: `pex3 interpreter inspect --markers --tags > platform.json`
516

517
            See https://docs.pex-tool.org for more information.
518
            """
519
        ),
520
        advanced=True,
521
    )
522

523
    invalid_lockfile_behavior = EnumOption(
12✔
524
        default=InvalidLockfileBehavior.error,
525
        help=softwrap(
526
            """
527
            The behavior when a lockfile has requirements or interpreter constraints that are
528
            not compatible with what the current build is using.
529

530
            We recommend keeping the default of `error` for CI builds.
531

532
            Note that `warn` will still expect a Pants lockfile header, it only won't error if
533
            the lockfile is stale and should be regenerated.
534

535
            Use `ignore` to avoid needing a lockfile header at all, e.g. if you are manually
536
            managing lockfiles rather than using the `generate-lockfiles` goal.
537
            """
538
        ),
539
        advanced=True,
540
    )
541
    resolves_generate_lockfiles = BoolOption(
12✔
542
        default=True,
543
        help=softwrap(
544
            """
545
            If False, Pants will not attempt to generate lockfiles for `[python].resolves` when
546
            running the `generate-lockfiles` goal.
547

548
            This is intended to allow you to manually generate lockfiles for your own code,
549
            rather than using Pex lockfiles. For example, when adopting Pants in a project already
550
            using Poetry, you can use `poetry export --dev` to create a requirements.txt-style
551
            lockfile understood by Pants, then point `[python].resolves` to the file.
552

553
            If you set this to False, Pants will not attempt to validate the metadata headers
554
            for your user lockfiles. This is useful so that you can keep
555
            `[python].invalid_lockfile_behavior` to `error` or `warn` if you'd like so that tool
556
            lockfiles continue to be validated, while user lockfiles are skipped.
557

558
            Warning: it will likely be slower to install manually generated user lockfiles than Pex
559
            ones because Pants cannot as efficiently extract the subset of requirements used for a
560
            particular task. See the option `[python].run_against_entire_lockfile`.
561
            """
562
        ),
563
        advanced=True,
564
    )
565
    run_against_entire_lockfile = BoolOption(
12✔
566
        default=False,
567
        help=softwrap(
568
            """
569
            If enabled, when running binaries, tests, and repls, Pants will use the entire
570
            lockfile file instead of just the relevant subset.
571

572
            If you are using Pex lockfiles, we generally do not recommend this. You will already
573
            get similar performance benefits to this option, without the downsides.
574

575
            Otherwise, this option can improve performance and reduce cache size.
576
            But it has two consequences:
577
            1) All cached test results will be invalidated if any requirement in the lockfile
578
               changes, rather than just those that depend on the changed requirement.
579
            2) Requirements unneeded by a test/run/repl will be present on the sys.path, which
580
               might in rare cases cause their behavior to change.
581

582
            This option does not affect packaging deployable artifacts, such as
583
            PEX files, wheels and cloud functions, which will still use just the exact
584
            subset of requirements needed.
585
            """
586
        ),
587
        advanced=True,
588
    )
589

590
    __constraints_deprecation_msg = softwrap(
12✔
591
        f"""
592
        We encourage instead migrating to `[python].enable_resolves` and `[python].resolves`,
593
        which is an improvement over this option. The `[python].resolves` feature ensures that
594
        your lockfiles are fully comprehensive, i.e. include all transitive dependencies;
595
        uses hashes for better supply chain security; and supports advanced features like VCS
596
        and local requirements, along with options `[python].resolves_to_only_binary`.
597

598
        To migrate, stop setting `[python].requirement_constraints` and
599
        `[python].resolve_all_constraints`, and instead set `[python].enable_resolves` to
600
        `true`. Then, run `{bin_name()} generate-lockfiles`.
601
        """
602
    )
603
    requirement_constraints = FileOption(
12✔
604
        default=None,
605
        help=softwrap(
606
            """
607
            When resolving third-party requirements for your own code (vs. tools you run),
608
            use this constraints file to determine which versions to use.
609

610
            Mutually exclusive with `[python].enable_resolves`, which we generally recommend as an
611
            improvement over constraints file.
612

613
            See https://pip.pypa.io/en/stable/user_guide/#constraints-files for more
614
            information on the format of constraint files and how constraints are applied in
615
            Pex and pip.
616

617
            This only applies when resolving user requirements, rather than tools you run
618
            like Black and Pytest. To constrain tools, set `[tool].lockfile`, e.g.
619
            `[black].lockfile`.
620
            """
621
        ),
622
        advanced=True,
623
        mutually_exclusive_group="lockfile",
624
        removal_version="3.0.0.dev0",
625
        removal_hint=__constraints_deprecation_msg,
626
    )
627
    _resolve_all_constraints = BoolOption(
12✔
628
        default=True,
629
        help=softwrap(
630
            """
631
            (Only relevant when using `[python].requirement_constraints.`) If enabled, when
632
            resolving requirements, Pants will first resolve your entire
633
            constraints file as a single global resolve. Then, if the code uses a subset of
634
            your constraints file, Pants will extract the relevant requirements from that
635
            global resolve so that only what's actually needed gets used. If disabled, Pants
636
            will not use a global resolve and will resolve each subset of your requirements
637
            independently.
638

639
            Usually this option should be enabled because it can result in far fewer resolves.
640
            """
641
        ),
642
        advanced=True,
643
        removal_version="3.0.0.dev0",
644
        removal_hint=__constraints_deprecation_msg,
645
    )
646
    resolver_manylinux = StrOption(
12✔
647
        default="manylinux2014",
648
        help=softwrap(
649
            """
650
            Whether to allow resolution of manylinux wheels when resolving requirements for
651
            foreign linux platforms. The value should be a manylinux platform upper bound,
652
            e.g. `'manylinux2010'`, or else the string `'no'` to disallow.
653
            """
654
        ),
655
        advanced=True,
656
    )
657

658
    tailor_source_targets = BoolOption(
12✔
659
        default=True,
660
        help=softwrap(
661
            """
662
            If true, add `python_sources`, `python_tests`, and `python_test_utils` targets with
663
            the `tailor` goal."""
664
        ),
665
        advanced=True,
666
    )
667
    tailor_ignore_empty_init_files = BoolOption(
12✔
668
        "--tailor-ignore-empty-init-files",
669
        default=True,
670
        help=softwrap(
671
            """
672
            If true, don't add `python_sources` targets for `__init__.py` files that are both empty
673
            and where there are no other Python files in the directory.
674

675
            Empty and solitary `__init__.py` files usually exist as import scaffolding rather than
676
            true library code, so it can be noisy to add BUILD files.
677

678
            Even if this option is set to true, Pants will still ensure the empty `__init__.py`
679
            files are included in the sandbox when running processes.
680

681
            If you set to false, you may also want to set `[python-infer].init_files = "always"`.
682
            """
683
        ),
684
        advanced=True,
685
    )
686
    tailor_requirements_targets = BoolOption(
12✔
687
        default=True,
688
        help=softwrap(
689
            """
690
            If true, add `python_requirements`, `poetry_requirements`, and `pipenv_requirements`
691
            target generators with the `tailor` goal.
692

693
            `python_requirements` targets are added for any file that matches the pattern
694
            `*requirements*.txt`. You will need to manually add `python_requirements` for different
695
            file names like `reqs.txt`.
696

697
            `poetry_requirements` targets are added for `pyproject.toml` files with `[tool.poetry`
698
            in them.
699
            """
700
        ),
701
        advanced=True,
702
    )
703
    tailor_pex_binary_targets = BoolOption(
12✔
704
        default=False,
705
        help=softwrap(
706
            """
707
            If true, add `pex_binary` targets for Python files named `__main__.py` or with a
708
            `__main__` clause with the `tailor` goal.
709
            """
710
        ),
711
        advanced=True,
712
    )
713
    tailor_py_typed_targets = BoolOption(
12✔
714
        default=True,
715
        help=softwrap(
716
            """
717
            If true, add `resource` targets for marker files named `py.typed` with the `tailor` goal.
718
            """
719
        ),
720
        advanced=True,
721
    )
722
    macos_big_sur_compatibility = BoolOption(
12✔
723
        default=False,
724
        help=softwrap(
725
            """
726
            If set, and if running on macOS Big Sur, use `macosx_10_16` as the platform
727
            when building wheels. Otherwise, the default of `macosx_11_0` will be used.
728
            This may be required for `pip` to be able to install the resulting distribution
729
            on Big Sur.
730
            """
731
        ),
732
        advanced=True,
733
    )
734
    enable_lockfile_targets = BoolOption(
12✔
735
        default=True,
736
        help=softwrap(
737
            """
738
            Create targets for all Python lockfiles defined in `[python].resolves`.
739

740
            The lockfile targets will then be used as dependencies to the `python_requirement`
741
            targets that use them, invalidating source targets per resolve when the lockfile
742
            changes.
743

744
            If another targets address is in conflict with the created lockfile target, it will
745
            shadow the lockfile target and it will not be available as a dependency for any
746
            `python_requirement` targets.
747
            """
748
        ),
749
        advanced=True,
750
    )
751
    repl_history = BoolOption(
12✔
752
        default=True,
753
        help="Whether to use the standard Python command history file when running a repl.",
754
    )
755

756
    @property
12✔
757
    def enable_synthetic_lockfiles(self) -> bool:
12✔
UNCOV
758
        return self.enable_resolves and self.enable_lockfile_targets
×
759

760
    @memoized_property
12✔
761
    def resolves_to_interpreter_constraints(self) -> dict[str, tuple[str, ...]]:
12✔
762
        result = {}
2✔
763
        unrecognized_resolves = []
2✔
764
        for resolve, ics in self._resolves_to_interpreter_constraints.items():
2✔
765
            if resolve not in self.resolves:
2✔
766
                unrecognized_resolves.append(resolve)
1✔
767
            if ics and self.warn_on_python2_usage:
2✔
768
                # Side-step import cycle.
UNCOV
769
                from pants.backend.python.util_rules.interpreter_constraints import (
×
770
                    warn_on_python2_usage_in_interpreter_constraints,
771
                )
772

UNCOV
773
                warn_on_python2_usage_in_interpreter_constraints(
×
774
                    ics,
775
                    description_of_origin=f"the `[python].resolves_to_interpreter_constraints` option for resolve {resolve}",
776
                )
777

778
            result[resolve] = tuple(ics)
2✔
779
        if unrecognized_resolves:
2✔
780
            raise UnrecognizedResolveNamesError(
1✔
781
                unrecognized_resolves,
782
                self.resolves.keys(),
783
                description_of_origin="the option `[python].resolves_to_interpreter_constraints`",
784
            )
785
        return result
2✔
786

787
    def _resolves_to_option_helper(
12✔
788
        self,
789
        option_value: dict[str, _T],
790
        option_name: str,
791
    ) -> dict[str, _T]:
792
        all_valid_resolves = set(self.resolves)
1✔
793
        unrecognized_resolves = set(option_value.keys()) - {
1✔
794
            RESOLVE_OPTION_KEY__DEFAULT,
795
            *all_valid_resolves,
796
        }
797
        if unrecognized_resolves:
1✔
798
            raise UnrecognizedResolveNamesError(
1✔
799
                sorted(unrecognized_resolves),
800
                {*all_valid_resolves, RESOLVE_OPTION_KEY__DEFAULT},
801
                description_of_origin=f"the option `[python].{option_name}`",
802
            )
803
        default_val = option_value.get(RESOLVE_OPTION_KEY__DEFAULT)
1✔
804
        if not default_val:
1✔
805
            return option_value
1✔
806
        return {resolve: option_value.get(resolve, default_val) for resolve in all_valid_resolves}
1✔
807

808
    @memoized_method
12✔
809
    def resolves_to_constraints_file(self) -> dict[str, str]:
12✔
810
        return self._resolves_to_option_helper(
1✔
811
            self._resolves_to_constraints_file,
812
            "resolves_to_constraints_file",
813
        )
814

815
    @memoized_method
12✔
816
    def resolves_to_no_binary(self) -> dict[str, list[str]]:
12✔
817
        return {
1✔
818
            resolve: [canonicalize_name(v) for v in vals]
819
            for resolve, vals in self._resolves_to_option_helper(
820
                self._resolves_to_no_binary,
821
                "resolves_to_no_binary",
822
            ).items()
823
        }
824

825
    @memoized_method
12✔
826
    def resolves_to_only_binary(self) -> dict[str, list[str]]:
12✔
827
        return {
1✔
828
            resolve: sorted([canonicalize_name(v) for v in vals])
829
            for resolve, vals in self._resolves_to_option_helper(
830
                self._resolves_to_only_binary,
831
                "resolves_to_only_binary",
832
            ).items()
833
        }
834

835
    @memoized_method
12✔
836
    def resolves_to_excludes(self) -> dict[str, list[str]]:
12✔
UNCOV
837
        return {
×
838
            resolve: sorted(vals)
839
            for resolve, vals in self._resolves_to_option_helper(
840
                self._resolves_to_excludes,
841
                "resolves_to_excludes",
842
            ).items()
843
        }
844

845
    @memoized_method
12✔
846
    def resolves_to_overrides(self) -> dict[str, list[str]]:
12✔
UNCOV
847
        return {
×
848
            resolve: sorted(vals)
849
            for resolve, vals in self._resolves_to_option_helper(
850
                self._resolves_to_overrides,
851
                "resolves_to_overrides",
852
            ).items()
853
        }
854

855
    @memoized_method
12✔
856
    def resolves_to_sources(self) -> dict[str, list[str]]:
12✔
UNCOV
857
        return {
×
858
            resolve: sorted(vals)
859
            for resolve, vals in self._resolves_to_option_helper(
860
                self._resolves_to_sources,
861
                "resolves_to_sources",
862
            ).items()
863
        }
864

865
    @memoized_method
12✔
866
    def resolves_to_lock_style(self) -> dict[str, str]:
12✔
UNCOV
867
        return self._resolves_to_option_helper(
×
868
            self._resolves_to_lock_style,
869
            "resolves_to_lock_style",
870
        )
871

872
    @memoized_method
12✔
873
    def resolves_to_complete_platforms(self) -> dict[str, list[str]]:
12✔
UNCOV
874
        return self._resolves_to_option_helper(
×
875
            self._resolves_to_complete_platforms,
876
            "resolves_to_complete_platforms",
877
        )
878

879
    @property
12✔
880
    def manylinux(self) -> str | None:
12✔
UNCOV
881
        manylinux = cast(str | None, self.resolver_manylinux)
×
UNCOV
882
        if manylinux is None or manylinux.lower() in ("false", "no", "none"):
×
UNCOV
883
            return None
×
UNCOV
884
        return manylinux
×
885

886
    @property
12✔
887
    def resolve_all_constraints(self) -> bool:
12✔
888
        if (
1✔
889
            self._resolve_all_constraints
890
            and not self.options.is_default("resolve_all_constraints")
891
            and not self.requirement_constraints
892
        ):
UNCOV
893
            raise ValueError(
×
894
                softwrap(
895
                    """
896
                    `[python].resolve_all_constraints` is enabled, so
897
                    `[python].requirement_constraints` must also be set.
898
                    """
899
                )
900
            )
901
        return self._resolve_all_constraints
1✔
902

903
    @property
12✔
904
    def scratch_dir(self):
12✔
UNCOV
905
        return os.path.join(self.options.pants_workdir, *self.options_scope.split("."))
×
906

907
    def compatibility_or_constraints(
12✔
908
        self, compatibility: Iterable[str] | None, resolve: str | None
909
    ) -> tuple[str, ...]:
910
        """Return either the given `compatibility` field or the global interpreter constraints.
911

912
        If interpreter constraints are supplied by the CLI flag, return those only.
913
        """
914
        if self.options.is_flagged("interpreter_constraints"):
1✔
UNCOV
915
            return self.interpreter_constraints
×
916
        if compatibility:
1✔
917
            return tuple(compatibility)
1✔
918
        if resolve and self.default_to_resolve_interpreter_constraints:
1✔
919
            return self.resolves_to_interpreter_constraints.get(
1✔
920
                resolve, self.interpreter_constraints
921
            )
922
        return self.interpreter_constraints
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

© 2025 Coveralls, Inc