• 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

84.4
/src/python/pants/backend/go/util_rules/build_opts.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from __future__ import annotations
11✔
4

5
import logging
11✔
6
from collections.abc import Iterable
11✔
7
from dataclasses import dataclass
11✔
8

9
from pants.backend.go.subsystems.golang import GolangSubsystem
11✔
10
from pants.backend.go.subsystems.gotest import GoTestSubsystem
11✔
11
from pants.backend.go.target_types import (
11✔
12
    GoAddressSanitizerEnabledField,
13
    GoAssemblerFlagsField,
14
    GoCgoEnabledField,
15
    GoCompilerFlagsField,
16
    GoLinkerFlagsField,
17
    GoMemorySanitizerEnabledField,
18
    GoRaceDetectorEnabledField,
19
    GoTestAddressSanitizerEnabledField,
20
    GoTestMemorySanitizerEnabledField,
21
    GoTestRaceDetectorEnabledField,
22
)
23
from pants.backend.go.util_rules import go_mod, goroot
11✔
24
from pants.backend.go.util_rules.coverage import GoCoverageConfig
11✔
25
from pants.backend.go.util_rules.go_mod import OwningGoModRequest, find_owning_go_mod
11✔
26
from pants.backend.go.util_rules.goroot import GoRoot
11✔
27
from pants.build_graph.address import Address
11✔
28
from pants.engine.engine_aware import EngineAwareParameter
11✔
29
from pants.engine.internals import graph
11✔
30
from pants.engine.internals.graph import resolve_target
11✔
31
from pants.engine.rules import collect_rules, implicitly, rule
11✔
32
from pants.engine.target import FieldSet, WrappedTargetRequest
11✔
33

34
logger = logging.getLogger(__name__)
11✔
35

36

37
@dataclass(frozen=True)
11✔
38
class GoBuildOptions:
11✔
39
    # Coverage configuration.
40
    # If this is set and a package's import path matches `import_path_include_patterns`, then the package
41
    # will be instrumented for code coverage. (A caller can also force code coverage instrumentation by setting
42
    # `with_coverage` to `True` on `BuildGoPackageTargetRequest`.)
43
    coverage_config: GoCoverageConfig | None = None
11✔
44

45
    # Controls whether cgo support is enabled.
46
    cgo_enabled: bool = True
11✔
47

48
    # Enable the Go data race detector is true.
49
    with_race_detector: bool = False
11✔
50

51
    # Enable interoperation with the C/C++ memory sanitizer.
52
    with_msan: bool = False
11✔
53

54
    # Enable interoperation with the C/C++ address sanitizer.
55
    with_asan: bool = False
11✔
56

57
    # Extra flags to pass to the Go compiler (i.e., `go tool compile`).
58
    # Note: These flags come from `go_mod` and `go_binary` targets. Package-specific compiler flags
59
    # may still come from `go_package` targets.
60
    compiler_flags: tuple[str, ...] = ()
11✔
61

62
    # Extra flags to pass to the Go linker (i.e., `go tool link`).
63
    # Note: These flags come from `go_mod` and `go_binary` targets.
64
    linker_flags: tuple[str, ...] = ()
11✔
65

66
    # Extra flags to pass to the Go assembler (i.e., `go tool asm`).
67
    # Note: These flags come from `go_mod` and `go_binary` targets. Package-specific assembler flags
68
    # may still come from `go_package` targets.
69
    assembler_flags: tuple[str, ...] = ()
11✔
70

71
    def __post_init__(self):
11✔
72
        assert not (self.with_race_detector and self.with_msan)
11✔
73
        assert not (self.with_race_detector and self.with_asan)
11✔
74
        assert not (self.with_msan and self.with_asan)
11✔
75

76

77
@dataclass(frozen=True)
11✔
78
class GoBuildOptionsFromTargetRequest(EngineAwareParameter):
11✔
79
    address: Address
11✔
80
    for_tests: bool = False
11✔
81

82
    def debug_hint(self) -> str | None:
11✔
83
        return self.address.spec
×
84

85

86
@dataclass(frozen=True)
11✔
87
class GoBuildOptionsFieldSet(FieldSet):
11✔
88
    required_fields = (
11✔
89
        GoCgoEnabledField,
90
        GoRaceDetectorEnabledField,
91
        GoMemorySanitizerEnabledField,
92
        GoAddressSanitizerEnabledField,
93
        GoCompilerFlagsField,
94
        GoLinkerFlagsField,
95
    )
96

97
    cgo_enabled: GoCgoEnabledField
11✔
98
    race: GoRaceDetectorEnabledField
11✔
99
    msan: GoMemorySanitizerEnabledField
11✔
100
    asan: GoAddressSanitizerEnabledField
11✔
101
    compiler_flags: GoCompilerFlagsField
