• 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

96.18
/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

4
from __future__ import annotations
11✔
5

6
import json
11✔
7
import logging
11✔
8
import os
11✔
9
from dataclasses import dataclass
11✔
10

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

45
logger = logging.getLogger(__name__)
11✔
46

47

48
@dataclass(frozen=True)
11✔
49
class FirstPartyPkgImportPath:
11✔
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

56
    import_path: str
11✔
57
    dir_path_rel_to_gomod: str
11✔
58

59

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

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

67

68
@dataclass(frozen=True)
11✔
69
class FirstPartyPkgAnalysis:
11✔
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

78
    import_path: str
11✔
79
    name: str
11✔
80
    dir_path: str
11✔
81

82
    imports: tuple[str, ...]
11✔
83
    test_imports: tuple[str, ...]
11✔
84
    xtest_imports: tuple[str, ...]
11✔
85

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

91
    cgo_flags: CGoCompilerFlags
11✔
92

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

100
    syso_files: tuple[str, ...]
11✔
101

102
    minimum_go_version: str | None
11✔
103

104
    embed_patterns: tuple[str, ...]
11✔
105
    test_embed_patterns: tuple[str, ...]
11✔
106
    xtest_embed_patterns: tuple[str, ...]
11✔
107

108

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

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

118
    @classmethod
11✔
119
    def from_process_result(
11✔
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:
9✔
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:
9✔
142
            metadata = json.loads(result.stdout)
9✔
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:
9✔
152
            error = metadata.get("Error", "")
5✔
153
            if error:
5✔
154
                error += "\n"
5✔
155
            if "InvalidGoFiles" in metadata:
5✔
156
                error += "\n".join(
5✔
157
                    f"{filename}: {error}"
158
                    for filename, error in metadata.get("InvalidGoFiles", {}).items()
159
                )
160
                error += "\n"
5✔
161
            return cls(analysis=None, import_path=import_path, exit_code=1, stderr=error)
5✔
162

163
        analysis = FirstPartyPkgAnalysis(
9✔
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)
9✔
195

196

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

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

206

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

211
    digest: Digest
11✔
212
    embed_config: EmbedConfig | None
11✔
213
    test_embed_config: EmbedConfig | None
11✔
214
    xtest_embed_config: EmbedConfig | None
11✔
215

216

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

221
    pkg_digest: FirstPartyPkgDigest | None
11✔
222
    exit_code: int = 0
11✔
223
    stderr: str | None = None
11✔
224

225

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

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

234

235
@rule
11✔
236
async def compute_first_party_package_import_path(
11✔
237
    request: FirstPartyPkgImportPathRequest,
238
) -> FirstPartyPkgImportPath:
239
    owning_go_mod = await find_owning_go_mod(OwningGoModRequest(request.address), **implicitly())
9✔
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)
9✔
244

245
    go_mod_info = await determine_go_mod_info(GoModInfoRequest(owning_go_mod.address))
9✔
246
    import_path = (
9✔
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)
9✔
252

253

254
@rule
11✔
255
async def analyze_first_party_package(
11✔
256
    request: FirstPartyPkgAnalysisRequest,
257
    analyzer: PackageAnalyzerSetup,
258
) -> FallibleFirstPartyPkgAnalysis:
259
    wrapped_target, import_path_info, owning_go_mod = await concurrently(
9✔
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))
9✔
270

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

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

279
    input_digest = await merge_digests(MergeDigests([pkg_sources.snapshot.digest, analyzer.digest]))
9✔
280
    result = await execute_process(
9✔
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(
9✔
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

302
@rule
11✔
303
async def setup_first_party_pkg_digest(
11✔
304
    request: FirstPartyPkgDigestRequest,
305
) -> FallibleFirstPartyPkgDigest:
306
    embedder, wrapped_target, maybe_analysis = await concurrently(
7✔
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:
7✔
322
        return FallibleFirstPartyPkgDigest(
3✔
323
            pkg_digest=None, exit_code=maybe_analysis.exit_code, stderr=maybe_analysis.stderr
324
        )
325
    analysis = maybe_analysis.analysis
7✔
326

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

334
    embed_config = None
7✔
335
    test_embed_config = None
7✔
336
    xtest_embed_config = None
7✔
337

338
    # Add `resources` targets to the package.
339
    dependencies = await resolve_targets(**implicitly(DependenciesRequest(tgt[Dependencies])))
7✔
340
    resources_sources = await determine_source_files(
7✔
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(
7✔
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:
7✔
361
        patterns_json = json.dumps(
2✔
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(
2✔
369
            create_digest(CreateDigest([FileContent("patterns.json", patterns_json)])),
370
            add_prefix(AddPrefix(sources_digest, "__sources__")),
371
        )
372
        input_digest = await merge_digests(
2✔
373
            MergeDigests((sources_digest_for_embedder, patterns_json_digest, embedder.digest))
374
        )
375

376
        embed_result = await execute_process(
2✔
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:
2✔
UNCOV
390
            return FallibleFirstPartyPkgDigest(
1✔
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)
2✔
397
        embed_config = EmbedConfig.from_json_dict(
2✔
398
            metadata.get("EmbedConfig", {}), prefix_to_strip="__sources__/"
399
        )
400
        test_embed_config = EmbedConfig.from_json_dict(
2✔
401
            metadata.get("TestEmbedConfig", {}), prefix_to_strip="__sources__/"
402
        )
403
        xtest_embed_config = EmbedConfig.from_json_dict(
2✔
404
            metadata.get("XTestEmbedConfig", {}), prefix_to_strip="__sources__/"
405
        )
406

407
    return FallibleFirstPartyPkgDigest(
7✔
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

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