Gradle: improve tasks registration API, fix test tasks registration

Previously test tasks may be created inside other registration task
lambda which may be called lazily. This is illegal in Gradle.

Also let introduce Project.createOrRegisterTask on Project receiver
and reified type parameter.
This commit is contained in:
Sergey Rostov
2019-04-13 10:26:20 +03:00
parent 39ce257e64
commit b96f26cc77
8 changed files with 88 additions and 61 deletions
@@ -25,6 +25,7 @@ import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.testing.internal.registerTestTask import org.jetbrains.kotlin.gradle.testing.internal.registerTestTask
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.util.concurrent.Callable import java.util.concurrent.Callable
@@ -272,7 +273,7 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
isVisible = false isVisible = false
isCanBeResolved = true // Needed for IDE import isCanBeResolved = true // Needed for IDE import
description = description =
"Runtime dependencies for $compilation (deprecated, use '${compilation.runtimeOnlyConfigurationName} ' instead)." "Runtime dependencies for $compilation (deprecated, use '${compilation.runtimeOnlyConfigurationName} ' instead)."
} }
val runtimeOnlyConfiguration = configurations.maybeCreate(compilation.runtimeOnlyConfigurationName).apply { val runtimeOnlyConfiguration = configurations.maybeCreate(compilation.runtimeOnlyConfigurationName).apply {
@@ -361,23 +362,27 @@ abstract class KotlinTargetConfigurator<KotlinCompilationType : KotlinCompilatio
} }
override fun configureTest(target: KotlinOnlyTarget<KotlinCompilationType>) { override fun configureTest(target: KotlinOnlyTarget<KotlinCompilationType>) {
val testCompilation = target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles<*> val testCompilation =
?: return // Otherwise, there is no runtime classpath target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles<*>
?: return // Otherwise, there is no runtime classpath
target.project.tasks.create(lowerCamelCaseName(target.disambiguationClassifier, testTaskNameSuffix), KotlinJvmTest::class.java).apply { val testTaskName = lowerCamelCaseName(target.disambiguationClassifier, testTaskNameSuffix)
registerTestTask(this) val testTask = target.project.createOrRegisterTask<KotlinJvmTest>(testTaskName) { testTask ->
testTask.targetName = target.disambiguationClassifier
}
targetName = target.disambiguationClassifier testTask.project.afterEvaluate {
// use afterEvaluate to override the JavaPlugin defaults for Test tasks
project.afterEvaluate { testTask.configure { testTask ->
// use afterEvaluate to override the JavaPlugin defaults for Test tasks testTask.conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs }
conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs } testTask.conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles }
conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles } testTask.description = "Runs the unit tests."
description = "Runs the unit tests." testTask.group = JavaBasePlugin.VERIFICATION_GROUP
group = JavaBasePlugin.VERIFICATION_GROUP testTask.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(testTask)
target.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(this@apply)
} }
} }
registerTestTask(testTask)
} }
private fun addJar(configuration: Configuration, jarArtifact: PublishArtifact) { private fun addJar(configuration: Configuration, jarArtifact: PublishArtifact) {
@@ -5,9 +5,16 @@
package org.jetbrains.kotlin.gradle.plugin package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
class LegacyTaskHolder<T : Task>(private val task: T) : TaskHolder<T> { class LegacyTaskHolder<out T : Task>(private val task: T) : TaskHolder<T> {
override val project: Project
get() = task.project
override val name: String
get() = task.name
override fun doGetTask() = task override fun doGetTask() = task
override fun getTaskOrProvider(): Any = task override fun getTaskOrProvider(): Any = task
@@ -24,7 +24,9 @@ import org.gradle.api.Project
* Reference to a org.gradle.api.Task or org.gradle.api.TaskProvider necessary in order to support flexible creation of tasks. * Reference to a org.gradle.api.Task or org.gradle.api.TaskProvider necessary in order to support flexible creation of tasks.
* For gradle versions < 4.9 tasks are created meanwhile for gradle with version >= 4.9 tasks are registered * For gradle versions < 4.9 tasks are created meanwhile for gradle with version >= 4.9 tasks are registered
*/ */
interface TaskHolder<T : Task> { interface TaskHolder<out T : Task> {
val project: Project
val name: String
/** /**
* Returns Task itself if task was created or TaskProvider<Task> if task was registered. * Returns Task itself if task was created or TaskProvider<Task> if task was registered.
@@ -16,14 +16,15 @@
package org.jetbrains.kotlin.gradle.plugin package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
class TaskProviderHolder<T : Task>( class TaskProviderHolder<out T : Task>(
private val name: String, override val name: String,
override val project: Project,
private val provider: TaskProvider<T> private val provider: TaskProvider<T>
) : TaskHolder<T> { ) : TaskHolder<T> {
override fun getTaskOrProvider(): Any = provider override fun getTaskOrProvider(): Any = provider
override fun doGetTask(): T = provider.get() override fun doGetTask(): T = provider.get()
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.testing.internal.configureConventions import org.jetbrains.kotlin.gradle.testing.internal.configureConventions
import org.jetbrains.kotlin.gradle.testing.internal.registerTestTask import org.jetbrains.kotlin.gradle.testing.internal.registerTestTask
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
@@ -40,7 +40,7 @@ internal class KotlinJsCompilationTestsConfigurator(
// apply plugin (cannot do it lazy) // apply plugin (cannot do it lazy)
val nodeJs = NodeJsPlugin[target.project] val nodeJs = NodeJsPlugin[target.project]
registerTask(project, testTaskName, KotlinJsTest::class.java) { testJs -> val testJs = project.createOrRegisterTask<KotlinJsTest>(testTaskName) { testJs ->
testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP
testJs.dependsOn(compileTestKotlin2Js, nodeJs.nodeJsSetupTask) testJs.dependsOn(compileTestKotlin2Js, nodeJs.nodeJsSetupTask)
@@ -54,7 +54,8 @@ internal class KotlinJsCompilationTestsConfigurator(
testJs.nodeModulesToLoad.add(compileTestKotlin2Js.outputFile.name) testJs.nodeModulesToLoad.add(compileTestKotlin2Js.outputFile.name)
testJs.configureConventions() testJs.configureConventions()
registerTestTask(testJs)
} }
registerTestTask(testJs)
} }
} }
@@ -129,24 +129,23 @@ open class KotlinNativeTargetConfigurator(
val taskName = binary.runTaskName ?: return val taskName = binary.runTaskName ?: return
if (binary.isDefaultTestExecutable) { if (binary.isDefaultTestExecutable) {
tasks.create(taskName, KotlinNativeTest::class.java).apply { val testTask = createOrRegisterTask<KotlinNativeTest>(taskName) { testTask ->
tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this) testTask.group = LifecycleBasePlugin.VERIFICATION_GROUP
testTask.description = "Executes Kotlin/Native unit tests for target ${binary.target.name}."
testTask.targetName = binary.compilation.target.targetName
group = LifecycleBasePlugin.VERIFICATION_GROUP testTask.enabled = binary.target.konanTarget.isCurrentHost
description = "Executes Kotlin/Native unit tests for target ${binary.target.name}."
targetName = binary.compilation.target.targetName
enabled = binary.target.konanTarget.isCurrentHost testTask.executable = binary.outputFile
testTask.workingDir = project.projectDir.absolutePath
executable = binary.outputFile testTask.onlyIf { binary.outputFile.exists() }
workingDir = project.projectDir.absolutePath testTask.dependsOn(binary.linkTaskName)
onlyIf { binary.outputFile.exists() } testTask.configureConventions()
dependsOn(binary.linkTaskName)
configureConventions()
registerTestTask(this)
} }
registerTestTask(testTask)
} else { } else {
tasks.create(taskName, Exec::class.java).apply { tasks.create(taskName, Exec::class.java).apply {
group = RUN_GROUP group = RUN_GROUP
@@ -31,9 +31,17 @@ internal val canLocateTask = org.jetbrains.kotlin.gradle.utils.isGradleVersionAt
* Registers the task with [name] and [type] and initialization script [body] * Registers the task with [name] and [type] and initialization script [body]
* If Gradle with version <4.9 is used the task will be created * If Gradle with version <4.9 is used the task will be created
*/ */
internal fun <T : Task> registerTask(project: Project, name: String, type: Class<T>, body: (T) -> (Unit)): TaskHolder<T> { @JvmName("registerTaskOld")
@Deprecated("please use Project.createOrRegisterTask", ReplaceWith("project.createOrRegisterTask(name, body)"))
internal fun <T : Task> registerTask(project: Project, name: String, type: Class<T>, body: (T) -> (Unit)): TaskHolder<T> =
project.createOrRegisterTask(name, type, body)
internal inline fun <reified T : Task> Project.createOrRegisterTask(name: String, noinline body: (T) -> (Unit)): TaskHolder<T> =
createOrRegisterTask(name, T::class.java, body)
internal fun <T : Task> Project.createOrRegisterTask(name: String, type: Class<T>, body: (T) -> (Unit)): TaskHolder<T> {
return if (useLazyTaskConfiguration) { return if (useLazyTaskConfiguration) {
TaskProviderHolder(name, project.tasks.register(name, type) { with(it, body) }) TaskProviderHolder(name, project, project.tasks.register(name, type) { with(it, body) })
} else { } else {
val result = LegacyTaskHolder(project.tasks.create(name, type)) val result = LegacyTaskHolder(project.tasks.create(name, type))
with(result.doGetTask(), body) with(result.doGetTask(), body)
@@ -41,13 +49,14 @@ internal fun <T : Task> registerTask(project: Project, name: String, type: Class
} }
} }
/** /**
* Locates a task by [name] and [type], without triggering its creation or configuration. * Locates a task by [name] and [type], without triggering its creation or configuration.
*/ */
internal fun <T : Task> locateTask(project: Project, name: String, type: Class<T>): TaskHolder<T>? = internal fun <T : Task> locateTask(project: Project, name: String, type: Class<T>): TaskHolder<T>? =
if (canLocateTask) { if (canLocateTask) {
try { try {
TaskProviderHolder(name, project.tasks.named(name, type)) TaskProviderHolder(name, project, project.tasks.named(name, type))
} catch (e: UnknownTaskException) { } catch (e: UnknownTaskException) {
null null
} }
@@ -89,7 +98,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
name: String, name: String,
compilation: KotlinCompilation<*>, compilation: KotlinCompilation<*>,
configureAction: (Kotlin2JsCompile) -> Unit configureAction: (Kotlin2JsCompile) -> Unit
): TaskHolder<out Kotlin2JsCompile> { ): TaskHolder<Kotlin2JsCompile> {
val properties = PropertiesProvider(project) val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<Kotlin2JsCompile, Kotlin2JsCompileWithWorkers>(properties) val taskClass = taskOrWorkersTask<Kotlin2JsCompile, Kotlin2JsCompileWithWorkers>(properties)
val result = registerTask(project, name, taskClass) { val result = registerTask(project, name, taskClass) {
@@ -104,7 +113,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
name: String, name: String,
compilation: KotlinCompilation<*>, compilation: KotlinCompilation<*>,
configureAction: (KotlinCompileCommon) -> (Unit) configureAction: (KotlinCompileCommon) -> (Unit)
): TaskHolder<out KotlinCompileCommon> { ): TaskHolder<KotlinCompileCommon> {
val properties = PropertiesProvider(project) val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompileCommon, KotlinCompileCommonWithWorkers>(properties) val taskClass = taskOrWorkersTask<KotlinCompileCommon, KotlinCompileCommonWithWorkers>(properties)
val result = registerTask(project, name, taskClass) { val result = registerTask(project, name, taskClass) {
@@ -115,7 +124,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
} }
open fun configure( open fun configure(
kotlinTaskHolder: TaskHolder<out AbstractKotlinCompile<*>>, kotlinTaskHolder: TaskHolder<AbstractKotlinCompile<*>>,
project: Project, project: Project,
propertiesProvider: PropertiesProvider, propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation<*> compilation: KotlinCompilation<*>
@@ -149,7 +158,7 @@ internal class AndroidTasksProvider(targetName: String) : KotlinTasksProvider(ta
RegexTaskToFriendTaskMapper.Android(targetName) RegexTaskToFriendTaskMapper.Android(targetName)
override fun configure( override fun configure(
kotlinTaskHolder: TaskHolder<out AbstractKotlinCompile<*>>, kotlinTaskHolder: TaskHolder<AbstractKotlinCompile<*>>,
project: Project, project: Project,
propertiesProvider: PropertiesProvider, propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation<*> compilation: KotlinCompilation<*>
@@ -34,31 +34,34 @@ private val Project.allTestsTask: TaskHolder<AggregateTestReport>
private fun cleanTaskName(taskName: String) = "clean" + taskName.capitalize() private fun cleanTaskName(taskName: String) = "clean" + taskName.capitalize()
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
internal fun registerTestTask(task: AbstractTestTask) { internal fun registerTestTask(taskHolder: TaskHolder<AbstractTestTask>) {
val project = task.project val project = taskHolder.project
val allTests = project.allTestsTask.doGetTask() val allTests = project.allTestsTask.doGetTask()
project.tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(task) val tasks = project.tasks
project.tasks.maybeCreate(cleanTaskName(allTests.name)).dependsOn(cleanTaskName(task.name)) tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(taskHolder.name)
tasks.maybeCreate(cleanTaskName(allTests.name)).dependsOn(cleanTaskName(taskHolder.name))
allTests.dependsOn(taskHolder.name)
allTests.dependsOn(task) taskHolder.configure { task ->
allTests.registerTestTask(task) allTests.registerTestTask(task)
project.gradle.taskGraph.whenReady { project.gradle.taskGraph.whenReady {
if (it.hasTask(allTests)) { if (it.hasTask(allTests)) {
// when [allTestsTask] task enabled, test failure should be reported only on [allTestsTask], // when [allTestsTask] task enabled, test failure should be reported only on [allTestsTask],
// not at individual target's test tasks. To do that, we need: // not at individual target's test tasks. To do that, we need:
// - disable all reporting in test tasks // - disable all reporting in test tasks
// - enable [checkFailedTests] on [allTestsTask] // - enable [checkFailedTests] on [allTestsTask]
task.ignoreFailures = true task.ignoreFailures = true
task.reports.html.isEnabled = false task.reports.html.isEnabled = false
task.reports.junitXml.isEnabled = false task.reports.junitXml.isEnabled = false
allTests.checkFailedTests = true allTests.checkFailedTests = true
allTests.ignoreFailures = false allTests.ignoreFailures = false
}
} }
}
ijListenTestTask(task) ijListenTestTask(task)
}
} }