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

pantsbuild / pants / 26260209689

21 May 2026 11:59PM UTC coverage: 75.453% (-15.7%) from 91.156%
26260209689

Pull #23365

github

web-flow
Merge 5fe873b58 into 7ea655ba0
Pull Request #23365: uv.lock -> pex optimization

5 of 16 new or added lines in 1 file covered. (31.25%)

10118 existing lines in 378 files now uncovered.

54669 of 72454 relevant lines covered (75.45%)

2.31 hits per line

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

69.74
/src/python/pants/init/options_initializer.py
1
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
5✔
5

6
import dataclasses
5✔
7
import importlib.util
5✔
8
import logging
5✔
9
import sys
5✔
10
from collections.abc import Iterator
5✔
11
from contextlib import contextmanager
5✔
12
from pathlib import Path
5✔
13

14
from pants.build_graph.build_configuration import BuildConfiguration
5✔
15
from pants.engine.env_vars import CompleteEnvironmentVars
5✔
16
from pants.engine.internals.native_engine import PyExecutor
5✔
17
from pants.engine.unions import UnionMembership
5✔
18
from pants.help.flag_error_help_printer import FlagErrorHelpPrinter
5✔
19
from pants.init.bootstrap_scheduler import BootstrapScheduler
5✔
20
from pants.init.engine_initializer import EngineInitializer
5✔
21
from pants.init.extension_loader import (
5✔
22
    load_backends_and_plugins,
23
    load_build_configuration_from_source,
24
)
25
from pants.init.plugin_resolver import PluginResolver
5✔
26
from pants.init.plugin_resolver import rules as plugin_resolver_rules
5✔
27
from pants.option.bootstrap_options import DynamicRemoteOptions
5✔
28
from pants.option.errors import UnknownFlagsError
5✔
29
from pants.option.options import Options
5✔
30
from pants.option.options_bootstrapper import OptionsBootstrapper
5✔
31
from pants.util.requirements import parse_requirements_file
5✔
32

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

35

36
def _initialize_build_configuration(
5✔
37
    plugin_resolver: PluginResolver,
38
    options_bootstrapper: OptionsBootstrapper,
39
    env: CompleteEnvironmentVars,
40
) -> BuildConfiguration:
41
    """Initialize a BuildConfiguration for the given OptionsBootstrapper.
42

43
    NB: This method:
44
      1. has the side effect of (idempotently) adding PYTHONPATH entries for this process
45
      2. is expensive to call, because it might resolve plugins from the network
46
    """
47

48
    bootstrap_options = options_bootstrapper.bootstrap_options.for_global_scope()
1✔
49

50
    # Add any extra paths to python path (e.g., for loading extra source backends).
51
    for path in bootstrap_options.pythonpath:
1✔
52
        if path not in sys.path:
×
53
            sys.path.append(path)
×
54

55
    # Resolve the actual Python code for any plugins. `sys.path` is modified as a side effect if
56
    # plugins were configured.
57
    backends_requirements = _collect_backends_requirements(bootstrap_options.backend_packages)
1✔
58
    plugin_resolver.resolve(options_bootstrapper, env, backends_requirements)
1✔
59

60
    # Load plugins and backends.
61
    return load_backends_and_plugins(
1✔
62
        bootstrap_options.plugins,
63
        bootstrap_options.backend_packages,
64
        BuildConfiguration.Builder(_pants_ng=bootstrap_options.pants_ng),
65
    )
66

67

68
def _collect_backends_requirements(backends: list[str]) -> list[str]:
5✔
69
    """Collects backend package dependencies, in case those are declared in an adjacent
70
    requirements.txt. Ignores any loading errors, assuming those will be later on handled by the
71
    backends loader.
72

73
    :param backends: An list of packages to load v2 backends requirements from.
74
    """
75
    requirements = []
1✔
76

77
    for backend_package in backends:
1✔
UNCOV
78
        try:
×
UNCOV
79
            backend_package_spec = importlib.util.find_spec(backend_package)
×
80
        except ModuleNotFoundError:
×
81
            continue
×
82

UNCOV
83
        if backend_package_spec is None:
×
84
            continue
×
85

UNCOV
86
        if backend_package_spec.origin is None:
×
UNCOV
87
            logger.warning(
×
88
                f"Can not check requirements for backend: '{backend_package}'. A __init__.py file is probably missing."
89
            )
UNCOV
90
            continue
×
91

92
        requirements_txt_file_path = Path(backend_package_spec.origin).parent.joinpath(
×
93
            "requirements.txt"
94
        )
