• 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/util_rules/first_party_pkg.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 json
×
UNCOV
7
import logging
×
UNCOV
8
import os
×
UNCOV
9
from dataclasses import dataclass
×
10

UNCOV
11
from pants.backend.go.go_sources import load_go_binary
×
UNCOV
12
from pants.backend.go.go_sources.load_go_binary import LoadedGoBinaryRequest, setup_go_binary
×
UNCOV
13
from pants.backend.go.target_types import GoPackageSourcesField
×
UNCOV
14
from pants.backend.go.util_rules import pkg_analyzer
×
UNCOV
15
from pants.backend.go.util_rules.build_opts import GoBuildOptions
×
UNCOV
16
from pants.backend.go.util_rules.cgo import CGoCompilerFlags
×
UNCOV
17
from pants.backend.go.util_rules.embedcfg import EmbedConfig
×
UNCOV
18
from pants.backend.go.util_rules.go_mod import (
×
19
    GoModInfoRequest,
20
    OwningGoModRequest,
21
    determine_go_mod_info,
22
    find_owning_go_mod,
23
)
UNCOV
24
from pants.backend.go.util_rules.pkg_analyzer import PackageAnalyzerSetup
×
UNCOV
25
from pants.build_graph.address import Address
×
UNCOV
26
from pants.core.target_types import ResourceSourceField
×
UNCOV
27
from pants.core.util_rules import source_files
×
UNCOV
28
from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
×
UNCOV
29
from pants.engine.engine_aware import EngineAwareParameter
×
UNCOV
30
from pants.engine.fs import AddPrefix, CreateDigest, Digest, FileContent, MergeDigests
×
UNCOV
31
from pants.engine.internals.graph import hydrate_sources, resolve_target, resolve_targets
×
UNCOV
32
from pants.engine.intrinsics import add_prefix, create_digest, execute_process, merge_digests
×
UNCOV
33
from pants.engine.process import FallibleProcessResult, Process
×
UNCOV
34
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
×
UNCOV
35
from pants.engine.target import (
×
36
    Dependencies,
37
    DependenciesRequest,
38
    HydrateSourcesRequest,
39
    SourcesField,
40
    WrappedTargetRequest,
41
)
UNCOV
42
from pants.util.dirutil import fast_relpath
×
UNCOV
43
from pants.util.logging import LogLevel
×
44

UNCOV
45
logger = logging.getLogger(__name__)
×
46

47

UNCOV
48
@dataclass(frozen=True)
×
UNCOV
49
class FirstPartyPkgImportPath:
×
50
    """The derived import path of a first party package, based on its owning go.mod.
51

52
    Use `FirstPartyPkgAnalysis` instead for more detailed information like parsed imports. Use
53
    `FirstPartyPkgDigest` for source files and embed config.
54
    """
55

UNCOV
56
    import_path: str
×
UNCOV
57
    dir_path_rel_to_gomod: str
×
58

59

UNCOV
60
@dataclass(frozen=True)
×
UNCOV
61
class FirstPartyPkgImportPathRequest(EngineAwareParameter):
×
UNCOV
62
    address: Address
×
63

UNCOV
64
    def debug_hint(self) -> str:
×
65
        return self.address.spec
×
66

67

UNCOV
68
@dataclass(frozen=True)
×
UNCOV
69
class FirstPartyPkgAnalysis:
×
70
    """All the metadata for a first-party Go package.
71

72
    `dir_path` is relative to the build root.
73

74
    Use `FirstPartyPkgImportPath` if you only need the derived import path. Use
75
    `FirstPartyPkgDigest` for the source files and embed config.
76
    """
77

UNCOV
78
    import_path: str
×
UNCOV
79
    name: str
×
UNCOV
80
    dir_path: str
×
81

UNCOV
82
    imports: tuple[str, ...]
×
UNCOV
83
    test_imports: tuple[str, ...]
