Skip to content

Commit 08512e6

Browse files
committed
#75 Spawn tasks using Worker API instead of adding scoverage's classpath to the main gradle classloader
1 parent eec9a26 commit 08512e6

File tree

5 files changed

+134
-76
lines changed

5 files changed

+134
-76
lines changed

Diff for: src/main/groovy/org/scoverage/ScoverageAggregate.groovy

+47-23
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package org.scoverage
22

33
import org.gradle.api.DefaultTask
4-
import org.gradle.api.file.FileCollection
4+
import org.gradle.api.file.ConfigurableFileCollection
5+
import org.gradle.api.logging.Logging
56
import org.gradle.api.provider.ListProperty
67
import org.gradle.api.provider.Property
7-
import org.gradle.api.tasks.Input
8-
import org.gradle.api.tasks.InputFiles
9-
import org.gradle.api.tasks.Nested
10-
import org.gradle.api.tasks.OutputDirectory
11-
import org.gradle.api.tasks.PathSensitive
12-
import org.gradle.api.tasks.TaskAction
8+
import org.gradle.api.provider.SetProperty
9+
import org.gradle.api.tasks.*
10+
import org.gradle.workers.WorkAction
11+
import org.gradle.workers.WorkParameters
1312
import scoverage.report.CoverageAggregator
1413

