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

pantsbuild / pants / 25443604553

06 May 2026 03:05PM UTC coverage: 92.879% (-0.04%) from 92.915%
25443604553

push

github

web-flow
[pants_ng] Scaffolding for a pants_ng mode. (#23319)

In this mode the command line is parsed as an
NG invocation, and dispatched appropriately.

Of course at the moment there are no
implementations to dispatch to. That will follow.

This does expose a new option, `pants_ng` to users. 
There is a big warning not to set it, but we're not trying
to hide that we're working on a new thing, so I am
comfortable with this.

25 of 76 new or added lines in 9 files covered. (32.89%)

1294 existing lines in 76 files now uncovered.

92234 of 99306 relevant lines covered (92.88%)

4.05 hits per line

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

92.08
/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
12✔
5
import importlib.metadata
12✔
6
import logging
12✔
7
import traceback
12✔
8
from importlib.metadata import Distribution
12✔
9

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

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

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

21

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

25

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

29

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

33

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

41
    :param plugins: plugins to load.
42
    :param backends: backends to load.
43
    :param bc_builder: The BuildConfiguration (for adding aliases).
44
    """
45
    load_build_configuration_from_source(bc_builder, backends)
3✔
46
    load_plugins(bc_builder, plugins)
3✔
47
    if not bc_builder._pants_ng:
3✔
48
        register_builtin_goals(bc_builder)
3✔
49
    return bc_builder.create()
3✔
50

51

52
def load_plugins(
12✔
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

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

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

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

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

UNCOV
100
        if load_after_entry_point := find_entry_point("load_after"):
1✔
UNCOV
101
            deps = load_after_entry_point.load()()
1✔
UNCOV
102
            for dep_name in deps:
1✔
UNCOV
103
                dep = Requirement(dep_name)
1✔
UNCOV
104
                dep_key = canonicalize_name(dep.name)
1✔
UNCOV
105
                if dep_key not in loaded:
1✔
UNCOV
106
                    raise PluginLoadOrderError(f"Plugin {plugin} must be loaded after {dep}")
1✔
UNCOV
107
        if target_types_entry_point := find_entry_point("target_types"):
1✔
UNCOV
108
            target_types = target_types_entry_point.load()()
1✔
UNCOV
109
            build_configuration.register_target_types(req_key, target_types)
1✔
UNCOV
110
        if build_file_aliases_entry_point := find_entry_point("build_file_aliases"):
1✔
UNCOV
111
            aliases = build_file_aliases_entry_point.load()()
1✔
UNCOV
112
            build_configuration.register_aliases(aliases)
1✔
UNCOV
113
        if rules_entry_point := find_entry_point("rules"):
1✔
UNCOV
114
            rules = rules_entry_point.load()()
1✔
UNCOV
115
            build_configuration.register_rules(req_key, rules)
1✔
UNCOV
116
        if remote_auth_entry_point := find_entry_point("remote_auth"):
1✔
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"):
1✔
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
1✔
127

128

129
def load_build_configuration_from_source(
12✔
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.
140
    backend_packages = FrozenOrderedSet(["pants.core", "pants.backend.project_info", *backends])
4✔
141
    for backend_package in backend_packages:
4✔
142
        load_backend(build_configuration, backend_package)
4✔
143

144

145
def load_backend(build_configuration: BuildConfiguration.Builder, backend_package: str) -> None:
12✔
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
    """
154
    backend_module = backend_package + ".register"
4✔
155
    try:
4✔
156
        module = importlib.import_module(backend_module)
4✔
UNCOV
157
    except ImportError as ex:
1✔
UNCOV
158
        traceback.print_exc()
1✔
UNCOV
159
        raise BackendConfigurationError(f"Failed to load the {backend_module} backend: {ex!r}")
1✔
160

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

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

© 2026 Coveralls, Inc