11package org.scoverage
22
33import org.apache.commons.io.FileUtils
4+ import org.gradle.api.Action
45import org.gradle.api.Plugin
56import org.gradle.api.Project
67import org.gradle.api.Task
78import org.gradle.api.invocation.Gradle
89import org.gradle.api.plugins.PluginAware
910import org.gradle.api.plugins.scala.ScalaPlugin
11+ import org.gradle.api.provider.Provider
1012import org.gradle.api.tasks.SourceSet
13+ import org.gradle.api.tasks.Sync
1114import org.gradle.api.tasks.TaskProvider
1215import org.gradle.api.tasks.scala.ScalaCompile
1316import org.gradle.api.tasks.testing.Test
17+ import org.gradle.api.tasks.util.PatternFilterable
1418
1519import java.nio.file.Files
1620import java.util.concurrent.ConcurrentHashMap
@@ -20,6 +24,7 @@ import static groovy.io.FileType.FILES
2024class ScoveragePlugin implements Plugin<PluginAware > {
2125
2226 static final String CONFIGURATION_NAME = ' scoverage'
27+ static final String MERGE_MEASUREMENTS_NAME = ' mergeScoverageMeasurements'
2328 static final String REPORT_NAME = ' reportScoverage'
2429 static final String CHECK_NAME = ' checkScoverage'
2530 static final String COMPILE_NAME = ' compileScoverageScala'
@@ -85,6 +90,26 @@ class ScoveragePlugin implements Plugin<PluginAware> {
8590 }
8691
8792 private void createTasks (Project project , ScoverageExtension extension ) {
93+ /**
94+ dataDir is split into subdirectories:
95+ workDir
96+ {testTaskName}MeasurementsDir
97+ {testTaskName}ReportDir.
98+
99+ workDir
100+ Directory where metadata/measurements are "produced"
101+ Two tasks produce files in that directory: compile and test. Because of that, this directory is not
102+ cacheable.
103+ Only one file from this directory is cached: metadata from compile task (because it is a single file).
104+
105+ testMeasurementsDir
106+ Directory where measurements are synced from "workDir". It is registered as an additional output to test task,
107+ which makes the measurements files cacheable.
108+
109+ reportDir
110+ Merges workDir/scoverage.coverage and testMeasurementsDir/scoverage.measurements.* for reporting.
111+ */
112+ def dataWorkDir = extension. dataDir. map {new File (it, " work" ) }
88113
89114 ScoverageRunner scoverageRunner = new ScoverageRunner (project. configurations. scoverage)
90115
@@ -106,6 +131,9 @@ class ScoveragePlugin implements Plugin<PluginAware> {
106131 def compileTask = project. tasks[instrumentedSourceSet. getCompileTaskName(" scala" )]
107132 compileTask. mustRunAfter(originalCompileTask)
108133
134+ // merges measurements from individual reports into one directory for use by globalReportTask
135+ def globalMergeMeasurementsTask = project. tasks. register(MERGE_MEASUREMENTS_NAME , Sync . class)
136+
109137 def globalReportTask = project. tasks. register(REPORT_NAME , ScoverageAggregate )
110138 def globalCheckTask = project. tasks. register(CHECK_NAME )
111139
@@ -122,17 +150,31 @@ class ScoveragePlugin implements Plugin<PluginAware> {
122150 List<ScoverageReport > reportTasks = testTasks. collect { testTask ->
123151 testTask. mustRunAfter(compileTask)
124152
125- def reportTaskName = " report${ testTask.name.capitalize()} Scoverage"
126- def taskReportDir = project. file(" ${ project.buildDir} /reports/scoverage${ testTask.name.capitalize()} " )
153+ def cTaskName = testTask. name. capitalize()
154+
155+ def reportTaskName = " report${ cTaskName} Scoverage"
156+ def taskReportDir = project. file(" ${ project.buildDir} /reports/scoverage${ cTaskName} " )
157+
158+ def scoverageSyncMetaWithOutputs =
159+ project. tasks. register(" sync${ cTaskName} ScoverageData" , Sync . class)
160+
161+ scoverageSyncMetaWithOutputs. configure {
162+ dependsOn compileTask, testTask
163+ from(dataWorkDir) {
164+ include(" scoverage.coverage" )
165+ }
166+ from(dataMeasurementsDir(extension, cTaskName))
167+ into(dataReportDir(extension, cTaskName))
168+ }
127169
128170 project. tasks. create(reportTaskName, ScoverageReport ) {
129- dependsOn originalJarTask, compileTask, testTask
130- onlyIf { extension . dataDir . get(). list() }
171+ dependsOn originalJarTask, compileTask, testTask, scoverageSyncMetaWithOutputs
172+ onlyIf { scoverageSyncMetaWithOutputs . get() . getDestinationDir (). list() }
131173 group = ' verification'
132174 runner = scoverageRunner
133175 reportDir = taskReportDir
134176 sources = originalSourceSet. scala. getSourceDirectories()
135- dataDir = extension . dataDir
177+ dataDir = scoverageSyncMetaWithOutputs . map {it . getDestinationDir()}
136178 sourceEncoding. set(detectedSourceEncoding)
137179 coverageOutputCobertura = extension. coverageOutputCobertura
138180 coverageOutputXML = extension. coverageOutputXML
@@ -141,17 +183,29 @@ class ScoveragePlugin implements Plugin<PluginAware> {
141183 }
142184 }
143185
144- globalReportTask. configure {
186+ globalMergeMeasurementsTask. configure {sync ->
187+ dependsOn(reportTasks)
188+ dependsOn(compileTask)
189+
145190 def dataDirs = reportTasks. findResults { it. dataDir. get() }
146191
147- dependsOn reportTasks
148- onlyIf { dataDirs. any { it. list() } }
192+ from(dataWorkDir. map {new File (it, ' scoverage.coverage' ) })
193+ from(dataDirs) {
194+ exclude(" scoverage.coverage" )
195+ }
196+ into(project. file(" ${ project.buildDir} /mergedScoverage" ))
197+ }
198+
199+ globalReportTask. configure {
200+ dependsOn globalMergeMeasurementsTask
201+
202+ onlyIf { globalMergeMeasurementsTask. get(). getDestinationDir(). list()}
149203
150204 group = ' verification'
151205 runner = scoverageRunner
152206 reportDir = extension. reportDir
153207 sources = originalSourceSet. scala. getSourceDirectories()
154- dirsToAggregateFrom = dataDirs
208+ dirsToAggregateFrom = globalMergeMeasurementsTask . map {[it . getDestinationDir()]}
155209 sourceEncoding. set(detectedSourceEncoding)
156210 deleteReportsOnAggregation = false
157211 coverageOutputCobertura = extension. coverageOutputCobertura
@@ -170,8 +224,12 @@ class ScoveragePlugin implements Plugin<PluginAware> {
170224 }
171225
172226 def scalaVersion = resolveScalaVersions(project)
227+
228+ // the compile task creates a store of measured statements
229+ outputs. file(dataWorkDir. map {new File (it, ' scoverage.coverage' ) })
230+
173231 if (scalaVersion. majorVersion < 3 ) {
174- parameters. add(" -P:scoverage:dataDir:${ extension.dataDir .get().absolutePath} " . toString())
232+ parameters. add(" -P:scoverage:dataDir:${ dataWorkDir .get().absolutePath} " . toString())
175233 parameters. add(" -P:scoverage:sourceRoot:${ extension.project.getRootDir().absolutePath} " . toString())
176234 if (extension. excludedPackages. get()) {
177235 def packages = extension. excludedPackages. get(). join(' ;' )
@@ -185,10 +243,7 @@ class ScoveragePlugin implements Plugin<PluginAware> {
185243 parameters. add(' -Yrangepos' )
186244 }
187245 scalaCompileOptions. additionalParameters = parameters
188- // the compile task creates a store of measured statements
189- outputs. file(new File (extension. dataDir. get(), ' scoverage.coverage' ))
190-
191- dependsOn project. configurations[CONFIGURATION_NAME ]
246+ dependsOn project. configurations. named(CONFIGURATION_NAME )
192247 doFirst {
193248 /*
194249 It is crucial that this would run in `doFirst`, as this resolves the (dependencies of the)
@@ -205,7 +260,7 @@ class ScoveragePlugin implements Plugin<PluginAware> {
205260 }
206261 } else {
207262 parameters. add(" -sourceroot:${ project.rootDir.absolutePath} " . toString())
208- parameters. add(" -coverage-out:${ extension.dataDir .get().absolutePath} " . toString())
263+ parameters. add(" -coverage-out:${ dataWorkDir .get().absolutePath} " . toString())
209264 scalaCompileOptions. additionalParameters = parameters
210265 }
211266 }
@@ -262,20 +317,26 @@ class ScoveragePlugin implements Plugin<PluginAware> {
262317 def hasAnyReportTask = reportTasks. any { graph. hasTask(it) }
263318
264319 if (hasAnyReportTask) {
320+
265321 project. tasks. withType(Test ). each { testTask ->
266322 testTask. configure {
323+ def cTaskName = testTask. name. capitalize()
324+
267325 project. logger. info(" Adding instrumented classes to '${ path} ' classpath" )
268326
269327 classpath = project. configurations. scoverage + instrumentedSourceSet. output + classpath
270328
271- outputs . upToDateWhen {
272- extension . dataDir . get() . listFiles( new FilenameFilter () {
273- @Override
274- boolean accept ( File dir , String name ) {
275- name . startsWith( " scoverage.measurements. " )
276- }
277- } )
329+ doLast {
330+ project . sync {
331+ from(dataWorkDir)
332+ exclude( " scoverage.coverage " )
333+ into(dataMeasurementsDir(extension, cTaskName) )
334+ }
335+ project . delete(project . fileTree(dataWorkDir) . exclude( " scoverage.coverage " ) )
278336 }
337+
338+ outputs. dir(dataMeasurementsDir(extension, cTaskName)). withPropertyName(" scoverage.measurements" )
339+
279340 }
280341 }
281342 }
@@ -323,6 +384,14 @@ class ScoveragePlugin implements Plugin<PluginAware> {
323384 }
324385 }
325386
387+ private Provider<File > dataMeasurementsDir (ScoverageExtension extension , String testName ) {
388+ return extension. dataDir. map {new File (it, " ${ testName} Measurements" ) }
389+ }
390+
391+ private Provider<File > dataReportDir (ScoverageExtension extension , String testName ) {
392+ return extension. dataDir. map { new File (it, " ${ testName} Report" ) }
393+ }
394+
326395 private void configureCheckTask (Project project , ScoverageExtension extension ,
327396 TaskProvider<Task > globalCheckTask ,
328397 TaskProvider<ScoverageAggregate > globalReportTask ) {
0 commit comments