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

pantsbuild / pants / 20332790708

18 Dec 2025 09:48AM UTC coverage: 64.992% (-15.3%) from 80.295%
20332790708

Pull #22949

github

web-flow
Merge f730a56cd into 407284c67
Pull Request #22949: Add experimental uv resolver for Python lockfiles

54 of 97 new or added lines in 5 files covered. (55.67%)

8270 existing lines in 295 files now uncovered.

48990 of 75379 relevant lines covered (64.99%)

1.81 hits per line

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

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

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import os
×
UNCOV
7
import re
×
UNCOV
8
from collections import defaultdict
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from pathlib import PurePath
×
11

UNCOV
12
from pants.backend.go.subsystems.golang import GolangSubsystem
×
UNCOV
13
from pants.backend.go.target_types import (
×
14
    GoBinaryMainPackageField,
15
    GoBinaryTarget,
16
    GoModTarget,
17
    GoPackageSourcesField,
18
    GoPackageTarget,
19
)
UNCOV
20
from pants.backend.go.util_rules.binary import (
×
21
    GoBinaryMainPackageRequest,
22
    determine_main_pkg_for_go_binary,
23
)
UNCOV
24
from pants.base.specs import AncestorGlobSpec, RawSpecs
×
UNCOV
25
from pants.core.goals.tailor import (
×
26
    AllOwnedSources,
27
    PutativeTarget,
28
    PutativeTargets,
29
    PutativeTargetsRequest,
30
)
UNCOV
31
from pants.engine.fs import PathGlobs
×
UNCOV
32
from pants.engine.internals.graph import resolve_unexpanded_targets
×
UNCOV
33
from pants.engine.intrinsics import get_digest_contents, path_globs_to_paths
×
UNCOV
34
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
35
from pants.engine.unions import UnionRule
×
UNCOV
36
from pants.util.dirutil import group_by_dir
×
UNCOV
37
from pants.util.logging import LogLevel
×
38

39

UNCOV
40
@dataclass(frozen=True)
×
UNCOV
41
class PutativeGoTargetsRequest(PutativeTargetsRequest):
×
UNCOV
42
    pass
×
43

44

UNCOV
45
_package_main_re = re.compile(rb"^package main\s*(//.*)?$", re.MULTILINE)
×
46

47

UNCOV
48
def has_package_main(content: bytes) -> bool:
×
UNCOV
49
    return _package_main_re.search(content) is not None
×
50

51

UNCOV
52
def has_go_mod_ancestor(dirname: str, all_go_mod_dirs: frozenset[str]) -> bool:
×
53
    """We shouldn't add package targets if there is no `go.mod`, as it will cause an error."""
UNCOV
54
    return any(dirname.startswith(go_mod_dir) for go_mod_dir in all_go_mod_dirs)
×
55

56

UNCOV
57
async def _find_go_mod_targets(
×
58
    all_go_mod_files: set[str], all_owned_sources: AllOwnedSources
59
) -> list[PutativeTarget]:
60
    unowned_go_mod_files = all_go_mod_files - set(all_owned_sources)
×
61
    return [
×
62
        PutativeTarget.for_target_type(
63
            GoModTarget,
64
            path=dirname,
65
            name=None,
66
            triggering_sources=sorted(filenames),
67
        )
68
        for dirname, filenames in group_by_dir(unowned_go_mod_files).items()
69
    ]
70

71

UNCOV
72
async def _find_cgo_sources(
×
73
    path: str, all_owned_sources: AllOwnedSources
74
) -> tuple[list[str], list[str]]:
75
    all_files_in_package = await path_globs_to_paths(PathGlobs([str(PurePath(path, "*"))]))
×
76
    ext_to_files: dict[str, set[str]] = defaultdict(set)
×
77
    for file_path in all_files_in_package.files:
×
78
        for ext in GoPackageSourcesField.expected_file_extensions:
×
79
            if ext == ".go":
×
80
                continue
×
81
            if file_path.endswith(ext):
×
82
                ext_to_files[ext].add(file_path)
×
83

84
    wildcard_globs: list[str] = []
×
85
    files_to_add: list[str] = []
×
86
    triggering_files: list[str] = []
×
87

88
    for ext, files in ext_to_files.items():
×
89
        wildcard = True
×
90
        for file in files:
×
91
            if file in all_owned_sources:
×
92
                wildcard = False
×
93

94
        base_files = sorted([PurePath(f).name for f in files])
×
95
        triggering_files.extend(base_files)
×
96

97
        if wildcard:
×
98
            wildcard_globs.append(f"*{ext}")
×
99
        else:
100
            files_to_add.extend(base_files)
×
101

102
    return [*wildcard_globs, *sorted(files_to_add)], sorted(triggering_files)
×
103

104

UNCOV
105
@dataclass(frozen=True)
×
UNCOV
106
class FindPutativeGoPackageTargetRequest:
×
UNCOV
107
    dir_path: str
×
UNCOV
108
    files: tuple[str, ...]
×
UNCOV
109
    all_go_mod_dirs: frozenset[str]
×
110

111

UNCOV
112
@dataclass(frozen=True)
×
UNCOV
113
class FindPutativeGoPackageTargetResult:
×
UNCOV
114
    putative_target: PutativeTarget | None
×
115

116

UNCOV
117
@rule
×
UNCOV
118
async def find_putative_go_package_target(
×
119
    request: FindPutativeGoPackageTargetRequest,
120
    all_owned_sources: AllOwnedSources,
121
) -> FindPutativeGoPackageTargetResult:
122
    # Ignore paths that have `testdata` or `vendor` in them.
