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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

0.0
/src/python/pants/backend/go/target_type_rules.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 logging
×
UNCOV
7
from collections import defaultdict
×
UNCOV
8
from dataclasses import dataclass
×
9

UNCOV
10
from pants.backend.go.dependency_inference import (
×
11
    AllGoModuleImportPathsMappings,
12
    GoImportPathsMappingAddressSet,
13
    GoModuleImportPathsMapping,
14
    GoModuleImportPathsMappings,
15
    GoModuleImportPathsMappingsHook,
16
    get_go_module_import_paths_mapping,
17
)
UNCOV
18
from pants.backend.go.target_types import (
×
19
    GoImportPathField,
20
    GoModSourcesField,
21
    GoModTarget,
22
    GoPackageSourcesField,
23
    GoThirdPartyPackageDependenciesField,
24
    GoThirdPartyPackageTarget,
25
)
UNCOV
26
from pants.backend.go.util_rules import build_opts, first_party_pkg, import_analysis
×
UNCOV
27
from pants.backend.go.util_rules.build_opts import (
×
28
    GoBuildOptions,
29
    GoBuildOptionsFromTargetRequest,
30
    go_extract_build_options_from_target,
31
)
UNCOV
32
from pants.backend.go.util_rules.first_party_pkg import (
×
33
    FirstPartyPkgAnalysisRequest,
34
    FirstPartyPkgImportPathRequest,
35
    analyze_first_party_package,
36
    compute_first_party_package_import_path,
37
)
UNCOV
38
from pants.backend.go.util_rules.go_mod import (
×
39
    GoModInfoRequest,
40
    OwningGoModRequest,
41
    determine_go_mod_info,
42
    find_owning_go_mod,
43
)
UNCOV
44
from pants.backend.go.util_rules.import_analysis import (
×
45
    GoStdLibPackagesRequest,
46
    analyze_go_stdlib_packages,
47
)
UNCOV
48
from pants.backend.go.util_rules.third_party_pkg import (
×
49
    AllThirdPartyPackagesRequest,
50
    ThirdPartyPkgAnalysis,
51
    ThirdPartyPkgAnalysisRequest,
52
    download_and_analyze_third_party_packages,
53
    extract_package_info,
54
)
UNCOV
55
from pants.core.target_types import (
×
56
    TargetGeneratorSourcesHelperSourcesField,
57
    TargetGeneratorSourcesHelperTarget,
58
)
UNCOV
59
from pants.engine.addresses import Address
×
UNCOV
60
from pants.engine.engine_aware import EngineAwareParameter
×
UNCOV
61
from pants.engine.intrinsics import digest_to_snapshot
×
UNCOV
62
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
63
from pants.engine.target import (
×
64
    AllTargets,
65
    Dependencies,
66
    FieldSet,
67
    GeneratedTargets,
68
    GenerateTargetsRequest,
69
    InferDependenciesRequest,
70
    InferredDependencies,
71
)
UNCOV
72
from pants.engine.unions import UnionMembership, UnionRule
×
UNCOV
73
from pants.util.frozendict import FrozenDict
×
UNCOV
74
from pants.util.logging import LogLevel
×
75

UNCOV
76
logger = logging.getLogger(__name__)
×
77

78

UNCOV
79
@dataclass(frozen=True)
×
UNCOV
80
class GoImportPathMappingRequest(EngineAwareParameter):
×
UNCOV
81
    go_mod_address: Address
×
82

UNCOV
83
    def debug_hint(self) -> str | None:
×
84
        return str(self.go_mod_address)
×
85

86

UNCOV
87
class FirstPartyGoModuleImportPathsMappingsHook(GoModuleImportPathsMappingsHook):
×
UNCOV
88
    pass
×
89

90

