Skip to content

Support for Scoverage 2.x #2010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ object Deps {
val scalametaTrees = ivy"org.scalameta::trees:4.5.13"
def scalaReflect(scalaVersion: String) = ivy"org.scala-lang:scala-reflect:${scalaVersion}"
def scalacScoveragePlugin = ivy"org.scoverage:::scalac-scoverage-plugin:1.4.11"
def scalacScoverage2Plugin = ivy"org.scoverage:::scalac-scoverage-plugin:2.0.2"
def scalacScoverage2Reporter = ivy"org.scoverage::scalac-scoverage-reporter:2.0.2"
def scalacScoverage2Domain = ivy"org.scoverage::scalac-scoverage-domain:2.0.2"
def scalacScoverage2Serializer = ivy"org.scoverage::scalac-scoverage-serializer:2.0.2"
val semanticDB = ivy"org.scalameta:::semanticdb-scalac:4.5.11"
val sourcecode = ivy"com.lihaoyi::sourcecode:0.3.0"
val upickle = ivy"com.lihaoyi::upickle:2.0.0"
Expand Down Expand Up @@ -745,7 +749,9 @@ object contrib extends MillModule {
override def testArgs = T {
val mapping = Map(
"MILL_SCOVERAGE_REPORT_WORKER" -> worker.compile().classes.path,
"MILL_SCOVERAGE_VERSION" -> Deps.scalacScoveragePlugin.dep.version
"MILL_SCOVERAGE2_REPORT_WORKER" -> worker2.compile().classes.path,
"MILL_SCOVERAGE_VERSION" -> Deps.scalacScoveragePlugin.dep.version,
"MILL_SCOVERAGE2_VERSION" -> Deps.scalacScoverage2Plugin.dep.version
)
scalalib.worker.testArgs() ++
scalalib.backgroundwrapper.testArgs() ++
Expand All @@ -758,18 +764,36 @@ object contrib extends MillModule {
contrib.buildinfo
)

object worker extends MillApiModule {
object worker extends MillInternalModule {
override def compileModuleDeps = Seq(main.api)
override def moduleDeps = Seq(scoverage.api)
override def compileIvyDeps = T {
Agg(
// compile-time only, need to provide the correct scoverage version runtime
// compile-time only, need to provide the correct scoverage version at runtime
Deps.scalacScoveragePlugin,
// provided by mill runtime
Deps.osLib
)
}
}

object worker2 extends MillInternalModule {
override def compileModuleDeps = Seq(main.api)

override def moduleDeps = Seq(scoverage.api)

override def compileIvyDeps = T {
Agg(
// compile-time only, need to provide the correct scoverage version at runtime
Deps.scalacScoverage2Plugin,
Deps.scalacScoverage2Reporter,
Deps.scalacScoverage2Domain,
Deps.scalacScoverage2Serializer,
// provided by mill runtime
Deps.osLib
)
}
}
}

object buildinfo extends MillModule {
Expand Down
26 changes: 24 additions & 2 deletions contrib/scoverage/api/src/ScoverageReportWorkerApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,31 @@ import mill.api.Ctx
trait ScoverageReportWorkerApi {
import ScoverageReportWorkerApi._

def report(reportType: ReportType, sources: Seq[os.Path], dataDirs: Seq[os.Path])(implicit
@deprecated("Use other overload instead.", "Mill after 0.10.7")
def report(
reportType: ReportType,
sources: Seq[os.Path],
dataDirs: Seq[os.Path]
)(implicit
ctx: Ctx
): Unit
): Unit = {
report(reportType, sources, dataDirs, ctx.workspace)
}

def report(
reportType: ReportType,
sources: Seq[os.Path],
dataDirs: Seq[os.Path],
sourceRoot: os.Path
)(implicit
ctx: Ctx
): Unit = {
// FIXME: We only call the deprecated version here, to preserve binary compatibility. Remove when appropriate.
ctx.log.error(
"Binary compatibility stub may cause infinite loops with StackOverflowError. You need to implement: def report(ReportType, Seq[Path], Seq[Path], os.Path): Unit"
)
report(reportType, sources, dataDirs)
}
}

object ScoverageReportWorkerApi {
Expand Down
85 changes: 69 additions & 16 deletions contrib/scoverage/src/ScoverageModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import mill._
import mill.api.{Loose, PathRef}
import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType
import mill.define.{Command, Persistent, Sources, Target, Task}
import mill.scalalib.api.ZincWorkerUtil
import mill.scalalib.{Dep, DepSyntax, JavaModule, ScalaModule}

/**
Expand Down Expand Up @@ -56,11 +57,38 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
*/
def scoverageVersion: T[String]

private def isScoverage2: Task[Boolean] = T.task { scoverageVersion().startsWith("2.") }

/** Binary compatibility shim. */
@deprecated("Use scoverageRuntimeDeps instead.", "Mill after 0.10.7")
def scoverageRuntimeDep: T[Dep] = T {
ivy"org.scoverage::scalac-scoverage-runtime:${outer.scoverageVersion()}"
T.log.error("scoverageRuntimeDep is no longer used. To customize your module, use scoverageRuntimeDeps.")
scoverageRuntimeDeps().toIndexedSeq.head
}

def scoverageRuntimeDeps: T[Agg[Dep]] = T {
Agg(ivy"org.scoverage::scalac-scoverage-runtime:${outer.scoverageVersion()}")
}

/** Binary compatibility shim. */
@deprecated("Use scoveragePluginDeps instead.", "Mill after 0.10.7")
def scoveragePluginDep: T[Dep] = T {
ivy"org.scoverage:::scalac-scoverage-plugin:${outer.scoverageVersion()}"
T.log.error("scoveragePluginDep is no longer used. To customize your module, use scoverageRuntimeDeps.")
scoveragePluginDeps().toIndexedSeq.head
}

def scoveragePluginDeps: T[Agg[Dep]] = T {
val sv = scoverageVersion()
if (isScoverage2()) {
Agg(
ivy"org.scoverage:::scalac-scoverage-plugin:${sv}",
ivy"org.scoverage::scalac-scoverage-domain:${sv}",
ivy"org.scoverage::scalac-scoverage-serializer:${sv}",
ivy"org.scoverage::scalac-scoverage-reporter:${sv}"
)
} else {
Agg(ivy"org.scoverage:::scalac-scoverage-plugin:${sv}")
}
}

@deprecated("Use scoverageToolsClasspath instead.", "mill after 0.10.0-M1")
Expand All @@ -71,23 +99,44 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
def scoverageToolsClasspath: T[Agg[PathRef]] = T {
scoverageReportWorkerClasspath() ++
resolveDeps(T.task {
Agg(
ivy"org.scoverage:scalac-scoverage-plugin_${mill.BuildInfo.scalaVersion}:${outer.scoverageVersion()}"
)
// we need to resolve with same Scala version used for Mill, not the project Scala version
val scalaBinVersion = ZincWorkerUtil.scalaBinaryVersion(BuildInfo.scalaVersion)
val sv = scoverageVersion()
if (isScoverage2()) {
Agg(
ivy"org.scoverage:scalac-scoverage-plugin_${mill.BuildInfo.scalaVersion}:${sv}",
ivy"org.scoverage:scalac-scoverage-domain_${scalaBinVersion}:${sv}",
ivy"org.scoverage:scalac-scoverage-serializer_${scalaBinVersion}:${sv}",
ivy"org.scoverage:scalac-scoverage-reporter_${scalaBinVersion}:${sv}"
)
} else {
Agg(
ivy"org.scoverage:scalac-scoverage-plugin_${mill.BuildInfo.scalaVersion}:${sv}"
)
}
})()
}

def scoverageClasspath: T[Agg[PathRef]] = T {
resolveDeps(T.task { Agg(scoveragePluginDep()) })()
resolveDeps(scoveragePluginDeps)()
}

def scoverageReportWorkerClasspath: T[Agg[PathRef]] = T {
val workerKey = "MILL_SCOVERAGE_REPORT_WORKER"
val isScov2 = isScoverage2()

val workerKey =
if (isScov2) "MILL_SCOVERAGE2_REPORT_WORKER"
else "MILL_SCOVERAGE_REPORT_WORKER"

val workerArtifact =
if (isScov2) "mill-contrib-scoverage-worker2"
else "mill-contrib-scoverage-worker"

mill.modules.Util.millProjectModule(
workerKey,
s"mill-contrib-scoverage-worker",
workerArtifact,
repositoriesTask(),
resolveFilter = _.toString.contains("mill-contrib-scoverage-worker")
resolveFilter = _.toString.contains(workerArtifact)
)
}

Expand All @@ -98,7 +147,7 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
ScoverageReportWorker
.scoverageReportWorker()
.bridge(scoverageToolsClasspath().map(_.path))
.report(reportType, allSources().map(_.path), Seq(data().path))
.report(reportType, allSources().map(_.path), Seq(data().path), T.workspace)
}

/**
Expand All @@ -121,16 +170,20 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
override def repositoriesTask: Task[Seq[Repository]] = T.task { outer.repositoriesTask() }
override def compileIvyDeps: Target[Loose.Agg[Dep]] = T { outer.compileIvyDeps() }
override def ivyDeps: Target[Loose.Agg[Dep]] =
T { outer.ivyDeps() ++ Agg(outer.scoverageRuntimeDep()) }
T { outer.ivyDeps() ++ outer.scoverageRuntimeDeps() }
override def unmanagedClasspath: Target[Loose.Agg[PathRef]] = T { outer.unmanagedClasspath() }

/** Add the scoverage scalac plugin. */
override def scalacPluginIvyDeps: Target[Loose.Agg[Dep]] =
T { outer.scalacPluginIvyDeps() ++ Agg(outer.scoveragePluginDep()) }
T { outer.scalacPluginIvyDeps() ++ outer.scoveragePluginDeps() }

/** Add the scoverage specific plugin settings (`dataDir`). */
override def scalacOptions: Target[Seq[String]] =
T { outer.scalacOptions() ++ Seq(s"-P:scoverage:dataDir:${data().path.toIO.getPath()}") }
T {
outer.scalacOptions() ++
Seq(s"-P:scoverage:dataDir:${data().path.toIO.getPath()}") ++
(if (isScoverage2()) Seq(s"-P:scoverage:sourceRoot:${T.workspace}") else Seq())
}

def htmlReport(): Command[Unit] = T.command { doReport(ReportType.Html) }
def xmlReport(): Command[Unit] = T.command { doReport(ReportType.Xml) }
Expand All @@ -142,15 +195,15 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
trait ScoverageTests extends outer.Tests {
override def upstreamAssemblyClasspath = T {
super.upstreamAssemblyClasspath() ++
resolveDeps(T.task { Agg(outer.scoverageRuntimeDep()) })()
resolveDeps(outer.scoverageRuntimeDeps)()
}
override def compileClasspath = T {
super.compileClasspath() ++
resolveDeps(T.task { Agg(outer.scoverageRuntimeDep()) })()
resolveDeps(outer.scoverageRuntimeDeps)()
}
override def runClasspath = T {
super.runClasspath() ++
resolveDeps(T.task { Agg(outer.scoverageRuntimeDep()) })()
resolveDeps(outer.scoverageRuntimeDeps)()
}

// Need the sources compiled with scoverage instrumentation to run.
Expand Down
2 changes: 1 addition & 1 deletion contrib/scoverage/src/ScoverageReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ trait ScoverageReport extends Module {
scoverageReportWorkerModule
.scoverageReportWorker()
.bridge(workerModule.scoverageToolsClasspath().map(_.path))
.report(reportType, sourcePaths, dataPaths)
.report(reportType, sourcePaths, dataPaths, T.workspace)
PathRef(T.dest)
}
}
Expand Down
43 changes: 35 additions & 8 deletions contrib/scoverage/test/src/HelloWorldTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,34 @@ trait HelloWorldTests extends utest.TestSuite {
evalCount > 0
)
}
"scalacPluginIvyDeps" - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) =
eval.apply(HelloWorld.core.scoverage.scalacPluginIvyDeps)

assert(
result == Agg(ivy"org.scoverage:::scalac-scoverage-plugin:${testScoverageVersion}"),
evalCount > 0
)
"scalacPluginIvyDeps" - {
"scoverage1x" - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) =
eval.apply(HelloWorld.core.scoverage.scalacPluginIvyDeps)
if (testScoverageVersion.startsWith("1.")) {
assert(
result == Agg(
ivy"org.scoverage:::scalac-scoverage-plugin:${testScoverageVersion}"
),
evalCount > 0
)
} else "skipped"
}
"scoverage2x" - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) =
eval.apply(HelloWorld.core.scoverage.scalacPluginIvyDeps)
if (testScoverageVersion.startsWith("2.")) {
assert(
result == Agg(
ivy"org.scoverage:::scalac-scoverage-plugin:${testScoverageVersion}",
ivy"org.scoverage::scalac-scoverage-domain:${testScoverageVersion}",
ivy"org.scoverage::scalac-scoverage-serializer:${testScoverageVersion}",
ivy"org.scoverage::scalac-scoverage-reporter:${testScoverageVersion}"
),
evalCount > 0
)
} else "skipped"
}
}
"data" - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverage.data)
Expand Down Expand Up @@ -201,3 +221,10 @@ object HelloWorldTests_2_13 extends HelloWorldTests {
override def testScoverageVersion = sys.props.getOrElse("MILL_SCOVERAGE_VERSION", ???)
override def testScalatestVersion = "3.0.8"
}