95
        if requirements_txt_file_path.exists():
×
96
            content = requirements_txt_file_path.read_text()
×
97
            backend_package_requirements = [
×
98
                str(r)
99
                for r in parse_requirements_file(content, rel_path=str(requirements_txt_file_path))
100
            ]
101
            requirements.extend(backend_package_requirements)
×
102

103
    return requirements
1✔
104

105

106
def create_bootstrap_scheduler(
5✔
107
    options_bootstrapper: OptionsBootstrapper, executor: PyExecutor
108
) -> BootstrapScheduler:
109
    bc_builder = BuildConfiguration.Builder()
1✔
110
    # To load plugins, we only need access to the Python/PEX rules.
111
    load_build_configuration_from_source(bc_builder, ["pants.backend.python"])
1✔
112
    # And to plugin-loading-specific rules.
113
    bc_builder.register_rules("_dummy_for_bootstrapping_", plugin_resolver_rules())
1✔
114
    # We allow unrecognized options to defer any option error handling until post-bootstrap.
115
    bc_builder.allow_unknown_options()
1✔
116
    return BootstrapScheduler(
1✔
117
        EngineInitializer.setup_graph(
118
            options_bootstrapper.bootstrap_options.for_global_scope(),
119
            bc_builder.create(),
120
            DynamicRemoteOptions.disabled(),
121
            executor,
122
            is_bootstrap=True,
123
        ).scheduler
124
    )
125

126

127
class OptionsInitializer:
5✔
128
    """Initializes BuildConfiguration and Options instances given an OptionsBootstrapper.
129

130
    NB: Although this constructor takes an instance of the OptionsBootstrapper, it is
131
    used only to construct a "bootstrap" Scheduler: actual calls to resolve plugins use a
132
    per-request instance of the OptionsBootstrapper, which might request different plugins.
133

134
    TODO: We would eventually like to use the bootstrap Scheduler to construct the
135
    OptionsBootstrapper as well, but for now we do the opposite thing, and the Scheduler is
136
    used only to resolve plugins.
137
      see https://github.com/pantsbuild/pants/pull/11568
138
    """
139

140
    def __init__(
5✔
141
        self,
142
        options_bootstrapper: OptionsBootstrapper,
143
        executor: PyExecutor,
144
    ) -> None:
145
        self._bootstrap_scheduler = create_bootstrap_scheduler(options_bootstrapper, executor)
1✔
146
        self._plugin_resolver = PluginResolver(self._bootstrap_scheduler)
1✔
147

148
    def build_config(
5✔
149
        self,
150
        options_bootstrapper: OptionsBootstrapper,
151
        env: CompleteEnvironmentVars,
152
    ) -> BuildConfiguration:
153
        return _initialize_build_configuration(self._plugin_resolver, options_bootstrapper, env)
1✔
154

155
    def options(
5✔
156
        self,
157
        options_bootstrapper: OptionsBootstrapper,
158
        env: CompleteEnvironmentVars,
159
        build_config: BuildConfiguration,
160
        union_membership: UnionMembership,
161
        *,
162
        raise_: bool,
163
    ) -> Options:
164
        with self.handle_unknown_flags(options_bootstrapper, env, raise_=raise_):
1✔
165
            return options_bootstrapper.full_options(
1✔
166
                build_config.known_scope_infos, union_membership, build_config.allow_unknown_options
167
            )
168

169
    @contextmanager
5✔
170
    def handle_unknown_flags(
5✔
171
        self,
172
        options_bootstrapper: OptionsBootstrapper,
173
        env: CompleteEnvironmentVars,
174
        *,
175
        raise_: bool,
176
    ) -> Iterator[None]:
177
        """If there are any unknown flags, print "Did you mean?" and possibly error."""
178
        try:
1✔
179
            yield
1✔
180
        except UnknownFlagsError as err:
×
181
            build_config = _initialize_build_configuration(
×
182
                self._plugin_resolver, options_bootstrapper, env
183
            )
184
            # We need an options instance in order to get "did you mean" suggestions, but we know
185
            # there are bad flags in the args, so we generate options with no flags.
186
            no_arg_bootstrapper = dataclasses.replace(
×
187
                options_bootstrapper, args=("dummy_first_arg",)
188
            )
189
            options = no_arg_bootstrapper.full_options(
×
190
                build_config.known_scope_infos,
191
                union_membership=UnionMembership.empty(),
192
            )
193
            FlagErrorHelpPrinter(options).handle_unknown_flags(err)
×
194
            if raise_:
×
195
                raise err
×
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