diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/CommonizerTaskTests.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/CommonizerTaskTests.kt new file mode 100644 index 00000000000..dae11e49302 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/CommonizerTaskTests.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName") + +package org.jetbrains.kotlin.gradle + +import org.gradle.api.internal.project.ProjectInternal +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation +import kotlin.test.Test + +class CommonizerTaskTests { + + private val rootProject = ProjectBuilder.builder().build() as ProjectInternal + private val subproject = ProjectBuilder.builder().withName("subproject").withParent(rootProject).build() as ProjectInternal + + private val kotlin = subproject.applyMultiplatformPlugin().apply { + linuxX64() + linuxArm64() + + targets.flatMap { it.compilations } + .filterIsInstance() + .forEach { compilation -> compilation.cinterops.create("dummy") } + } + + init { + rootProject.enableCInteropCommonization() + subproject.enableCInteropCommonization() + + rootProject.evaluate() + subproject.evaluate() + } + + @Test + fun `test runCommonizer task`() { + subproject.tasks.getByName("runCommonizer") + .assertDependsOn(subproject.tasks.getByName("commonize")) + + /* + Since commonizing the native distribution is done on the root project, + we can also expect that the umbrella tasks are present there as well! + */ + rootProject.tasks.getByName("runCommonizer") + .assertDependsOn(rootProject.tasks.getByName("commonize")) + } + + @Test + fun `test commonizeNativeDistributionTask`() { + val commonizeNativeDistributionTaskName = "commonizeNativeDistribution" + subproject.assertContainsNoTaskWithName(commonizeNativeDistributionTaskName) + + /* Native Distribution Commonization is only done on the root project */ + val rootProjectCommonizeNativeDistributionTask = rootProject.assertContainsTaskWithName(commonizeNativeDistributionTaskName) + rootProject.tasks.getByName("commonize").assertDependsOn(rootProjectCommonizeNativeDistributionTask) + subproject.tasks.getByName("commonize").assertDependsOn(rootProjectCommonizeNativeDistributionTask) + } + + @Test + fun `test commonizeCInteropTask`() { + val commonizeCInteropTaskName = "commonizeCInterop" + val commonizeCInteropTask = subproject.assertContainsTaskWithName(commonizeCInteropTaskName) + subproject.tasks.getByName("commonize").assertDependsOn(commonizeCInteropTask) + rootProject.assertContainsNoTaskWithName(commonizeCInteropTaskName) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/assertions.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/assertions.kt new file mode 100644 index 00000000000..d794b446ed4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/assertions.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle + +import org.gradle.api.Project +import org.gradle.api.Task +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion +import kotlin.test.assertEquals +import kotlin.test.fail + +fun Task.isDependsOn(other: Task): Boolean { + return other in this.taskDependencies.getDependencies(null) +} + +fun Task.assertDependsOn(other: Task) { + if (!isDependsOn(other)) { + fail("Expected ${this.path} to depend on ${other.path}") + } +} + +fun Task.assertNotDependsOn(other: Task) { + if (isDependsOn(other)) { + fail("Expected ${this.path} to *not* depend on ${other.path}") + } +} + +fun Task.assertTaskDependenciesEquals(dependencies: Set) { + assertEquals( + dependencies, this.taskDependencies.getDependencies(null), + "Expected given set of taskDependencies for task ${this.path}" + ) +} + +fun Project.assertContainsTaskWithName(taskName: String): Task { + this.getKotlinPluginVersion() + return project.tasks.findByName(taskName) + ?: fail("Expected task with name $taskName in project ${this.path}") +} + +fun Project.assertContainsNoTaskWithName(taskName: String) { + if (taskName in tasks.names) { + fail("Expected *no* task with name $taskName in project ${this.path}") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/PrepareKotlinIdeaImportTaskTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/PrepareKotlinIdeaImportTaskTest.kt new file mode 100644 index 00000000000..7ac15ba8f4d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/PrepareKotlinIdeaImportTaskTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName") +@file:OptIn(Idea222Api::class) + +package org.jetbrains.kotlin.gradle.ide + +import org.gradle.api.internal.project.ProjectInternal +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.kotlin.gradle.applyMultiplatformPlugin +import org.jetbrains.kotlin.gradle.assertDependsOn +import org.jetbrains.kotlin.gradle.assertTaskDependenciesEquals +import org.jetbrains.kotlin.gradle.enableCInteropCommonization +import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api +import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn +import org.jetbrains.kotlin.gradle.plugin.ide.prepareKotlinIdeaImportTask +import org.jetbrains.kotlin.gradle.utils.UnsafeApi +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@OptIn(UnsafeApi::class) +class PrepareKotlinIdeaImportTaskTest { + + private companion object { + const val prepareKotlinIdeaImportTaskName = "prepareKotlinIdeaImport" + } + + @Test + fun `test prepareKotlinIdeaImport task depending on commonizer and cinterop tasks`() { + val rootProject = ProjectBuilder.builder().build() as ProjectInternal + val subproject = ProjectBuilder.builder().withParent(rootProject).withName("subproject").build() as ProjectInternal + rootProject.enableCInteropCommonization() + subproject.enableCInteropCommonization() + + val kotlin = subproject.applyMultiplatformPlugin() + kotlin.linuxX64() + kotlin.linuxArm64() + + rootProject.evaluate() + subproject.evaluate() + + assertTrue( + prepareKotlinIdeaImportTaskName in subproject.tasks.names, + "Expected a task named '$prepareKotlinIdeaImportTaskName' to be registered" + ) + + subproject.prepareKotlinIdeaImportTask.get().assertDependsOn(subproject.tasks.getByName("commonize")) + subproject.prepareKotlinIdeaImportTask.get().assertDependsOn(subproject.tasks.getByName("copyCommonizeCInteropForIde")) + } + + @Test + fun `test declaring dependsOnIdeaImport`() { + val project = ProjectBuilder.builder().build() + + assertNull( + project.tasks.findByName(prepareKotlinIdeaImportTaskName), + "Expected task $prepareKotlinIdeaImportTaskName to not be registered when no task declares a dependency" + ) + + val testTaskA = project.tasks.register("testTaskA") + project.ideaImportDependsOn(testTaskA) + + val prepareKotlinIdeaImportTask = assertNotNull( + project.tasks.findByName(prepareKotlinIdeaImportTaskName), + "Expected task $prepareKotlinIdeaImportTaskName to be registered after $testTaskA declared dependency" + ) + + prepareKotlinIdeaImportTask.assertTaskDependenciesEquals(setOf(testTaskA.get())) + + val testTaskB = project.tasks.register("testTaskB") + project.ideaImportDependsOn(testTaskB) + + prepareKotlinIdeaImportTask.assertTaskDependenciesEquals(setOf(testTaskA.get(), testTaskB.get())) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/Idea222Api.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/Idea222Api.kt new file mode 100644 index 00000000000..da21bb81b4d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/Idea222Api.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.ide + +@RequiresOptIn("This API is only supported by IDEA 2022.2 or higher", level = RequiresOptIn.Level.ERROR) +internal annotation class Idea222Api diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/PrepareKotlinIdeaImportTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/PrepareKotlinIdeaImportTask.kt new file mode 100644 index 00000000000..56d8dc541a6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/PrepareKotlinIdeaImportTask.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin.ide + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.tasks.dependsOn +import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask +import org.jetbrains.kotlin.gradle.utils.UnsafeApi + +@Idea222Api +internal fun Project.ideaImportDependsOn(task: TaskProvider<*>) { + @OptIn(UnsafeApi::class) + prepareKotlinIdeaImportTask.dependsOn(task) +} + +@UnsafeApi("Use 'ideaImportDependsOn' instead") +internal val Project.prepareKotlinIdeaImportTask: TaskProvider + get() = locateOrRegisterTask( + "prepareKotlinIdeaImport", + configureTask = { + description = "Umbrella for all tasks required to run before IDEA/Gradle import" + } + ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt index 2b97ccdc5ab..513ebe2f6cd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt @@ -56,4 +56,4 @@ import org.jetbrains.kotlin.gradle.plugin.getTestedVariantData val compileJavaTaskProvider: TaskProvider? get() = androidVariant.getJavaTaskProvider() - } \ No newline at end of file + } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt index c3c79dbc17c..71121106390 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt @@ -84,7 +84,7 @@ abstract class AbstractKotlinNativeTargetPreset( createTargetConfigurator().configureTarget(result) SingleActionPerProject.run(project, "setUpKotlinNativePlatformDependencies") { - project.gradle.projectsEvaluated { + project.whenEvaluated { project.setupKotlinNativePlatformDependencies() } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt index eb35b863d6c..2db6f94a1d4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/cocoapods/KotlinCocoapodsPlugin.kt @@ -18,7 +18,10 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.addExtension import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency -import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.* +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.Git +import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension.CocoapodsDependency.PodLocation.Url +import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api +import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn import org.jetbrains.kotlin.gradle.plugin.mpp.Framework import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType @@ -541,7 +544,8 @@ open class KotlinCocoapodsPlugin : Plugin { kotlinExtension: KotlinMultiplatformExtension ) { val podInstallTaskProvider = project.tasks.named(POD_INSTALL_TASK_NAME, PodInstallTask::class.java) - project.tasks.register(POD_IMPORT_TASK_NAME) { + val podImportTaskProvider = project.tasks.register(POD_IMPORT_TASK_NAME) { + it.group = TASK_GROUP it.description = "Called on Gradle sync, depends on Cinterop tasks for every used pod" it.dependsOn(podInstallTaskProvider) @@ -553,6 +557,10 @@ open class KotlinCocoapodsPlugin : Plugin { } } } + + /* Older IDEs will explicitly call 'podImport' instead */ + @OptIn(Idea222Api::class) + project.ideaImportDependsOn(podImportTaskProvider) } private fun configureTestBinaries(project: Project, cocoapodsExtension: CocoapodsExtension) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt index 6871db09e44..e5288f3ce9e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt @@ -13,6 +13,8 @@ import org.gradle.api.file.FileCollection import org.gradle.api.tasks.* import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api +import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets @@ -57,7 +59,14 @@ internal fun Project.locateOrRegisterCInteropMetadataDependencyTransformationTas return locateOrRegisterTask( lowerCamelCaseName("transform", sourceSet.name, "CInteropDependenciesMetadataForIde"), - invokeWhenRegistered = { commonizeTask.dependsOn(this) }, + invokeWhenRegistered = { + @OptIn(Idea222Api::class) + ideaImportDependsOn(this) + + /* Older IDEs will still enqueue 'runCommonizer' task before import */ + @Suppress("deprecation") + runCommonizerTask.dependsOn(this) + }, args = listOf( sourceSet, /* outputDirectory = */ diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTasks.kt index 5eb2a47ca98..0f97176a486 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTasks.kt @@ -11,6 +11,8 @@ import org.gradle.api.tasks.Delete import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.internal.isInIdeaSync import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider +import org.jetbrains.kotlin.gradle.plugin.ide.Idea222Api +import org.jetbrains.kotlin.gradle.plugin.ide.ideaImportDependsOn import org.jetbrains.kotlin.gradle.plugin.whenEvaluated import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask @@ -26,7 +28,14 @@ internal val Project.isOptimisticNumberCommonizationEnabled: Boolean internal val Project.commonizeTask: TaskProvider get() = locateOrRegisterTask( "commonize", - invokeWhenRegistered = { @Suppress("deprecation") runCommonizerTask.dependsOn(this) }, + invokeWhenRegistered = { + @OptIn(Idea222Api::class) + ideaImportDependsOn(this) + + /* 'runCommonizer' is called by older IDEs during import */ + @Suppress("deprecation") + runCommonizerTask.dependsOn(this) + }, configureTask = { group = "interop" description = "Aggregator task for all c-interop & Native distribution commonizer tasks" @@ -40,7 +49,6 @@ internal val Project.commonizeTask: TaskProvider internal val Project.runCommonizerTask: TaskProvider get() = locateOrRegisterTask( "runCommonizer", - invokeWhenRegistered = { dependsOn(commonizeTask) }, configureTask = { group = "interop" description = "[Deprecated: Use 'commonize' instead]" @@ -74,7 +82,15 @@ internal val Project.copyCommonizeCInteropForIdeTask: TaskProvider 'commonize' tasks */ + if (isInIdeaSync) { + commonizeTask.dependsOn(this) + } + }, configureTask = { group = "interop" description = "Copies the output of ${commonizeCInteropTask.get().name} into " + @@ -90,7 +106,11 @@ internal val Project.commonizeNativeDistributionTask: TaskProvider = listFiles().orEmpty() internal val Project.isNativeDependencyPropagationEnabled: Boolean get() = PropertiesProvider(this).nativeDependencyPropagation ?: true -//for reflection call from KotlinCommonizerModelBuilder -// DO NOT REFACTOR THIS FUNCTION! -// TODO SELLMAIR: Resolve fragile reflection call from IDE plugin -@JvmOverloads +/** + * Function signature needs to be kept stable since this is used during import + * in IDEs (KotlinCommonizerModelBuilder) < 222 + * + * IDEs >= will use the [ideaImportDependsOn] infrastructure + */ @JvmName("isAllowCommonizer") -internal fun Project.isAllowCommonizer( - kotlinVersion: String = getKotlinPluginVersion() -): Boolean { +internal fun Project.isAllowCommonizer(): Boolean { assert(state.executed) { "'isAllowCommonizer' can only be called after project evaluation" } multiplatformExtensionOrNull ?: return false - //register commonizer only for 1.4+, only for HMPP projects - return compareVersionNumbers(kotlinVersion, "1.4") >= 0 - && multiplatformExtension.targets.any { it.platformType == KotlinPlatformType.native } + return multiplatformExtension.targets.any { it.platformType == KotlinPlatformType.native } && isKotlinGranularMetadataEnabled && !isNativeDependencyPropagationEnabled // temporary fix: turn on commonizer only when native deps propagation is disabled } 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 16cf8a124be..582e50b42f1 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 @@ -58,6 +58,8 @@ internal fun Project.registerTask( internal fun TaskProvider<*>.dependsOn(other: TaskProvider<*>) = configure { it.dependsOn(other) } +internal fun TaskProvider<*>.dependsOn(other: Task) = configure { it.dependsOn(other) } + internal inline fun TaskCollection.withType(): TaskCollection = withType(S::class.java) /**