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

pantsbuild / pants / 25441711719

06 May 2026 02:31PM UTC coverage: 92.915%. Remained the same
25441711719

push

github

web-flow
use sha pin (with comment) format for generated actions (#23312)

Per the GitHub Action best practices we recently enabled at #23249, we
should pin each action to a SHA so that the reference is actually
immutable.

This will -- I hope -- knock out a large chunk of the 421 alerts we
currently get from zizmor. The next followup would then be upgrades and
harmonizing the generated and none-generated pins.

Notice: This idea was suggested by Claude while going over pinact output
and I was surprised to see that post processing the yaml wasn't too
gross.

92206 of 99237 relevant lines covered (92.91%)

4.04 hits per line

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

94.26
/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
9✔
5

6
from collections.abc import Iterable, Iterator
9✔
7
from dataclasses import dataclass
9✔
8

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

43

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

48

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

54

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

60

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

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

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

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

82

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

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

95

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

99

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

106

107
@rule
9✔
108
async def add_resolve_name_to_plugin_request(
9✔
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(
9✔
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
2✔
128

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

131
    parametrizations = await resolve_target_parametrizations(
2✔
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(
2✔
146
        address, consumer_target, field_defaults, target_types_to_generate_requests
147
    )
148
    if (
2✔
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())
1✔
153
        if len(generated_tgts) > 1:
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:
1✔
165
            target = generated_tgts[0]
1✔
166

167
    return WrappedTarget(target)
2✔
168

169

170
@rule
9✔
171
async def resolve_scala_plugins_for_target(
9✔
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
8✔
181
    resolve = request.resolve_name
8✔
182

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

188
    candidate_plugins = []
8✔
189
    candidate_artifacts = []
8✔
190
    for plugin in all_scala_plugins:
8✔
191
        if _plugin_name(plugin) not in plugin_names:
2✔
192
            continue
×
193
        candidate_plugins.append(plugin)
2✔
194
        artifact_field = plugin[ScalacPluginArtifactField]
2✔
195
        wrapped_target = await _resolve_scalac_plugin_artifact(
2✔
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)
2✔
203

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

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

211
    for plugin_name in plugin_names:
8✔
212
        if plugin_name not in plugins:
2✔
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 ((), ())
8✔
219
    return ScalaPluginTargetsForTarget(Targets(plugin_targets), Targets(artifact_targets))
8✔
220

221

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

225

226
@rule
9✔
227
async def fetch_plugins(request: ScalaPluginsRequest) -> ScalaPlugins:
9✔
228
    # Fetch all the artifacts
229
    coarsened_targets = await coarsened_targets_get(
8✔
230
        **implicitly(Addresses(target.address for target in request.artifacts))
231
    )
232
    fallible_artifacts = await concurrently(
8✔
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)
8✔
238
    if artifacts is None:
8✔
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))
8✔
243
    merged = ClasspathEntry.merge(merged_classpath_digest, artifacts)
8✔
244

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

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

249

250
def rules():
9✔
251
    return (
9✔
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