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.plugin.mpp.*
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.utils.lowerCamelCaseName
import java.util.concurrent.Callable
@@ -272,7 +273,7 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
isVisible = false
isCanBeResolved = true // Needed for IDE import
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 {
@@ -361,23 +362,27 @@ abstract class KotlinTargetConfigurator<KotlinCompilationType : KotlinCompilatio
}
override fun configureTest(target: KotlinOnlyTarget<KotlinCompilationType>) {
val testCompilation = target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME) as? KotlinCompilationToRunnableFiles<*>
?: return // Otherwise, there is no runtime classpath
val testCompilation =
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 {
registerTestTask(this)
val testTaskName = lowerCamelCaseName(target.disambiguationClassifier, testTaskNameSuffix)
val testTask = target.project.createOrRegisterTask<KotlinJvmTest>(testTaskName) { testTask ->
testTask.targetName = target.disambiguationClassifier
}
targetName = target.disambiguationClassifier
project.afterEvaluate {
// use afterEvaluate to override the JavaPlugin defaults for Test tasks
conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs }
conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles }
description = "Runs the unit tests."
group = JavaBasePlugin.VERIFICATION_GROUP
target.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(this@apply)
testTask.project.afterEvaluate {
// use afterEvaluate to override the JavaPlugin defaults for Test tasks
testTask.configure { testTask ->
testTask.conventionMapping.map("testClassesDirs") { testCompilation.output.classesDirs }
testTask.conventionMapping.map("classpath") { testCompilation.runtimeDependencyFiles }
testTask.description = "Runs the unit tests."
testTask.group = JavaBasePlugin.VERIFICATION_GROUP
testTask.project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(testTask)
}
}
registerTestTask(testTask)
}
private fun addJar(configuration: Configuration, jarArtifact: PublishArtifact) {
@@ -5,9 +5,16 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
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 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.
* 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.
@@ -16,14 +16,15 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider
class TaskProviderHolder<T : Task>(
private val name: String,
class TaskProviderHolder<out T : Task>(
override val name: String,
override val project: Project,
private val provider: TaskProvider<T>
) : TaskHolder<T> {
override fun getTaskOrProvider(): Any = provider
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.testing.KotlinJsTest
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.registerTestTask
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -40,7 +40,7 @@ internal class KotlinJsCompilationTestsConfigurator(
// apply plugin (cannot do it lazy)
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.dependsOn(compileTestKotlin2Js, nodeJs.nodeJsSetupTask)
@@ -54,7 +54,8 @@ internal class KotlinJsCompilationTestsConfigurator(
testJs.nodeModulesToLoad.add(compileTestKotlin2Js.outputFile.name)
testJs.configureConventions()
registerTestTask(testJs)
}
registerTestTask(testJs)
}
}
@@ -129,24 +129,23 @@ open class KotlinNativeTargetConfigurator(
val taskName = binary.runTaskName ?: return
if (binary.isDefaultTestExecutable) {
tasks.create(taskName, KotlinNativeTest::class.java).apply {
tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this)
val testTask = createOrRegisterTask<KotlinNativeTest>(taskName) { testTask ->
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
description = "Executes Kotlin/Native unit tests for target ${binary.target.name}."
targetName = binary.compilation.target.targetName
testTask.enabled = binary.target.konanTarget.isCurrentHost
enabled = binary.target.konanTarget.isCurrentHost
testTask.executable = binary.outputFile
testTask.workingDir = project.projectDir.absolutePath
executable = binary.outputFile
workingDir = project.projectDir.absolutePath
testTask.onlyIf { binary.outputFile.exists() }
testTask.dependsOn(binary.linkTaskName)
onlyIf { binary.outputFile.exists() }
dependsOn(binary.linkTaskName)
configureConventions()
registerTestTask(this)
testTask.configureConventions()
}
registerTestTask(testTask)
} else {
tasks.create(taskName, Exec::class.java).apply {
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]
* 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) {
TaskProviderHolder(name, project.tasks.register(name, type) { with(it, body) })
TaskProviderHolder(name, project, project.tasks.register(name, type) { with(it, body) })
} else {
val result = LegacyTaskHolder(project.tasks.create(name, type))
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.
*/
internal fun <T : Task> locateTask(project: Project, name: String, type: Class<T>): TaskHolder<T>? =
if (canLocateTask) {
try {
TaskProviderHolder(name, project.tasks.named(name, type))
TaskProviderHolder(name, project, project.tasks.named(name, type))
} catch (e: UnknownTaskException) {
null
}
@@ -89,7 +98,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
name: String,
compilation: KotlinCompilation<*>,
configureAction: (Kotlin2JsCompile) -> Unit
): TaskHolder<out Kotlin2JsCompile> {
): TaskHolder<Kotlin2JsCompile> {
val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<Kotlin2JsCompile, Kotlin2JsCompileWithWorkers>(properties)
val result = registerTask(project, name, taskClass) {
@@ -104,7 +113,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
name: String,
compilation: KotlinCompilation<*>,
configureAction: (KotlinCompileCommon) -> (Unit)
): TaskHolder<out KotlinCompileCommon> {
): TaskHolder<KotlinCompileCommon> {
val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompileCommon, KotlinCompileCommonWithWorkers>(properties)
val result = registerTask(project, name, taskClass) {
@@ -115,7 +124,7 @@ internal open class KotlinTasksProvider(val targetName: String) {
}
open fun configure(
kotlinTaskHolder: TaskHolder<out AbstractKotlinCompile<*>>,
kotlinTaskHolder: TaskHolder<AbstractKotlinCompile<*>>,
project: Project,
propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation<*>
@@ -149,7 +158,7 @@ internal class AndroidTasksProvider(targetName: String) : KotlinTasksProvider(ta
RegexTaskToFriendTaskMapper.Android(targetName)
override fun configure(
kotlinTaskHolder: TaskHolder<out AbstractKotlinCompile<*>>,
kotlinTaskHolder: TaskHolder<AbstractKotlinCompile<*>>,
project: Project,
propertiesProvider: PropertiesProvider,
compilation: KotlinCompilation<*>
@@ -34,31 +34,34 @@ private val Project.allTestsTask: TaskHolder<AggregateTestReport>
private fun cleanTaskName(taskName: String) = "clean" + taskName.capitalize()
@Suppress("UnstableApiUsage")
internal fun registerTestTask(task: AbstractTestTask) {
val project = task.project
internal fun registerTestTask(taskHolder: TaskHolder<AbstractTestTask>) {
val project = taskHolder.project
val allTests = project.allTestsTask.doGetTask()
project.tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(task)
project.tasks.maybeCreate(cleanTaskName(allTests.name)).dependsOn(cleanTaskName(task.name))
val tasks = project.tasks
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)
allTests.registerTestTask(task)
taskHolder.configure { task ->
allTests.registerTestTask(task)
project.gradle.taskGraph.whenReady {
if (it.hasTask(allTests)) {
// when [allTestsTask] task enabled, test failure should be reported only on [allTestsTask],
// not at individual target's test tasks. To do that, we need:
// - disable all reporting in test tasks
// - enable [checkFailedTests] on [allTestsTask]
project.gradle.taskGraph.whenReady {
if (it.hasTask(allTests)) {
// when [allTestsTask] task enabled, test failure should be reported only on [allTestsTask],
// not at individual target's test tasks. To do that, we need:
// - disable all reporting in test tasks
// - enable [checkFailedTests] on [allTestsTask]
task.ignoreFailures = true
task.reports.html.isEnabled = false
task.reports.junitXml.isEnabled = false
task.ignoreFailures = true
task.reports.html.isEnabled = false
task.reports.junitXml.isEnabled = false
allTests.checkFailedTests = true
allTests.ignoreFailures = false
allTests.checkFailedTests = true
allTests.ignoreFailures = false
}
}
}
ijListenTestTask(task)
ijListenTestTask(task)
}
}