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

pantsbuild / pants / 25443604553

06 May 2026 03:05PM UTC coverage: 92.879% (-0.04%) from 92.915%
25443604553

push

github

web-flow
[pants_ng] Scaffolding for a pants_ng mode. (#23319)

In this mode the command line is parsed as an
NG invocation, and dispatched appropriately.

Of course at the moment there are no
implementations to dispatch to. That will follow.

This does expose a new option, `pants_ng` to users. 
There is a big warning not to set it, but we're not trying
to hide that we're working on a new thing, so I am
comfortable with this.

25 of 76 new or added lines in 9 files covered. (32.89%)

1294 existing lines in 76 files now uncovered.

92234 of 99306 relevant lines covered (92.88%)

4.05 hits per line

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

98.15
/src/python/pants/backend/python/macros/common_requirements_rule.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
7✔
5

6
import itertools
7✔
7
import logging
7✔
8
import os
7✔
9
from collections.abc import Callable, Iterable
7✔
10
from typing import cast
7✔
11

12
from packaging.utils import canonicalize_name as canonicalize_project_name
7✔
13

14
from pants.backend.python.goals.lockfile import synthetic_lockfile_target_name
7✔
15
from pants.backend.python.macros.common_fields import (
7✔
16
    ModuleMappingField,
17
    TypeStubsModuleMappingField,
18
)
19
from pants.backend.python.subsystems.setup import PythonSetup
7✔
20
from pants.backend.python.target_types import (
7✔
21
    PythonRequirementModulesField,
22
    PythonRequirementResolveField,
23
    PythonRequirementsField,
24
    PythonRequirementTarget,
25
    PythonRequirementTypeStubModulesField,
26
)
27
from pants.core.target_types import (
7✔
28
    TargetGeneratorSourcesHelperSourcesField,
29
    TargetGeneratorSourcesHelperTarget,
30
)
31
from pants.engine.addresses import Address
7✔
32
from pants.engine.fs import GlobMatchErrorBehavior, PathGlobs
7✔
33
from pants.engine.internals.build_files import find_target_adaptor
7✔
34
from pants.engine.internals.target_adaptor import TargetAdaptorRequest
7✔
35
from pants.engine.intrinsics import get_digest_contents
7✔
36
from pants.engine.rules import implicitly
7✔
37
from pants.engine.target import (
7✔
38
    Dependencies,
39
    GenerateTargetsRequest,
40
    InvalidFieldException,
41
    SingleSourceField,
42
)
43
from pants.engine.unions import UnionMembership
7✔
44
from pants.util.pip_requirement import PipRequirement
7✔
45
from pants.util.strutil import softwrap
7✔
46

47
logger = logging.getLogger(__name__)
7✔
48
ParseRequirementsCallback = Callable[[bytes, str], Iterable[PipRequirement]]
7✔
49

50

51
async def _generate_requirements(
7✔
52
    request: GenerateTargetsRequest,
53
    union_membership: UnionMembership,
54
    python_setup: PythonSetup,
55
    parse_requirements_callback: ParseRequirementsCallback,
56
) -> Iterable[PythonRequirementTarget]:
57
    generator = request.generator
5✔
58
    requirements_rel_path = generator[SingleSourceField].value
5✔
59
    requirements_full_path = generator[SingleSourceField].file_path
5✔
60
    overrides = {
5✔
61
        canonicalize_project_name(k): v
62
        for k, v in request.require_unparametrized_overrides().items()
63
    }
64

65
    # Pretend this is just another generated target, for typing purposes.
66
    file_tgt = cast(
5✔
67
        "PythonRequirementTarget",
68
        TargetGeneratorSourcesHelperTarget(
69
            {TargetGeneratorSourcesHelperSourcesField.alias: requirements_rel_path},
70
            Address(
71
                request.template_address.spec_path,
72
                target_name=request.template_address.target_name,
73
                relative_file_path=requirements_rel_path,
74
            ),
75
            union_membership,
76
        ),
77
    )
78

79
    req_deps = [file_tgt.address.spec]
5✔
80

81
    resolve = request.template.get(
5✔
82
        PythonRequirementResolveField.alias, python_setup.default_resolve
83
    )
84
    lockfile = (
5✔
85
        python_setup.resolves.get(resolve) if python_setup.enable_synthetic_lockfiles else None
86
    )
87
    if lockfile:
5✔
88
        lockfile_address = Address(
3✔
89
            os.path.dirname(lockfile),
90
            target_name=synthetic_lockfile_target_name(resolve),
91
        )
92
        target_adaptor = await find_target_adaptor(
3✔
93
            TargetAdaptorRequest(
94
                description_of_origin=f"{generator.alias} lockfile dep for the {resolve} resolve",
95
                address=lockfile_address,
96
            ),
97
        )
98
        if target_adaptor.type_alias == "_lockfiles":
3✔
99
            req_deps.append(f"{lockfile}:{synthetic_lockfile_target_name(resolve)}")
3✔
100
        else:
UNCOV
101
            logger.warning(
1✔
102
                softwrap(
103
                    f"""
104
                    The synthetic lockfile target for {lockfile} is being shadowed by the
105
                    {target_adaptor.type_alias} target {lockfile_address}.
106

107
                    There will not be any dependency to the lockfile.
108

109
                    Resolve by either renaming the shadowing target, the resolve {resolve!r} or
110
                    moving the target or the lockfile to another directory.
111
                    """
112
                )
113
            )
114

115
    digest_contents = await get_digest_contents(
5✔
116
        **implicitly(
117
            PathGlobs(
118
                [requirements_full_path],
119
                glob_match_error_behavior=GlobMatchErrorBehavior.error,
120
                description_of_origin=f"{generator}'s field `{SingleSourceField.alias}`",
121
            )
122
        )
123
    )
124

125
    module_mapping = generator[ModuleMappingField].value
5✔
126
    stubs_mapping = generator[TypeStubsModuleMappingField].value
5✔
127

128
    def generate_tgt(
5✔
129
        project_name: str, parsed_reqs: Iterable[PipRequirement]
130
    ) -> PythonRequirementTarget:
131
        normalized_proj_name = canonicalize_project_name(project_name)
5✔
132
        tgt_overrides = overrides.pop(normalized_proj_name, {})
5✔
133
        if Dependencies.alias in tgt_overrides:
5✔
134
            tgt_overrides = tgt_overrides | {  # type: ignore[operator]
3✔
135
                Dependencies.alias: list(tgt_overrides[Dependencies.alias]) + req_deps
136
            }
137

138
        return PythonRequirementTarget(
5✔
139
            {
140
                **request.template,
141
                PythonRequirementsField.alias: list(parsed_reqs),
142
                PythonRequirementModulesField.alias: module_mapping.get(normalized_proj_name),
143
                PythonRequirementTypeStubModulesField.alias: stubs_mapping.get(
144
                    normalized_proj_name
145
                ),
146
                # This may get overridden by `tgt_overrides`, which will have already added in
147
                # the file tgt.
148
                Dependencies.alias: req_deps,
149
                **tgt_overrides,
150
            },
151
            request.template_address.create_generated(project_name),
152
            union_membership,
153
        )
154

155
    requirements = parse_requirements_callback(digest_contents[0].content, requirements_full_path)
5✔
156
    grouped_requirements = itertools.groupby(requirements, lambda parsed_req: parsed_req.name)
5✔
157
    result = tuple(
5✔
158
        generate_tgt(project_name, parsed_reqs_)
159
        for project_name, parsed_reqs_ in grouped_requirements
160
    ) + (file_tgt,)
161

162
    if overrides:
5✔
163
        raise InvalidFieldException(
×
164
            softwrap(
165
                f"""
166
                Unused key in the `overrides` field for {request.template_address}:
167
                {sorted(overrides)}
168
                """
169
            )
170
        )
171

172
    return result
5✔
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