• 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

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

4
import importlib
1✔
5
import importlib.metadata
1✔
6
import logging
1✔
7
import traceback
1✔
8
from importlib.metadata import Distribution
1✔
9

10
from packaging.requirements import InvalidRequirement, Requirement
1✔
11
from packaging.utils import NormalizedName, canonicalize_name
1✔
12

13
from pants.base.exceptions import BackendConfigurationError
1✔
14
from pants.build_graph.build_configuration import BuildConfiguration
1✔
15
from pants.goal.builtins import register_builtin_goals
1✔
16
from pants.init.import_util import find_matching_distributions
1✔
17
from pants.util.ordered_set import FrozenOrderedSet
1✔
18

19
logger = logging.getLogger(__name__)
1✔
20

21

22
class PluginLoadingError(Exception):
1✔
23
    pass
1✔
24

25

26
class PluginNotFound(PluginLoadingError):
1✔
27
    pass
1✔
28

29

30
class PluginLoadOrderError(PluginLoadingError):
1✔
31
    pass
1✔
32

33

34
def load_backends_and_plugins(
1✔
35
    plugins: list[str],
36
    backends: list[str],
37
    bc_builder: BuildConfiguration.Builder | None = None,
38
) -> BuildConfiguration:
39
    """Load named plugins and source backends.
40

41
    :param plugins: v2 plugins to load.
42
    :param backends: v2 backends to load.
43
    :param bc_builder: The BuildConfiguration (for adding aliases).
44
    """
UNCOV
45
    bc_builder = bc_builder or BuildConfiguration.Builder()
×
UNCOV
46
    load_build_configuration_from_source(bc_builder, backends)
×
UNCOV
47
    load_plugins(bc_builder, plugins)
×
UNCOV
48
    register_builtin_goals(bc_builder)
×
UNCOV
49
    return bc_builder.create()
×
50

51

52
def load_plugins(
1✔
53
    build_configuration: BuildConfiguration.Builder,
54
    plugins: list[str],
55
) -> None:
56
    """Load named plugins from the current working_set into the supplied build_configuration.
57

58
    "Loading" a plugin here refers to calling registration methods -- it is assumed each plugin
59
    is already on the path and an error will be thrown if it is not. Plugins should define their
60
    entrypoints in the `pantsbuild.plugin` group when configuring their distribution.
61

62
    Like source backends, the `build_file_aliases`, and `register_goals` methods are called if
63
    those entry points are defined.
64

65
    * Plugins are loaded in the order they are provided. *
66

67
    This is important as loading can add, remove or replace existing tasks installed by other plugins.
68

69
    If a plugin needs to assert that another plugin is registered before it, it can define an
70
    entrypoint "load_after" which can return a list of plugins which must have been loaded before it
71
    can be loaded. This does not change the order or what plugins are loaded in any way -- it is
72
    purely an assertion to guard against misconfiguration.
73

74
    :param build_configuration: The BuildConfiguration (for adding aliases).
75
    :param plugins: A list of plugin names optionally with versions, in requirement format.
76
                              eg ['widgetpublish', 'widgetgen==1.2'].
77
    """
78

UNCOV
79
    loaded: dict[NormalizedName, Distribution] = {}
×
UNCOV
80
    for plugin in plugins or []:
×
UNCOV
81
        try:
×
UNCOV
82
            req = Requirement(plugin)
×
UNCOV
83
            req_key = canonicalize_name(req.name)
×
84
        except InvalidRequirement:
×
85
            raise PluginNotFound(f"Could not find plugin: {req}")
×
86

UNCOV
87
        dists = list(find_matching_distributions(req))
×
UNCOV
88
        if not dists:
×
UNCOV
89
            raise PluginNotFound(f"Could not find plugin: {req}")
×
UNCOV
90
        dist = dists[0]
×
91

UNCOV
92
        entry_points = dist.entry_points.select(group="pantsbuild.plugin")
×
93

UNCOV
94
        def find_entry_point(entry_point_name: str) -> importlib.metadata.EntryPoint | None:
×
UNCOV
95
            for entry_point in entry_points:
×
UNCOV
96
                if entry_point.name == entry_point_name:
×
UNCOV
97
                    return entry_point
×
UNCOV
98
            return None
×
99

UNCOV
100
        if load_after_entry_point := find_entry_point("load_after"):
×
UNCOV
101
            deps = load_after_entry_point.load()()
×
UNCOV
102
            for dep_name in deps:
×
UNCOV
103
                dep = Requirement(dep_name)
×
UNCOV
104
                dep_key = canonicalize_name(dep.name)
×
UNCOV
105
                if dep_key not in loaded:
×
UNCOV
106
                    raise PluginLoadOrderError(f"Plugin {plugin} must be loaded after {dep}")
×
UNCOV
107
        if target_types_entry_point := find_entry_point("target_types"):
×
UNCOV
108
            target_types = target_types_entry_point.load()()
