diff --git a/src/python/pants/backend/codegen/protobuf/scala/dependency_inference.py b/src/python/pants/backend/codegen/protobuf/scala/dependency_inference.py index 0bf0e416134..06b78716e38 100644 --- a/src/python/pants/backend/codegen/protobuf/scala/dependency_inference.py +++ b/src/python/pants/backend/codegen/protobuf/scala/dependency_inference.py @@ -55,8 +55,7 @@ async def resolve_scalapb_runtime_for_resolve( scalapb: ScalaPBSubsystem, ) -> ScalaPBRuntimeForResolve: scala_version = scala_subsystem.version_for_resolve(request.resolve_name) - # TODO: Does not handle Scala 3 suffix which is just `_3` nor X.Y.Z versions. - scala_binary_version, _, _ = scala_version.rpartition(".") + scala_binary_version = scala_version.binary version = scalapb.version addresses = find_jvm_artifacts_or_raise( diff --git a/src/python/pants/backend/codegen/protobuf/scala/rules.py b/src/python/pants/backend/codegen/protobuf/scala/rules.py index 70b75f69aba..30a9552fb29 100644 --- a/src/python/pants/backend/codegen/protobuf/scala/rules.py +++ b/src/python/pants/backend/codegen/protobuf/scala/rules.py @@ -14,6 +14,11 @@ ProtobufSourceTarget, ) from pants.backend.scala.target_types import ScalaSourceField +from pants.backend.scala.util_rules.versions import ( + ScalaArtifactsForVersionRequest, + ScalaArtifactsForVersionResult, + ScalaVersion, +) from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.core.util_rules import distdir from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest @@ -46,7 +51,7 @@ from pants.jvm.dependency_inference import artifact_mapper from pants.jvm.goals import lockfile from pants.jvm.jdk_rules import InternalJdk, JvmProcess -from pants.jvm.resolve.common import ArtifactRequirements, Coordinate, GatherJvmCoordinatesRequest +from pants.jvm.resolve.common import ArtifactRequirements, GatherJvmCoordinatesRequest from pants.jvm.resolve.coursier_fetch import ToolClasspath, ToolClasspathRequest from pants.jvm.resolve.jvm_tool import GenerateJvmLockfileFromTool, GenerateJvmToolLockfileSentinel from pants.jvm.target_types import PrefixedJvmJdkField, PrefixedJvmResolveField @@ -234,7 +239,7 @@ async def materialize_jvm_plugins( return MaterializedJvmPlugins(merged_plugins_digest, materialized_plugins) -SHIM_SCALA_VERSION = "2.13.7" +SHIM_SCALA_VERSION = ScalaVersion.parse("2.13.7") # TODO(13879): Consolidate compilation of wrapper binaries to common rules. @@ -253,30 +258,17 @@ async def setup_scalapb_shim_classfiles( scalapb_shim_source = FileContent("ScalaPBShim.scala", scalapb_shim_content) - lockfile_request = await Get(GenerateJvmLockfileFromTool, ScalapbcToolLockfileSentinel()) + lockfile_request, scala_artifacts = await MultiGet( + Get(GenerateJvmLockfileFromTool, ScalapbcToolLockfileSentinel()), + Get(ScalaArtifactsForVersionResult, ScalaArtifactsForVersionRequest(SHIM_SCALA_VERSION)), + ) tool_classpath, shim_classpath, source_digest = await MultiGet( Get( ToolClasspath, ToolClasspathRequest( prefix="__toolcp", artifact_requirements=ArtifactRequirements.from_coordinates( - [ - Coordinate( - group="org.scala-lang", - artifact="scala-compiler", - version=SHIM_SCALA_VERSION, - ), - Coordinate( - group="org.scala-lang", - artifact="scala-library", - version=SHIM_SCALA_VERSION, - ), - Coordinate( - group="org.scala-lang", - artifact="scala-reflect", - version=SHIM_SCALA_VERSION, - ), - ] + scala_artifacts.all_coordinates ), ), ), diff --git a/src/python/pants/backend/codegen/thrift/scrooge/java/rules.py b/src/python/pants/backend/codegen/thrift/scrooge/java/rules.py index ae93b6e3a65..220681be126 100644 --- a/src/python/pants/backend/codegen/thrift/scrooge/java/rules.py +++ b/src/python/pants/backend/codegen/thrift/scrooge/java/rules.py @@ -103,7 +103,7 @@ async def resolve_scrooge_thrift_java_runtime_for_resolve( scala_subsystem: ScalaSubsystem, ) -> ScroogeThriftJavaRuntimeForResolve: scala_version = scala_subsystem.version_for_resolve(request.resolve_name) - scala_binary_version, _, _ = scala_version.rpartition(".") + scala_binary_version = scala_version.binary addresses = find_jvm_artifacts_or_raise( required_coordinates=[ UnversionedCoordinate( diff --git a/src/python/pants/backend/codegen/thrift/scrooge/scala/rules.py b/src/python/pants/backend/codegen/thrift/scrooge/scala/rules.py index 1806e80ddc0..1c4eb43cd9c 100644 --- a/src/python/pants/backend/codegen/thrift/scrooge/scala/rules.py +++ b/src/python/pants/backend/codegen/thrift/scrooge/scala/rules.py @@ -103,7 +103,7 @@ async def resolve_scrooge_thrift_scala_runtime_for_resolve( scala_subsystem: ScalaSubsystem, ) -> ScroogeThriftScalaRuntimeForResolve: scala_version = scala_subsystem.version_for_resolve(request.resolve_name) - scala_binary_version, _, _ = scala_version.rpartition(".") + scala_binary_version = scala_version.binary addresses = find_jvm_artifacts_or_raise( required_coordinates=[ UnversionedCoordinate( diff --git a/src/python/pants/backend/scala/bsp/rules.py b/src/python/pants/backend/scala/bsp/rules.py index 8341a0e21b9..6e949108e84 100644 --- a/src/python/pants/backend/scala/bsp/rules.py +++ b/src/python/pants/backend/scala/bsp/rules.py @@ -31,6 +31,7 @@ from pants.backend.scala.util_rules.versions import ( ScalaArtifactsForVersionRequest, ScalaArtifactsForVersionResult, + ScalaVersion, ) from pants.base.build_root import BuildRoot from pants.bsp.protocol import BSPHandlerMapping @@ -158,7 +159,7 @@ async def collect_thirdparty_modules( ) -async def _materialize_scala_runtime_jars(scala_version: str) -> Snapshot: +async def _materialize_scala_runtime_jars(scala_version: ScalaVersion) -> Snapshot: scala_artifacts = await Get( ScalaArtifactsForVersionResult, ScalaArtifactsForVersionRequest(scala_version) ) @@ -283,17 +284,11 @@ async def bsp_resolve_scala_metadata( java_version=f"1.{jdk.jre_major_version}", ) - scala_version_parts = scala_version.split(".") - scala_binary_version = ( - ".".join(scala_version_parts[0:2]) - if int(scala_version_parts[0]) < 3 - else scala_version_parts[0] - ) return BSPBuildTargetsMetadataResult( metadata=ScalaBuildTarget( scala_organization="org.scala-lang", - scala_version=scala_version, - scala_binary_version=scala_binary_version, + scala_version=str(scala_version), + scala_binary_version=scala_version.binary, platform=ScalaPlatform.JVM, jars=scala_jar_uris, jvm_build_target=jvm_build_target, diff --git a/src/python/pants/backend/scala/compile/scalac.py b/src/python/pants/backend/scala/compile/scalac.py index 6d66e7f9cd8..0d23e2802df 100644 --- a/src/python/pants/backend/scala/compile/scalac.py +++ b/src/python/pants/backend/scala/compile/scalac.py @@ -23,6 +23,7 @@ from pants.backend.scala.util_rules.versions import ( ScalaArtifactsForVersionRequest, ScalaArtifactsForVersionResult, + ScalaVersion, ) from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.fs import EMPTY_DIGEST, Digest, MergeDigests @@ -58,7 +59,7 @@ class CompileScalaSourceRequest(ClasspathEntryRequest): @dataclass(frozen=True) class ScalaLibraryRequest: - version: str + version: ScalaVersion # TODO: This code is duplicated in the scalac and BSP rules. diff --git a/src/python/pants/backend/scala/dependency_inference/scala_parser.py b/src/python/pants/backend/scala/dependency_inference/scala_parser.py index f98f3d16efe..3ae0ea545db 100644 --- a/src/python/pants/backend/scala/dependency_inference/scala_parser.py +++ b/src/python/pants/backend/scala/dependency_inference/scala_parser.py @@ -10,6 +10,11 @@ from pants.backend.scala.subsystems.scala import ScalaSubsystem from pants.backend.scala.subsystems.scalac import Scalac +from pants.backend.scala.util_rules.versions import ( + ScalaArtifactsForVersionRequest, + ScalaArtifactsForVersionResult, + ScalaVersion, +) from pants.core.goals.generate_lockfiles import DEFAULT_TOOL_LOCKFILE, GenerateToolLockfileSentinel from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.fs import ( @@ -30,7 +35,7 @@ from pants.jvm.compile import ClasspathEntry from pants.jvm.jdk_rules import InternalJdk, JvmProcess from pants.jvm.jdk_rules import rules as jdk_rules -from pants.jvm.resolve.common import ArtifactRequirements, Coordinate +from pants.jvm.resolve.common import ArtifactRequirements from pants.jvm.resolve.coursier_fetch import ToolClasspath, ToolClasspathRequest from pants.jvm.resolve.jvm_tool import GenerateJvmLockfileFromTool, GenerateJvmToolLockfileSentinel from pants.jvm.subsystems import JvmSubsystem @@ -43,8 +48,8 @@ logger = logging.getLogger(__name__) -_PARSER_SCALA_VERSION = "2.13.8" -_PARSER_SCALA_BINARY_VERSION = _PARSER_SCALA_VERSION.rpartition(".")[0] +_PARSER_SCALA_VERSION = ScalaVersion.parse("2.13.8") +_PARSER_SCALA_BINARY_VERSION = _PARSER_SCALA_VERSION.binary class ScalaParserToolLockfileSentinel(GenerateJvmToolLockfileSentinel): @@ -194,7 +199,7 @@ class ScalaParserCompiledClassfiles(ClasspathEntry): @dataclass(frozen=True) class AnalyzeScalaSourceRequest: source_files: SourceFiles - scala_version: str + scala_version: ScalaVersion source3: bool @@ -274,7 +279,7 @@ async def analyze_scala_source_dependencies( "org.pantsbuild.backend.scala.dependency_inference.ScalaParser", analysis_output_path, source_path, - request.scala_version, + str(request.scala_version), str(request.source3), ], input_digest=prefixed_source_files_digest, @@ -319,8 +324,9 @@ async def setup_scala_parser_classfiles(jdk: InternalJdk) -> ScalaParserCompiled parser_source = FileContent("ScalaParser.scala", parser_source_content) - parser_lockfile_request = await Get( - GenerateJvmLockfileFromTool, ScalaParserToolLockfileSentinel() + parser_lockfile_request, scala_artifacts = await MultiGet( + Get(GenerateJvmLockfileFromTool, ScalaParserToolLockfileSentinel()), + Get(ScalaArtifactsForVersionResult, ScalaArtifactsForVersionRequest(_PARSER_SCALA_VERSION)), ) tool_classpath, parser_classpath, source_digest = await MultiGet( @@ -329,23 +335,7 @@ async def setup_scala_parser_classfiles(jdk: InternalJdk) -> ScalaParserCompiled ToolClasspathRequest( prefix="__toolcp", artifact_requirements=ArtifactRequirements.from_coordinates( - [ - Coordinate( - group="org.scala-lang", - artifact="scala-compiler", - version=_PARSER_SCALA_VERSION, - ), - Coordinate( - group="org.scala-lang", - artifact="scala-library", - version=_PARSER_SCALA_VERSION, - ), - Coordinate( - group="org.scala-lang", - artifact="scala-reflect", - version=_PARSER_SCALA_VERSION, - ), - ] + scala_artifacts.all_coordinates ), ), ), @@ -397,15 +387,18 @@ async def setup_scala_parser_classfiles(jdk: InternalJdk) -> ScalaParserCompiled @rule -def generate_scala_parser_lockfile_request( +async def generate_scala_parser_lockfile_request( _: ScalaParserToolLockfileSentinel, ) -> GenerateJvmLockfileFromTool: + scala_artifacts = await Get( + ScalaArtifactsForVersionResult, ScalaArtifactsForVersionRequest(_PARSER_SCALA_VERSION) + ) return GenerateJvmLockfileFromTool( artifact_inputs=FrozenOrderedSet( { f"org.scalameta:scalameta_{_PARSER_SCALA_BINARY_VERSION}:4.8.7", f"io.circe:circe-generic_{_PARSER_SCALA_BINARY_VERSION}:0.14.1", - f"org.scala-lang:scala-library:{_PARSER_SCALA_VERSION}", + scala_artifacts.library_coordinate.to_coord_str(), } ), artifact_option_name="n/a", diff --git a/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py b/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py index dbd34c0c320..b35f50bb1e3 100644 --- a/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py +++ b/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py @@ -13,6 +13,7 @@ ScalaSourceDependencyAnalysis, ) from pants.backend.scala.target_types import ScalaSourceField, ScalaSourceTarget +from pants.backend.scala.util_rules import versions from pants.build_graph.address import Address from pants.core.util_rules import source_files from pants.core.util_rules.source_files import SourceFilesRequest @@ -37,6 +38,7 @@ def rule_runner() -> RuleRunner: *target_types.rules(), *jvm_util_rules.rules(), *process.rules(), + *versions.rules(), QueryRule(AnalyzeScalaSourceRequest, (SourceFilesRequest,)), QueryRule(ScalaSourceDependencyAnalysis, (AnalyzeScalaSourceRequest,)), ], diff --git a/src/python/pants/backend/scala/resolve/lockfile.py b/src/python/pants/backend/scala/resolve/lockfile.py index f64d2d4196a..85657008f93 100644 --- a/src/python/pants/backend/scala/resolve/lockfile.py +++ b/src/python/pants/backend/scala/resolve/lockfile.py @@ -88,9 +88,9 @@ async def validate_scala_runtime_is_present_in_resolve( artifact.coordinate.group == SCALA_LIBRARY_GROUP and artifact.coordinate.artifact == scala_artifacts.library_coordinate.artifact ): - if artifact.coordinate.version != scala_version: + if artifact.coordinate.version != str(scala_version): raise ConflictingScalaLibraryVersionInResolveError( - request.resolve_name, scala_version, artifact.coordinate + request.resolve_name, str(scala_version), artifact.coordinate ) # This does not `break` so the loop can validate the entire set of requirements to ensure no conflicting diff --git a/src/python/pants/backend/scala/subsystems/scala.py b/src/python/pants/backend/scala/subsystems/scala.py index 5f6dd3b393d..952c4624644 100644 --- a/src/python/pants/backend/scala/subsystems/scala.py +++ b/src/python/pants/backend/scala/subsystems/scala.py @@ -3,15 +3,12 @@ from __future__ import annotations -import logging - +from pants.backend.scala.util_rules.versions import ScalaVersion from pants.option.option_types import BoolOption, DictOption from pants.option.subsystem import Subsystem from pants.util.strutil import softwrap -DEFAULT_SCALA_VERSION = "2.13.6" - -_logger = logging.getLogger(__name__) +DEFAULT_SCALA_VERSION = ScalaVersion.parse("2.13.6") class ScalaSubsystem(Subsystem): @@ -42,8 +39,8 @@ class ScalaSubsystem(Subsystem): advanced=True, ) - def version_for_resolve(self, resolve: str) -> str: + def version_for_resolve(self, resolve: str) -> ScalaVersion: version = self._version_for_resolve.get(resolve) if version: - return version + return ScalaVersion.parse(version) return DEFAULT_SCALA_VERSION diff --git a/src/python/pants/backend/scala/target_types.py b/src/python/pants/backend/scala/target_types.py index 5a6ed11b219..e8cf5112456 100644 --- a/src/python/pants/backend/scala/target_types.py +++ b/src/python/pants/backend/scala/target_types.py @@ -4,14 +4,16 @@ from __future__ import annotations from dataclasses import dataclass -from enum import Enum -from typing import ClassVar +from typing import ClassVar, Optional from pants.backend.scala.subsystems.scala import ScalaSubsystem from pants.backend.scala.subsystems.scala_infer import ScalaInferSubsystem +from pants.backend.scala.util_rules.versions import ScalaCrossVersionMode +from pants.base.deprecated import warn_or_error from pants.build_graph.address import AddressInput from pants.build_graph.build_file_aliases import BuildFileAliases from pants.core.goals.test import TestExtraEnvVarsField, TestTimeoutField +from pants.engine.addresses import Address from pants.engine.rules import collect_rules, rule from pants.engine.target import ( COMMON_TARGET_FIELDS, @@ -413,22 +415,29 @@ class ScalaArtifactArtifactField(StringField): ) -class ScalaCrossVersion(Enum): - PARTIAL = "partial" - FULL = "full" - - class ScalaArtifactCrossversionField(StringField): alias = "crossversion" - default = ScalaCrossVersion.PARTIAL.value + default = ScalaCrossVersionMode.BINARY.value help = help_text( - """ + f""" Whether to use the full Scala version or the partial one to determine the artifact name suffix. - Default is `partial`. + Default is `{ScalaCrossVersionMode.BINARY.value}`. """ ) - valid_choices = ScalaCrossVersion + valid_choices = ScalaCrossVersionMode + + @classmethod + def compute_value(cls, raw_value: Optional[str], address: Address) -> Optional[str]: + computed_value = super().compute_value(raw_value, address) + if computed_value == ScalaCrossVersionMode.PARTIAL.value: + warn_or_error( + "2.21.0", + f"Scala cross version value '{computed_value}' in target: {address}", + "Use value `binary` instead", + start_version="2.20.0", + ) + return computed_value @dataclass(frozen=True) @@ -441,28 +450,36 @@ class ScalaArtifactExclusion(JvmArtifactExclusion): """ ) - crossversion: str = ScalaCrossVersion.PARTIAL.value + crossversion: str = ScalaCrossVersionMode.BINARY.value - def validate(self) -> set[str]: - errors = super().validate() - valid_crossversions = [x.value for x in ScalaCrossVersion] + def validate(self, address: Address) -> set[str]: + errors = super().validate(address) + valid_crossversions = [x.value for x in ScalaCrossVersionMode] if self.crossversion not in valid_crossversions: errors.add( softwrap( f""" - Invalid `crossversion` value: {self.crossversion}. Valid values are: + Invalid `crossversion` value '{self.crossversion}' in in list of + exclusions at target: {address}. Valid values are: {', '.join(valid_crossversions)} """ ) ) + if self.crossversion == ScalaCrossVersionMode.PARTIAL.value: + warn_or_error( + "2.21.0", + f"Scala cross version value '{self.crossversion}' in list of exclusions at target: {address}", + "Use value `binary` instead", + start_version="2.20.0", + ) return errors class ScalaArtifactExclusionsField(JvmArtifactExclusionsField): help = _jvm_artifact_exclusions_field_help( - lambda: ScalaArtifactExclusionsField.supported_rule_types + lambda: ScalaArtifactExclusionsField.supported_exclusion_types ) - supported_rule_types: ClassVar[tuple[type[JvmArtifactExclusion], ...]] = ( + supported_exclusion_types: ClassVar[tuple[type[JvmArtifactExclusion], ...]] = ( JvmArtifactExclusion, ScalaArtifactExclusion, ) @@ -539,15 +556,6 @@ async def generate_jvm_artifact_targets( field_set = ScalaArtifactFieldSet.create(request.generator) resolve_name = request.template.get(JvmArtifactResolveField.alias) or jvm.default_resolve scala_version = scala.version_for_resolve(resolve_name) - scala_version_parts = scala_version.split(".") - - def scala_suffix(crossversion: ScalaCrossVersion) -> str: - if crossversion == ScalaCrossVersion.FULL: - return scala_version - elif int(scala_version_parts[0]) >= 3: - return scala_version_parts[0] - - return f"{scala_version_parts[0]}.{scala_version_parts[1]}" exclusions_field = {} if field_set.exclusions.value: @@ -558,15 +566,19 @@ def scala_suffix(crossversion: ScalaCrossVersion) -> str: else: excluded_artifact_name = None if exclusion.artifact: - crossversion = ScalaCrossVersion(exclusion.crossversion) - excluded_artifact_name = f"{exclusion.artifact}_{scala_suffix(crossversion)}" + cross_mode = ScalaCrossVersionMode(exclusion.crossversion) + excluded_artifact_name = ( + f"{exclusion.artifact}_{scala_version.crossversion(cross_mode)}" + ) exclusions.append( JvmArtifactExclusion(group=exclusion.group, artifact=excluded_artifact_name) ) exclusions_field[JvmArtifactExclusionsField.alias] = exclusions - crossversion = ScalaCrossVersion(field_set.crossversion.value) - artifact_name = f"{field_set.artifact.value}_{scala_suffix(crossversion)}" + cross_mode = ScalaCrossVersionMode( + field_set.crossversion.value or ScalaArtifactCrossversionField.default + ) + artifact_name = f"{field_set.artifact.value}_{scala_version.crossversion(cross_mode)}" jvm_artifact_target = JvmArtifactTarget( { **request.template, diff --git a/src/python/pants/backend/scala/util_rules/BUILD b/src/python/pants/backend/scala/util_rules/BUILD index 95c6150585e..a84207eff89 100644 --- a/src/python/pants/backend/scala/util_rules/BUILD +++ b/src/python/pants/backend/scala/util_rules/BUILD @@ -2,3 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). python_sources() + +python_tests( + name="tests", +) diff --git a/src/python/pants/backend/scala/util_rules/versions.py b/src/python/pants/backend/scala/util_rules/versions.py index 46bf82fcb04..53911b07d76 100644 --- a/src/python/pants/backend/scala/util_rules/versions.py +++ b/src/python/pants/backend/scala/util_rules/versions.py @@ -2,15 +2,75 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). from __future__ import annotations +import re from dataclasses import dataclass +from enum import Enum from pants.engine.rules import collect_rules, rule from pants.jvm.resolve.common import Coordinate +from pants.util.strutil import softwrap + + +class InvalidScalaVersion(ValueError): + def __init__(self, scala_version: str) -> None: + super().__init__( + softwrap( + f"""Value '{scala_version}' is not a valid Scala version. + It should be formed of [major].[minor].[patch]""" + ) + ) + + +class ScalaCrossVersionMode(Enum): + PARTIAL = "partial" + BINARY = "binary" + FULL = "full" + + +_SCALA_VERSION_PATTERN = re.compile(r"^([0-9]+)\.([0-9]+)\.([0-9]+)(\-(.+))?$") + + +@dataclass(frozen=True) +class ScalaVersion: + major: int + minor: int + patch: int + suffix: str | None = None + + @classmethod + def parse(cls, scala_version: str) -> ScalaVersion: + matched = _SCALA_VERSION_PATTERN.match(scala_version) + if not matched: + raise InvalidScalaVersion(scala_version) + + return cls( + major=int(matched.groups()[0]), + minor=int(matched.groups()[1]), + patch=int(matched.groups()[2]), + suffix=matched.groups()[4] if len(matched.groups()) == 5 else None, + ) + + def crossversion(self, mode: ScalaCrossVersionMode) -> str: + if mode == ScalaCrossVersionMode.FULL: + return str(self) + if self.major >= 3: + return str(self.major) + return f"{self.major}.{self.minor}" + + @property + def binary(self) -> str: + return self.crossversion(ScalaCrossVersionMode.BINARY) + + def __str__(self) -> str: + version_str = f"{self.major}.{self.minor}.{self.patch}" + if self.suffix: + version_str += f"-{self.suffix}" + return version_str @dataclass(frozen=True) class ScalaArtifactsForVersionRequest: - scala_version: str + scala_version: ScalaVersion @dataclass(frozen=True) @@ -33,18 +93,17 @@ def all_coordinates(self) -> tuple[Coordinate, ...]: async def resolve_scala_artifacts_for_version( request: ScalaArtifactsForVersionRequest, ) -> ScalaArtifactsForVersionResult: - version_parts = request.scala_version.split(".") - if version_parts[0] == "3": + if request.scala_version.major == 3: return ScalaArtifactsForVersionResult( compiler_coordinate=Coordinate( group="org.scala-lang", artifact="scala3-compiler_3", - version=request.scala_version, + version=str(request.scala_version), ), library_coordinate=Coordinate( group="org.scala-lang", artifact="scala3-library_3", - version=request.scala_version, + version=str(request.scala_version), ), reflect_coordinate=None, compiler_main="dotty.tools.dotc.Main", @@ -55,17 +114,17 @@ async def resolve_scala_artifacts_for_version( compiler_coordinate=Coordinate( group="org.scala-lang", artifact="scala-compiler", - version=request.scala_version, + version=str(request.scala_version), ), library_coordinate=Coordinate( group="org.scala-lang", artifact="scala-library", - version=request.scala_version, + version=str(request.scala_version), ), reflect_coordinate=Coordinate( group="org.scala-lang", artifact="scala-reflect", - version=request.scala_version, + version=str(request.scala_version), ), compiler_main="scala.tools.nsc.Main", repl_main="scala.tools.nsc.MainGenericRunner", diff --git a/src/python/pants/backend/scala/util_rules/versions_test.py b/src/python/pants/backend/scala/util_rules/versions_test.py new file mode 100644 index 00000000000..85ddf19d360 --- /dev/null +++ b/src/python/pants/backend/scala/util_rules/versions_test.py @@ -0,0 +1,27 @@ +# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). +import pytest + +from pants.backend.scala.util_rules.versions import ScalaCrossVersionMode, ScalaVersion + + +@pytest.mark.parametrize( + "version_str, expected_obj, expected_binary", + [ + ("2.12.0", ScalaVersion(2, 12, 0), "2.12"), + ("3.3.1", ScalaVersion(3, 3, 1), "3"), + ("3.0.1-RC2", ScalaVersion(3, 0, 1, "RC2"), "3"), + ("2.13.8-jfhd8fyd834-SNAPSHOT", ScalaVersion(2, 13, 8, "jfhd8fyd834-SNAPSHOT"), "2.13"), + ], +) +def test_scala_version_parser( + version_str: str, expected_obj: ScalaVersion, expected_binary: str +) -> None: + parsed = ScalaVersion.parse(version_str) + assert parsed == expected_obj + assert str(parsed) == version_str + assert parsed.binary == expected_binary + assert parsed.crossversion(ScalaCrossVersionMode.FULL) == version_str + assert parsed.crossversion(ScalaCrossVersionMode.PARTIAL) == parsed.crossversion( + ScalaCrossVersionMode.BINARY + ) diff --git a/src/python/pants/jvm/target_types.py b/src/python/pants/jvm/target_types.py index 264b6aa9495..846c8a2bdc7 100644 --- a/src/python/pants/jvm/target_types.py +++ b/src/python/pants/jvm/target_types.py @@ -279,7 +279,7 @@ class JvmArtifactExclusion: group: str artifact: str | None = None - def validate(self) -> set[str]: + def validate(self, _: Address) -> set[str]: return set() def to_coord_str(self) -> str: @@ -310,10 +310,12 @@ def _jvm_artifact_exclusions_field_help( class JvmArtifactExclusionsField(SequenceField[JvmArtifactExclusion]): alias = "exclusions" help = _jvm_artifact_exclusions_field_help( - lambda: JvmArtifactExclusionsField.supported_rule_types + lambda: JvmArtifactExclusionsField.supported_exclusion_types ) - supported_rule_types: ClassVar[tuple[type[JvmArtifactExclusion], ...]] = (JvmArtifactExclusion,) + supported_exclusion_types: ClassVar[tuple[type[JvmArtifactExclusion], ...]] = ( + JvmArtifactExclusion, + ) expected_element_type = JvmArtifactExclusion expected_type_description = "an iterable of JvmArtifactExclusionRule" @@ -326,7 +328,7 @@ def compute_value( if computed_value: errors: list[str] = [] for exclusion_rule in computed_value: - err = exclusion_rule.validate() + err = exclusion_rule.validate(address) if err: errors.extend(err) @@ -334,8 +336,8 @@ def compute_value( raise InvalidFieldException( softwrap( f""" - Invalid value for `{JvmArtifactExclusionsField.alias}` field. - Found following errors: + Invalid value for `{JvmArtifactExclusionsField.alias}` field at target + {address}. Found following errors: {bullet_list(errors)} """ @@ -713,8 +715,8 @@ def compute_value( raise InvalidFieldException( softwrap( f""" - Invalid value for `{DeployJarDuplicatePolicyField.alias}` field. - Found following errors: + Invalid value for `{DeployJarDuplicatePolicyField.alias}` field at target: + {address}. Found following errors: {bullet_list(errors)} """