From e5cd895bb631db3cb12eebad2f1e083fdcb52bf6 Mon Sep 17 00:00:00 2001 From: Eugene Platonov Date: Fri, 15 Dec 2023 18:03:30 -0500 Subject: [PATCH] Add Scala 3 support --- README.md | 2 +- src/it/integration_tests_parent/pom.xml | 3 +- .../invoker.properties | 1 + .../pom.xml | 46 +++++++++++++ .../src/main/scala/HelloServiceScala.scala | 6 ++ .../test/scala/HelloServiceScalaTest.scala | 12 ++++ .../validate.groovy | 19 ++++++ .../invoker.properties | 1 + .../pom.xml | 54 +++++++++++++++ .../src/main/scala/HelloServiceScala.scala | 6 ++ .../test/scala/HelloServiceScalaTest.scala | 12 ++++ .../validate.groovy | 19 ++++++ .../plugin/SCoveragePreCompileMojo.java | 65 ++++++++++++------- 13 files changed, 220 insertions(+), 26 deletions(-) create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/invoker.properties create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/pom.xml create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/main/scala/HelloServiceScala.scala create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/test/scala/HelloServiceScalaTest.scala create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/validate.groovy create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/invoker.properties create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/pom.xml create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/main/scala/HelloServiceScala.scala create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/test/scala/HelloServiceScalaTest.scala create mode 100644 src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/validate.groovy diff --git a/README.md b/README.md index e98c7114..7ace3280 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ This can be set as project property. ##### Scala version configuration -Plugin supports Scala 2.12.x and 2.13.x versions by automatically loading and configuring matching `scalac-scoverage-plugin` Scalac SCoverage Plugin artifact. For this to work Scala version has to be set. It can be done by defining `scalaVersion` plugin configuration parameter or `scala.version` project property. Without this setting, coverage will not be calculated. +Plugin supports Scala 2.12.8+, 2.13.0+ and 3.2.0+ versions by automatically loading and configuring matching `scalac-scoverage-plugin` Scalac SCoverage Plugin artifact. For this to work Scala version has to be set. It can be done by defining `scalaVersion` plugin configuration parameter or `scala.version` project property. Without this setting, coverage will not be calculated. ```xml diff --git a/src/it/integration_tests_parent/pom.xml b/src/it/integration_tests_parent/pom.xml index 3abf6f8d..4fa71478 100644 --- a/src/it/integration_tests_parent/pom.xml +++ b/src/it/integration_tests_parent/pom.xml @@ -26,12 +26,13 @@ 2.13 12 ${scala.compat.version}.${scala.minor.version} + scala-library org.scala-lang - scala-library + ${scala.library.artifact.id} ${scala.version} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/invoker.properties b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/invoker.properties new file mode 100644 index 00000000..3e51a5a6 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/invoker.properties @@ -0,0 +1 @@ +invoker.goals=clean verify site -e -ntp \ No newline at end of file diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/pom.xml b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/pom.xml new file mode 100644 index 00000000..c1a64586 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + + it.scoverage-maven-plugin + integration_tests_parent + 1.0-SNAPSHOT + ../integration_tests_parent/pom.xml + + + test_ScalaMavenPlugin_Scala32Plus_ScalaTest + 1.0-SNAPSHOT + jar + Test Scoverage Report using scala-maven-plugin, Scala 3.2+ and ScalaTest + Test Scoverage Report using scala-maven-plugin, Scala 3.2+ and ScalaTest + + + 3 + 3.3.1 + scala3-library_3 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + net.alchim31.maven + scala-maven-plugin + + + org.scalatest + scalatest-maven-plugin + + + @project.groupId@ + @project.artifactId@ + + + + diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/main/scala/HelloServiceScala.scala b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/main/scala/HelloServiceScala.scala new file mode 100644 index 00000000..f946ee95 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/main/scala/HelloServiceScala.scala @@ -0,0 +1,6 @@ +package service + +object HelloServiceScala { + def hello = { "Hello" } + +} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/test/scala/HelloServiceScalaTest.scala b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/test/scala/HelloServiceScalaTest.scala new file mode 100644 index 00000000..cf1636c0 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/src/test/scala/HelloServiceScalaTest.scala @@ -0,0 +1,12 @@ +package service + +import org.scalatest.wordspec.AnyWordSpec + +class HelloServiceScalaTest extends AnyWordSpec { + + "HelloService" should { + "say hello" in { + assert(HelloServiceScala.hello == "Hello") + } + } +} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/validate.groovy b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/validate.groovy new file mode 100644 index 00000000..ce78045d --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest/validate.groovy @@ -0,0 +1,19 @@ +try { + + def logFile = new File(basedir, "build.log") + def lines = logFile.readLines() + assert lines.contains("[INFO] Statement coverage.: 100.00%") + assert lines.contains("[INFO] Branch coverage....: 100.00%") + + def scoverageFile = new File(basedir, "target/scoverage.xml") + assert scoverageFile.exists() + + def reportFile = new File(basedir, "target/site/scoverage/index.html") + assert reportFile.exists() + + return true + +} catch (Throwable e) { + e.printStackTrace() + return false +} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/invoker.properties b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/invoker.properties new file mode 100644 index 00000000..3e51a5a6 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/invoker.properties @@ -0,0 +1 @@ +invoker.goals=clean verify site -e -ntp \ No newline at end of file diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/pom.xml b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/pom.xml new file mode 100644 index 00000000..57e2b500 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + + + it.scoverage-maven-plugin + integration_tests_parent + 1.0-SNAPSHOT + ../integration_tests_parent/pom.xml + + + test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set + 1.0-SNAPSHOT + jar + Test Scoverage Report using scala-maven-plugin, Scala 3.2+ and ScalaTest when scala.version is not set + Test Scoverage Report using scala-maven-plugin, Scala 3.2+ and ScalaTest when scala.version is not set + + + 3 + + scala3-library_3 + + + + + org.scala-lang + ${scala.library.artifact.id} + 3.3.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + net.alchim31.maven + scala-maven-plugin + + + org.scalatest + scalatest-maven-plugin + + + @project.groupId@ + @project.artifactId@ + + + + diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/main/scala/HelloServiceScala.scala b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/main/scala/HelloServiceScala.scala new file mode 100644 index 00000000..f946ee95 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/main/scala/HelloServiceScala.scala @@ -0,0 +1,6 @@ +package service + +object HelloServiceScala { + def hello = { "Hello" } + +} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/test/scala/HelloServiceScalaTest.scala b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/test/scala/HelloServiceScalaTest.scala new file mode 100644 index 00000000..cf1636c0 --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/src/test/scala/HelloServiceScalaTest.scala @@ -0,0 +1,12 @@ +package service + +import org.scalatest.wordspec.AnyWordSpec + +class HelloServiceScalaTest extends AnyWordSpec { + + "HelloService" should { + "say hello" in { + assert(HelloServiceScala.hello == "Hello") + } + } +} diff --git a/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/validate.groovy b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/validate.groovy new file mode 100644 index 00000000..ce78045d --- /dev/null +++ b/src/it/test_ScalaMavenPlugin_Scala32Plus_ScalaTest_ScalaVersion_Not_Set/validate.groovy @@ -0,0 +1,19 @@ +try { + + def logFile = new File(basedir, "build.log") + def lines = logFile.readLines() + assert lines.contains("[INFO] Statement coverage.: 100.00%") + assert lines.contains("[INFO] Branch coverage....: 100.00%") + + def scoverageFile = new File(basedir, "target/scoverage.xml") + assert scoverageFile.exists() + + def reportFile = new File(basedir, "target/site/scoverage/index.html") + assert reportFile.exists() + + return true + +} catch (Throwable e) { + e.printStackTrace() + return false +} diff --git a/src/main/java/org/scoverage/plugin/SCoveragePreCompileMojo.java b/src/main/java/org/scoverage/plugin/SCoveragePreCompileMojo.java index d78e86eb..acf25e5f 100644 --- a/src/main/java/org/scoverage/plugin/SCoveragePreCompileMojo.java +++ b/src/main/java/org/scoverage/plugin/SCoveragePreCompileMojo.java @@ -24,9 +24,7 @@ import java.io.OutputStreamWriter; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; -import edu.emory.mathcs.backport.java.util.Arrays; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; @@ -228,6 +226,7 @@ public void execute() throws MojoExecutionException String scalaBinaryVersion = null; String resolvedScalaVersion = resolveScalaVersion(); + boolean scala2 = true; if ( resolvedScalaVersion != null ) { if ( "2.12".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWith( "2.12." ) ) @@ -238,9 +237,14 @@ else if ( "2.13".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWi { scalaBinaryVersion = "2.13"; } + else if ( resolvedScalaVersion.compareTo("3.2.") > 0 ) // Scala 3 is supported from 3.2.0 + { + scalaBinaryVersion = "3"; + scala2 = false; + } else { - getLog().warn( String.format( "Skipping SCoverage execution - unsupported Scala version \"%s\"", + getLog().warn( String.format( "Skipping SCoverage execution - unsupported Scala version \"%s\". Supported Scala versions are 2.12.8+, 2.13.0+ and 3.2.0+ .", resolvedScalaVersion ) ); return; } @@ -278,16 +282,18 @@ else if ( "2.13".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWi try { - List pluginArtifacts = getScalaScoveragePluginArtifacts( resolvedScalaVersion, scalaBinaryVersion ); - Artifact runtimeArtifact = getScalaScoverageRuntimeArtifact( scalaBinaryVersion ); - - addScoverageDependenciesToClasspath( runtimeArtifact ); + List pluginArtifacts = getScalaScoveragePluginArtifacts( resolvedScalaVersion, scalaBinaryVersion, scala2 ); + if ( scala2 ) // Scala 3 doesn't need scalac-scoverage-runtime + { + Artifact runtimeArtifact = getScalaScoverageRuntimeArtifact( scalaBinaryVersion ); + addScoverageDependenciesToClasspath( runtimeArtifact ); + } - String arg = DATA_DIR_OPTION + dataDirectory.getAbsolutePath(); + String arg = ( scala2 ? SCALA2_DATA_DIR_OPTION : SCALA3_COVERAGE_OUT_OPTION ) + dataDirectory.getAbsolutePath(); String _scalacOptions = quoteArgument( arg ); String addScalacArgs = arg; - arg = SOURCE_ROOT_OPTION + project.getBasedir().getAbsolutePath(); + arg = scala2 ? ( SOURCE_ROOT_OPTION + project.getBasedir().getAbsolutePath() ) : ""; _scalacOptions = _scalacOptions + SPACE + quoteArgument( arg ); addScalacArgs = addScalacArgs + PIPE + arg; @@ -305,17 +311,19 @@ else if ( "2.13".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWi addScalacArgs = addScalacArgs + PIPE + arg; } - if ( highlighting ) + if ( highlighting && scala2 ) { _scalacOptions = _scalacOptions + SPACE + "-Yrangepos"; addScalacArgs = addScalacArgs + PIPE + "-Yrangepos"; } - String _scalacPlugins = pluginArtifacts.stream() - .map(x -> String.format("%s:%s:%s", x.getGroupId(), x.getArtifactId(),x.getVersion())).collect(Collectors.joining(" ")); + String _scalacPlugins = scala2 ? pluginArtifacts.stream() + .map(x -> String.format("%s:%s:%s", x.getGroupId(), x.getArtifactId(), x.getVersion())).collect(Collectors.joining(" ")) : ""; - arg = PLUGIN_OPTION + pluginArtifacts.stream().map(x -> x.getFile().getAbsolutePath()).collect(Collectors.joining(String.valueOf(java.io.File.pathSeparatorChar))); - addScalacArgs = addScalacArgs + PIPE + arg; + if ( scala2 ) { + arg = PLUGIN_OPTION + pluginArtifacts.stream().map(x -> x.getFile().getAbsolutePath()).collect(Collectors.joining(String.valueOf(java.io.File.pathSeparatorChar))); + addScalacArgs = addScalacArgs + PIPE + arg; + } Properties projectProperties = project.getProperties(); @@ -358,9 +366,11 @@ else if ( "2.13".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWi // Private utility methods private static final String SCALA_LIBRARY_GROUP_ID = "org.scala-lang"; - private static final String SCALA_LIBRARY_ARTIFACT_ID = "scala-library"; + private static final String SCALA2_LIBRARY_ARTIFACT_ID = "scala-library"; + private static final String SCALA3_LIBRARY_ARTIFACT_ID = "scala3-library_3"; - private static final String DATA_DIR_OPTION = "-P:scoverage:dataDir:"; + private static final String SCALA2_DATA_DIR_OPTION = "-P:scoverage:dataDir:"; + private static final String SCALA3_COVERAGE_OUT_OPTION = "-coverage-out:"; private static final String SOURCE_ROOT_OPTION = "-P:scoverage:sourceRoot:"; private static final String EXCLUDED_PACKAGES_OPTION = "-P:scoverage:excludedPackages:"; private static final String EXCLUDED_FILES_OPTION = "-P:scoverage:excludedFiles:"; @@ -378,7 +388,7 @@ private String quoteArgument( String arg ) private String resolveScalaVersion() { String result = scalaVersion; - if ( result == null ) + if ( result == null || result.isEmpty() ) { // check project direct dependencies (transitive dependencies cannot be checked in this Maven lifecycle phase) @SuppressWarnings( "unchecked" ) @@ -386,7 +396,11 @@ private String resolveScalaVersion() for ( Dependency dependency: dependencies ) { if ( SCALA_LIBRARY_GROUP_ID.equals( dependency.getGroupId() ) - && SCALA_LIBRARY_ARTIFACT_ID.equals( dependency.getArtifactId() ) ) + && ( + SCALA2_LIBRARY_ARTIFACT_ID.equals( dependency.getArtifactId() ) || + SCALA3_LIBRARY_ARTIFACT_ID.equals( dependency.getArtifactId() ) + ) + ) { result = dependency.getVersion(); break; @@ -428,22 +442,25 @@ private ArtifactVersion getScalacPluginVersion() { } } - private List getScalaScoveragePluginArtifacts(String resolvedScalaVersion, String scalaMainVersion ) + private List getScalaScoveragePluginArtifacts( String resolvedScalaVersion, String scalaBinaryVersion, boolean scala2 ) throws ArtifactNotFoundException, ArtifactResolutionException { String resolvedScalacPluginVersion = getScalacPluginVersion().toString(); List resolvedArtifacts = new ArrayList<>(); - resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-plugin_" + resolvedScalaVersion, resolvedScalacPluginVersion)); - resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-domain_" + scalaMainVersion, resolvedScalacPluginVersion)); - resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-serializer_" + scalaMainVersion, resolvedScalacPluginVersion)); + if ( scala2 ) // Scala 3 doesn't need scalac-scoverage-plugin + { + resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-plugin_" + resolvedScalaVersion, resolvedScalacPluginVersion)); + } + resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-domain_" + scalaBinaryVersion, resolvedScalacPluginVersion)); + resolvedArtifacts.add(getResolvedArtifact("org.scoverage", "scalac-scoverage-serializer_" + scalaBinaryVersion, resolvedScalacPluginVersion)); return resolvedArtifacts; } - private Artifact getScalaScoverageRuntimeArtifact( String scalaMainVersion ) + private Artifact getScalaScoverageRuntimeArtifact( String scalaBinaryVersion ) throws ArtifactNotFoundException, ArtifactResolutionException { return getResolvedArtifact( - "org.scoverage", "scalac-scoverage-runtime_" + scalaMainVersion, + "org.scoverage", "scalac-scoverage-runtime_" + scalaBinaryVersion, getScalacPluginVersion().toString() ); }