Skip to content

Commit

Permalink
[Gradle] Pass actual root module publication coordinates to GMDT
Browse files Browse the repository at this point in the history
So it matches with PSM content.

To illustrate consider two projects App and Lib. App depends on Lib.
When lib has configured Maven Publication with custom GAV coordinates.
They correctly appears in its PSM file this happens in
[ExportRootModuleCoordinates].

However, when another project (App) resolves dependency to Lib
it will get its default gav coordinates.
And this will cause inconsistencies during
Metadata Dependencies Transformations because Lib's PSM and resolved
graph will have different GAVs.

To solve this it is necessary to use correct root module GAV.

^KT-73620 Verification Pending
  • Loading branch information
antohaby authored and Space Team committed Jan 7, 2025
1 parent f7150fe commit 71ddc3d
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
*/
package org.jetbrains.kotlin.gradle.mpp

import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.KmpIsolatedProjectsSupport
import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceText
import org.jetbrains.kotlin.gradle.util.testResolveAllConfigurations
import org.jetbrains.kotlin.test.TestMetadata
import org.jetbrains.kotlin.utils.addToStdlib.countOccurrencesOf
import org.junit.jupiter.params.ParameterizedTest
import kotlin.io.path.createDirectories
import kotlin.io.path.writeText
import kotlin.test.assertEquals
import kotlin.test.assertTrue

Expand Down Expand Up @@ -129,4 +135,77 @@ class MppMetadataResolutionIT : KGPBaseTest() {
}
}
}