object Scoverage2Tests_2_13 extends HelloWorldTests {
override def threadCount = Some(1)
override def testScalaVersion: String = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???)
override def testScoverageVersion = sys.props.getOrElse("MILL_SCOVERAGE2_VERSION", ???)
override def testScalatestVersion = "3.0.8"
}
9 changes: 7 additions & 2 deletions contrib/scoverage/worker/src/ScoverageReportWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import _root_.scoverage.report.{CoverageAggregator, ScoverageHtmlWriter, Scovera
import mill.api.Ctx
import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType

/**
* Scoverage Worker for Scoverage 1.x
*/
class ScoverageReportWorkerImpl extends ScoverageReportWorkerApi {

override def report(
reportType: ReportType,
sources: Seq[os.Path],
dataDirs: Seq[os.Path]
dataDirs: Seq[os.Path],
// ignored in Scoverage 1.x
sourceRoot: os.Path
)(implicit ctx: Ctx): Unit =
try {
ctx.log.info(s"Processing coverage data for ${dataDirs.size} data locations")
Expand All @@ -34,7 +39,7 @@ class ScoverageReportWorkerImpl extends ScoverageReportWorkerApi {
ctx.log.error(s"No coverage data found in [${dataDirs.mkString(", ")}]")
}
} catch {
case e =>
case e: Throwable =>
ctx.log.error(s"Exception while building coverage report. ${e.getMessage()}")
e.printStackTrace()
throw e
Expand Down
46 changes: 46 additions & 0 deletions contrib/scoverage/worker2/src/ScoverageReportWorkerImpl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package mill.contrib.scoverage.worker

import mill.contrib.scoverage.api.ScoverageReportWorkerApi
import _root_.scoverage.reporter.{CoverageAggregator, ScoverageHtmlWriter, ScoverageXmlWriter}
import mill.api.Ctx
import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType

/**
* Scoverage Worker for Scoverage 1.x
*/
class ScoverageReportWorkerImpl extends ScoverageReportWorkerApi {

override def report(
reportType: ReportType,
sources: Seq[os.Path],
dataDirs: Seq[os.Path],
sourceRoot: os.Path
)(implicit ctx: Ctx): Unit =
try {
ctx.log.info(s"Processing coverage data for ${dataDirs.size} data locations")
CoverageAggregator.aggregate(dataDirs.map(_.toIO), sourceRoot.toIO) match {
case Some(coverage) =>
val sourceFolders = sources.map(_.toIO)
val folder = ctx.dest
os.makeDir.all(folder)
reportType match {
case ReportType.Html =>
new ScoverageHtmlWriter(sourceFolders, folder.toIO, None)
.write(coverage)
case ReportType.Xml =>
new ScoverageXmlWriter(sourceFolders, folder.toIO, false, None)
.write(coverage)
case ReportType.Console =>
ctx.log.info(s"Statement coverage.: ${coverage.statementCoverageFormatted}%")
ctx.log.info(s"Branch coverage....: ${coverage.branchCoverageFormatted}%")
}
case None =>
ctx.log.error(s"No coverage data found in [${dataDirs.mkString(", ")}]")
}
} catch {
case e: Throwable =>
ctx.log.error(s"Exception while building coverage report. ${e.getMessage()}")
e.printStackTrace()
throw e
}
}