UNCOV
91
@rule(desc="Analyze and map Go import paths for all modules.", level=LogLevel.DEBUG)
×
UNCOV
92
async def go_map_import_paths_by_module(
×
93
    _request: FirstPartyGoModuleImportPathsMappingsHook,
94
    all_targets: AllTargets,
95
) -> GoModuleImportPathsMappings:
96
    import_paths_by_module: dict[Address, dict[str, set[Address]]] = defaultdict(
×
97
        lambda: defaultdict(set)
98
    )
99

100
    candidate_go_source_targets = [
×
101
        tgt
102
        for tgt in all_targets
103
        if (tgt.has_field(GoImportPathField) or tgt.has_field(GoPackageSourcesField))
104
    ]
105

106
    owning_go_mod_targets = await concurrently(
×
107
        find_owning_go_mod(OwningGoModRequest(tgt.address), **implicitly())
108
        for tgt in candidate_go_source_targets
109
    )
110

111
    first_party_gets_metadata = []
×
112
    first_party_gets = []
×
113

114
    for tgt, owning_go_mod in zip(candidate_go_source_targets, owning_go_mod_targets):
×
115
        if tgt.has_field(GoImportPathField):
×
116
            import_path = tgt[GoImportPathField].value
×
117
            import_paths_by_module[owning_go_mod.address][import_path].add(tgt.address)
×
118
        elif tgt.has_field(GoPackageSourcesField):
×
119
            first_party_gets_metadata.append((tgt.address, owning_go_mod))
×
120
            first_party_gets.append(
×
121
                compute_first_party_package_import_path(FirstPartyPkgImportPathRequest(tgt.address))
122
            )
123

124
    first_party_import_paths = await concurrently(first_party_gets)
×
125
    for import_path_info, (addr, owning_go_mod) in zip(
×
126
        first_party_import_paths, first_party_gets_metadata
127
    ):
128
        import_paths_by_module[owning_go_mod.address][import_path_info.import_path].add(addr)
×
129

130
    return GoModuleImportPathsMappings(
×
131
        FrozenDict(
132
            {
133
                go_mod_addr: GoModuleImportPathsMapping(
134
                    mapping=FrozenDict(
135
                        {
136
                            import_path: GoImportPathsMappingAddressSet(
137
                                addresses=tuple(sorted(addresses)), infer_all=False
138
                            )
139
                            for import_path, addresses in import_path_mapping.items()
140
                        }
141
                    ),
142
                    address_to_import_path=FrozenDict(
143
                        {
144
                            address: import_path
145
                            for import_path, addresses in import_path_mapping.items()
146
                            for address in addresses
147
                        }
148
                    ),
149
                )
150
                for go_mod_addr, import_path_mapping in import_paths_by_module.items()
151
            }
152
        )
153
    )
154

155

UNCOV
156
@rule(desc="Analyze Go import paths for all modules.", level=LogLevel.DEBUG)
×
UNCOV
157
async def go_merge_import_paths_analysis(
×
158
    union_membership: UnionMembership,
159
) -> AllGoModuleImportPathsMappings:
160
    import_path_mappers = union_membership.get(GoModuleImportPathsMappingsHook)
×
161
    all_results = await concurrently(
×
162
        get_go_module_import_paths_mapping(**implicitly({impl(): GoModuleImportPathsMappingsHook}))
163
        for impl in import_path_mappers
164
    )
165

166
    import_paths_by_module: dict[Address, dict[str, set[Address]]] = defaultdict(
×
167
        lambda: defaultdict(set)
168
    )
169
    infer_all_by_module: dict[Address, dict[str, bool]] = defaultdict(lambda: defaultdict(bool))
×
170

171
    # Merge all of the mappings together.
172
    for result in all_results:
×
173
        for go_mod_address, mapping in result.modules.items():
×
174
            imports_paths_for_module = import_paths_by_module[go_mod_address]
×
175
            for import_path, address_set in mapping.mapping.items():
×
176
                for address in address_set.addresses:
×
177
                    imports_paths_for_module[import_path].add(address)
×
178
                if address_set.infer_all:
×
179
                    infer_all_by_module[go_mod_address][import_path] = True
