From 08512e6f88ce011650365485e0e8b127c3746cdb Mon Sep 17 00:00:00 2001 From: Eyal Roth Date: Sat, 11 Sep 2021 20:38:21 +0300 Subject: [PATCH] #75 Spawn tasks using Worker API instead of adding scoverage's classpath to the main gradle classloader --- .../org/scoverage/ScoverageAggregate.groovy | 70 ++++++++++++------ .../org/scoverage/ScoverageExtension.groovy | 5 -- .../org/scoverage/ScoveragePlugin.groovy | 21 ++++-- .../org/scoverage/ScoverageReport.groovy | 73 +++++++++++++------ .../org/scoverage/ScoverageRunner.groovy | 41 ++++++----- 5 files changed, 134 insertions(+), 76 deletions(-) diff --git a/src/main/groovy/org/scoverage/ScoverageAggregate.groovy b/src/main/groovy/org/scoverage/ScoverageAggregate.groovy index 9815dd1..9735e22 100644 --- a/src/main/groovy/org/scoverage/ScoverageAggregate.groovy +++ b/src/main/groovy/org/scoverage/ScoverageAggregate.groovy @@ -1,15 +1,14 @@ package org.scoverage import org.gradle.api.DefaultTask -import org.gradle.api.file.FileCollection +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.logging.Logging import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters import scoverage.report.CoverageAggregator import static org.gradle.api.tasks.PathSensitivity.RELATIVE @@ -21,7 +20,7 @@ class ScoverageAggregate extends DefaultTask { @InputFiles @PathSensitive(RELATIVE) - final Property sources = project.objects.property(FileCollection) + final ConfigurableFileCollection sources = project.objects.fileCollection() @OutputDirectory final Property reportDir = project.objects.property(File) @@ -29,9 +28,6 @@ class ScoverageAggregate extends DefaultTask { @Input final ListProperty dirsToAggregateFrom = project.objects.listProperty(File) - @Input - final Property deleteReportsOnAggregation = project.objects.property(Boolean) - @Input final Property sourceEncoding = project.objects.property(String) @@ -51,24 +47,52 @@ class ScoverageAggregate extends DefaultTask { @TaskAction def aggregate() { - runner.run { - reportDir.get().deleteDir() - reportDir.get().mkdirs() + runner.run(AggregateAction.class) { parameters -> + parameters.sources.from(sources) + parameters.reportDir = reportDir + parameters.dirsToAggregateFrom = dirsToAggregateFrom + parameters.sourceEncoding = sourceEncoding + parameters.coverageOutputCobertura = coverageOutputCobertura + parameters.coverageOutputXML = coverageOutputXML + parameters.coverageOutputHTML = coverageOutputHTML + parameters.coverageDebug = coverageDebug + } + } + + static interface Parameters extends WorkParameters { + ConfigurableFileCollection getSources() + Property getReportDir() + ListProperty getDirsToAggregateFrom() + Property getSourceEncoding() + Property getCoverageOutputCobertura() + Property getCoverageOutputXML() + Property getCoverageOutputHTML() + Property getCoverageDebug() + } + + static abstract class AggregateAction implements WorkAction { + + @Override + void execute() { + def logger = Logging.getLogger(AggregateAction.class) + + getParameters().reportDir.get().deleteDir() + getParameters().reportDir.get().mkdirs() def dirs = [] - dirs.addAll(dirsToAggregateFrom.get()) + dirs.addAll(getParameters().dirsToAggregateFrom.get()) def coverage = CoverageAggregator.aggregate(dirs.unique() as File[]) if (coverage.nonEmpty()) { - new ScoverageWriter(project.logger).write( - sources.get().getFiles(), - reportDir.get(), + new ScoverageWriter(logger).write( + getParameters().sources.getFiles(), + getParameters().reportDir.get(), coverage.get(), - sourceEncoding.get(), - coverageOutputCobertura.get(), - coverageOutputXML.get(), - coverageOutputHTML.get(), - coverageDebug.get() + getParameters().sourceEncoding.get(), + getParameters().coverageOutputCobertura.get(), + getParameters().coverageOutputXML.get(), + getParameters().coverageOutputHTML.get(), + getParameters().coverageDebug.get() ) } } diff --git a/src/main/groovy/org/scoverage/ScoverageExtension.groovy b/src/main/groovy/org/scoverage/ScoverageExtension.groovy index 210a825..64d9422 100644 --- a/src/main/groovy/org/scoverage/ScoverageExtension.groovy +++ b/src/main/groovy/org/scoverage/ScoverageExtension.groovy @@ -41,8 +41,6 @@ class ScoverageExtension { final Property coverageOutputHTML final Property coverageDebug - final Property deleteReportsOnAggregation - final List checks = new ArrayList<>() final Property coverageType @@ -86,9 +84,6 @@ class ScoverageExtension { coverageDebug = project.objects.property(Boolean) coverageDebug.set(false) - deleteReportsOnAggregation = project.objects.property(Boolean) - deleteReportsOnAggregation.set(false) - coverageType = project.objects.property(CoverageType) minimumRate = project.objects.property(BigDecimal) } diff --git a/src/main/groovy/org/scoverage/ScoveragePlugin.groovy b/src/main/groovy/org/scoverage/ScoveragePlugin.groovy index 4f1ba18..70d9673 100644 --- a/src/main/groovy/org/scoverage/ScoveragePlugin.groovy +++ b/src/main/groovy/org/scoverage/ScoveragePlugin.groovy @@ -11,7 +11,9 @@ import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.scala.ScalaCompile import org.gradle.api.tasks.testing.Test +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject import java.nio.file.Files import java.util.concurrent.ConcurrentHashMap @@ -29,9 +31,15 @@ class ScoveragePlugin implements Plugin { static final String DEFAULT_REPORT_DIR = 'reports' + File.separatorChar + 'scoverage' + private final WorkerExecutor workerExecutor private final ConcurrentHashMap> crossProjectTaskDependencies = new ConcurrentHashMap<>() private final ConcurrentHashMap> sameProjectTaskDependencies = new ConcurrentHashMap<>() + @Inject + ScoveragePlugin(WorkerExecutor workerExecutor) { + this.workerExecutor = workerExecutor + } + @Override void apply(PluginAware pluginAware) { if (pluginAware instanceof Project) { @@ -80,7 +88,8 @@ class ScoveragePlugin implements Plugin { private void createTasks(Project project, ScoverageExtension extension) { - ScoverageRunner scoverageRunner = new ScoverageRunner(project.configurations.scoverage) + ScoverageRunner scoverageRunner = new ScoverageRunner(workerExecutor) + scoverageRunner.runtimeClasspath = project.configurations.scoverage def originalSourceSet = project.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) def instrumentedSourceSet = project.sourceSets.create('scoverage') { @@ -125,7 +134,7 @@ class ScoveragePlugin implements Plugin { group = 'verification' runner = scoverageRunner reportDir = taskReportDir - sources = originalSourceSet.scala.getSourceDirectories() + sources.from(originalSourceSet.scala.getSourceDirectories()) dataDir = extension.dataDir sourceEncoding.set(detectedSourceEncoding) coverageOutputCobertura = extension.coverageOutputCobertura @@ -144,10 +153,9 @@ class ScoveragePlugin implements Plugin { group = 'verification' runner = scoverageRunner reportDir = extension.reportDir - sources = originalSourceSet.scala.getSourceDirectories() + sources.from(originalSourceSet.scala.getSourceDirectories()) dirsToAggregateFrom = dataDirs sourceEncoding.set(detectedSourceEncoding) - deleteReportsOnAggregation = false coverageOutputCobertura = extension.coverageOutputCobertura coverageOutputXML = extension.coverageOutputXML coverageOutputHTML = extension.coverageOutputHTML @@ -308,7 +316,7 @@ class ScoveragePlugin implements Plugin { def allReportTasks = childReportTasks + globalReportTask.get() def allSources = project.objects.fileCollection() allReportTasks.each { - allSources = allSources.plus(it.sources.get()) + allSources = allSources.plus(it.sources) } def aggregationTask = project.tasks.create(AGGREGATE_NAME, ScoverageAggregate) { def dataDirs = allReportTasks.findResults { it.dirsToAggregateFrom.get() }.flatten() @@ -319,10 +327,9 @@ class ScoveragePlugin implements Plugin { group = 'verification' runner = scoverageRunner reportDir = extension.reportDir - sources = allSources + sources.from(allSources) sourceEncoding.set(detectedSourceEncoding) dirsToAggregateFrom = dataDirs - deleteReportsOnAggregation = extension.deleteReportsOnAggregation coverageOutputCobertura = extension.coverageOutputCobertura coverageOutputXML = extension.coverageOutputXML coverageOutputHTML = extension.coverageOutputHTML diff --git a/src/main/groovy/org/scoverage/ScoverageReport.groovy b/src/main/groovy/org/scoverage/ScoverageReport.groovy index 8a15aab..0a45056 100644 --- a/src/main/groovy/org/scoverage/ScoverageReport.groovy +++ b/src/main/groovy/org/scoverage/ScoverageReport.groovy @@ -1,16 +1,13 @@ package org.scoverage import org.gradle.api.DefaultTask -import org.gradle.api.file.FileCollection +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.logging.Logging import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters import scoverage.report.CoverageAggregator import static org.gradle.api.tasks.PathSensitivity.RELATIVE @@ -27,7 +24,7 @@ class ScoverageReport extends DefaultTask { @InputFiles @PathSensitive(RELATIVE) - final Property sources = project.objects.property(FileCollection) + final ConfigurableFileCollection sources = project.objects.fileCollection() @OutputDirectory final Property reportDir = project.objects.property(File) @@ -46,25 +43,55 @@ class ScoverageReport extends DefaultTask { @TaskAction def report() { - runner.run { - reportDir.get().delete() - reportDir.get().mkdirs() + runner.run(ReportAction.class) { parameters -> + parameters.dataDir = dataDir + parameters.sources.from(sources) + parameters.reportDir = reportDir + parameters.sourceEncoding = sourceEncoding + parameters.coverageOutputCobertura = coverageOutputCobertura + parameters.coverageOutputXML = coverageOutputXML + parameters.coverageOutputHTML = coverageOutputHTML + parameters.coverageDebug = coverageDebug + } + } + + static interface Parameters extends WorkParameters { + Property getDataDir() + ConfigurableFileCollection getSources() + Property getReportDir() + Property getSourceEncoding() + Property getCoverageOutputCobertura() + Property getCoverageOutputXML() + Property getCoverageOutputHTML() + Property getCoverageDebug() + } + + static abstract class ReportAction implements WorkAction { - def coverage = CoverageAggregator.aggregate([dataDir.get()] as File[]) + @Override + void execute() { + getParameters().reportDir.get().delete() + getParameters().reportDir.get().mkdirs() + + def coverage = CoverageAggregator.aggregate([getParameters().dataDir.get()] as File[]) + + def logger = Logging.getLogger(ReportAction.class) if (coverage.isEmpty()) { - project.logger.info("[scoverage] Could not find coverage file, skipping...") + logger.info("[scoverage] Could not find coverage file, skipping...") } else { - new ScoverageWriter(project.logger).write( - sources.get().getFiles(), - reportDir.get(), + new ScoverageWriter(logger).write( + getParameters().sources.getFiles(), + getParameters().reportDir.get(), coverage.get(), - sourceEncoding.get(), - coverageOutputCobertura.get(), - coverageOutputXML.get(), - coverageOutputHTML.get(), - coverageDebug.get()) + getParameters().sourceEncoding.get(), + getParameters().coverageOutputCobertura.get(), + getParameters().coverageOutputXML.get(), + getParameters().coverageOutputHTML.get(), + getParameters().coverageDebug.get()) } + } + } } diff --git a/src/main/groovy/org/scoverage/ScoverageRunner.groovy b/src/main/groovy/org/scoverage/ScoverageRunner.groovy index a5dd552..c85e72e 100644 --- a/src/main/groovy/org/scoverage/ScoverageRunner.groovy +++ b/src/main/groovy/org/scoverage/ScoverageRunner.groovy @@ -1,34 +1,39 @@ package org.scoverage +import org.gradle.api.Action import org.gradle.api.file.FileCollection import org.gradle.api.tasks.Classpath +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor -import java.lang.reflect.Method +import javax.inject.Inject class ScoverageRunner { + private final WorkerExecutor workerExecutor + @Classpath - final FileCollection runtimeClasspath + FileCollection runtimeClasspath - ScoverageRunner(FileCollection runtimeClasspath) { + @Inject + ScoverageRunner(WorkerExecutor workerExecutor) { - this.runtimeClasspath = runtimeClasspath + this.workerExecutor = workerExecutor } - def run(Closure action) { - - URLClassLoader cloader = (URLClassLoader) Thread.currentThread().getContextClassLoader() - - Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class) - method.setAccessible(true) - - runtimeClasspath.files.each { f -> - def url = f.toURI().toURL() - if (!cloader.getURLs().contains(url)) { - method.invoke(cloader, url) - } + /** + * The runner makes use of Gradle's worker API to run tasks with scoverage's classpath without affecting the main + * gradle classloader/classpath. + * + * @see Worker API guide + * @see Worker API guide issue + */ + def void run(Class> workActionClass, Action parameterAction) { + + def queue = workerExecutor.classLoaderIsolation() {spec -> + spec.getClasspath().from(runtimeClasspath) } - - action.call() + queue.submit(workActionClass, parameterAction) } }