11✔
102
    linker_flags: GoLinkerFlagsField
11✔
103
    assembler_flags: GoAssemblerFlagsField
11✔
104

105

106
@dataclass(frozen=True)
11✔
107
class GoTestBuildOptionsFieldSet(FieldSet):
11✔
108
    required_fields = (
11✔
109
        GoTestRaceDetectorEnabledField,
110
        GoTestMemorySanitizerEnabledField,
111
        GoTestAddressSanitizerEnabledField,
112
    )
113

114
    test_race: GoTestRaceDetectorEnabledField
11✔
115
    test_msan: GoTestMemorySanitizerEnabledField
11✔
116
    test_asan: GoTestAddressSanitizerEnabledField
11✔
117

118

119
def _first_non_none_value(items: Iterable[tuple[bool | None, str] | None]) -> tuple[bool, str]:
11✔
120
    """Return the first non-None value from the iterator."""
121
    for item_opt in items:
9✔
122
        if item_opt is not None:
9✔
123
            item, reason = item_opt
9✔
124
            if item is not None:
9✔
125
                return item, reason
9✔
126
    return False, "default"
×
127

128

129
# Adapted from https://github.com/golang/go/blob/920f87adda5412a41036a862cf2139bed24aa533/src/internal/platform/supported.go#L7-L23.
130
def race_detector_supported(goroot: GoRoot) -> bool:
11✔
131
    """Returns True if the Go data race detector is supported for the `goroot`'s platform."""
UNCOV
132
    if goroot.goos == "linux":
1✔
UNCOV
133
        return goroot.goarch in ("amd64", "ppc64le", "arm64", "s390x")
1✔
134
    elif goroot.goos == "darwin":
×
135
        return goroot.goarch in ("amd64", "arm64")
×
136
    elif goroot.goos in ("freebsd", "netbsd", "openbsd", "windows"):
×
137
        return goroot.goarch == "amd64"
×
138
    else:
139
        return False
×
140

141

142
# Adapted from https://github.com/golang/go/blob/920f87adda5412a41036a862cf2139bed24aa533/src/internal/platform/supported.go#L25-L37
143
def msan_supported(goroot: GoRoot) -> bool:
11✔
144
    """Returns True if this platform supports interoperation with the C/C++ memory sanitizer."""
UNCOV
145
    if goroot.goos == "linux":
1✔
UNCOV
146
        return goroot.goarch in ("amd64", "arm64")
1✔
147
    elif goroot.goos == "freebsd":
×
148
        return goroot.goarch == "amd64"
×
149
    else:
150
        return False
×
151

152

153
# Adapted from https://github.com/golang/go/blob/920f87adda5412a41036a862cf2139bed24aa533/src/internal/platform/supported.go#L42-L49
154
def asan_supported(goroot: GoRoot) -> bool:
11✔
155
    """Returns True if this platform supports interoperation with the C/C++ address sanitizer."""
UNCOV
156
    if goroot.goos == "linux":
1✔
UNCOV
157
        return goroot.goarch in ("arm64", "amd64", "riscv64", "ppc64le")
1✔
158
    else:
159
        return False
×
160

161

162
@rule
11✔
163
async def go_extract_build_options_from_target(
11✔
164
    request: GoBuildOptionsFromTargetRequest,
165
    goroot: GoRoot,
166
    golang: GolangSubsystem,
167
    go_test_subsystem: GoTestSubsystem,
168
) -> GoBuildOptions:
169
    wrapped_target = await resolve_target(
9✔
170
        WrappedTargetRequest(
171
            request.address, description_of_origin="the `go_extract_build_options_from_target` rule"
172
        ),
173
        **implicitly(),
174
    )
175
    target = wrapped_target.target
9✔
176
    target_fields: GoBuildOptionsFieldSet | None = None
9✔
177
    if GoBuildOptionsFieldSet.is_applicable(target):
9✔
178
        target_fields = GoBuildOptionsFieldSet.create(target)
9✔
179

180
    test_target_fields: GoTestBuildOptionsFieldSet | None = None
9✔
181
    if request.for_tests and GoTestBuildOptionsFieldSet.is_applicable(target):
9✔
UNCOV
182
        test_target_fields = GoTestBuildOptionsFieldSet.create(target)
1✔
183

184
    # Find the owning `go_mod` target so any unspecified fields on a target like `go_binary` will then
185
    # fallback to the `go_mod`. If the target does not have build option fields, then only the `go_mod`
186
    # will be used.
187
    owning_go_mod = await find_owning_go_mod(OwningGoModRequest(request.address), **implicitly())
9✔
188
    wrapped_target_go_mod = await resolve_target(
9✔
189
        WrappedTargetRequest(
190
            owning_go_mod.address,
191
            description_of_origin="the `go_extract_build_options_from_target` rule",
192
        ),
193
        **implicitly(),
194
    )