×
180

181
    return AllGoModuleImportPathsMappings(
×
182
        FrozenDict(
183
            {
184
                go_mod_addr: GoModuleImportPathsMapping(
185
                    mapping=FrozenDict(
186
                        {
187
                            import_path: GoImportPathsMappingAddressSet(
188
                                addresses=tuple(sorted(addresses)),
189
                                infer_all=infer_all_by_module[go_mod_addr][import_path],
190
                            )
191
                            for import_path, addresses in import_path_mapping.items()
192
                        }
193
                    ),
194
                    address_to_import_path=FrozenDict(
195
                        {
196
                            address: import_path
197
                            for import_path, addresses in import_path_mapping.items()
198
                            for address in addresses
199
                        }
200
                    ),
201
                )
202
                for go_mod_addr, import_path_mapping in import_paths_by_module.items()
203
            }
204
        )
205
    )
206

207

UNCOV
208
@rule(desc="Map Go targets owned by module to import paths", level=LogLevel.DEBUG)
×
UNCOV
209
async def map_import_paths_to_packages(
×
210
    request: GoImportPathMappingRequest,
211
    module_import_path_mappings: AllGoModuleImportPathsMappings,
212
) -> GoModuleImportPathsMapping:
213
    return module_import_path_mappings.modules[request.go_mod_address]
×
214

215

UNCOV
216
@dataclass(frozen=True)
×
UNCOV
217
class GoPackageDependenciesInferenceFieldSet(FieldSet):
×
UNCOV
218
    required_fields = (GoPackageSourcesField,)
×
219

UNCOV
220
    sources: GoPackageSourcesField
×
221

222

UNCOV
223
class InferGoPackageDependenciesRequest(InferDependenciesRequest):
×
UNCOV
224
    infer_from = GoPackageDependenciesInferenceFieldSet
×
225

226

UNCOV
227
@rule(desc="Infer dependencies for first-party Go packages", level=LogLevel.DEBUG)
×
UNCOV
228
async def infer_go_dependencies(
×
229
    request: InferGoPackageDependenciesRequest,
230
) -> InferredDependencies:
231
    go_mod_addr = await find_owning_go_mod(
×
232
        OwningGoModRequest(request.field_set.address), **implicitly()
233
    )
234
    package_mapping, build_opts = await concurrently(
×
235
        map_import_paths_to_packages(
236
            GoImportPathMappingRequest(go_mod_addr.address), **implicitly()
237
        ),
238
        go_extract_build_options_from_target(
239
            GoBuildOptionsFromTargetRequest(go_mod_addr.address), **implicitly()
240
        ),
241
    )
242

243
    addr = request.field_set.address
×
244
    maybe_pkg_analysis, stdlib_packages = await concurrently(
×
245
        analyze_first_party_package(
246
            FirstPartyPkgAnalysisRequest(addr, build_opts=build_opts), **implicitly()
247
        ),
248
        analyze_go_stdlib_packages(
249
            GoStdLibPackagesRequest(with_race_detector=build_opts.with_race_detector)
250
        ),
251
    )
252

253
    if maybe_pkg_analysis.analysis is None:
×
254
        logger.error(
×
255
            f"Failed to analyze {maybe_pkg_analysis.import_path} for dependency inference:\n"
256
            f"{maybe_pkg_analysis.stderr}"
257
        )
258
        return InferredDependencies([])
×
259
    pkg_analysis = maybe_pkg_analysis.analysis
×
260

261
    inferred_dependencies: list[Address] = []
×
262
    for import_path in (
×
263
        *pkg_analysis.imports,
264
        *pkg_analysis.test_imports,
265
        *pkg_analysis.xtest_imports,
266
    ):
267
        # Avoid a dependency cycle caused by external test imports of this package (i.e., "xtest").
268
        if import_path == pkg_analysis.import_path:
×
269
            continue
×
270
        candidate_packages = package_mapping.mapping.get(import_path)
×
271
        if candidate_packages:
