[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. 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' Unwrapping the lazy before the buildscript is evaluated will fail with 'future not completed'
*/ */
val moduleId = if (currentProject.kotlinExtensionOrNull != null) currentProject.future { val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }.lenient
KotlinPluginLifecycle.Stage.AfterFinaliseDsl.await()
ModuleIds.idOfRootModule(currentProject)
}.lenient else CompletableFuture<Lazy<ModuleDependencyIdentifier>>().apply {
currentProject.whenEvaluated {
complete(lazy { ModuleIds.idOfRootModule(currentProject) })
}
}.map { it.value }.lenient
GranularMetadataTransformation.ProjectData( GranularMetadataTransformation.ProjectData(
path = path, 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.await
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinUsageContext.PublishOnlyIf import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinUsageContext.PublishOnlyIf
import org.jetbrains.kotlin.gradle.targets.metadata.* 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.future import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.gradle.utils.markResolvable 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, val publicationDelegate: MavenPublication? get() = project.kotlinMultiplatformRootPublication.lenient.getOrNull()
// which is needed for compatibility with older Gradle versions
var publicationDelegate: MavenPublication? = null
} }
class KotlinSoftwareComponentWithCoordinatesAndPublication(project: Project, name: String, kotlinTargets: Iterable<KotlinTarget>) : 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.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedComponentResult 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.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.utils.currentBuild import org.jetbrains.kotlin.gradle.utils.currentBuild
import org.jetbrains.kotlin.gradle.utils.future
internal object ModuleIds { internal object ModuleIds {
fun fromDependency(dependency: Dependency): ModuleDependencyIdentifier = when (dependency) { 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) else -> ModuleDependencyIdentifier(dependency.group, dependency.name)
} }
@@ -39,9 +39,13 @@ internal object ModuleIds {
else else
fromComponentId(thisProject, component.id) 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) { if (project.multiplatformExtensionOrNull != null) {
val rootPublication = project.multiplatformExtension.rootSoftwareComponent.publicationDelegate val rootPublication = project.kotlinMultiplatformRootPublication.await()
val group = rootPublication?.groupId ?: project.group.toString() val group = rootPublication?.groupId ?: project.group.toString()
val name = rootPublication?.artifactId ?: project.name val name = rootPublication?.artifactId ?: project.name
ModuleDependencyIdentifier(group, name) ModuleDependencyIdentifier(group, name)
@@ -53,5 +57,6 @@ internal object ModuleIds {
ModuleDependencyIdentifier(null, name) ModuleDependencyIdentifier(null, name)
private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier = private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier =
@Suppress("DEPRECATION")
idOfRootModule(thisProject.project(projectPath)) 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.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.tooling.buildKotlinToolingMetadataTask 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.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 { internal val MultiplatformPublishingSetupAction = KotlinProjectSetupAction {
project.pluginManager.withPlugin("maven-publish") { project.withPluginId(
if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) { pluginId = "maven-publish",
project.extensions.configure(PublishingExtension::class.java) { publishing -> whenApplied = {
createRootPublication(project, publishing) if (project.kotlinPropertiesProvider.createDefaultMultiplatformPublications) {
createTargetPublications(project, publishing) 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 * 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 val kotlinSoftwareComponent = project.multiplatformExtension.rootSoftwareComponent
publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply { return publishing.publications.create("kotlinMultiplatform", MavenPublication::class.java).apply {
from(kotlinSoftwareComponent) from(kotlinSoftwareComponent)
(this as MavenPublicationInternal).publishWithOriginalFileName() (this as MavenPublicationInternal).publishWithOriginalFileName()
kotlinSoftwareComponent.publicationDelegate = this@apply
addKotlinToolingMetadataArtifactIfNeeded(project) 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)
}
}
}