[Gradle] Extract publicationDelegate as Project Stored Property

Previously it was attached to RootSoftwareComponent which has
heavy initialization work, for instance it creates new configurations
upon creation. The `publicationDelegate` is used in
`idOfRootModule` and should not trigger RootSoftwareComponent
initialization. After the change `idOfRootModule` became cross-project
safe. I.e. Project `foo` can call `idOfRootModule` on project `bar`.

^KT-62877 Verification Pending
^KTIJ-27374 Verification Pending
This commit is contained in:
Anton Lakotka
2023-10-25 18:02:06 +02:00
committed by Space Team
parent 6bb33cc8bc
commit 5607ae1bc3
6 changed files with 139 additions and 27 deletions
@@ -338,15 +338,7 @@ private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransf
This double deferral ensures that the value indeed is accessed as late as possible.
Unwrapping the lazy before the buildscript is evaluated will fail with 'future not completed'
*/
val moduleId = if (currentProject.kotlinExtensionOrNull != null) currentProject.future {
KotlinPluginLifecycle.Stage.AfterFinaliseDsl.await()
ModuleIds.idOfRootModule(currentProject)
}.lenient else CompletableFuture<Lazy<ModuleDependencyIdentifier>>().apply {
currentProject.whenEvaluated {
complete(lazy { ModuleIds.idOfRootModule(currentProject) })
}
}.map { it.value }.lenient
val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }.lenient
GranularMetadataTransformation.ProjectData(
path = path,
@@ -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<KotlinTarget>) :
@@ -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))
}
@@ -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<MavenPublication?>
by projectStoredProperty { CompletableFuture() }
internal val Project.kotlinMultiplatformRootPublication: Future<MavenPublication?>
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)
}
@@ -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 <T> Project.withPluginId(
pluginId: String,
whenApplied: () -> T,
whenNotApplied: () -> T,
): Future<T> {
val result = CompletableFuture<T>()
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
}
@@ -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)
}
}
}