×
272
            if candidate_packages.infer_all:
×
273
                inferred_dependencies.extend(candidate_packages.addresses)
×
274
            else:
275
                if len(candidate_packages.addresses) > 1:
×
276
                    # TODO(#12761): Use ExplicitlyProvidedDependencies for disambiguation.
277
                    logger.warning(
×
278
                        f"Ambiguous mapping for import path {import_path} on packages at addresses: {candidate_packages}"
279
                    )
280
                elif len(candidate_packages.addresses) == 1:
×
281
                    inferred_dependencies.append(candidate_packages.addresses[0])
×
282
                else:
283
                    logger.debug(
×
284
                        f"Unable to infer dependency for import path '{import_path}' "
285
                        f"in go_package at address '{addr}'."
286
                    )
287
        else:
288
            logger.debug(
×
289
                f"Unable to infer dependency for import path '{import_path}' "
290
                f"in go_package at address '{addr}'."
291
            )
292

293
    return InferredDependencies(inferred_dependencies)
×
294

295

UNCOV
296
@dataclass(frozen=True)
×
UNCOV
297
class GoThirdPartyPackageInferenceFieldSet(FieldSet):
×
UNCOV
298
    required_fields = (GoThirdPartyPackageDependenciesField, GoImportPathField)
×
299

UNCOV
300
    dependencies: GoThirdPartyPackageDependenciesField
×
UNCOV
301
    import_path: GoImportPathField
×
302

303

UNCOV
304
class InferGoThirdPartyPackageDependenciesRequest(InferDependenciesRequest):
×
UNCOV
305
    infer_from = GoThirdPartyPackageInferenceFieldSet
×
306

307

UNCOV
308
@rule(desc="Infer dependencies for third-party Go packages", level=LogLevel.DEBUG)
×
UNCOV
309
async def infer_go_third_party_package_dependencies(
×
310
    request: InferGoThirdPartyPackageDependenciesRequest,
311
) -> InferredDependencies:
312
    addr = request.field_set.address
×
313
    go_mod_address = addr.maybe_convert_to_target_generator()
×
314

315
    package_mapping, go_mod_info, build_opts = await concurrently(
×
316
        map_import_paths_to_packages(GoImportPathMappingRequest(go_mod_address), **implicitly()),
317
        determine_go_mod_info(GoModInfoRequest(go_mod_address)),
318
        go_extract_build_options_from_target(
319
            GoBuildOptionsFromTargetRequest(go_mod_address), **implicitly()
320
        ),
321
    )
322

323
    pkg_info, stdlib_packages = await concurrently(
×
324
        extract_package_info(
325
            ThirdPartyPkgAnalysisRequest(
326
                request.field_set.import_path.value,
327
                go_mod_address,
328
                go_mod_info.digest,
329
                go_mod_info.mod_path,
330
                build_opts=build_opts,
331
            )
332
        ),
333
        analyze_go_stdlib_packages(
334
            GoStdLibPackagesRequest(with_race_detector=build_opts.with_race_detector)
335
        ),
336
    )
337

338
    inferred_dependencies: list[Address] = []
×
339
    for import_path in pkg_info.imports:
×
340
        candidate_packages = package_mapping.mapping.get(import_path, ())
×
341
        if candidate_packages:
×
342
            if candidate_packages.infer_all:
×
343
                inferred_dependencies.extend(candidate_packages.addresses)
×
344
            else:
345
                if len(candidate_packages.addresses) > 1:
×
346
                    # TODO(#12761): Use ExplicitlyProvidedDependencies for disambiguation.
347
                    logger.warning(
×
348
                        f"Ambiguous mapping for import path {import_path} on packages at addresses: {candidate_packages}"
349
                    )
350
                elif len(candidate_packages.addresses) == 1:
×
351
                    inferred_dependencies.append(candidate_packages.addresses[0])
×
352
                else:
