diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index 15358ec1b8c..674388b9204 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -338,15 +338,7 @@ private fun Project.collectAllProjectsData(): Map>().apply { - currentProject.whenEvaluated { - complete(lazy { ModuleIds.idOfRootModule(currentProject) }) - } - }.map { it.value }.lenient - + val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }.lenient GranularMetadataTransformation.ProjectData( path = path, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 43047cc9681..1783e0e8779 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPro import org.jetbrains.kotlin.gradle.plugin.await import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinUsageContext.PublishOnlyIf import org.jetbrains.kotlin.gradle.targets.metadata.* +import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.Future import org.jetbrains.kotlin.gradle.utils.future import org.jetbrains.kotlin.gradle.utils.markResolvable @@ -136,9 +137,7 @@ abstract class KotlinSoftwareComponent( } } - // This property is declared in the parent type to allow the usages to reference it without forcing the subtypes to load, - // which is needed for compatibility with older Gradle versions - var publicationDelegate: MavenPublication? = null + val publicationDelegate: MavenPublication? get() = project.kotlinMultiplatformRootPublication.lenient.getOrNull() } class KotlinSoftwareComponentWithCoordinatesAndPublication(project: Project, name: String, kotlinTargets: Iterable) : diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt index 7335745ea77..280e30d8082 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt @@ -12,13 +12,13 @@ import org.gradle.api.artifacts.component.ComponentIdentifier import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.result.ResolvedComponentResult -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.utils.currentBuild +import org.jetbrains.kotlin.gradle.utils.future internal object ModuleIds { fun fromDependency(dependency: Dependency): ModuleDependencyIdentifier = when (dependency) { - is ProjectDependency -> idOfRootModule(dependency.dependencyProject) + is ProjectDependency -> @Suppress("DEPRECATION") idOfRootModule(dependency.dependencyProject) else -> ModuleDependencyIdentifier(dependency.group, dependency.name) } @@ -39,9 +39,13 @@ internal object ModuleIds { else fromComponentId(thisProject, component.id) - fun idOfRootModule(project: Project): ModuleDependencyIdentifier = + // TODO KT-62911: Replace unsafe idOfRootModule with suspendable version idRootModule + @Deprecated("Use suspendable version if possible", replaceWith = ReplaceWith("idOfRootModuleSafe(project)")) + fun idOfRootModule(project: Project): ModuleDependencyIdentifier = project.future { idOfRootModuleSafe(project) }.getOrThrow() + + suspend fun idOfRootModuleSafe(project: Project): ModuleDependencyIdentifier = if (project.multiplatformExtensionOrNull != null) { - val rootPublication = project.multiplatformExtension.rootSoftwareComponent.publicationDelegate + val rootPublication = project.kotlinMultiplatformRootPublication.await() val group = rootPublication?.groupId ?: project.group.toString() val name = rootPublication?.artifactId ?: project.name ModuleDependencyIdentifier(group, name) @@ -53,5 +57,6 @@ internal object ModuleIds { ModuleDependencyIdentifier(null, name) private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier = + @Suppress("DEPRECATION") idOfRootModule(thisProject.project(projectPath)) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt index fdb9ca78f5a..e3b8f96ce41 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt @@ -26,31 +26,45 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask +import org.jetbrains.kotlin.gradle.utils.CompletableFuture +import org.jetbrains.kotlin.gradle.utils.Future +import org.jetbrains.kotlin.gradle.utils.projectStoredProperty import org.jetbrains.kotlin.gradle.utils.whenEvaluated +import org.jetbrains.kotlin.gradle.utils.withPluginId + +private val Project.completableFutureForKotlinMultiplatformRootPublication: CompletableFuture + by projectStoredProperty { CompletableFuture() } +internal val Project.kotlinMultiplatformRootPublication: Future + get() = completableFutureForKotlinMultiplatformRootPublication internal val MultiplatformPublishingSetupAction = KotlinProjectSetupAction { - project.pluginManager.withPlugin("maven-publish") { - if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { - project.extensions.configure(PublishingExtension::class.java) { publishing -> - createRootPublication(project, publishing) - createTargetPublications(project, publishing) + project.withPluginId( + pluginId = "maven-publish", + whenApplied = { + if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { + project.extensions.configure(PublishingExtension::class.java) { publishing -> + createRootPublication(project, publishing).also(completableFutureForKotlinMultiplatformRootPublication::complete) + createTargetPublications(project, publishing) + } + } else { + completableFutureForKotlinMultiplatformRootPublication.complete(null) } - } - project.components.add(project.multiplatformExtension.rootSoftwareComponent) - } + project.components.add(project.multiplatformExtension.rootSoftwareComponent) + }, + whenNotApplied = { completableFutureForKotlinMultiplatformRootPublication.complete(null) } + ) } /** * The root publication that references the platform specific publications as its variants */ -private fun createRootPublication(project: Project, publishing: PublishingExtension) { +private fun createRootPublication(project: Project, publishing: PublishingExtension): MavenPublication { val kotlinSoftwareComponent = project.multiplatformExtension.rootSoftwareComponent - publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply { + return publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply { from(kotlinSoftwareComponent) (this as MavenPublicationInternal).publishWithOriginalFileName() - kotlinSoftwareComponent.publicationDelegate = this@apply addKotlinToolingMetadataArtifactIfNeeded(project) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt new file mode 100644 index 00000000000..26db825f196 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/PluginManagerUtils.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2023 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.utils + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterEvaluateBuildscript +import org.jetbrains.kotlin.gradle.plugin.launchInStage + +/** + * Executes the given block of code if the Gradle Plugin with [pluginId] is applied to the project. Otherwise, + * executes another block of code. + */ +internal fun Project.withPluginId( + pluginId: String, + whenApplied: () -> T, + whenNotApplied: () -> T, +): Future { + val result = CompletableFuture() + pluginManager.withPlugin(pluginId) { result.complete(whenApplied()) } + // All plugins must be applied during evaluation of build script + launchInStage(AfterEvaluateBuildscript) { if (!result.isCompleted) result.complete(whenNotApplied()) } + + return result +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt new file mode 100644 index 00000000000..fab2eb09829 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PluginManagerUtilTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2023 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.unitTests + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterEvaluateBuildscript +import org.jetbrains.kotlin.gradle.plugin.currentKotlinPluginLifecycle +import org.jetbrains.kotlin.gradle.util.buildProject +import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP +import org.jetbrains.kotlin.gradle.util.kotlin +import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest +import org.jetbrains.kotlin.gradle.utils.withPluginId +import kotlin.test.Test +import kotlin.test.assertTrue + +class PluginManagerUtilTest { + + private fun buildSampleProject(action: Project.() -> Unit = {}) = buildProjectWithMPP { + action() + + kotlin { + jvm() + linuxX64() + } + } + + @Test + fun `test - withPluginId - when plugin applied`() { + val project = buildProject { + plugins.apply("maven-publish") + } + val future = project.withPluginId( + pluginId = "maven-publish", + whenApplied = { true }, + whenNotApplied = { false } + ) + + assertTrue(future.getOrThrow()) + } + + @Test + fun `test - withPluginId - call withPluginId before plugin is applied`() { + val project = buildProject {} + val future = project.withPluginId( + pluginId = "maven-publish", + whenApplied = { true }, + whenNotApplied = { false } + ) + project.plugins.apply("maven-publish") + + assertTrue(future.getOrThrow()) + } + + @Test + fun `test - withPluginId - plugin not applied`() { + val project = buildSampleProject() + + val future = project.withPluginId( + pluginId = "some-plugin-id", + whenApplied = { false }, + whenNotApplied = { true }, + ) + + project.runLifecycleAwareTest { + assertTrue(future.await()) + val currentStage = currentKotlinPluginLifecycle().stage + assertTrue(currentStage <= AfterEvaluateBuildscript) + } + } +} \ No newline at end of file