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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

43.42
/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
1✔
5

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

14
# import pkg_resources
15

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

35
logger = logging.getLogger(__name__)
1✔
36

37

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

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

UNCOV
50
    bootstrap_options = options_bootstrapper.bootstrap_options.for_global_scope()
×
51

52
    # Add any extra paths to python path (e.g., for loading extra source backends).
UNCOV
53
    for path in bootstrap_options.pythonpath:
×
54
        if path not in sys.path:
×
55
            sys.path.append(path)
×
56
            # pkg_resources.fixup_namespace_packages(path)
57

58
    # Resolve the actual Python code for any plugins. `sys.path` is modified as a side effect if
59
    # plugins were configured.
UNCOV
60
    backends_requirements = _collect_backends_requirements(bootstrap_options.backend_packages)
×
UNCOV
61
    plugin_resolver.resolve(options_bootstrapper, env, backends_requirements)
×
62

63
    # Load plugins and backends.
UNCOV
64
    return load_backends_and_plugins(
×
65
        bootstrap_options.plugins,
66
        bootstrap_options.backend_packages,
67
    )
68

69

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

75
    :param backends: An list of packages to load v2 backends requirements from.
76
    """
UNCOV
77
    requirements = []
×
78

UNCOV
79
    for backend_package in backends:
×
UNCOV
80
        try:
×
UNCOV
81
            backend_package_spec = importlib.util.find_spec(backend_package)
×
82
        except ModuleNotFoundError:
×
83
            continue
×
84

UNCOV
85
        if backend_package_spec is None:
×
86
            continue
×
87

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

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

UNCOV
105
    return requirements
×
106

107

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

128

129
class OptionsInitializer:
1✔
130
    """Initializes BuildConfiguration and Options instances given an OptionsBootstrapper.
131

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

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

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

150
    def build_config(
1✔
151
        self,
152
        options_bootstrapper: OptionsBootstrapper,
153
        env: CompleteEnvironmentVars,
154
    ) -> BuildConfiguration:
UNCOV
155
        return _initialize_build_configuration(self._plugin_resolver, options_bootstrapper, env)
×
156

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

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

© 2025 Coveralls, Inc