1514
import static org.gradle.api.tasks.PathSensitivity.RELATIVE
@@ -21,17 +20,14 @@ class ScoverageAggregate extends DefaultTask {
2120

2221
@InputFiles
2322
@PathSensitive(RELATIVE)
24-
final Property<FileCollection> sources = project.objects.property(FileCollection)
23+
final ConfigurableFileCollection sources = project.objects.fileCollection()
2524

2625
@OutputDirectory
2726
final Property<File> reportDir = project.objects.property(File)
2827

2928
@Input
3029
final ListProperty<File> dirsToAggregateFrom = project.objects.listProperty(File)
3130

32-
@Input
33-
final Property<Boolean> deleteReportsOnAggregation = project.objects.property(Boolean)
34-
3531
@Input
3632
final Property<String> sourceEncoding = project.objects.property(String)
3733

@@ -51,24 +47,52 @@ class ScoverageAggregate extends DefaultTask {
5147

5248
@TaskAction
5349
def aggregate() {
54-
runner.run {
55-
reportDir.get().deleteDir()
56-
reportDir.get().mkdirs()
50+
runner.run(AggregateAction.class) { parameters ->
51+
parameters.sources.from(sources)
52+
parameters.reportDir = reportDir
53+
parameters.dirsToAggregateFrom = dirsToAggregateFrom
54+
parameters.sourceEncoding = sourceEncoding
55+
parameters.coverageOutputCobertura = coverageOutputCobertura
56+
parameters.coverageOutputXML = coverageOutputXML
57+
parameters.coverageOutputHTML = coverageOutputHTML
58+
parameters.coverageDebug = coverageDebug
59+
}
60+
}
61+
62+
static interface Parameters extends WorkParameters {
63+
ConfigurableFileCollection getSources()
64+
Property<File> getReportDir()
65+
ListProperty<File> getDirsToAggregateFrom()
66+
Property<String> getSourceEncoding()
67+
Property<Boolean> getCoverageOutputCobertura()
68+
Property<Boolean> getCoverageOutputXML()
69+
Property<Boolean> getCoverageOutputHTML()
70+
Property<Boolean> getCoverageDebug()
71+
}
72+
73+
static abstract class AggregateAction implements WorkAction<Parameters> {
74+
75+
@Override
76+
void execute() {
77+
def logger = Logging.getLogger(AggregateAction.class)
78+
79+
getParameters().reportDir.get().deleteDir()
80+
getParameters().reportDir.get().mkdirs()
5781

5882
def dirs = []
59-
dirs.addAll(dirsToAggregateFrom.get())
83+
dirs.addAll(getParameters().dirsToAggregateFrom.get())
6084
def coverage = CoverageAggregator.aggregate(dirs.unique() as File[])
6185

6286
if (coverage.nonEmpty()) {
63-
new ScoverageWriter(project.logger).write(
64-
sources.get().getFiles(),
65-
reportDir.get(),
87+
new ScoverageWriter(logger).write(
88+
getParameters().sources.getFiles(),
89+
getParameters().reportDir.get(),
6690
coverage.get(),
67-
sourceEncoding.get(),
68-
coverageOutputCobertura.get(),
69-
coverageOutputXML.get(),
70-
coverageOutputHTML.get(),
71-
coverageDebug.get()
91+
getParameters().sourceEncoding.get(),
92+
getParameters().coverageOutputCobertura.get(),
93+
getParameters().coverageOutputXML.get(),
94+
getParameters().coverageOutputHTML.get(),
95+
getParameters().coverageDebug.get()
7296
)
7397
}
7498
}

Diff for: src/main/groovy/org/scoverage/ScoverageExtension.groovy

-5
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ class ScoverageExtension {
4141
final Property<Boolean> coverageOutputHTML
4242
final Property<Boolean> coverageDebug
4343

44-
final Property<Boolean> deleteReportsOnAggregation
45-
4644
final List<CheckConfig> checks = new ArrayList<>()
4745

4846
final Property<CoverageType> coverageType
@@ -86,9 +84,6 @@ class ScoverageExtension {
8684
coverageDebug = project.objects.property(Boolean)
8785
coverageDebug.set(false)
8886

89-
deleteReportsOnAggregation = project.objects.property(Boolean)
90-
deleteReportsOnAggregation.set(false)
91-
9287
coverageType = project.objects.property(CoverageType)
9388
minimumRate = project.objects.property(BigDecimal)
9489
}

Diff for: src/main/groovy/org/scoverage/ScoveragePlugin.groovy

+14-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import org.gradle.api.tasks.SourceSet
1111
import org.gradle.api.tasks.TaskProvider
1212
import org.gradle.api.tasks.scala.ScalaCompile
1313
import org.gradle.api.tasks.testing.Test
14+
import org.gradle.workers.WorkerExecutor
1415

16+
import javax.inject.Inject
1517
import java.nio.file.Files
1618
import java.util.concurrent.ConcurrentHashMap
1719

@@ -29,9 +31,15 @@ class ScoveragePlugin implements Plugin<PluginAware> {
2931

3032
static final String DEFAULT_REPORT_DIR = 'reports' + File.separatorChar + 'scoverage'
3133

34+
private final WorkerExecutor workerExecutor
3235
private final ConcurrentHashMap<Task, Set<? extends Task>> crossProjectTaskDependencies = new ConcurrentHashMap<>()
3336
private final ConcurrentHashMap<Task, Set<? extends Task>> sameProjectTaskDependencies = new ConcurrentHashMap<>()
3437

38+
@Inject
39+
ScoveragePlugin(WorkerExecutor workerExecutor) {
40+
this.workerExecutor = workerExecutor
41+
}
42+
3543
@Override
3644
void apply(PluginAware pluginAware) {
3745
if (pluginAware instanceof Project) {
@@ -80,7 +88,8 @@ class ScoveragePlugin implements Plugin<PluginAware> {
8088

8189
private void createTasks(Project project, ScoverageExtension extension) {
8290

83-
ScoverageRunner scoverageRunner = new ScoverageRunner(project.configurations.scoverage)
91+
ScoverageRunner scoverageRunner = new ScoverageRunner(workerExecutor)
92+
scoverageRunner.runtimeClasspath = project.configurations.scoverage
8493

8594
def originalSourceSet = project.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
8695
def instrumentedSourceSet = project.sourceSets.create('scoverage') {
@@ -125,7 +134,7 @@ class ScoveragePlugin implements Plugin<PluginAware> {
125134
group = 'verification'
126135
runner = scoverageRunner
127136
reportDir = taskReportDir
128-
sources = originalSourceSet.scala.getSourceDirectories()
137+
sources.from(originalSourceSet.scala.getSourceDirectories())
129138
dataDir = extension.dataDir
130139
sourceEncoding.set(detectedSourceEncoding)
131140
coverageOutputCobertura = extension.coverageOutputCobertura
@@ -144,10 +153,9 @@ class ScoveragePlugin implements Plugin<PluginAware> {
144153
group = 'verification'
145154
runner = scoverageRunner
146155
reportDir = extension.reportDir
147-
sources = originalSourceSet.scala.getSourceDirectories()
156+
sources.from(originalSourceSet.scala.getSourceDirectories())
148157
dirsToAggregateFrom = dataDirs
149158
sourceEncoding.set(detectedSourceEncoding)
150-
deleteReportsOnAggregation = false
151159
coverageOutputCobertura = extension.coverageOutputCobertura
152160
coverageOutputXML = extension.coverageOutputXML
153161
coverageOutputHTML = extension.coverageOutputHTML
@@ -308,7 +316,7 @@ class ScoveragePlugin implements Plugin<PluginAware> {
308316
def allReportTasks = childReportTasks + globalReportTask.get()
309317
def allSources = project.objects.fileCollection()
310318
allReportTasks.each {
311-
allSources = allSources.plus(it.sources.get())
319+
allSources = allSources.plus(it.sources)
312320
}
313321
def aggregationTask = project.tasks.create(AGGREGATE_NAME, ScoverageAggregate) {
314322
def dataDirs = allReportTasks.findResults { it.dirsToAggregateFrom.get() }.flatten()
@@ -319,10 +327,9 @@ class ScoveragePlugin implements Plugin<PluginAware> {
319327
group = 'verification'
320328
runner = scoverageRunner
321329
reportDir = extension.reportDir
322-
sources = allSources
330+
sources.from(allSources)
323331
sourceEncoding.set(detectedSourceEncoding)
324332
dirsToAggregateFrom = dataDirs
325-
deleteReportsOnAggregation = extension.deleteReportsOnAggregation
326333
coverageOutputCobertura = extension.coverageOutputCobertura
327334
coverageOutputXML = extension.coverageOutputXML
328335
coverageOutputHTML = extension.coverageOutputHTML

Diff for: src/main/groovy/org/scoverage/ScoverageReport.groovy

+50-23
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package org.scoverage
22

33
import org.gradle.api.DefaultTask
4-
import org.gradle.api.file.FileCollection
4+
import org.gradle.api.file.ConfigurableFileCollection
5+
import org.gradle.api.logging.Logging
56
import org.gradle.api.provider.Property
6-
import org.gradle.api.tasks.CacheableTask
7-
import org.gradle.api.tasks.Input
8-
import org.gradle.api.tasks.InputDirectory
9-
import org.gradle.api.tasks.InputFiles
10-
import org.gradle.api.tasks.Nested
11-
import org.gradle.api.tasks.OutputDirectory
12-
import org.gradle.api.tasks.PathSensitive
13-
import org.gradle.api.tasks.TaskAction
7+
import org.gradle.api.provider.SetProperty
8+
import org.gradle.api.tasks.*
9+
import org.gradle.workers.WorkAction
10+
import org.gradle.workers.WorkParameters
1411
import scoverage.report.CoverageAggregator
1512

1613
import static org.gradle.api.tasks.PathSensitivity.RELATIVE
@@ -27,7 +24,7 @@ class ScoverageReport extends DefaultTask {
2724

2825
@InputFiles
2926
@PathSensitive(RELATIVE)
30-
final Property<FileCollection> sources = project.objects.property(FileCollection)
27+
final ConfigurableFileCollection sources = project.objects.fileCollection()
3128

3229
@OutputDirectory
3330
final Property<File> reportDir = project.objects.property(File)
@@ -46,25 +43,55 @@ class ScoverageReport extends DefaultTask {
4643

4744
@TaskAction
4845
def report() {
49-
runner.run {
50-
reportDir.get().delete()
51-
reportDir.get().mkdirs()
46+
runner.run(ReportAction.class) { parameters ->
47+
parameters.dataDir = dataDir
48+
parameters.sources.from(sources)
49+
parameters.reportDir = reportDir
50+
parameters.sourceEncoding = sourceEncoding
51+
parameters.coverageOutputCobertura = coverageOutputCobertura
52+
parameters.coverageOutputXML = coverageOutputXML
53+
parameters.coverageOutputHTML = coverageOutputHTML
54+
parameters.coverageDebug = coverageDebug
55+
}
56+
}
57+
58+
static interface Parameters extends WorkParameters {
59+
Property<File> getDataDir()
60+
ConfigurableFileCollection getSources()
61+
Property<File> getReportDir()
62+
Property<String> getSourceEncoding()
63+
Property<Boolean> getCoverageOutputCobertura()
64+
Property<Boolean> getCoverageOutputXML()
65+
Property<Boolean> getCoverageOutputHTML()
66+
Property<Boolean> getCoverageDebug()
67+
}
68+
69+
static abstract class ReportAction implements WorkAction<Parameters> {
5270

53-
def coverage = CoverageAggregator.aggregate([dataDir.get()] as File[])
71+
@Override
72+
void execute() {
73+
getParameters().reportDir.get().delete()
74+
getParameters().reportDir.get().mkdirs()
75+
76+
def coverage = CoverageAggregator.aggregate([getParameters().dataDir.get()] as File[])
77+
78+
def logger = Logging.getLogger(ReportAction.class)
5479

5580
if (coverage.isEmpty()) {
56-
project.logger.info("[scoverage] Could not find coverage file, skipping...")
81+
logger.info("[scoverage] Could not find coverage file, skipping...")
5782
} else {
58-
new ScoverageWriter(project.logger).write(
59-
sources.get().getFiles(),
60-
reportDir.get(),
83+
new ScoverageWriter(logger).write(
84+
getParameters().sources.getFiles(),
85+
getParameters().reportDir.get(),
6186
coverage.get(),
62-
sourceEncoding.get(),
63-
coverageOutputCobertura.get(),
64-
coverageOutputXML.get(),
65-
coverageOutputHTML.get(),
66-
coverageDebug.get())
87+
getParameters().sourceEncoding.get(),
88+
getParameters().coverageOutputCobertura.get(),
89+
getParameters().coverageOutputXML.get(),
90+
getParameters().coverageOutputHTML.get(),
91+
getParameters().coverageDebug.get())
6792
}
93+
6894
}
95+
6996
}
7097
}

Diff for: src/main/groovy/org/scoverage/ScoverageRunner.groovy

+23-18
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
package org.scoverage
22

3+
import org.gradle.api.Action
34
import org.gradle.api.file.FileCollection
45
import org.gradle.api.tasks.Classpath
6+
import org.gradle.workers.WorkAction
7+
import org.gradle.workers.WorkParameters
8+
import org.gradle.workers.WorkerExecutor
59

6-
import java.lang.reflect.Method
10+
import javax.inject.Inject
711

812
class ScoverageRunner {
913

14+
private final WorkerExecutor workerExecutor
15+
1016
@Classpath
11-
final FileCollection runtimeClasspath
17+
FileCollection runtimeClasspath
1218

13-
ScoverageRunner(FileCollection runtimeClasspath) {
19+
@Inject
20+
ScoverageRunner(WorkerExecutor workerExecutor) {
1421

15-
this.runtimeClasspath = runtimeClasspath
22+
this.workerExecutor = workerExecutor
1623
}
1724

18-
def run(Closure<?> action) {
19-
20-
URLClassLoader cloader = (URLClassLoader) Thread.currentThread().getContextClassLoader()
21-
22-
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
23-
method.setAccessible(true)
24-
25-
runtimeClasspath.files.each { f ->
26-
def url = f.toURI().toURL()
27-
if (!cloader.getURLs().contains(url)) {
28-
method.invoke(cloader, url)
29-
}
25+
/**
26+
* The runner makes use of Gradle's worker API to run tasks with scoverage's classpath without affecting the main
27+
* gradle classloader/classpath.
28+
*
29+
* @see <a href="https://docs.gradle.org/current/userguide/custom_tasks.html#worker_api">Worker API guide</a>
30+
* @see <a href="https://github.com/gradle/guides/issues/295">Worker API guide issue<a/>
31+
*/
32+
def <T extends WorkParameters> void run(Class<? extends WorkAction<T>> workActionClass, Action<? super T> parameterAction) {
33+
34+
def queue = workerExecutor.classLoaderIsolation() {spec ->
35+
spec.getClasspath().from(runtimeClasspath)
3036
}
31-
32-
action.call()
37+
queue.submit(workActionClass, parameterAction)
3338
}
3439
}

0 commit comments

Comments
 (0)