@GradleTest
@GradleTestVersions
@ParameterizedTest(name = "{0} isolated projects support: {1} {displayName}")
@GradleTestExtraStringArguments("ENABLE", "DISABLE")
fun testCustomGroupForMppPublicationInTransitiveDependencies(
gradleVersion: GradleVersion,
kmpIsolatedProjectsSupport: String,
) {
var buildOptions = defaultBuildOptions.copy(
kmpIsolatedProjectsSupport = KmpIsolatedProjectsSupport.valueOf(kmpIsolatedProjectsSupport)
)

fun GradleProject.configureKotlinMultiplatform() {
buildScriptInjection {
project.group = "default.group"

kotlinMultiplatform.jvm()
kotlinMultiplatform.linuxX64()
}
}

project("base-kotlin-multiplatform-library", gradleVersion) {
includeOtherProjectAsSubmodule("base-kotlin-multiplatform-library", newSubmoduleName = "lib1") {
configureKotlinMultiplatform()
buildScriptInjection {
project.plugins.apply("maven-publish")
val publishing = project.extensions.getByName("publishing") as PublishingExtension
publishing.publications.withType(MavenPublication::class.java).configureEach {
if (it.name == "kotlinMultiplatform") {
it.groupId = "custom.group"
it.artifactId = "custom-artifact-id"
}
}
}

kotlinSourcesDir("commonMain")
.also { it.createDirectories() }
.resolve("Lib1.kt")
.writeText("interface Lib1")
}

includeOtherProjectAsSubmodule("base-kotlin-multiplatform-library", newSubmoduleName = "lib2") {
configureKotlinMultiplatform()
buildScriptInjection {
kotlinMultiplatform.sourceSets.getByName("commonMain").dependencies {
api(project(":lib1"))
}
}

kotlinSourcesDir("commonMain")
.also { it.createDirectories() }
.resolve("Lib2.kt")
.writeText("interface Lib2 : Lib1")
}

includeOtherProjectAsSubmodule("base-kotlin-multiplatform-library", newSubmoduleName = "lib3") {
configureKotlinMultiplatform()
buildScriptInjection {
kotlinMultiplatform.sourceSets.getByName("commonMain").dependencies {
api(project(":lib2"))
}
}

kotlinSourcesDir("commonMain")
.also { it.createDirectories() }
.resolve("Lib3.kt")
.writeText("class Lib3 : Lib2, Lib1")
}

build(":lib3:metadataCommonMainClasses", buildOptions = buildOptions)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,9 @@ class TestProject(
otherProjectName: String,
pathPrefix: String = "",
newSubmoduleName: String = otherProjectName,
isKts: Boolean = false,
isKts: Boolean = settingsGradleKts.exists(),
localRepoDir: Path? = null,
configure: GradleProject.() -> Unit = {},
) {
val otherProjectPath = if (pathPrefix.isEmpty()) {
otherProjectName.testProjectPath
Expand All @@ -443,6 +444,7 @@ class TestProject(
)

localRepoDir?.let { subProject(newSubmoduleName).configureLocalRepository(localRepoDir) }
subProject(newSubmoduleName).configure()
}

fun includeOtherProjectAsIncludedBuild(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
kotlin("multiplatform")
}

// To be filled by test code using buildScriptInjection API
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ internal val Project.kotlinSecondaryVariantsDataSharing: KotlinSecondaryVariants
internal interface KotlinShareableDataAsSecondaryVariant

/**
* Service to share configuration state between Kotlin Projects as Configuration Secondary Variants
* Service to share configuration state between Kotlin Projects as Configuration Secondary Variants.
* So this data can be consumed during Task Execution. And is trackable via Task Inputs.
*
* This is an alternative to BuildServices that are not trackable as Task Inputs.
*
*/
internal class KotlinSecondaryVariantsDataSharing(
private val project: Project,
Expand Down Expand Up @@ -102,6 +106,12 @@ internal class KotlinSecondaryVariantsDataSharing(
private val kotlinProjectSharedDataAttribute = Attribute.of("org.jetbrains.kotlin.project-shared-data", String::class.java)

/**
* Represents a provider that can extract some [T] that was published by a project in the current build as
* a secondary variant via [kotlinSecondaryVariantsDataSharing].
* And the current (consumer) project has dependency to the producer project.
*
* Data is meant to be extracted at Task Execution Phase.
*
* This class is Configuration Cache safe. It can be stored in a Task field.
*/
internal class KotlinProjectSharedDataProvider<T : KotlinShareableDataAsSecondaryVariant>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.jetbrains.kotlin.gradle.plugin.diagnostics.PreparedKotlinToolingDiagn
import org.jetbrains.kotlin.gradle.plugin.internal.KotlinProjectSharedDataProvider
import org.jetbrains.kotlin.gradle.plugin.internal.kotlinSecondaryVariantsDataSharing
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.publishing.KotlinProjectCoordinatesData
import org.jetbrains.kotlin.gradle.plugin.mpp.publishing.consumeRootModuleCoordinates
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.projectStructureMetadataResolvableConfiguration
import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.gradle.utils.*
Expand Down Expand Up @@ -106,6 +108,7 @@ internal class GranularMetadataTransformation(
val projectData: Map<String, ProjectData>,
val platformCompilationSourceSets: Set<String>,
val projectStructureMetadataResolvableConfiguration: LazyResolvedConfiguration?,
val coordinatesOfProjectDependencies: KotlinProjectSharedDataProvider<KotlinProjectCoordinatesData>?,
val objects: ObjectFactory,
val kotlinKmpProjectIsolationEnabled: Boolean,
val sourceSetMetadataLocationsOfProjectDependencies: KotlinProjectSharedDataProvider<SourceSetMetadataLocations>,
Expand All @@ -115,11 +118,18 @@ internal class GranularMetadataTransformation(
sourceSetName = kotlinSourceSet.name,
resolvedMetadataConfiguration = LazyResolvedConfiguration(kotlinSourceSet.internal.resolvableMetadataConfiguration),
sourceSetVisibilityProvider = SourceSetVisibilityProvider(project),
projectStructureMetadataExtractorFactory = if (project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) project.kotlinMppDependencyProjectStructureMetadataExtractorFactory else project.kotlinMppDependencyProjectStructureMetadataExtractorFactoryDeprecated,
projectData = if (project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) emptyMap<String, ProjectData>() else project.allProjectsData,
projectStructureMetadataExtractorFactory =
if (project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) project.kotlinMppDependencyProjectStructureMetadataExtractorFactory
else project.kotlinMppDependencyProjectStructureMetadataExtractorFactoryDeprecated,
projectData =
if (project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) emptyMap<String, ProjectData>()
else project.allProjectsData,
platformCompilationSourceSets = project.multiplatformExtension.platformCompilationSourceSets,
projectStructureMetadataResolvableConfiguration =
kotlinSourceSet.internal.projectStructureMetadataResolvableConfiguration?.let { LazyResolvedConfiguration(it) },
coordinatesOfProjectDependencies =
if (project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) project.kotlinSecondaryVariantsDataSharing.consumeRootModuleCoordinates(kotlinSourceSet.internal)
else null,
objects = project.objects,
kotlinKmpProjectIsolationEnabled = project.kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled,
sourceSetMetadataLocationsOfProjectDependencies = project.kotlinSecondaryVariantsDataSharing
Expand All @@ -130,6 +140,7 @@ internal class GranularMetadataTransformation(
class ProjectData(
val path: String,
val sourceSetMetadataOutputs: LenientFuture<Map<String, SourceSetMetadataOutputs>>,
val moduleId: Future<ModuleDependencyIdentifier>,
) {
override fun toString(): String = "ProjectData[path='$path']"
}
Expand Down Expand Up @@ -353,7 +364,13 @@ internal class GranularMetadataTransformation(
is ModuleComponentIdentifier -> ModuleDependencyIdentifier(componentId.group, componentId.module)
is ProjectComponentIdentifier -> {
if (componentId in params.build) {
ModuleDependencyIdentifier(component.moduleVersion?.group, componentId.projectName)
if (params.coordinatesOfProjectDependencies != null) {
val projectCoordinates = params.coordinatesOfProjectDependencies.getProjectDataFromDependencyOrNull(this)
projectCoordinates?.moduleId ?: ModuleDependencyIdentifier(component.moduleVersion?.group, componentId.projectName)
} else {
params.projectData[componentId.projectPath]?.moduleId?.getOrThrow()
?: error("Cant find project Module ID by ${componentId.projectPath}")
}
} else {
ModuleDependencyIdentifier(
component.moduleVersion?.group ?: "unspecified",
Expand All @@ -374,10 +391,12 @@ private val Project.allProjectsData: Map<String, GranularMetadataTransformation.

private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransformation.ProjectData> {
return rootProject.allprojects.associateBy { it.path }.mapValues { (path, currentProject) ->
val moduleId = currentProject.future { ModuleIds.idOfRootModuleSafe(currentProject) }

GranularMetadataTransformation.ProjectData(
path = path,
sourceSetMetadataOutputs = currentProject.future { currentProject.collectSourceSetMetadataOutputs() }.lenient,
moduleId = moduleId,
)
}
}
Expand Down

0 comments on commit 71ddc3d

Please sign in to comment.