forked from com-lihaoyi/mill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathScoverageModule.scala
271 lines (237 loc) · 10 KB
/
ScoverageModule.scala
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package mill.contrib.scoverage
import coursier.Repository
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}
import mill.api.Result
/**
* 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.12.9"
* def scoverageVersion = "1.4.0"
*
* 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 by default is available at `out/foo/scoverage/dataDir.dest/`,
* the html report is saved in `out/foo/scoverage/htmlReport.dest/`,
* and the xml report is saved in `out/foo/scoverage/xmlReport.dest/`.
*/
trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
/**
* The Scoverage version to use.
*/
def scoverageVersion: T[String]
private def isScoverage2: Task[Boolean] = T.task { scoverageVersion().startsWith("2.") }
private def isScala3: Task[Boolean] = T.task { ZincWorkerUtil.isScala3(outer.scalaVersion()) }
private def isScala2: Task[Boolean] = T.task { !isScala3() }
/** Binary compatibility shim. */
@deprecated("Use scoverageRuntimeDeps instead.", "Mill after 0.10.7")
def scoverageRuntimeDep: T[Dep] = T {
T.log.error(
"scoverageRuntimeDep is no longer used. To customize your module, use scoverageRuntimeDeps."
)
val result: Result[Dep] = if (isScala3()) {
Result.Failure("When using Scala 3 there is no external runtime dependency")
} else {
scoverageRuntimeDeps().toIndexedSeq.head
}
result
}
def scoverageRuntimeDeps: T[Agg[Dep]] = T {
if (isScala3()) {
Agg.empty
} else {
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 {
T.log.error(
"scoveragePluginDep is no longer used. To customize your module, use scoverageRuntimeDeps."
)
val result: Result[Dep] = if (isScala3()) {
Result.Failure("When using Scala 3 there is no external plugin dependency")
} else {
scoveragePluginDeps().toIndexedSeq.head
}
result
}
def scoveragePluginDeps: T[Agg[Dep]] = T {
val sv = scoverageVersion()
if (isScala3()) {
Agg.empty
} else {
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")
def toolsClasspath: T[Agg[PathRef]] = T {
scoverageToolsClasspath()
}
private def checkVersions = T.task {
val sv = scalaVersion()
val isSov2 = scoverageVersion().startsWith("2.")
(sv.split('.'), isSov2) match {
case (Array("3", "0" | "1", _*), _) => Result.Failure(
"Scala 3.0 and 3.1 is not supported by Scoverage. You have to update to at least Scala 3.2 and Scoverage 2.0"
)
case (Array("3", _*), false) => Result.Failure(
"Scoverage 1.x does not support Scala 3. You have to update to at least Scala 3.2 and Scoverage 2.0"
)
case (Array("2", "11", _*), true) => Result.Failure(
"Scoverage 2.x is not compatible with Scala 2.11. Consider using Scoverage 1.x or switch to a newer Scala version."
)
case _ =>
}
}
def scoverageToolsClasspath: T[Agg[PathRef]] = T {
checkVersions()
scoverageReportWorkerClasspath() ++
resolveDeps(T.task {
// 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()
val baseDeps = Agg(
ivy"org.scoverage:scalac-scoverage-domain_${scalaBinVersion}:${sv}",
ivy"org.scoverage:scalac-scoverage-serializer_${scalaBinVersion}:${sv}",
ivy"org.scoverage:scalac-scoverage-reporter_${scalaBinVersion}:${sv}"
)
val pluginDep =
Agg(ivy"org.scoverage:scalac-scoverage-plugin_${mill.BuildInfo.scalaVersion}:${sv}")
if (isScala3() && isScoverage2()) {
baseDeps
} else if (isScoverage2()) {
baseDeps ++ pluginDep
} else {
pluginDep
}
})()
}
def scoverageClasspath: T[Agg[PathRef]] = T {
resolveDeps(scoveragePluginDeps)()
}
def scoverageReportWorkerClasspath: T[Agg[PathRef]] = T {
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,
workerArtifact,
repositoriesTask(),
resolveFilter = _.toString.contains(workerArtifact)
)
}
val scoverage: ScoverageData = new ScoverageData(implicitly)
class ScoverageData(ctx0: mill.define.Ctx) extends Module()(ctx0) with ScalaModule {
def doReport(reportType: ReportType): Task[Unit] = T.task {
ScoverageReportWorker
.scoverageReportWorker()
.bridge(scoverageToolsClasspath().map(_.path))
.report(reportType, allSources().map(_.path), Seq(data().path), T.workspace)
}
/**
* The persistent data dir used to store scoverage coverage data.
* Use to store coverage data at compile-time and by the various report targets.
*/
def data: Persistent[PathRef] = T.persistent {
// via the persistent target, we ensure, the dest dir doesn't get cleared
PathRef(T.dest)
}
override def generatedSources: Target[Seq[PathRef]] = T { outer.generatedSources() }
override def allSources: Target[Seq[PathRef]] = T { outer.allSources() }
override def moduleDeps: Seq[JavaModule] = outer.moduleDeps
override def compileModuleDeps: Seq[JavaModule] = outer.compileModuleDeps
override def sources: Sources = T.sources { outer.sources() }
override def resources: Sources = T.sources { outer.resources() }
override def scalaVersion = T { outer.scalaVersion() }
override def repositories: Seq[Repository] = outer.repositories
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() ++ 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() ++ outer.scoveragePluginDeps() }
/** Add the scoverage specific plugin settings (`dataDir`). */
override def scalacOptions: Target[Seq[String]] =
T {
val extras =
if (isScala3()) {
Seq(s"-coverage-out:${data().path.toIO.getPath()}")
} else {
val base = s"-P:scoverage:dataDir:${data().path.toIO.getPath()}"
if (isScoverage2()) Seq(base, s"-P:scoverage:sourceRoot:${T.workspace}")
else Seq(base)
}
outer.scalacOptions() ++ extras
}
def htmlReport(): Command[Unit] = T.command { doReport(ReportType.Html) }
def xmlReport(): Command[Unit] = T.command { doReport(ReportType.Xml) }
def consoleReport(): Command[Unit] = T.command { doReport(ReportType.Console) }
override def skipIdea = outer.skipIdea
}
trait ScoverageTests extends outer.Tests {
override def upstreamAssemblyClasspath = T {
super.upstreamAssemblyClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
}
override def compileClasspath = T {
super.compileClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
}
override def runClasspath = T {
super.runClasspath() ++
resolveDeps(outer.scoverageRuntimeDeps)()
}
// Need the sources compiled with scoverage instrumentation to run.
override def moduleDeps: Seq[JavaModule] = Seq(outer.scoverage)
}
}