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

pantsbuild / pants / 23074067894

13 Mar 2026 11:06PM UTC coverage: 64.165% (-28.8%) from 92.932%
23074067894

Pull #23171

github

web-flow
Merge 17d8ea7d8 into f07276df6
Pull Request #23171: Debug reapi test cache misses

42163 of 65710 relevant lines covered (64.17%)

0.99 hits per line

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

77.05
/src/python/pants/backend/scala/compile/scalac_plugins.py
1
# Copyright 2021 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
from collections.abc import Iterable, Iterator
1✔
7
from dataclasses import dataclass
1✔
8

9
from pants.backend.scala.subsystems.scalac import Scalac
1✔
10
from pants.backend.scala.target_types import (
1✔
11
    ScalaConsumedPluginNamesField,
12
    ScalacPluginArtifactField,
13
    ScalacPluginNameField,
14
)
15
from pants.build_graph.address import AddressInput
1✔
16
from pants.engine.addresses import Addresses
1✔
17
from pants.engine.environment import ChosenLocalEnvironmentName, EnvironmentName
1✔
18
from pants.engine.internals.build_files import resolve_address
1✔
19
from pants.engine.internals.graph import resolve_coarsened_targets as coarsened_targets_get
1✔
20
from pants.engine.internals.graph import resolve_target_parametrizations
1✔
21
from pants.engine.internals.native_engine import MergeDigests
1✔
22
from pants.engine.internals.parametrize import _TargetParametrizationsRequest
1✔
23
from pants.engine.intrinsics import merge_digests
1✔
24
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
1✔
25
from pants.engine.target import (
1✔
26
    AllTargets,
27
    FieldDefaults,
28
    Target,
29
    Targets,
30
    TargetTypesToGenerateTargetsRequests,
31
    WrappedTarget,
32
)
33
from pants.jvm.compile import ClasspathEntry, FallibleClasspathEntry
1✔
34
from pants.jvm.goals import lockfile
1✔
35
from pants.jvm.resolve.coursier_fetch import CoursierFetchRequest, fetch_with_coursier
1✔
36
from pants.jvm.resolve.jvm_tool import rules as jvm_tool_rules
1✔
37
from pants.jvm.resolve.key import CoursierResolveKey
1✔
38
from pants.jvm.subsystems import JvmSubsystem
1✔
39
from pants.jvm.target_types import JvmResolveField
1✔
40
from pants.util.ordered_set import OrderedSet
1✔
41
from pants.util.strutil import bullet_list, softwrap
1✔
42

43

44
@dataclass(frozen=True)
1✔
45
class ScalaPluginsForTargetWithoutResolveRequest:
1✔
46
    target: Target
1✔
47

48

49
@dataclass(frozen=True)
1✔
50
class ScalaPluginsForTargetRequest:
1✔
51
    target: Target
1✔
52
    resolve_name: str
1✔
53

54

55
@dataclass(frozen=True)
1✔
56
class ScalaPluginTargetsForTarget:
1✔
57
    plugins: Targets
1✔
58
    artifacts: Targets
1✔
59

60

61
@dataclass(frozen=True)
1✔
62
class ScalaPluginsRequest:
1✔
63
    plugins: Targets
1✔
64
    artifacts: Targets
1✔
65
    resolve: CoursierResolveKey
1✔
66

67
    @classmethod
1✔
68
    def from_target_plugins(
1✔
69
        cls,
70
        seq: Iterable[ScalaPluginTargetsForTarget],
71
        resolve: CoursierResolveKey,
72
    ) -> ScalaPluginsRequest:
73
        plugins: OrderedSet[Target] = OrderedSet()
1✔
74
        artifacts: OrderedSet[Target] = OrderedSet()
1✔
75

76
        for spft in seq:
1✔
77
            plugins.update(spft.plugins)
1✔
78
            artifacts.update(spft.artifacts)
1✔
79

80
        return ScalaPluginsRequest(Targets(plugins), Targets(artifacts), resolve)
1✔
81

82

83
@dataclass(frozen=True)
1✔
84
class ScalaPlugins:
1✔
85
    names: tuple[str, ...]
1✔
86
    classpath: ClasspathEntry
1✔
87

88
    def args(self, prefix: str | None = None) -> Iterator[str]:
1✔
89
        p = f"{prefix}/" if prefix else ""
1✔
90
        for scalac_plugin_path in self.classpath.filenames:
1✔
91
            yield f"-Xplugin:{p}{scalac_plugin_path}"
×
92
        for name in self.names:
1✔
93
            yield f"-Xplugin-require:{name}"
×
94

95

96
class AllScalaPluginTargets(Targets):
1✔
97
    pass
1✔
98

99

100
@rule
1✔
101
async def all_scala_plugin_targets(targets: AllTargets) -> AllScalaPluginTargets:
1✔
102
    return AllScalaPluginTargets(
1✔
103
        tgt for tgt in targets if tgt.has_fields((ScalacPluginArtifactField, ScalacPluginNameField))
104
    )
105

106

107
@rule
1✔
108
async def add_resolve_name_to_plugin_request(
1✔
109
    request: ScalaPluginsForTargetWithoutResolveRequest, jvm: JvmSubsystem
110
) -> ScalaPluginsForTargetRequest:
111
    return ScalaPluginsForTargetRequest(
×
112
        request.target, request.target[JvmResolveField].normalized_value(jvm)
113
    )
114

115

116
async def _resolve_scalac_plugin_artifact(
1✔
117
    field: ScalacPluginArtifactField,
118
    consumer_target: Target,
119
    target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests,
120
    local_environment_name: ChosenLocalEnvironmentName,
121
    field_defaults: FieldDefaults,
122
) -> WrappedTarget:
123
    """Helps resolve the actual artifact for a scalac plugin even in the scenario in which the
124
    artifact has been declared as a scala_artifact and it has been parametrized (i.e. across
125
    multiple resolves for cross building)."""