×
UNCOV
84
    xtest_imports: tuple[str, ...]
×
85

UNCOV
86
    go_files: tuple[str, ...]
×
UNCOV
87
    cgo_files: tuple[str, ...]
×
UNCOV
88
    test_go_files: tuple[str, ...]
×
UNCOV
89
    xtest_go_files: tuple[str, ...]
×
90

UNCOV
91
    cgo_flags: CGoCompilerFlags
×
92

UNCOV
93
    c_files: tuple[str, ...]
×
UNCOV
94
    cxx_files: tuple[str, ...]
×
UNCOV
95
    m_files: tuple[str, ...]
×
UNCOV
96
    h_files: tuple[str, ...]
×
UNCOV
97
    f_files: tuple[str, ...]
×
UNCOV
98
    s_files: tuple[str, ...]
×
99

UNCOV
100
    syso_files: tuple[str, ...]
×
101

UNCOV
102
    minimum_go_version: str | None
×
103

UNCOV
104
    embed_patterns: tuple[str, ...]
×
UNCOV
105
    test_embed_patterns: tuple[str, ...]
×
UNCOV
106
    xtest_embed_patterns: tuple[str, ...]
×
107

108

UNCOV
109
@dataclass(frozen=True)
×
UNCOV
110
class FallibleFirstPartyPkgAnalysis:
×
111
    """Metadata for a Go package, but fallible if our analysis failed."""
112

UNCOV
113
    analysis: FirstPartyPkgAnalysis | None
×
UNCOV
114
    import_path: str
×
UNCOV
115
    exit_code: int = 0
×
UNCOV
116
    stderr: str | None = None
×
117

UNCOV
118
    @classmethod
×
UNCOV
119
    def from_process_result(
×
120
        cls,
121
        result: FallibleProcessResult,
122
        *,
123
        dir_path: str,
124
        import_path: str,
125
        minimum_go_version: str,
126
        description_of_source: str,
127
    ) -> FallibleFirstPartyPkgAnalysis:
128
        if result.exit_code != 0:
×
129
            return cls(
×
130
                analysis=None,
131
                import_path=import_path,
132
                exit_code=result.exit_code,
133
                stderr=(
134
                    f"Failed to analyze Go sources generated from {import_path}.\n\n"
135
                    "This may be a bug in Pants. Please report this issue at "
136
                    "https://github.com/pantsbuild/pants/issues/new/choose and include the following data: "
137
                    f"error:\n{result.stderr.decode()}"
138
                ),
139
            )
140

141
        try:
×
142
            metadata = json.loads(result.stdout)
×
143
        except json.JSONDecodeError as ex:
×
144
            return cls(
×
145
                analysis=None,
146
                import_path=import_path,
147
                exit_code=1,
148
                stderr=f"Failed to decode JSON document from analysis: {ex}",
149
            )
150

151
        if "Error" in metadata or "InvalidGoFiles" in metadata:
×
152
            error = metadata.get("Error", "")
×
153
            if error:
×
154
                error += "\n"
×
155
            if "InvalidGoFiles" in metadata:
×
156
                error += "\n".join(
×
157
                    f"{filename}: {error}"
158
                    for filename, error in metadata.get("InvalidGoFiles", {}).items()
159
                )
160
                error += "\n"
×
161
            return cls(analysis=None, import_path=import_path, exit_code=1, stderr=error)
×
162