×
UNCOV
109
            build_configuration.register_target_types(req_key, target_types)
×
UNCOV
110
        if build_file_aliases_entry_point := find_entry_point("build_file_aliases"):
×
UNCOV
111
            aliases = build_file_aliases_entry_point.load()()
×
UNCOV
112
            build_configuration.register_aliases(aliases)
×
UNCOV
113
        if rules_entry_point := find_entry_point("rules"):
×
UNCOV
114
            rules = rules_entry_point.load()()
×
UNCOV
115
            build_configuration.register_rules(req_key, rules)
×
UNCOV
116
        if remote_auth_entry_point := find_entry_point("remote_auth"):
×
117
            remote_auth_func = remote_auth_entry_point.load()
×
118
            logger.debug(
×
119
                f"register remote auth function {remote_auth_func.__module__}.{remote_auth_func.__name__} from plugin: {plugin}"
120
            )
121
            build_configuration.register_remote_auth_plugin(remote_auth_func)
×
UNCOV
122
        if auxiliary_goals_entry_point := find_entry_point("auxiliary_goals"):
×
123
            auxiliary_goals = auxiliary_goals_entry_point.load()()
×
124
            build_configuration.register_auxiliary_goals(req_key, auxiliary_goals)
×
125

UNCOV
126
        loaded[req_key] = dist
×
127

128

129
def load_build_configuration_from_source(
1✔
130
    build_configuration: BuildConfiguration.Builder, backends: list[str]
131
) -> None:
132
    """Installs pants backend packages to provide BUILD file symbols and cli goals.
133

134
    :param build_configuration: The BuildConfiguration (for adding aliases).
135
    :param backends: An list of packages to load v2 backends from.
136
    :raises: :class:``pants.base.exceptions.BuildConfigurationError`` if there is a problem loading
137
      the build configuration.
138
    """
139
    # NB: Backends added here must be explicit dependencies of this module.
UNCOV
140
    backend_packages = FrozenOrderedSet(["pants.core", "pants.backend.project_info", *backends])
×
UNCOV
141
    for backend_package in backend_packages:
×
UNCOV
142
        load_backend(build_configuration, backend_package)
×
143

144

145
def load_backend(build_configuration: BuildConfiguration.Builder, backend_package: str) -> None:
1✔
146
    """Installs the given backend package into the build configuration.
147

148
    :param build_configuration: the BuildConfiguration to install the backend plugin into.
149
    :param backend_package: the package name containing the backend plugin register module that
150
      provides the plugin entrypoints.
151
    :raises: :class:``pants.base.exceptions.BuildConfigurationError`` if there is a problem loading
152
      the build configuration.
153
    """
UNCOV
154
    backend_module = backend_package + ".register"
×
UNCOV
155
    try:
×
UNCOV
156
        module = importlib.import_module(backend_module)
×
UNCOV
157
    except ImportError as ex:
×
UNCOV
158
        traceback.print_exc()
×
UNCOV
159
        raise BackendConfigurationError(f"Failed to load the {backend_module} backend: {ex!r}")
×
160

UNCOV
161
    def invoke_entrypoint(name: str):
×
UNCOV
162
        entrypoint = getattr(module, name, lambda: None)
×
UNCOV
163
        try:
×
UNCOV
164
            return entrypoint()
×
UNCOV
165
        except TypeError as e:
×
UNCOV
166
            traceback.print_exc()
×
UNCOV
167
            raise BackendConfigurationError(
×
168
                f"Entrypoint {name} in {backend_module} must be a zero-arg callable: {e!r}"
169
            )
170

UNCOV
171
    target_types = invoke_entrypoint("target_types")
×
UNCOV
172
    if target_types:
×
UNCOV
173
        build_configuration.register_target_types(backend_package, target_types)
×
UNCOV
174
    build_file_aliases = invoke_entrypoint("build_file_aliases")
×
UNCOV
175
    if build_file_aliases:
×
UNCOV
176
        build_configuration.register_aliases(build_file_aliases)
×
UNCOV
177
    rules = invoke_entrypoint("rules")
×
UNCOV
178
    if rules:
×
UNCOV
179
        build_configuration.register_rules(backend_package, rules)
×
UNCOV
180
    remote_auth_func = getattr(module, "remote_auth", None)
×
UNCOV
181
    if remote_auth_func:
×
UNCOV
182
        logger.debug(
×
183
            f"register remote auth function {remote_auth_func.__module__}.{remote_auth_func.__name__} from backend: {backend_package}"
184
        )
UNCOV
185
        build_configuration.register_remote_auth_plugin(remote_auth_func)
×
UNCOV
186
    auxiliary_goals = invoke_entrypoint("auxiliary_goals")
×
UNCOV
187
    if auxiliary_goals:
×
188
        build_configuration.register_auxiliary_goals(backend_package, auxiliary_goals)
×
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