Skip to content

Commit e1f3fbc

Browse files
committed
Added Gradle configuration cache support
Resolves #142 - all inputs for Gradle beans (tasks, actions, extensions, etc.) are serializable - there is no direct access to the Project instance during the task execution, only a call from providers is allowed
1 parent 3cd1d5b commit e1f3fbc

File tree

12 files changed

+145
-113
lines changed

12 files changed

+145
-113
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package kotlinx.kover.test.functional.cases
2+
3+
import kotlinx.kover.test.functional.core.*
4+
import kotlin.test.*
5+
6+
internal class ConfigurationCacheTests: BaseGradleScriptTest() {
7+
@Test
8+
fun testConfigCache() {
9+
builder("Testing configuration cache support")
10+
.sources("simple")
11+
.build()
12+
.run("build", "koverMergedReport", "koverMergedVerify", "koverReport", "koverVerify", "--configuration-cache")
13+
}
14+
}

src/main/kotlin/kotlinx/kover/KoverPlugin.kt

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.gradle.api.provider.*
3333
import org.gradle.api.tasks.*
3434
import org.gradle.api.tasks.testing.*
3535
import org.gradle.process.*
36+
import java.io.File
3637
import kotlin.reflect.*
3738

3839
class KoverPlugin : Plugin<Project> {
@@ -160,23 +161,25 @@ class KoverPlugin : Plugin<Project> {
160161
providers: BuildProviders,
161162
block: (T) -> Unit
162163
): T {
163-
return tasks.create(taskName, type.java) { task ->
164-
task.group = VERIFICATION_GROUP
164+
val task = tasks.create(taskName, type.java)
165165

166-
providers.projects.forEach { (projectName, m) ->
167-
task.binaryReportFiles.put(projectName, NestedFiles(task.project.objects, m.reports))
168-
task.srcDirs.put(projectName, NestedFiles(task.project.objects, m.sources))
169-
task.outputDirs.put(projectName, NestedFiles(task.project.objects, m.output))
170-
}
166+
task.group = VERIFICATION_GROUP
171167

172-
task.coverageEngine.set(providers.engine)
173-
task.classpath.set(providers.classpath)
174-
task.dependsOn(providers.merged.tests)
168+
providers.projects.forEach { (projectName, m) ->
169+
task.binaryReportFiles.put(projectName, NestedFiles(task.project.objects, m.reports))
170+
task.srcDirs.put(projectName, NestedFiles(task.project.objects, m.sources))
171+
task.outputDirs.put(projectName, NestedFiles(task.project.objects, m.output))
172+
}
175173

176-
task.onlyIf { !providers.merged.disabled.get() }
174+
task.coverageEngine.set(providers.engine)
175+
task.classpath.set(providers.classpath)
176+
task.dependsOn(providers.merged.tests)
177177

178-
block(task)
179-
}
178+
val disabledProvider = providers.merged.disabled
179+
task.onlyIf { !disabledProvider.get() }
180+
181+
block(task)
182+
return task
180183
}
181184

182185
private fun Project.createCollectingTask() {
@@ -214,23 +217,25 @@ class KoverPlugin : Plugin<Project> {
214217
throw GradleException("Kover task '$taskName' already exist. Plugin should not be applied in child project if it has already been applied in one of the parent projects.")
215218
}
216219

217-
return tasks.create(taskName, type.java) { task ->
218-
task.group = VERIFICATION_GROUP
220+
val task = tasks.create(taskName, type.java)
221+
task.group = VERIFICATION_GROUP
219222

220-
task.coverageEngine.set(providers.engine)
221-
task.classpath.set(providers.classpath)
222-
task.srcDirs.set(projectProviders.sources)
223-
task.outputDirs.set(projectProviders.output)
223+
task.coverageEngine.set(providers.engine)
224+
task.classpath.set(providers.classpath)
225+
task.srcDirs.set(projectProviders.sources)
226+
task.outputDirs.set(projectProviders.output)
224227

225-
// it is necessary to read all binary reports because project's classes can be invoked in another project
226-
task.binaryReportFiles.set(projectProviders.reports)
227-
task.dependsOn(projectProviders.tests)
228+
// it is necessary to read all binary reports because project's classes can be invoked in another project
229+
task.binaryReportFiles.set(projectProviders.reports)
230+
task.dependsOn(projectProviders.tests)
228231

229-
task.onlyIf { !projectProviders.disabled.get() }
230-
task.onlyIf { !task.binaryReportFiles.get().isEmpty }
232+
val disabledProvider = projectProviders.disabled
233+
task.onlyIf { !disabledProvider.get() }
234+
task.onlyIf { !(it as KoverProjectTask).binaryReportFiles.get().isEmpty }
231235

232-
block(task)
233-
}
236+
block(task)
237+
238+
return task
234239
}
235240

236241
private fun Project.createKoverExtension(): KoverExtension {
@@ -249,26 +254,33 @@ class KoverPlugin : Plugin<Project> {
249254
val taskExtension = extensions.create(TASK_EXTENSION_NAME, KoverTaskExtension::class.java, project.objects)
250255

251256
taskExtension.isDisabled = false
252-
taskExtension.binaryReportFile.set(this.project.provider {
257+
taskExtension.binaryReportFile.set(project.provider {
253258
val koverExtension = providers.koverExtension.get()
254259
val suffix = if (koverExtension.coverageEngine.get() == CoverageEngine.INTELLIJ) ".ic" else ".exec"
255260
project.layout.buildDirectory.get().file("kover/$name$suffix").asFile
256261
})
257262

263+
val pluginContainer = project.plugins
258264
val excludeAndroidPackages =
259-
project.provider { project.androidPluginIsApplied && !providers.koverExtension.get().instrumentAndroidPackage }
265+
project.provider { pluginContainer.androidPluginIsApplied && !providers.koverExtension.get().instrumentAndroidPackage }
260266

261267
jvmArgumentProviders.add(
262268
CoverageArgumentProvider(
263269
this,
264270
agents,
271+
taskExtension,
265272
providers.koverExtension,
266273
excludeAndroidPackages
267274
)
268275
)
269276

270-
doFirst(BinaryReportCleanupAction(providers.koverExtension, taskExtension))
271-
doLast(IntellijErrorLogCopyAction(taskExtension))
277+
val sourceErrorProvider = project.provider {
278+
File(taskExtension.binaryReportFile.get().parentFile, "coverage-error.log")
279+
}
280+
val targetErrorProvider = project.layout.buildDirectory.file("kover/errors/$name.log").map { it.asFile }
281+
282+
doFirst(BinaryReportCleanupAction(project.name, providers.koverExtension, taskExtension))
283+
doLast(MoveIntellijErrorLogAction(sourceErrorProvider, targetErrorProvider))
272284
}
273285
}
274286

@@ -277,6 +289,7 @@ class KoverPlugin : Plugin<Project> {
277289
For this reason, before starting the tests, it is necessary to clear the file from the results of previous runs.
278290
*/
279291
private class BinaryReportCleanupAction(
292+
private val projectName: String,
280293
private val koverExtensionProvider: Provider<KoverExtension>,
281294
private val taskExtension: KoverTaskExtension
282295
) : Action<Task> {
@@ -289,7 +302,7 @@ private class BinaryReportCleanupAction(
289302

290303
if (!taskExtension.isDisabled
291304
&& !koverExtension.isDisabled
292-
&& !koverExtension.disabledProjects.contains(task.project.name)
305+
&& !koverExtension.disabledProjects.contains(projectName)
293306
&& koverExtension.coverageEngine.get() == CoverageEngine.INTELLIJ
294307
) {
295308
// IntelliJ engine expected empty file for parallel test execution.
@@ -299,26 +312,28 @@ private class BinaryReportCleanupAction(
299312
}
300313
}
301314

302-
private class IntellijErrorLogCopyAction(private val taskExtension: KoverTaskExtension) : Action<Task> {
315+
private class MoveIntellijErrorLogAction(
316+
private val sourceFile: Provider<File>,
317+
private val targetFile: Provider<File>
318+
) : Action<Task> {
303319
override fun execute(task: Task) {
304-
task.project.copyIntellijErrorLog(
305-
task.project.layout.buildDirectory.get().file("kover/errors/${task.name}.log").asFile,
306-
taskExtension.binaryReportFile.get().parentFile
307-
)
320+
val origin = sourceFile.get()
321+
if (origin.exists() && origin.isFile) {
322+
origin.copyTo(targetFile.get(), true)
323+
origin.delete()
324+
}
308325
}
309326
}
310327

311328
private class CoverageArgumentProvider(
312329
private val task: Task,
313330
private val agents: Map<CoverageEngine, CoverageAgent>,
331+
@get:Nested val taskExtension: KoverTaskExtension,
314332
@get:Nested val koverExtension: Provider<KoverExtension>,
315333
@get:Input val excludeAndroidPackage: Provider<Boolean>
316334
) : CommandLineArgumentProvider, Named {
317335

318-
@get:Nested
319-
val taskExtension: Provider<KoverTaskExtension> = task.project.provider {
320-
task.extensions.getByType(KoverTaskExtension::class.java)
321-
}
336+
private val projectName: String = task.project.name
322337

323338
@Internal
324339
override fun getName(): String {
@@ -327,11 +342,10 @@ private class CoverageArgumentProvider(
327342

328343
override fun asArguments(): MutableIterable<String> {
329344
val koverExtensionValue = koverExtension.get()
330-
val taskExtensionValue = taskExtension.get()
331345

332-
if (taskExtensionValue.isDisabled
346+
if (taskExtension.isDisabled
333347
|| koverExtensionValue.isDisabled
334-
|| koverExtensionValue.disabledProjects.contains(task.project.name)
348+
|| koverExtensionValue.disabledProjects.contains(projectName)
335349
) {
336350
return mutableListOf()
337351
}
@@ -346,9 +360,9 @@ private class CoverageArgumentProvider(
346360
347361
FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation
348362
*/
349-
taskExtensionValue.excludes = taskExtensionValue.excludes + "android.*" + "com.android.*"
363+
taskExtension.excludes = taskExtension.excludes + "android.*" + "com.android.*"
350364
}
351365

352-
return agents.getFor(koverExtensionValue.coverageEngine.get()).buildCommandLineArgs(task, taskExtensionValue)
366+
return agents.getFor(koverExtensionValue.coverageEngine.get()).buildCommandLineArgs(task, taskExtension)
353367
}
354368
}

src/main/kotlin/kotlinx/kover/adapters/PluginAdaptersFactory.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
package kotlinx.kover.adapters
66

77
import kotlinx.kover.adapters.api.*
8-
import org.gradle.api.*
9-
import org.gradle.api.file.*
10-
import java.io.*
8+
import org.gradle.api.plugins.PluginContainer
119

1210
internal fun createAdapters(): List<CompilationPluginAdapter> {
1311
return listOf(
@@ -18,7 +16,7 @@ internal fun createAdapters(): List<CompilationPluginAdapter> {
1816
)
1917
}
2018

21-
val Project.androidPluginIsApplied: Boolean
19+
val PluginContainer.androidPluginIsApplied: Boolean
2220
get() {
23-
return plugins.findPlugin("android") != null || plugins.findPlugin("kotlin-android") != null
21+
return findPlugin("android") != null || findPlugin("kotlin-android") != null
2422
}

src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ package kotlinx.kover.engines.commons
22

33
import java.io.*
44

5-
internal class Report(val files: List<File>, val projects: List<ProjectInfo>)
5+
internal class Report(
6+
val files: List<File>,
7+
val projects: List<ProjectInfo>,
8+
val includes: List<String> = emptyList(),
9+
val excludes: List<String> = emptyList()
10+
)
611
internal class ProjectInfo(val sources: Iterable<File>, val outputs: Iterable<File>)
712

813
private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet()

src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,30 @@ import kotlinx.kover.engines.commons.CoverageAgent
1010
import org.gradle.api.*
1111
import org.gradle.api.artifacts.*
1212
import org.gradle.api.file.*
13+
import org.gradle.api.provider.Provider
1314
import java.io.*
1415

1516

1617
internal fun Project.createIntellijAgent(koverExtension: KoverExtension): CoverageAgent {
1718
val intellijConfig = createIntellijConfig(koverExtension)
18-
return IntellijAgent(intellijConfig)
19+
val jarProvider = provider { intellijConfig.fileCollection { it.name == "intellij-coverage-agent" }.singleFile }
20+
return IntellijAgent(intellijConfig, jarProvider)
1921
}
2022

21-
private class IntellijAgent(private val config: Configuration): CoverageAgent {
23+
private class IntellijAgent(override val classpath: FileCollection, private val jarProvider: Provider<File>): CoverageAgent {
2224
private val trackingPerTest = false // a flag to enable tracking per test coverage
2325
private val calculateForUnloadedClasses = false // a flag to calculate coverage for unloaded classes
2426
private val appendToDataFile = true // a flag to use data file as initial coverage
2527
private val samplingMode = false //a flag to run coverage in sampling mode or in tracing mode otherwise
2628

2729
override val engine: CoverageEngine = CoverageEngine.INTELLIJ
28-
override val classpath: FileCollection = config
2930

3031
override fun buildCommandLineArgs(task: Task, extension: KoverTaskExtension): MutableList<String> {
3132
val argsFile = File(task.temporaryDir, "intellijagent.args")
3233
argsFile.writeArgsToFile(extension)
33-
val jarFile = config.fileCollection { it.name == "intellij-coverage-agent" }.singleFile
34+
3435
return mutableListOf(
35-
"-javaagent:${jarFile.canonicalPath}=${argsFile.canonicalPath}",
36+
"-javaagent:${jarProvider.get().canonicalPath}=${argsFile.canonicalPath}",
3637
"-Didea.new.sampling.coverage=true",
3738
"-Didea.new.tracing.coverage=true",
3839
"-Didea.coverage.log.level=error",

src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import kotlinx.kover.engines.commons.*
99
import kotlinx.kover.engines.commons.Report
1010
import org.gradle.api.*
1111
import org.gradle.api.file.*
12+
import org.gradle.process.ExecOperations
1213
import java.io.*
1314
import java.util.*
1415

1516
internal fun Task.intellijReport(
17+
exec: ExecOperations,
1618
report: Report,
1719
xmlFile: File?,
1820
htmlDir: File?,
19-
includes: List<String>,
20-
excludes: List<String>,
2121
classpath: FileCollection
2222
) {
2323
xmlFile?.let {
@@ -30,29 +30,14 @@ internal fun Task.intellijReport(
3030

3131
val argsFile = File(temporaryDir, "intellijreport.json")
3232
argsFile.printWriter().use { pw ->
33-
pw.writeReportsJson(report, xmlFile, htmlDir, includes, excludes)
33+
pw.writeReportsJson(report, xmlFile, htmlDir)
3434
}
3535

36-
project.javaexec { e ->
36+
exec.javaexec { e ->
3737
e.mainClass.set("com.intellij.rt.coverage.report.Main")
3838
e.classpath = classpath
3939
e.args = mutableListOf(argsFile.canonicalPath)
4040
}
41-
42-
project.copyIntellijErrorLog(project.layout.buildDirectory.get().file("kover/errors/$name.log").asFile)
43-
}
44-
45-
internal fun Project.copyIntellijErrorLog(toFile: File, customDirectory: File? = null) {
46-
var errorLog = customDirectory?.let { File(it, "coverage-error.log") }
47-
48-
if (errorLog == null || !errorLog.exists()) {
49-
errorLog = File(projectDir, "coverage-error.log")
50-
}
51-
52-
if (errorLog.exists() && errorLog.isFile) {
53-
errorLog.copyTo(toFile, true)
54-
errorLog.delete()
55-
}
5641
}
5742

5843
/*
@@ -97,9 +82,7 @@ JSON example:
9782
private fun Writer.writeReportsJson(
9883
report: Report,
9984
xmlFile: File?,
100-
htmlDir: File?,
101-
includes: List<String>,
102-
excludes: List<String>,
85+
htmlDir: File?
10386
) {
10487
appendLine("{")
10588

@@ -116,6 +99,9 @@ private fun Writer.writeReportsJson(
11699
appendLine(""" "html": ${it.jsonString},""")
117100
}
118101

102+
val includes = report.includes
103+
val excludes = report.excludes
104+
119105
if (includes.isNotEmpty()) {
120106
appendLine(""" "include": {""")
121107
appendLine(includes.joinToString(", ", """ "classes": [""", "]") { i -> i.wildcardsToRegex().jsonString })

0 commit comments

Comments
 (0)