163
        analysis = FirstPartyPkgAnalysis(
×
164
            dir_path=dir_path,
165
            import_path=import_path,
166
            name=metadata["Name"],
167
            imports=tuple(metadata.get("Imports", [])),
168
            test_imports=tuple(metadata.get("TestImports", [])),
169
            xtest_imports=tuple(metadata.get("XTestImports", [])),
170
            go_files=tuple(metadata.get("GoFiles", [])),
171
            cgo_files=tuple(metadata.get("CgoFiles", [])),
172
            test_go_files=tuple(metadata.get("TestGoFiles", [])),
173
            xtest_go_files=tuple(metadata.get("XTestGoFiles", [])),
174
            cgo_flags=CGoCompilerFlags(
175
                cflags=tuple(metadata.get("CgoCFLAGS", [])),
176
                cppflags=tuple(metadata.get("CgoCPPFLAGS", [])),
177
                cxxflags=tuple(metadata.get("CgoCXXFLAGS", [])),
178
                fflags=tuple(metadata.get("CgoFFLAGS", [])),
179
                ldflags=tuple(metadata.get("CgoLDFLAGS", [])),
180
                pkg_config=tuple(metadata.get("CgoPkgConfig", [])),
181
            ),
182
            c_files=tuple(metadata.get("CFiles", [])),
183
            cxx_files=tuple(metadata.get("CXXFiles", [])),
184
            m_files=tuple(metadata.get("MFiles", [])),
185
            h_files=tuple(metadata.get("HFiles", [])),
186
            f_files=tuple(metadata.get("FFiles", [])),
187
            s_files=tuple(metadata.get("SFiles", [])),
188
            syso_files=tuple(metadata.get("SysoFiles", ())),
189
            minimum_go_version=minimum_go_version,
190
            embed_patterns=tuple(metadata.get("EmbedPatterns", [])),
191
            test_embed_patterns=tuple(metadata.get("TestEmbedPatterns", [])),
192
            xtest_embed_patterns=tuple(metadata.get("XTestEmbedPatterns", [])),
193
        )
194
        return cls(analysis, import_path)
×
195

196

UNCOV
197
@dataclass(frozen=True)
×
UNCOV
198
class FirstPartyPkgAnalysisRequest(EngineAwareParameter):
×
UNCOV
199
    address: Address
×
UNCOV
200
    build_opts: GoBuildOptions
×
UNCOV
201
    extra_build_tags: tuple[str, ...] = ()
×
202

UNCOV
203
    def debug_hint(self) -> str:
×
204
        return self.address.spec
×
205

206

UNCOV
207
@dataclass(frozen=True)
×
UNCOV
208
class FirstPartyPkgDigest:
×
209
    """The source files needed to build the package."""
210

UNCOV
211
    digest: Digest
×
UNCOV
212
    embed_config: EmbedConfig | None
×
UNCOV
213
    test_embed_config: EmbedConfig | None
×
UNCOV
214
    xtest_embed_config: EmbedConfig | None
×
215

216

UNCOV
217
@dataclass(frozen=True)
×
UNCOV
218
class FallibleFirstPartyPkgDigest:
×
219
    """The source files for a Go package, but fallible if embed preparation failed."""
220

UNCOV
221
    pkg_digest: FirstPartyPkgDigest | None
×
UNCOV
222
    exit_code: int = 0
×
UNCOV
223
    stderr: str | None = None
×
224

225

UNCOV
226
@dataclass(frozen=True)
×
UNCOV
227
class FirstPartyPkgDigestRequest(EngineAwareParameter):
×
UNCOV
228
    address: Address
×
UNCOV
229
    build_opts: GoBuildOptions
×
230

UNCOV
231
    def debug_hint(self) -> str:
×
232
        return self.address.spec
×
233

234

UNCOV
235
@rule
×
UNCOV
236
async def compute_first_party_package_import_path(
×
237
    request: FirstPartyPkgImportPathRequest,
238
) -> FirstPartyPkgImportPath:
239
    owning_go_mod = await find_owning_go_mod(OwningGoModRequest(request.address), **implicitly())
×
240

241
    # We validate that the sources are for the target's directory, e.g. don't use `**`, so we can
242
    # simply look at the address to get the subpath.
243
    dir_path_rel_to_gomod = fast_relpath(request.address.spec_path, owning_go_mod.address.spec_path)
×
244

