summaryrefslogtreecommitdiff
path: root/contrib/scoverage/src/ScoverageModule.scala
blob: 734c317ffce38f2d3eb266df5eeb9654f2121f3a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package mill
package contrib
package scoverage

import coursier.MavenRepository
import mill.api.Result
import mill.eval.PathRef
import mill.util.Ctx
import mill.scalalib.{DepSyntax, JavaModule, Lib, ScalaModule, TestModule, Dep}
import mill.moduledefs.Cacher


/** Adds targets to a [[mill.scalalib.ScalaModule]] to create test coverage reports.
 *
 * This module allows you to generate code coverage reports for Scala projects with
 * [[https://github.com/scoverage Scoverage]] via the
 * [[https://github.com/scoverage/scalac-scoverage-plugin scoverage compiler plugin]].
 *
 * To declare a module for which you want to generate coverage reports you can
 * Extends the `mill.contrib.scoverage.ScoverageModule` trait when defining your
 * Module. Additionally, you must define a submodule that extends the
 * `ScoverageTests` trait that belongs to your instance of `ScoverageModule`.
 *
 * {{{
 * // You have to replace VERSION
 * import $ivy.`com.lihaoyi::mill-contrib-buildinfo:VERSION`
 * import mill.contrib.scoverage.ScoverageModule
 *
 * Object foo extends ScoverageModule  {
 *   def scalaVersion = "2.11.8"
 *   def scoverageVersion = "1.3.1"
 *
 *   object test extends ScoverageTests {
 *     def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.5")
 *     def testFrameworks = Seq("org.scalatest.tools.Framework")
 *   }
 * }
 * }}}
 *
 * In addition to the normal tasks available to your Scala module, Scoverage
 * Modules introduce a few new tasks and changes the behavior of an existing one.
 *
 * - mill foo.scoverage.compile      # compiles your module with test instrumentation
 *                                 # (you don't have to run this manually, running the test task will force its invocation)
 *
 * - mill foo.test                   # tests your project and collects metrics on code coverage
 * - mill foo.scoverage.htmlReport   # uses the metrics collected by a previous test run to generate a coverage report in html format
 * - mill foo.scoverage.xmlReport    # uses the metrics collected by a previous test run to generate a coverage report in xml format
 *
 * The measurement data is available at `out/foo/scoverage/data/`,
 * the html report is saved in `out/foo/scoverage/htmlReport/`,
 * and the xml report is saved in `out/foo/scoverage/xmlReport/`.
 */
trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
  def scoverageVersion: T[String]
  private def scoverageRuntimeDep = T {
    ivy"org.scoverage::scalac-scoverage-runtime:${outer.scoverageVersion()}"
  }
  private def scoveragePluginDep = T {
    ivy"org.scoverage::scalac-scoverage-plugin:${outer.scoverageVersion()}"
  }

  private def toolsClasspath = T {
    scoverageReportWorkerClasspath() ++ scoverageClasspath()
  }

  def scoverageClasspath = T {
    Lib.resolveDependencies(
      Seq(coursier.LocalRepositories.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
      Lib.depToDependency(_, outer.scalaVersion()),
      Seq(scoveragePluginDep()),
      ctx = Some(implicitly[mill.util.Ctx.Log])
    )
  }

  def scoverageReportWorkerClasspath = T {
    val workerKey = "MILL_SCOVERAGE_REPORT_WORKER_" + scoverageVersion().replace(".", "_")
    mill.modules.Util.millProjectModule(
      workerKey,
      s"mill-contrib-scoverage-worker-${outer.scoverageVersion()}",
      repositories,
      resolveFilter = _.toString.contains("mill-contrib-scoverage-worker")
    )
  }

  object scoverage extends ScalaModule {
    def selfDir = T { T.ctx().dest / os.up / os.up }
    def dataDir = T { selfDir() / "data" }

    def generatedSources = outer.generatedSources()
    def allSources = outer.allSources()
    def moduleDeps = outer.moduleDeps
    def sources = outer.sources
    def resources = outer.resources
    def scalaVersion = outer.scalaVersion()
    def repositories = outer.repositories
    def compileIvyDeps = outer.compileIvyDeps()
    def ivyDeps = outer.ivyDeps() ++ Agg(outer.scoverageRuntimeDep())
    def scalacPluginIvyDeps = outer.scalacPluginIvyDeps() ++ Agg(outer.scoveragePluginDep())
    def scalacOptions = outer.scalacOptions() ++
      Seq(s"-P:scoverage:dataDir:${dataDir()}")

    def htmlReport() = T.command {
      ScoverageReportWorkerApi
        .scoverageReportWorker()
        .bridge(toolsClasspath().map(_.path))
        .htmlReport(allSources(), dataDir().toString, selfDir().toString)
    }
    def xmlReport() = T.command {
      ScoverageReportWorkerApi
        .scoverageReportWorker()
        .bridge(toolsClasspath().map(_.path))
        .xmlReport(allSources(), dataDir().toString, selfDir().toString)
    }
  }

  trait ScoverageTests extends outer.Tests {
    override def upstreamAssemblyClasspath = T {
      super.upstreamAssemblyClasspath() ++
      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
    }
    override def compileClasspath = T {
      super.compileClasspath() ++
      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
    }
    override def runClasspath = T {
      super.runClasspath() ++
      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
    }

    // Need the sources compiled with scoverage instrumentation to run.
    override def moduleDeps: Seq[JavaModule] = Seq(outer.scoverage)
  }
}