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

pantsbuild / pants / 20333307239

18 Dec 2025 10:07AM UTC coverage: 75.452% (-4.8%) from 80.295%
20333307239

Pull #22949

github

web-flow
Merge b07232683 into 407284c67
Pull Request #22949: Add experimental uv resolver for Python lockfiles

51 of 96 new or added lines in 5 files covered. (53.13%)

2857 existing lines in 120 files now uncovered.

66315 of 87890 relevant lines covered (75.45%)

2.78 hits per line

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

25.93
/src/python/pants/backend/go/util_rules/cgo_pkgconfig.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
9✔
4

5
from dataclasses import dataclass
9✔
6

7
from pants.backend.go.util_rules import cgo_binaries
9✔
8
from pants.backend.go.util_rules.cgo_binaries import CGoBinaryPathRequest, find_cgo_binary_path
9✔
9
from pants.backend.go.util_rules.cgo_security import (
9✔
10
    check_compiler_flags,
11
    check_linker_flags,
12
    safe_arg,
13
)
14
from pants.core.util_rules.system_binaries import BinaryPathTest
9✔
15
from pants.engine.internals.selectors import concurrently
9✔
16
from pants.engine.process import Process, fallible_to_exec_result_or_raise
9✔
17
from pants.engine.rules import collect_rules, implicitly, rule
9✔
18

19
# Adapted from the Go toolchain
20
#
21
# Original copyright:
22
#   // Copyright 2011 The Go Authors. All rights reserved.
23
#   // Use of this source code is governed by a BSD-style
24
#   // license that can be found in the LICENSE file.
25

26

27
@dataclass(frozen=True)
9✔
28
class CGoPkgConfigFlagsRequest:
9✔
29
    """Request resolution of pkg-config arguments into CFLAGS and LDFLAGS."""
30

31
    pkg_config_args: tuple[str, ...]
9✔
32

33

34
@dataclass(frozen=True)
9✔
35
class CGoPkgConfigFlagsResult:
9✔
36
    cflags: tuple[str, ...]
9✔
37
    ldflags: tuple[str, ...]
9✔
38

39

40
# _split_pkg_config_output parses the pkg-config output into a tuple of flags. This implements the algorithm from
41
# https://github.com/pkgconf/pkgconf/blob/master/libpkgconf/argvsplit.c
42
# See https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/cmd/go/internal/work/exec.go#L1414-L1473
43
def _split_pkg_config_output(content: bytes) -> tuple[str, ...]:
9✔
UNCOV
44
    if not content:
×
UNCOV
45
        return ()
×
46

UNCOV
47
    flags: list[str] = []
×
UNCOV
48
    flag = bytearray()
×
UNCOV
49
    escaped = False
×
UNCOV
50
    quote = 0
×
UNCOV
51
    for c in content:
×
UNCOV
52
        if escaped:
×
UNCOV
53
            if quote != 0:
×
UNCOV
54
                if c not in b'$`"\\':
×
UNCOV
55
                    flag.extend(b"\\")
×
UNCOV
56
                flag.append(c)
×
57
            else:
UNCOV
58
                flag.append(c)
×
UNCOV
59
            escaped = False
×
UNCOV
60
        elif quote != 0:
×
UNCOV
61
            if c == quote:
×
UNCOV
62
                quote = 0
×
63
            else:
UNCOV
64
                if c == ord("\\"):
×
UNCOV
65
                    escaped = True
×
66
                else:
UNCOV
67
                    flag.append(c)
×
UNCOV
68
        elif c not in b" \t\n\v\f\r":
×
UNCOV
69
            if c == ord(b"\\"):
×
UNCOV
70
                escaped = True
×
UNCOV
71
            elif c in b"'\"":
×
UNCOV
72
                quote = c
×
73
            else:
UNCOV
74
                flag.append(c)
×
UNCOV
75
        elif len(flag) != 0:
×
UNCOV
76
            flags.append(flag.decode())
×
UNCOV
77
            flag = bytearray()
×
78

UNCOV
79
    if escaped:
×
80
        raise ValueError("broken character escaping in pkg-config output")
×
UNCOV
81
    if quote != 0:
×
82
        raise ValueError("unterminated quoted string in pkgconf output")
×
UNCOV
83
    elif len(flag) != 0:
×
UNCOV
84
        flags.append(flag.decode())
×
85

UNCOV
86
    return tuple(flags)
×
87

88

89
@rule
9✔
90
async def resolve_cgo_pkg_config_args(request: CGoPkgConfigFlagsRequest) -> CGoPkgConfigFlagsResult:
9✔
91
    if not request.pkg_config_args:
×
92
        return CGoPkgConfigFlagsResult(cflags=(), ldflags=())
×
93

94
    pkg_config_flags = []
×
95
    pkgs = []
×
96
    for arg in request.pkg_config_args:
×
97
        if arg == "--":
×
98
            # Skip the `--` separator as we will add our own later.
99
            pass
×
100
        elif arg.startswith("--"):
×
101
            pkg_config_flags.append(arg)
×
102
        else:
103
            pkgs.append(arg)
×
104

105
    for pkg in pkgs:
×
106
        if not safe_arg(pkg):
×
107
            raise ValueError(f"invalid pkg-config package name: {pkg}")
×
108

109
    pkg_config_path = await find_cgo_binary_path(
×
110
        CGoBinaryPathRequest(
111
            binary_name="pkg-config",
112
            binary_path_test=BinaryPathTest(["--version"]),
113
        ),
114
        **implicitly(),
115
    )
116

117
    cflags_result, ldflags_result = await concurrently(
×
118
        fallible_to_exec_result_or_raise(
119
            **implicitly(
120
                Process(
121
                    argv=[pkg_config_path.path, "--cflags", *pkg_config_flags, "--", *pkgs],
122
                    description=f"Run pkg-config for CFLAGS for packages: {pkgs}",
123
                )
124
            )
125
        ),
126
        fallible_to_exec_result_or_raise(
127
            **implicitly(
128
                Process(
129
                    argv=[pkg_config_path.path, "--libs", *pkg_config_flags, "--", *pkgs],
130
                    description=f"Run pkg-config for LDFLAGS for packages: {pkgs}",
131
                )
132
            )
133
        ),
134
    )
135

136
    cflags: tuple[str, ...] = ()
×
137
    if cflags_result.stdout:
×
138
        cflags = _split_pkg_config_output(cflags_result.stdout)
×
139
        check_compiler_flags(cflags, "pkg-config --cflags")
×
140

141
    ldflags: tuple[str, ...] = ()
×
142
    if ldflags_result.stdout:
×
143
        # NOTE: we don't attempt to parse quotes and unescapes here. pkg-config
144
        # is typically used within shell backticks, which treats quotes literally.
145
        ldflags = tuple(arg.decode() for arg in ldflags_result.stdout.split())
×
146
        check_linker_flags(ldflags, "pkg-config --libs")
×
147

148
    return CGoPkgConfigFlagsResult(cflags=cflags, ldflags=ldflags)
×
149

150

151
def rules():
9✔
152
    return (
9✔
153
        *collect_rules(),
154
        *cgo_binaries.rules(),
155
    )
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