245
    go_mod_info = await determine_go_mod_info(GoModInfoRequest(owning_go_mod.address))
×
246
    import_path = (
×
247
        f"{go_mod_info.import_path}/{dir_path_rel_to_gomod}"
248
        if dir_path_rel_to_gomod
249
        else go_mod_info.import_path
250
    )
251
    return FirstPartyPkgImportPath(import_path, dir_path_rel_to_gomod)
×
252

253

UNCOV
254
@rule
×
UNCOV
255
async def analyze_first_party_package(
×
256
    request: FirstPartyPkgAnalysisRequest,
257
    analyzer: PackageAnalyzerSetup,
258
) -> FallibleFirstPartyPkgAnalysis:
259
    wrapped_target, import_path_info, owning_go_mod = await concurrently(
×
260
        resolve_target(
261
            WrappedTargetRequest(
262
                request.address, description_of_origin="<first party pkg analysis>"
263
            ),
264
            **implicitly(),
265
        ),
266
        compute_first_party_package_import_path(FirstPartyPkgImportPathRequest(request.address)),
267
        find_owning_go_mod(OwningGoModRequest(request.address), **implicitly()),
268
    )
269
    go_mod_info = await determine_go_mod_info(GoModInfoRequest(owning_go_mod.address))
×
270

271
    pkg_sources = await hydrate_sources(
×
272
        HydrateSourcesRequest(wrapped_target.target[GoPackageSourcesField]), **implicitly()
273
    )
274

275
    extra_build_tags_env = {}
×
276
    if request.extra_build_tags:
×
277
        extra_build_tags_env = {"EXTRA_BUILD_TAGS": ",".join(request.extra_build_tags)}
×
278

279
    input_digest = await merge_digests(MergeDigests([pkg_sources.snapshot.digest, analyzer.digest]))
×
280
    result = await execute_process(
×
281
        Process(
282
            (analyzer.path, request.address.spec_path or "."),
283
            input_digest=input_digest,
284
            description=f"Determine metadata for {request.address}",
285
            level=LogLevel.DEBUG,
286
            env={
287
                "CGO_ENABLED": "1" if request.build_opts.cgo_enabled else "0",
288
                **extra_build_tags_env,
289
            },
290
        ),
291
        **implicitly(),
292
    )
293
    return FallibleFirstPartyPkgAnalysis.from_process_result(
×
294
        result,
295
        dir_path=request.address.spec_path,
296
        import_path=import_path_info.import_path,
297
        minimum_go_version=go_mod_info.minimum_go_version or "",
298
        description_of_source=f"first-party Go package `{request.address}`",
299
    )
300

301

UNCOV
302
@rule
×
UNCOV
303
async def setup_first_party_pkg_digest(
×
304
    request: FirstPartyPkgDigestRequest,
305
) -> FallibleFirstPartyPkgDigest:
306
    embedder, wrapped_target, maybe_analysis = await concurrently(
×
307
        setup_go_binary(
308
            LoadedGoBinaryRequest("embedcfg", ("main.go",), "./embedder"), **implicitly()
309
        ),
310
        resolve_target(
311
            WrappedTargetRequest(
312
                request.address, description_of_origin="<first party digest setup>"
313
            ),
314
            **implicitly(),
315
        ),
316
        analyze_first_party_package(
317
            FirstPartyPkgAnalysisRequest(request.address, build_opts=request.build_opts),
318
            **implicitly(),
319
        ),
320
    )
321
    if maybe_analysis.analysis is None:
×
322
        return FallibleFirstPartyPkgDigest(
×
323
            pkg_digest=None, exit_code=maybe_analysis.exit_code, stderr=maybe_analysis.stderr
324
        )
325
    analysis = maybe_analysis.analysis
×
326

327
    tgt = wrapped_target.target
×
328
    pkg_sources = await hydrate_sources(
×
329
        HydrateSourcesRequest(tgt[GoPackageSourcesField]), **implicitly()
330
    )
