• 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

43.26
/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
5✔
4

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

9
from pants.backend.go.subsystems.golang import GolangSubsystem
5✔
10
from pants.backend.go.subsystems.gotest import GoTestSubsystem
5✔
11
from pants.backend.go.target_types import (
5✔
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
5✔
24
from pants.backend.go.util_rules.coverage import GoCoverageConfig
5✔
25
from pants.backend.go.util_rules.go_mod import OwningGoModRequest, find_owning_go_mod
5✔
26
from pants.backend.go.util_rules.goroot import GoRoot
5✔
27
from pants.build_graph.address import Address
5✔
28
from pants.engine.engine_aware import EngineAwareParameter
5✔
29
from pants.engine.internals import graph
5✔
30
from pants.engine.internals.graph import resolve_target
5✔
31
from pants.engine.rules import collect_rules, implicitly, rule
5✔
32
from pants.engine.target import FieldSet, WrappedTargetRequest
5✔
33

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

36

37
@dataclass(frozen=True)
5✔
38
class GoBuildOptions:
5✔
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
5✔
44

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

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

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

54
    # Enable interoperation with the C/C++ address sanitizer.
55
    with_asan: bool = False
5✔
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, ...] = ()
5✔
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, ...] = ()
5✔
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, ...] = ()
5✔
70

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

76

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

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

85

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

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

105

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

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

118

119
def _first_non_none_value(items: Iterable[tuple[bool | None, str] | None]) -> tuple[bool, str]:
5✔
120
    """Return the first non-None value from the iterator."""
121
    for item_opt in items:
×
122
        if item_opt is not None:
×
123
            item, reason = item_opt
×
124
            if item is not None:
×
125
                return item, reason
×
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:
5✔
131
    """Returns True if the Go data race detector is supported for the `goroot`'s platform."""
UNCOV
132
    if goroot.goos == "linux":
×
UNCOV
133
        return goroot.goarch in ("amd64", "ppc64le", "arm64", "s390x")
×
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:
5✔
144
    """Returns True if this platform supports interoperation with the C/C++ memory sanitizer."""
UNCOV
145
    if goroot.goos == "linux":
×
UNCOV
146
        return goroot.goarch in ("amd64", "arm64")
×
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:
5✔
155
    """Returns True if this platform supports interoperation with the C/C++ address sanitizer."""
UNCOV
156
    if goroot.goos == "linux":
×
UNCOV
157
        return goroot.goarch in ("arm64", "amd64", "riscv64", "ppc64le")
×
158
    else:
159
        return False
×
160

161

162
@rule
5✔
163
async def go_extract_build_options_from_target(
5✔
164
    request: GoBuildOptionsFromTargetRequest,
165
    goroot: GoRoot,
166
    golang: GolangSubsystem,
167
    go_test_subsystem: GoTestSubsystem,
168
) -> GoBuildOptions:
169
    wrapped_target = await resolve_target(
×
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
×
176
    target_fields: GoBuildOptionsFieldSet | None = None
×
177
    if GoBuildOptionsFieldSet.is_applicable(target):
×
178
        target_fields = GoBuildOptionsFieldSet.create(target)
×
179

180
    test_target_fields: GoTestBuildOptionsFieldSet | None = None
×
181
    if request.for_tests and GoTestBuildOptionsFieldSet.is_applicable(target):
×
182
        test_target_fields = GoTestBuildOptionsFieldSet.create(target)
×
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())
×
188
    wrapped_target_go_mod = await resolve_target(
×
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
×
196
    go_mod_target_fields = GoBuildOptionsFieldSet.create(go_mod_target)
×
197

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

209
    # Extract the `with_race_detector` value for this target.
210
    with_race_detector, race_detector_reason = _first_non_none_value(
×
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):
×
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(
×
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):
×
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(
×
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):
×
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:
×
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:
×
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:
×
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] = []
×
359
    if go_mod_target_fields is not None:
×
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:
×
363
            compiler_flags.extend(go_mod_target_fields.compiler_flags.value or [])
×
364
    if target_fields is not None:
×
365
        compiler_flags.extend(target_fields.compiler_flags.value or [])
×
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] = []
×
370
    if go_mod_target_fields is not None:
×
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:
×
374
            linker_flags.extend(go_mod_target_fields.linker_flags.value or [])
×
375
    if target_fields is not None:
×
376
        linker_flags.extend(target_fields.linker_flags.value or [])
×
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] = []
×
381
    if go_mod_target_fields is not None:
×
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:
×
385
            assembler_flags.extend(go_mod_target_fields.assembler_flags.value or [])
×
386
    if target_fields is not None:
×
387
        assembler_flags.extend(target_fields.assembler_flags.value or [])
×
388

389
    return GoBuildOptions(
×
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():
5✔
401
    return (
5✔
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