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

pantsbuild / pants / 20632486505

01 Jan 2026 04:21AM UTC coverage: 43.231% (-37.1%) from 80.281%
20632486505

Pull #22962

github

web-flow
Merge 08d5c63b0 into f52ab6675
Pull Request #22962: Bump the gha-deps group across 1 directory with 6 updates

26122 of 60424 relevant lines covered (43.23%)

0.86 hits per line

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

0.0
/src/python/pants/bsp/goal.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
×
4

5
import json
×
6
import logging
×
7
import os
×
8
import shlex
×
9
import sys
×
10
import textwrap
×
11
from collections.abc import Mapping
×
12

13
from pants.base.build_root import BuildRoot
×
14
from pants.base.exiter import PANTS_FAILED_EXIT_CODE, PANTS_SUCCEEDED_EXIT_CODE, ExitCode
×
15
from pants.bsp.context import BSPContext
×
16
from pants.bsp.protocol import BSPConnection
×
17
from pants.bsp.util_rules.lifecycle import BSP_VERSION, BSPLanguageSupport
×
18
from pants.engine.env_vars import CompleteEnvironmentVars
×
19
from pants.engine.internals.session import SessionValues
×
20
from pants.engine.unions import UnionMembership
×
21
from pants.goal.auxiliary_goal import AuxiliaryGoal, AuxiliaryGoalContext
×
22
from pants.init.engine_initializer import GraphSession
×
23
from pants.option.option_types import BoolOption, FileListOption, StrListOption
×
24
from pants.option.option_value_container import OptionValueContainer
×
25
from pants.util.docutil import bin_name
×
26
from pants.util.strutil import softwrap
×
27
from pants.version import VERSION
×
28

29
_logger = logging.getLogger(__name__)
×
30

31

32
class BSPGoal(AuxiliaryGoal):
×
33
    name = "experimental-bsp"
×
34
    help = "Setup repository for Build Server Protocol (https://build-server-protocol.github.io/)."
×
35

36
    server = BoolOption(
×
37
        default=False,
38
        advanced=True,
39
        help=softwrap(
40
            """
41
            Run the Build Server Protocol server. Pants will receive BSP RPC requests via the console.
42
            This should only ever be invoked via the IDE.
43
            """
44
        ),
45
    )
46
    runner_env_vars = StrListOption(
×
47
        default=["PATH"],
48
        help=softwrap(
49
            f"""
50
            Environment variables to set in the BSP runner script when setting up BSP in a repository.
51
            Entries are either strings in the form `ENV_VAR=value` to set an explicit value;
52
            or just `ENV_VAR` to copy the value from Pants' own environment when the {name} goal was run.
53

54
            This option only takes effect when the BSP runner script is written. If the option changes, you
55
            must run `{bin_name()} {name}` again to write a new copy of the BSP runner script.
56

57
            Note: The environment variables passed to the Pants BSP server will be those set for your IDE
58
            and not your shell. For example, on macOS, the IDE is generally launched by `launchd` after
59
            clicking on a Dock icon, and not from the shell. Thus, any environment variables set for your
60
            shell will likely not be seen by the Pants BSP server. At the very least, on macOS consider
61
            writing an explicit PATH into the BSP runner script via this option.
62
            """
63
        ),
64
        advanced=True,
65
    )
66

67
    groups_config_files = FileListOption(
×
68
        help=softwrap(
69
            """
70
            A list of config files that define groups of Pants targets to expose to IDEs via Build Server Protocol.
71

72
            Pants generally uses fine-grained targets to define the components of a build (in many cases on a file-by-file
73
            basis). Many IDEs, however, favor coarse-grained targets that contain large numbers of source files.
74
            To accommodate this distinction, the Pants BSP server will compute a set of BSP build targets to use
75
            from the groups specified in the config files set for this option. Each group will become one or more
76
            BSP build targets.
77

78
            Each config file is a TOML file with a `groups` dictionary with the following format for an entry:
79

80
                # The dictionary key is used to identify the group. It must be unique.
81
                [groups.ID1]:
82
                # One or more Pants address specs defining what targets to include in the group.
83
                addresses = [
84
                  "src/jvm::",
85
                  "tests/jvm::",
86
                ]
87
                # Filter targets to a specific resolve. Targets in a group must be from a single resolve.
88
                # Format of filter is `TYPE:RESOLVE_NAME`. The only supported TYPE is `jvm`. RESOLVE_NAME must be
89
                # a valid resolve name.
90
                resolve = "jvm:jvm-default"
91
                display_name = "Display Name"  # (Optional) Name shown to the user in the IDE.
92
                base_directory = "path/from/build/root"  # (Optional) Hint to the IDE for where the build target should "live."
93

94
            Pants will merge the contents of the config files together. If the same ID is used for a group definition,
95
            in multiple config files, the definition in the latter config file will take effect.
96
            """
97
        ),
98
    )
99

100
    def run(
×
101
        self,
102
        context: AuxiliaryGoalContext,
103
    ) -> ExitCode:
104
        goal_options = context.options.for_scope(self.name)
×
105
        if goal_options.server:
×
106
            return self._run_server(
×
107
                graph_session=context.graph_session,
108
                union_membership=context.union_membership,
109
            )
110
        current_session_values = context.graph_session.scheduler_session.py_session.session_values
