Skip to content

Commit

Permalink
Support usage of scala_artifact addresses in scalac_plugin targets (
Browse files Browse the repository at this point in the history
pantsbuild#19205)

This allows to reference a `scala_artifact` target in the `artifact`
field of a `scalac_plugin`
  • Loading branch information
alonsodomin authored Jun 5, 2023
1 parent c7b74fd commit 704f7ba
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 21 deletions.
9 changes: 1 addition & 8 deletions src/python/pants/backend/kotlin/compile/kotlinc_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,7 @@ async def resolve_kotlinc_plugins_for_target(
continue
candidate_plugins.append(plugin)
artifact_field = plugin[KotlincPluginArtifactField]
address_input = AddressInput.parse(
artifact_field.value,
relative_to=target.address.spec_path,
description_of_origin=(
f"the `{artifact_field.alias}` field from the target {artifact_field.address}"
),
)
artifact_address_gets.append(Get(Address, AddressInput, address_input))
artifact_address_gets.append(Get(Address, AddressInput, artifact_field.to_address_input()))

artifact_addresses = await MultiGet(artifact_address_gets)
candidate_artifacts = await Get(Targets, Addresses(artifact_addresses))
Expand Down
10 changes: 10 additions & 0 deletions src/python/pants/backend/kotlin/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from dataclasses import dataclass

from pants.engine.internals.native_engine import AddressInput
from pants.engine.target import (
COMMON_TARGET_FIELDS,
AsyncFieldMixin,
Expand Down Expand Up @@ -187,6 +188,15 @@ class KotlincPluginArtifactField(StringField, AsyncFieldMixin):
value: str
help = "The address of a `jvm_artifact` that defines a plugin for `kotlinc`."

def to_address_input(self) -> AddressInput:
return AddressInput.parse(
self.value,
relative_to=self.address.spec_path,
description_of_origin=(
f"the `{self.alias}` field in the `{KotlincPluginTarget.alias}` target {self.address}"
),
)


class KotlincPluginIdField(StringField):
alias = "plugin_id"
Expand Down
40 changes: 40 additions & 0 deletions src/python/pants/backend/scala/compile/acyclic-scala212.test.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This lockfile was autogenerated by Pants. To regenerate, run:
#
# pants internal-generate-test-lockfile-fixtures ::
#
# --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE ---
# {
# "version": 1,
# "generated_with_requirements": [
# "com.lihaoyi:acyclic_2.12:0.2.1,url=not_provided,jar=not_provided",
# "org.scala-lang:scala-library:2.12.15,url=not_provided,jar=not_provided"
# ]
# }
# --- END PANTS LOCKFILE METADATA ---

[[entries]]
directDependencies = []
dependencies = []
file_name = "com.lihaoyi_acyclic_2.12_0.2.1.jar"

[entries.coord]
group = "com.lihaoyi"
artifact = "acyclic_2.12"
version = "0.2.1"
packaging = "jar"
[entries.file_digest]
fingerprint = "f20318175f5404bd9d5128c9147d561f62dcf11bf9ffb9911ab66dbadcaf2ba3"
serialized_bytes_length = 64455
[[entries]]
directDependencies = []
dependencies = []
file_name = "org.scala-lang_scala-library_2.12.15.jar"

[entries.coord]
group = "org.scala-lang"
artifact = "scala-library"
version = "2.12.15"
packaging = "jar"
[entries.file_digest]
fingerprint = "e518bb640e2175de5cb1f8e326679b8d975376221f1b547757de429bbf4563f0"
serialized_bytes_length = 5443542
93 changes: 80 additions & 13 deletions src/python/pants/backend/scala/compile/scalac_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,22 @@
)
from pants.build_graph.address import Address, AddressInput
from pants.engine.addresses import Addresses
from pants.engine.environment import ChosenLocalEnvironmentName, EnvironmentName
from pants.engine.internals.native_engine import Digest, MergeDigests
from pants.engine.internals.parametrize import (
_TargetParametrizations,
_TargetParametrizationsRequest,
)
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import AllTargets, CoarsenedTargets, Target, Targets
from pants.engine.target import (
AllTargets,
CoarsenedTargets,
FieldDefaults,
Target,
Targets,
TargetTypesToGenerateTargetsRequests,
WrappedTarget,
)
from pants.jvm.compile import ClasspathEntry, FallibleClasspathEntry
from pants.jvm.goals import lockfile
from pants.jvm.resolve.coursier_fetch import CoursierFetchRequest
Expand All @@ -25,6 +38,7 @@
from pants.jvm.subsystems import JvmSubsystem
from pants.jvm.target_types import JvmResolveField
from pants.util.ordered_set import OrderedSet
from pants.util.strutil import bullet_list, softwrap


@dataclass(frozen=True)
Expand Down Expand Up @@ -99,12 +113,68 @@ async def add_resolve_name_to_plugin_request(
)


async def _resolve_scalac_plugin_artifact(
field: ScalacPluginArtifactField,
consumer_target: Target,
target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests,
local_environment_name: ChosenLocalEnvironmentName,
field_defaults: FieldDefaults,
) -> WrappedTarget:
"""Helps resolving the actual artifact for a scalac plugin even in the scenario in which the
artifact has been declared as a scala_artifact and it has been parametrized (i.e. across
multiple resolves for cross building)."""

environment_name = local_environment_name.val

address = await Get(Address, AddressInput, field.to_address_input())

parametrizations = await Get(
_TargetParametrizations,
{
_TargetParametrizationsRequest(
address.maybe_convert_to_target_generator(),
description_of_origin=(
f"the target generator {address.maybe_convert_to_target_generator()}"
),
): _TargetParametrizationsRequest,
environment_name: EnvironmentName,
},
)

target = parametrizations.get_subset(
address, consumer_target, field_defaults, target_types_to_generate_requests
)
if (
target_types_to_generate_requests.is_generator(target)
and not target.address.is_generated_target
):
generated_tgts = list(parametrizations.generated_for(target.address).values())
if len(generated_tgts) > 1:
raise Exception(
softwrap(
f"""
Could not resolve scalac plugin artifact {address} from target {field.address}
as it points to a target generator that produced more than one target:
{bullet_list([tgt.address.spec for tgt in generated_tgts])}
"""
)
)
if len(generated_tgts) == 1:
target = generated_tgts[0]

return WrappedTarget(target)


@rule
async def resolve_scala_plugins_for_target(
request: ScalaPluginsForTargetRequest,
all_scala_plugins: AllScalaPluginTargets,
jvm: JvmSubsystem,
scalac: Scalac,
target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests,
local_environment_name: ChosenLocalEnvironmentName,
field_defaults: FieldDefaults,
) -> ScalaPluginTargetsForTarget:
target = request.target
resolve = request.resolve_name
Expand All @@ -115,23 +185,20 @@ async def resolve_scala_plugins_for_target(
plugin_names = tuple(plugin_names_by_resolve.get(resolve, ()))

candidate_plugins = []
artifact_address_gets = []
candidate_artifacts = []
for plugin in all_scala_plugins:
if _plugin_name(plugin) not in plugin_names:
continue
candidate_plugins.append(plugin)
artifact_field = plugin[ScalacPluginArtifactField]
address_input = AddressInput.parse(
artifact_field.value,
relative_to=target.address.spec_path,
description_of_origin=(
f"the `{artifact_field.alias}` field from the target {artifact_field.address}"
),
wrapped_target = await _resolve_scalac_plugin_artifact(
artifact_field,
request.target,
target_types_to_generate_requests,
local_environment_name,
field_defaults,
)
artifact_address_gets.append(Get(Address, AddressInput, address_input))

artifact_addresses = await MultiGet(artifact_address_gets)
candidate_artifacts = await Get(Targets, Addresses(artifact_addresses))
candidate_artifacts.append(wrapped_target.target)

plugins: dict[str, tuple[Target, Target]] = {} # Maps plugin name to relevant JVM artifact
for plugin, artifact in zip(candidate_plugins, candidate_artifacts):
Expand All @@ -144,7 +211,7 @@ async def resolve_scala_plugins_for_target(
if plugin_name not in plugins:
raise Exception(
f"Could not find Scala plugin `{plugin_name}` in resolve `{resolve}` "
f"for target {request.target}"
f"for target {request.target.address}."
)

plugin_targets, artifact_targets = zip(*plugins.values()) if plugins else ((), ())
Expand Down
137 changes: 137 additions & 0 deletions src/python/pants/backend/scala/compile/scalac_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from pants.core.goals.check import CheckResults
from pants.core.util_rules import source_files
from pants.engine.addresses import Addresses
from pants.engine.internals.parametrize import Parametrize
from pants.engine.internals.scheduler import ExecutionError
from pants.engine.target import CoarsenedTargets
from pants.jvm import jdk_rules, testutil
Expand Down Expand Up @@ -72,6 +73,9 @@ def rule_runner() -> RuleRunner:
ScalaSourcesGeneratorTarget,
ScalacPluginTarget,
],
objects={
"parametrize": Parametrize,
},
)
rule_runner.set_options(
args=["--scala-version-for-resolve={'jvm-default':'2.13.8'}"],
Expand Down Expand Up @@ -966,3 +970,136 @@ def test_compile_dep_on_scala_artifact(
"META-INF/MANIFEST.MF",
}
}


@pytest.fixture
def acyclic_scala212_lockfile_def() -> JVMLockfileFixtureDefinition:
return JVMLockfileFixtureDefinition(
"acyclic-scala212.test.lock",
[
"com.lihaoyi:acyclic_2.12:0.2.1",
"org.scala-lang:scala-library:2.12.15",
],
)


@pytest.fixture
def acyclic_scala212_lockfile(
acyclic_scala212_lockfile_def: JVMLockfileFixtureDefinition, request
) -> JVMLockfileFixture:
return acyclic_scala212_lockfile_def.load(request)


@maybe_skip_jdk_test
def test_cross_compile_with_scalac_plugin(
rule_runner: RuleRunner,
acyclic_jvm_lockfile: JVMLockfileFixture,
acyclic_scala212_lockfile: JVMLockfileFixture,
scala_2_12_lockfile: JVMLockfileFixture,
scala_stdlib_jvm_lockfile: JVMLockfileFixture,
) -> None:
rule_runner.write_files(
{
"3rdparty/jvm/BUILD": dedent(
"""\
scala_artifact(
name="acyclic",
group="com.lihaoyi",
artifact="acyclic",
version="0.2.1",
resolve=parametrize("scala2.12", "scala2.13"),
)
"""
),
"3rdparty/jvm/BUILD.2_12": scala_2_12_lockfile.requirements_as_jvm_artifact_targets(
version_in_target_name=True, resolve="scala2.12"
),
"3rdparty/jvm/BUILD.2_13": scala_stdlib_jvm_lockfile.requirements_as_jvm_artifact_targets(
version_in_target_name=True, resolve="scala2.13"
),
"3rdparty/jvm/scala213.lock": acyclic_jvm_lockfile.serialized_lockfile,
"3rdparty/jvm/scala212.lock": acyclic_scala212_lockfile.serialized_lockfile,
"lib/BUILD": dedent(
"""\
scalac_plugin(
name = "acyclic",
artifact = "3rdparty/jvm:acyclic",
)
scala_sources(
name="main",
scalac_plugins=["acyclic"],
resolve=parametrize("scala2.12", "scala2.13")
)
"""
),
"lib/A.scala": dedent(
"""
package lib
import acyclic.file
class A {
val b: B = null
}
"""
),
"lib/B.scala": dedent(
"""
package lib
class B {
val a: A = null
}
"""
),
}
)

rule_runner.set_options(
[
'--scala-version-for-resolve={"scala2.12":"2.12.15","scala2.13":"2.13.8"}',
'--jvm-resolves={"scala2.12":"3rdparty/jvm/scala212.lock","scala2.13":"3rdparty/jvm/scala213.lock"}',
],
env_inherit=PYTHON_BOOTSTRAP_ENV,
)
classpath_2_12 = rule_runner.request(
FallibleClasspathEntry,
[
CompileScalaSourceRequest(
component=expect_single_expanded_coarsened_target(
rule_runner,
Address(
spec_path="lib",
target_name="main",
relative_file_path="A.scala",
parameters={"resolve": "scala2.12"},
),
),
resolve=make_resolve(rule_runner, "scala2.12", "3rdparty/jvm/scala212.lock"),
)
],
)

assert classpath_2_12.result == CompileResult.FAILED and classpath_2_12.stderr
assert "error: Unwanted cyclic dependency" in classpath_2_12.stderr

classpath_2_13 = rule_runner.request(
FallibleClasspathEntry,
[
CompileScalaSourceRequest(
component=expect_single_expanded_coarsened_target(
rule_runner,
Address(
spec_path="lib",
target_name="main",
relative_file_path="A.scala",
parameters={"resolve": "scala2.13"},
),
),
resolve=make_resolve(rule_runner, "scala2.13", "3rdparty/jvm/scala213.lock"),
)
],
)

assert classpath_2_13.result == CompileResult.FAILED and classpath_2_13.stderr
assert "error: Unwanted cyclic dependency" in classpath_2_13.stderr
Loading

0 comments on commit 704f7ba

Please sign in to comment.