123
    # From `go help packages`: Note, however, that a directory named vendor that itself
124
    # contains code is not a vendored package: cmd/vendor would be a command named vendor.
125
    dirname_parts = PurePath(request.dir_path).parts
×
126
    if "testdata" in dirname_parts or "vendor" in dirname_parts[0:-1]:
×
127
        return FindPutativeGoPackageTargetResult(None)
×
128
    if not has_go_mod_ancestor(request.dir_path, request.all_go_mod_dirs):
×
129
        return FindPutativeGoPackageTargetResult(None)
×
130

131
    cgo_sources, triggering_cgo_files = await _find_cgo_sources(request.dir_path, all_owned_sources)
×
132
    kwargs = {}
×
133
    if cgo_sources:
×
134
        kwargs = {"sources": ("*.go", *cgo_sources)}
×
135

136
    return FindPutativeGoPackageTargetResult(
×
137
        PutativeTarget.for_target_type(
138
            GoPackageTarget,
139
            path=request.dir_path,
140
            name=None,
141
            kwargs=kwargs,
142
            triggering_sources=[*sorted(request.files), *triggering_cgo_files],
143
        )
144
    )
145

146

UNCOV
147
async def _find_go_package_targets(
×
148
    request: PutativeGoTargetsRequest,
149
    all_go_mod_dirs: frozenset[str],
150
    all_owned_sources: AllOwnedSources,
151
) -> list[PutativeTarget]:
152
    all_go_files = await path_globs_to_paths(request.path_globs("*.go"))
×
153
    unowned_go_files = set(all_go_files.files) - set(all_owned_sources)
×
154
    candidate_putative_targets = await concurrently(
×
155
        find_putative_go_package_target(
156
            FindPutativeGoPackageTargetRequest(
157
                dir_path=dirname,
158
                files=tuple(filenames),
159
                all_go_mod_dirs=all_go_mod_dirs,
160
            ),
161
            **implicitly(),
162
        )
163
        for dirname, filenames in group_by_dir(unowned_go_files).items()
164
    )
165
    return [
×
166
        ptgt.putative_target
167
        for ptgt in candidate_putative_targets
168
        if ptgt.putative_target is not None
169
    ]
170

171

UNCOV
172
async def _find_go_binary_targets(
×
173
    request: PutativeGoTargetsRequest, all_go_mod_dirs: frozenset[str]
174
) -> list[PutativeTarget]:
175
    all_go_files_digest_contents = await get_digest_contents(
×
176
        **implicitly(request.path_globs("*.go"))
177
    )
178

179
    main_package_dirs = []
×
180
    for file_content in all_go_files_digest_contents:
×
181
        dirname = os.path.dirname(file_content.path)
×
182
        if has_package_main(file_content.content) and has_go_mod_ancestor(dirname, all_go_mod_dirs):
×
183
            main_package_dirs.append(dirname)
×
184

185
    existing_targets = await resolve_unexpanded_targets(
×
186
        **implicitly(
187
            RawSpecs(
188
                ancestor_globs=tuple(AncestorGlobSpec(d) for d in main_package_dirs),
189
                description_of_origin="the `go_binary` tailor rule",
190
            ),
191
        )
192
    )
193
    owned_main_packages = await concurrently(
×
194
        determine_main_pkg_for_go_binary(GoBinaryMainPackageRequest(t[GoBinaryMainPackageField]))
195
        for t in existing_targets
196
        if t.has_field(GoBinaryMainPackageField)
197
    )
198
    unowned_main_package_dirs = set(main_package_dirs) - {
×
199
        # NB: We assume the `go_package` lives in the directory it's defined, which we validate
200
        # by e.g. banning `**` in its sources field.
201
        pkg.address.spec_path
202
        for pkg in owned_main_packages
203
    }
204
    return [
×
205
        PutativeTarget.for_target_type(
206
            GoBinaryTarget,
207
            path=main_pkg_dir,
208
            name="bin",
209
            triggering_sources=tuple(),
210
        )
211
        for main_pkg_dir in unowned_main_package_dirs
212
    ]
213

214

UNCOV
215
@rule(level=LogLevel.DEBUG, desc="Determine candidate Go targets to create")
×
UNCOV
216
async def find_putative_go_targets(
×
217
    request: PutativeGoTargetsRequest,
218
    all_owned_sources: AllOwnedSources,
219
    golang_subsystem: GolangSubsystem,
220
) -> PutativeTargets:
221
    putative_targets = []
×
222
    _all_go_mod_paths = await path_globs_to_paths(request.path_globs("go.mod"))
×
223
    all_go_mod_files = set(_all_go_mod_paths.files)
×
224
    all_go_mod_dirs = frozenset(os.path.dirname(fp) for fp in all_go_mod_files)
×
225

226
    if golang_subsystem.tailor_go_mod_targets:
×
227
        putative_targets.extend(await _find_go_mod_targets(all_go_mod_files, all_owned_sources))
×
228

229
    if golang_subsystem.tailor_package_targets:
×
230
        putative_targets.extend(
×
231
            await _find_go_package_targets(request, all_go_mod_dirs, all_owned_sources)
232
        )
233

234
    if golang_subsystem.tailor_binary_targets:
×
235
        putative_targets.extend(await _find_go_binary_targets(request, all_go_mod_dirs))
×
236

237
    return PutativeTargets(putative_targets)
×
238

239

UNCOV
240
def rules():
×
UNCOV
241
    return [*collect_rules(), UnionRule(PutativeTargetsRequest, PutativeGoTargetsRequest)]
×
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