×
111
        env = current_session_values[CompleteEnvironmentVars]
×
112
        return self._setup_bsp_connection(
×
113
            union_membership=context.union_membership, env=env, options=goal_options
114
        )
115

116
    def _setup_bsp_connection(
×
117
        self,
118
        union_membership: UnionMembership,
119
        env: Mapping[str, str],
120
        options: OptionValueContainer,
121
    ) -> ExitCode:
122
        """Setup the BSP connection file."""
123

124
        build_root = BuildRoot()
×
125
        bsp_conn_path = build_root.pathlib_path / ".bsp" / "pants.json"
×
126
        if bsp_conn_path.exists():
×
127
            print(
×
128
                f"ERROR: A BSP connection file already exists at path `{bsp_conn_path}`. "
129
                "Please delete that file if you intend to re-setup BSP in this repository.",
130
                file=sys.stderr,
131
            )
132
            return PANTS_FAILED_EXIT_CODE
×
133

134
        bsp_dir = build_root.pathlib_path / ".pants.d" / "bsp"
×
135

136
        bsp_scripts_dir = bsp_dir / "scripts"
×
137
        bsp_scripts_dir.mkdir(exist_ok=True, parents=True)
×
138

139
        bsp_logs_dir = bsp_dir / "logs"
×
140
        bsp_logs_dir.mkdir(exist_ok=True, parents=True)
×
141

142
        # Determine which environment variables to set in the BSP runner script.
143
        # TODO: Consider whether some of this logic could be shared with
144
        #  `pants.engine.environment.CompleteEnvironmentVars.get_subset`.
145
        run_script_env_lines: list[str] = []
×
146
        for env_var in options.runner_env_vars:
×
147
            if "=" in env_var:
×
148
                run_script_env_lines.append(env_var)
×
149
            else:
150
                if env_var not in env:
×
151
                    print(
×
152
                        f"ERROR: The `[{self.name}].runner_env_vars` option is configured to add the `{env_var}` "
153
                        "environment variable to the BSP runner script using its value in the current environment. "
154
                        "That environment variable, however, is not present in the current environment. "
155
                        "Please either set it in the current environment first or else configure a specific value "
156
                        "in `pants.toml`.",
157
                        file=sys.stderr,
158
                    )
159
                    return PANTS_FAILED_EXIT_CODE
×
160
                run_script_env_lines.append(f"{env_var}={env[env_var]}")
×
161

162
        run_script_env_lines_str = "\n".join(
×
163
            [f"export {shlex.quote(line)}" for line in run_script_env_lines]
164
        )
165

166
        run_script_path = bsp_scripts_dir / "run-bsp.sh"
×
167
        run_script_path.write_text(
×
168
            textwrap.dedent(  # noqa: PNT20
169
                f"""\
170
                #!/bin/sh
171
                {run_script_env_lines_str}
172
                exec 2>>{shlex.quote(str(bsp_logs_dir / "stderr.log"))}
173
                env 1>&2
174
                exec {shlex.quote(bin_name())} --no-pantsd {self.name} --server
175
                """
176
            )
177
        )
178
        run_script_path.chmod(0o755)
×
179
        _logger.info(f"Wrote BSP runner script to `{run_script_path}`.")
×
180

181
        bsp_conn_data = {
×
182
            "name": "Pants",
183
            "version": VERSION,
184
            "bspVersion": BSP_VERSION,
185
            "languages": sorted(
186
                [lang.language_id for lang in union_membership.get(BSPLanguageSupport)]
187
            ),
188
            "argv": ["./.pants.d/bsp/scripts/run-bsp.sh"],
189
        }
190

191
        bsp_conn_path.parent.mkdir(exist_ok=True, parents=True)
×
192
        bsp_conn_path.write_text(json.dumps(bsp_conn_data))
×
193
        _logger.info(f"Wrote BSP connection file to `{bsp_conn_path}`.")
×
194

195
        return PANTS_SUCCEEDED_EXIT_CODE
×
196

197
    def _run_server(
×
198
        self,
199
        *,
200
        graph_session: GraphSession,
201
        union_membership: UnionMembership,
202
    ) -> ExitCode:
203
        """Run the BSP server."""
204

205
        current_session_values = graph_session.scheduler_session.py_session.session_values
×
206
        context = BSPContext()
×
207
        session_values = SessionValues(
×
208
            {
209
                **current_session_values,
210
                BSPContext: context,
211
            }
212
        )
213
        scheduler_session = graph_session.scheduler_session.scheduler.new_session(
×
214
            build_id="bsp", dynamic_ui=False, session_values=session_values
215
        )
216

217
        saved_stdout = sys.stdout
×
218
        saved_stdin = sys.stdin
×
219
        try:
×
220
            sys.stdout = os.fdopen(sys.stdout.fileno(), "wb", buffering=0)
×
221
            sys.stdin = os.fdopen(sys.stdin.fileno(), "rb", buffering=0)
×
222
            conn = BSPConnection(
×
223
                scheduler_session,
224
                union_membership,
225
                context,
226
                sys.stdin,
227
                sys.stdout,
228
            )
229
            conn.run()
×
230
        finally:
231
            sys.stdout = saved_stdout
×
232
            sys.stdin = saved_stdin
×
233

234
        return ExitCode(0)
×
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