From b96f26cc77c41d12c4b0f7ff0e88288ca63df7e9 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Sat, 13 Apr 2019 10:26:20 +0300 Subject: [PATCH] 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. --- .../gradle/plugin/KotlinTargetConfigurator.kt | 33 ++++++++------- .../kotlin/gradle/plugin/LegacyTaskHolder.kt | 9 +++- .../kotlin/gradle/plugin/TaskHolder.kt | 4 +- .../gradle/plugin/TaskProviderHolder.kt | 7 ++-- .../KotlinJsCompilationTestsConfigurator.kt | 7 ++-- .../native/KotlinNativeTargetConfigurator.kt | 25 ++++++----- .../kotlin/gradle/tasks/TasksProvider.kt | 23 +++++++---- .../gradle/testing/internal/testsRegistry.kt | 41 ++++++++++--------- 8 files changed, 88 insertions(+), 61 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index 160f6fc045a..8d40c68ed4e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -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 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) { - 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(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) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyTaskHolder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyTaskHolder.kt index b70d8ce6c24..1418e72d0de 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyTaskHolder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/LegacyTaskHolder.kt @@ -5,9 +5,16 @@ package org.jetbrains.kotlin.gradle.plugin +import org.gradle.api.Project import org.gradle.api.Task -class LegacyTaskHolder(private val task: T) : TaskHolder { +class LegacyTaskHolder(private val task: T) : TaskHolder { + override val project: Project + get() = task.project + + override val name: String + get() = task.name + override fun doGetTask() = task override fun getTaskOrProvider(): Any = task diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskHolder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskHolder.kt index 9fe5fd2cc22..c1fcb0e312d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskHolder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskHolder.kt @@ -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 { +interface TaskHolder { + val project: Project + val name: String /** * Returns Task itself if task was created or TaskProvider if task was registered. diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskProviderHolder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskProviderHolder.kt index f39219344c8..04816d4254b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskProviderHolder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/TaskProviderHolder.kt @@ -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( - private val name: String, +class TaskProviderHolder( + override val name: String, + override val project: Project, private val provider: TaskProvider ) : TaskHolder { - override fun getTaskOrProvider(): Any = provider override fun doGetTask(): T = provider.get() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt index 419e1c1a71b..b9366b82d3e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt @@ -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(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) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index 99fc67ac858..2c4180af4de 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -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(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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt index cdb3834b6e5..d0f00981dc8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt @@ -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 registerTask(project: Project, name: String, type: Class, body: (T) -> (Unit)): TaskHolder { +@JvmName("registerTaskOld") +@Deprecated("please use Project.createOrRegisterTask", ReplaceWith("project.createOrRegisterTask(name, body)")) +internal fun registerTask(project: Project, name: String, type: Class, body: (T) -> (Unit)): TaskHolder = + project.createOrRegisterTask(name, type, body) + +internal inline fun Project.createOrRegisterTask(name: String, noinline body: (T) -> (Unit)): TaskHolder = + createOrRegisterTask(name, T::class.java, body) + +internal fun Project.createOrRegisterTask(name: String, type: Class, body: (T) -> (Unit)): TaskHolder { 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 registerTask(project: Project, name: String, type: Class } } + /** * Locates a task by [name] and [type], without triggering its creation or configuration. */ internal fun locateTask(project: Project, name: String, type: Class): TaskHolder? = 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 { + ): TaskHolder { val properties = PropertiesProvider(project) val taskClass = taskOrWorkersTask(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 { + ): TaskHolder { val properties = PropertiesProvider(project) val taskClass = taskOrWorkersTask(properties) val result = registerTask(project, name, taskClass) { @@ -115,7 +124,7 @@ internal open class KotlinTasksProvider(val targetName: String) { } open fun configure( - kotlinTaskHolder: TaskHolder>, + kotlinTaskHolder: TaskHolder>, 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>, + kotlinTaskHolder: TaskHolder>, project: Project, propertiesProvider: PropertiesProvider, compilation: KotlinCompilation<*> diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt index 4430332dbd9..895a920e694 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt @@ -34,31 +34,34 @@ private val Project.allTestsTask: TaskHolder private fun cleanTaskName(taskName: String) = "clean" + taskName.capitalize() @Suppress("UnstableApiUsage") -internal fun registerTestTask(task: AbstractTestTask) { - val project = task.project +internal fun registerTestTask(taskHolder: TaskHolder) { + 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) + } } \ No newline at end of file