195
    go_mod_target = wrapped_target_go_mod.target
9✔
196
    go_mod_target_fields = GoBuildOptionsFieldSet.create(go_mod_target)
9✔
197

198
    # Extract the `cgo_enabled` value for this target.
199
    cgo_enabled: bool | None = None
9✔
200
    if target_fields is not None:
9✔
201
        if target_fields.cgo_enabled.value is not None:
9✔
202
            cgo_enabled = target_fields.cgo_enabled.value
×
203
    if cgo_enabled is None:
9✔
204
        if go_mod_target_fields.cgo_enabled.value is not None:
9✔
205
            cgo_enabled = go_mod_target_fields.cgo_enabled.value
×
206
    if cgo_enabled is None:
9✔
207
        cgo_enabled = golang.cgo_enabled
9✔
208

209
    # Extract the `with_race_detector` value for this target.
210
    with_race_detector, race_detector_reason = _first_non_none_value(
9✔
211
        [
212
            (
213
                True if go_test_subsystem.force_race and test_target_fields else None,
214
                "the `[go-test].force_race` option is in effect",
215
            ),
216
            (
217
                (
218
                    test_target_fields.test_race.value,
219
                    f"{GoTestRaceDetectorEnabledField.alias}={test_target_fields.test_race.value} on target `{request.address}`",
220
                )
221
                if test_target_fields
222
                else None
223
            ),
224
            (
225
                (
226
                    target_fields.race.value,
227
                    f"{GoRaceDetectorEnabledField.alias}={target_fields.race.value} on target `{request.address}`",
228
                )
229
                if target_fields
230
                else None
231
            ),
232
            (
233
                (
234
                    go_mod_target_fields.race.value,
235
                    f"{GoRaceDetectorEnabledField.alias}={go_mod_target_fields.race.value} on target `{request.address}`",
236
                )
237
                if go_mod_target_fields
238
                else None
239
            ),
240
            (False, "default"),
241
        ]
242
    )
243
    if with_race_detector and not race_detector_supported(goroot):
9✔
244
        logger.warning(
×
245
            f"The Go data race detector would have been enabled for target `{request.address} "
246
            f"because {race_detector_reason}, "
247
            f"but the race detector is not supported on platform {goroot.goos}/{goroot.goarch}."
248
        )
249
        with_race_detector = False
×
250

251
    # Extract the `with_msan` value for this target.
252
    with_msan, msan_reason = _first_non_none_value(
9✔
253
        [
254
            (
255
                True if go_test_subsystem.force_msan and test_target_fields else None,
256
                "the `[go-test].force_msan` option is in effect",
257
            ),
258
            (
259
                (
260
                    test_target_fields.test_msan.value,
261
                    f"{GoTestMemorySanitizerEnabledField.alias}={test_target_fields.test_msan.value} on target `{request.address}`",
262
                )
263
                if test_target_fields
264
                else None
265
            ),
266
            (
267
                (
268
                    target_fields.msan.value,
269
                    f"{GoMemorySanitizerEnabledField.alias}={target_fields.msan.value} on target `{request.address}`",
270
                )
271
                if target_fields
272
                else None
273
            ),
274
            (
275
                (
276
                    go_mod_target_fields.msan.value,
277
                    f"{GoMemorySanitizerEnabledField.alias}={go_mod_target_fields.msan.value} on target `{request.address}`",
278
                )
279
                if go_mod_target_fields
280
                else None
281
            ),
282
            (False, "default"),
283
        ]
284
    )
285
    if with_msan and not msan_supported(goroot):
9✔
286
        logger.warning(
×
287
            f"Interoperation with the C/C++ memory sanitizer would have been enabled for target `{request.address}` "
288
            f"because {msan_reason}, "
289
            f"but the memory sanitizer is not supported on platform {goroot.goos}/{goroot.goarch}."
290
        )
291
        with_msan = False
×
292

293
    # Extract the `with_asan` value for this target.
294
    with_asan, asan_reason = _first_non_none_value(
9✔
295
        [
296
            (
297
                True if go_test_subsystem.force_asan and test_target_fields else None,
298
                "the `[go-test].force_asan` option is in effect",
299
            ),
300
            (
301
                (
302
                    test_target_fields.test_asan.value,
303
                    f"{GoTestAddressSanitizerEnabledField.alias}={test_target_fields.test_asan.value} on target `{request.address}`",
304
                )
305
                if test_target_fields
306
                else None
307
            ),
308
            (
309
                (
310
                    target_fields.asan.value,
311
                    f"{GoAddressSanitizerEnabledField.alias}={target_fields.asan.value} on target `{request.address}`",
312
                )
313
                if target_fields
314
                else None
315
            ),
316
            (
317
                (
318
                    go_mod_target_fields.asan.value,
319
                    f"{GoAddressSanitizerEnabledField.alias}={go_mod_target_fields.asan.value} on target `{request.address}`",
320
                )
321
                if go_mod_target_fields
322
                else None
323
            ),
324
            (False, "default"),
325
        ]
326
    )