331
    sources_digest = pkg_sources.snapshot.digest
×
332
    dir_path = analysis.dir_path if analysis.dir_path else "."
×
333

334
    embed_config = None
×
335
    test_embed_config = None
×
336
    xtest_embed_config = None
×
337

338
    # Add `resources` targets to the package.
339
    dependencies = await resolve_targets(**implicitly(DependenciesRequest(tgt[Dependencies])))
×
340
    resources_sources = await determine_source_files(
×
341
        SourceFilesRequest(
342
            (
343
                t.get(SourcesField)
344
                for t in dependencies
345
                # You can only embed resources located at or below the directory of the
346
                # `go_package`. This is a restriction from Go.
347
                # TODO(#13795): Error if you depend on resources above the go_package?
348
                if t.address.spec_path.startswith(request.address.spec_path)
349
            ),
350
            for_sources_types=(ResourceSourceField,),
351
            # TODO: Switch to True. We need to be confident though that the generated files
352
            #  are located below the go_package.
353
            enable_codegen=False,
354
        )
355
    )
356
    sources_digest = await merge_digests(
×
357
        MergeDigests([sources_digest, resources_sources.snapshot.digest])
358
    )
359

360
    if analysis.embed_patterns or analysis.test_embed_patterns or analysis.xtest_embed_patterns:
×
361
        patterns_json = json.dumps(
×
362
            {
363
                "EmbedPatterns": analysis.embed_patterns,
364
                "TestEmbedPatterns": analysis.test_embed_patterns,
365
                "XTestEmbedPatterns": analysis.xtest_embed_patterns,
366
            }
367
        ).encode("utf-8")
368
        patterns_json_digest, sources_digest_for_embedder = await concurrently(
×
369
            create_digest(CreateDigest([FileContent("patterns.json", patterns_json)])),
370
            add_prefix(AddPrefix(sources_digest, "__sources__")),
371
        )
372
        input_digest = await merge_digests(
×
373
            MergeDigests((sources_digest_for_embedder, patterns_json_digest, embedder.digest))
374
        )
375

376
        embed_result = await execute_process(
×
377
            Process(
378
                (
379
                    "./embedder",
380
                    "patterns.json",
381
                    os.path.normpath(os.path.join("__sources__", dir_path)),
382
                ),
383
                input_digest=input_digest,
384
                description=f"Create embed mapping for {request.address}",
385
                level=LogLevel.DEBUG,
386
            ),
387
            **implicitly(),
388
        )
389
        if embed_result.exit_code != 0:
×
390
            return FallibleFirstPartyPkgDigest(
×
391
                pkg_digest=None,
392
                exit_code=embed_result.exit_code,
393
                stderr=embed_result.stdout.decode() + "\n" + embed_result.stderr.decode(),
394
            )
395

396
        metadata = json.loads(embed_result.stdout)
×
397
        embed_config = EmbedConfig.from_json_dict(
×
398
            metadata.get("EmbedConfig", {}), prefix_to_strip="__sources__/"
399
        )
400
        test_embed_config = EmbedConfig.from_json_dict(
×
401
            metadata.get("TestEmbedConfig", {}), prefix_to_strip="__sources__/"
402
        )
403
        xtest_embed_config = EmbedConfig.from_json_dict(
×
404
            metadata.get("XTestEmbedConfig", {}), prefix_to_strip="__sources__/"
405
        )
406

407
    return FallibleFirstPartyPkgDigest(
×
408
        FirstPartyPkgDigest(
409
            sources_digest,
410
            embed_config=embed_config,
411
            test_embed_config=test_embed_config,
412
            xtest_embed_config=xtest_embed_config,
413
        )
414
    )
415

416

UNCOV
417
def rules():
×
UNCOV
418
    return (*collect_rules(), *source_files.rules(), *load_go_binary.rules(), *pkg_analyzer.rules())
×
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