126

127
    environment_name = local_environment_name.val
×
128

129
    address = await resolve_address(**implicitly({field.to_address_input(): AddressInput}))
×
130

131
    parametrizations = await resolve_target_parametrizations(
×
132
        **implicitly(
133
            {
134
                _TargetParametrizationsRequest(
135
                    address.maybe_convert_to_target_generator(),
136
                    description_of_origin=(
137
                        f"the target generator {address.maybe_convert_to_target_generator()}"
138
                    ),
139
                ): _TargetParametrizationsRequest,
140
                environment_name: EnvironmentName,
141
            }
142
        )
143
    )
144

145
    target = parametrizations.get_subset(
×
146
        address, consumer_target, field_defaults, target_types_to_generate_requests
147
    )
148
    if (
×
149
        target_types_to_generate_requests.is_generator(target)
150
        and not target.address.is_generated_target
151
    ):
152
        generated_tgts = list(parametrizations.generated_for(target.address).values())
×
153
        if len(generated_tgts) > 1:
×
154
            raise Exception(
×
155
                softwrap(
156
                    f"""
157
                    Could not resolve scalac plugin artifact {address} from target {field.address}
158
                    as it points to a target generator that produced more than one target:
159

160
                    {bullet_list([tgt.address.spec for tgt in generated_tgts])}
161
                    """
162
                )
163
            )
164
        if len(generated_tgts) == 1:
×
165
            target = generated_tgts[0]
×
166

167
    return WrappedTarget(target)
×
168

169

170
@rule
1✔
171
async def resolve_scala_plugins_for_target(
1✔
172
    request: ScalaPluginsForTargetRequest,
173
    all_scala_plugins: AllScalaPluginTargets,
174
    jvm: JvmSubsystem,
175
    scalac: Scalac,
176
    target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests,
177
    local_environment_name: ChosenLocalEnvironmentName,
178
    field_defaults: FieldDefaults,
179
) -> ScalaPluginTargetsForTarget:
180
    target = request.target
1✔
181
    resolve = request.resolve_name
1✔
182

183
    plugin_names = target.get(ScalaConsumedPluginNamesField).value
1✔
184
    if plugin_names is None:
1✔
185
        plugin_names_by_resolve = scalac.parsed_default_plugins()
1✔
186
        plugin_names = tuple(plugin_names_by_resolve.get(resolve, ()))
1✔
187

188
    candidate_plugins = []
1✔
189
    candidate_artifacts = []
1✔
190
    for plugin in all_scala_plugins:
1✔
191
        if _plugin_name(plugin) not in plugin_names:
×
192
            continue
×
193
        candidate_plugins.append(plugin)
×
194
        artifact_field = plugin[ScalacPluginArtifactField]
×
195
        wrapped_target = await _resolve_scalac_plugin_artifact(
×
196
            artifact_field,
197
            request.target,
198
            target_types_to_generate_requests,
199
            local_environment_name,
200
            field_defaults,
201
        )
202
        candidate_artifacts.append(wrapped_target.target)
×
203

204
    plugins: dict[str, tuple[Target, Target]] = {}  # Maps plugin name to relevant JVM artifact
1✔
205
    for plugin, artifact in zip(candidate_plugins, candidate_artifacts):
1✔
206
        if artifact[JvmResolveField].normalized_value(jvm) != resolve:
×
207
            continue
×
208

209
        plugins[_plugin_name(plugin)] = (plugin, artifact)
×
210

211
    for plugin_name in plugin_names:
1✔
212
        if plugin_name not in plugins:
×
213
            raise Exception(
×
214
                f"Could not find Scala plugin `{plugin_name}` in resolve `{resolve}` "
215
                f"for target {request.target.address}."
216
            )
217

218
    plugin_targets, artifact_targets = zip(*plugins.values()) if plugins else ((), ())
1✔
219
    return ScalaPluginTargetsForTarget(Targets(plugin_targets), Targets(artifact_targets))
1✔
220

221

222
def _plugin_name(target: Target) -> str:
1✔
223
    return target[ScalacPluginNameField].value or target.address.target_name
×
224

225

226
@rule
1✔
227
async def fetch_plugins(request: ScalaPluginsRequest) -> ScalaPlugins:
1✔
228
    # Fetch all the artifacts
229
    coarsened_targets = await coarsened_targets_get(
1✔
230
        **implicitly(Addresses(target.address for target in request.artifacts))
231
    )
232
    fallible_artifacts = await concurrently(
1✔
233
        fetch_with_coursier(CoursierFetchRequest(ct, resolve=request.resolve))
234
        for ct in coarsened_targets
235
    )
236

237
    artifacts = FallibleClasspathEntry.if_all_succeeded(fallible_artifacts)
1✔
238
    if artifacts is None:
1✔
239
        failed = [i for i in fallible_artifacts if i.exit_code != 0]
×
240
        raise Exception(f"Fetching local scala plugins failed: {failed}")
×
241

242
    merged_classpath_digest = await merge_digests(MergeDigests(i.digest for i in artifacts))
1✔
243
    merged = ClasspathEntry.merge(merged_classpath_digest, artifacts)
1✔
244

245
    names = tuple(_plugin_name(target) for target in request.plugins)
1✔
246

247
    return ScalaPlugins(names=names, classpath=merged)
1✔
248

249

250
def rules():
1✔
251
    return (
1✔
252
        *collect_rules(),
253
        *jvm_tool_rules(),
254
        *lockfile.rules(),
255
    )
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