327
    if with_asan and not asan_supported(goroot):
9✔
328
        logger.warning(
×
329
            f"Interoperation with the C/C++ address sanitizer would have been enabled for target `{request.address}` "
330
            f"because {asan_reason}, "
331
            f"but the address sanitizer is not supported on platform {goroot.goos}/{goroot.goarch}."
332
        )
333
        with_asan = False
×
334

335
    # Ensure that only one of the race detector, memory sanitizer, and address sanitizer are ever enabled
336
    # at a single time.
337
    if with_race_detector and with_msan:
9✔
338
        raise ValueError(
×
339
            "The Go data race detector and C/C++ memory sanitizer cannot be enabled at the same time. "
340
            f"The Go data race detector is enabled because {race_detector_reason}. "
341
            f"The C/C++ memory sanitizer is enabled because {msan_reason}."
342
        )
343
    if with_race_detector and with_asan:
9✔
344
        raise ValueError(
×
345
            "The Go data race detector and C/C++ address sanitizer cannot be enabled at the same time. "
346
            f"The Go data race detector is enabled because {race_detector_reason}. "
347
            f"The C/C++ address sanitizer is enabled because {asan_reason}."
348
        )
349
    if with_msan and with_asan:
9✔
350
        raise ValueError(
×
351
            "The C/C++ memory and address sanitizers cannot be enabled at the same time. "
352
            f"The C/C++ memory sanitizer is enabled because {msan_reason}. "
353
            f"The C/C++ address sanitizer is enabled because {asan_reason}."
354
        )
355

356
    # Extract any extra compiler flags specified on `go_mod` or `go_binary` targets.
357
    # Note: A `compiler_flags` field specified on a `go_package` target is extracted elsewhere.
358
    compiler_flags: list[str] = []
9✔
359
    if go_mod_target_fields is not None:
9✔
360
        # To avoid duplication of options, only add the module-specific compiler flags if the target for this request
361
        # is not a `go_mod` target (i.e., because it does not conform to the field set or has a different address).
362
        if target_fields is None or go_mod_target_fields.address != target_fields.address:
9✔
363
            compiler_flags.extend(go_mod_target_fields.compiler_flags.value or [])
6✔
364
    if target_fields is not None:
9✔
365
        compiler_flags.extend(target_fields.compiler_flags.value or [])
9✔
366

367
    # Extract any extra linker flags specified on `go_mod` or `go_binary` targets.
368
    # Note: A `compiler_flags` field specified on a `go_package` target is extracted elsewhere.
369
    linker_flags: list[str] = []
9✔
370
    if go_mod_target_fields is not None:
9✔
371
        # To avoid duplication of options, only add the module-specific compiler flags if the target for this request
372
        # is not a `go_mod` target (i.e., because it does not conform to the field set or has a different address).
373
        if target_fields is None or go_mod_target_fields.address != target_fields.address:
9✔
374
            linker_flags.extend(go_mod_target_fields.linker_flags.value or [])
6✔
375
    if target_fields is not None:
9✔
376
        linker_flags.extend(target_fields.linker_flags.value or [])
9✔
377

378
    # Extract any extra assembler flags specified on `go_mod` or `go_binary` targets.
379
    # Note: An `assembler_flags` field specified on a `go_package` target is extracted elsewhere.
380
    assembler_flags: list[str] = []
9✔
381
    if go_mod_target_fields is not None:
9✔
382
        # To avoid duplication of options, only add the module-specific assembler flags if the target for this request
383
        # is not a `go_mod` target (i.e., because it does not conform to the field set or has a different address).
384
        if target_fields is None or go_mod_target_fields.address != target_fields.address:
9✔
385
            assembler_flags.extend(go_mod_target_fields.assembler_flags.value or [])
6✔
386
    if target_fields is not None:
9✔
387
        assembler_flags.extend(target_fields.assembler_flags.value or [])
9✔
388

389
    return GoBuildOptions(
9✔
390
        cgo_enabled=cgo_enabled,
391
        with_race_detector=with_race_detector,
392
        with_msan=with_msan,
393
        with_asan=with_asan,
394
        compiler_flags=tuple(compiler_flags),
395
        linker_flags=tuple(linker_flags),
396
        assembler_flags=tuple(assembler_flags),
397
    )
398

399

400
def rules():
11✔
401
    return (
11✔
402
        *collect_rules(),
403
        *go_mod.rules(),
404
        *goroot.rules(),
405
        *graph.rules(),
406
    )
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