353
                    logger.debug(
×
354
                        f"Unable to infer dependency for import path '{import_path}' "
355
                        f"in go_third_party_package at address '{addr}'."
356
                    )
357
        else:
358
            logger.debug(
×
359
                f"Unable to infer dependency for import path '{import_path}' "
360
                f"in go_third_party_package at address '{addr}'."
361
            )
362

363
    return InferredDependencies(inferred_dependencies)
×
364

365

366
# -----------------------------------------------------------------------------------------------
367
# Generate `go_third_party_package` targets
368
# -----------------------------------------------------------------------------------------------
369

370

UNCOV
371
class GenerateTargetsFromGoModRequest(GenerateTargetsRequest):
×
UNCOV
372
    generate_from = GoModTarget
×
373

374

UNCOV
375
@rule(desc="Generate `go_third_party_package` targets from `go_mod` target", level=LogLevel.DEBUG)
×
UNCOV
376
async def generate_targets_from_go_mod(
×
377
    request: GenerateTargetsFromGoModRequest,
378
    union_membership: UnionMembership,
379
) -> GeneratedTargets:
380
    generator_addr = request.generator.address
×
381
    go_mod_sources = request.generator[GoModSourcesField]
×
382
    go_mod_info = await determine_go_mod_info(GoModInfoRequest(go_mod_sources))
×
383
    go_mod_snapshot = await digest_to_snapshot(go_mod_info.digest)
×
384
    all_packages = await download_and_analyze_third_party_packages(
×
385
        AllThirdPartyPackagesRequest(
386
            generator_addr,
387
            go_mod_info.digest,
388
            go_mod_info.mod_path,
389
            # TODO: There is a rule graph cycle in this rule if this rule tries to use GoBuildOptionsFromTargetRequest.
390
            # For now, just use a default set of options to facilitate analyzing third-party dependencies and
391
            # generating targets.
392
            build_opts=GoBuildOptions(),
393
        )
394
    )
395

396
    def gen_file_tgt(fp: str) -> TargetGeneratorSourcesHelperTarget:
×
397
        return TargetGeneratorSourcesHelperTarget(
×
398
            {TargetGeneratorSourcesHelperSourcesField.alias: fp},
399
            generator_addr.create_file(fp),
400
            union_membership,
401
        )
402

403
    file_tgts = [gen_file_tgt("go.mod")]
×
404
    if go_mod_sources.go_sum_path in go_mod_snapshot.files:
×
405
        file_tgts.append(gen_file_tgt("go.sum"))
×
406

407
    def create_tgt(pkg_info: ThirdPartyPkgAnalysis) -> GoThirdPartyPackageTarget:
×
408
        return GoThirdPartyPackageTarget(
×
409
            {
410
                **request.template,
411
                GoImportPathField.alias: pkg_info.import_path,
412
                Dependencies.alias: [t.address.spec for t in file_tgts],
413
            },
414
            # E.g. `src/go:mod#github.com/google/uuid`.
415
            generator_addr.create_generated(pkg_info.import_path),
416
            union_membership,
417
            residence_dir=generator_addr.spec_path,
418
        )
419

420
    result = tuple(
×
421
        create_tgt(pkg_info) for pkg_info in all_packages.import_paths_to_pkg_info.values()
422
    ) + tuple(file_tgts)
423
    return GeneratedTargets(request.generator, result)
×
424

425

UNCOV
426
def rules():
×
UNCOV
427
    return (
×
428
        *collect_rules(),
429
        *build_opts.rules(),
430
        *first_party_pkg.rules(),
431
        *import_analysis.rules(),
432
        UnionRule(InferDependenciesRequest, InferGoPackageDependenciesRequest),
433
        UnionRule(InferDependenciesRequest, InferGoThirdPartyPackageDependenciesRequest),
434
        UnionRule(GenerateTargetsRequest, GenerateTargetsFromGoModRequest),
435
        UnionRule(GoModuleImportPathsMappingsHook, FirstPartyGoModuleImportPathsMappingsHook),
436
    )
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