diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt index 67c99007564..d7d1ae8ce56 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationCacheIT.kt @@ -18,6 +18,27 @@ class ConfigurationCacheIT : AbstractConfigurationCacheIT() { testConfigurationCacheOf(":compileKotlin") } + @Test + fun testJvmWithMavenPublish() = with(Project("kotlinProject")) { + setupWorkingDir() + gradleBuildScript().appendText(""" + apply plugin: "maven-publish" + group = "com.example" + version = "1.0" + publishing.repositories { + maven { + url = "${'$'}buildDir/repo" + } + } + publishing.publications { + maven(MavenPublication) { + from(components["java"]) + } + } + """.trimIndent()) + testConfigurationCacheOf(":publishMavenPublicationToMavenRepository", checkUpToDateOnRebuild = false) + } + @Test fun testIncrementalKaptProject() = with(Project("kaptIncrementalCompilationProject")) { setupIncrementalAptProject("AGGREGATING") @@ -57,6 +78,7 @@ abstract class AbstractConfigurationCacheIT : BaseGradleIT() { protected fun Project.testConfigurationCacheOf( vararg taskNames: String, executedTaskNames: List? = null, + checkUpToDateOnRebuild: Boolean = true, buildOptions: BuildOptions = defaultBuildOptions() ) { // First, run a build that serializes the tasks state for instant execution in further builds @@ -81,9 +103,11 @@ abstract class AbstractConfigurationCacheIT : BaseGradleIT() { assertContains("Reusing configuration cache.") } - build(*taskNames, options = buildOptions) { - assertSuccessful() - assertTasksUpToDate(executedTask) + if (checkUpToDateOnRebuild) { + build(*taskNames, options = buildOptions) { + assertSuccessful() + assertTasksUpToDate(executedTask) + } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index ee3320b92ee..abe7d2761a5 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -22,6 +22,8 @@ import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.plugins.MavenPluginConvention import org.gradle.api.provider.Provider import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPom +import org.gradle.api.artifacts.maven.MavenPom as OldMavenPom import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.* import org.gradle.api.tasks.compile.AbstractCompile @@ -415,19 +417,32 @@ internal abstract class AbstractKotlinPlugin( project.components.addAll(target.components) } + private fun rewritePom(pom: MavenPom, rewriter: PomDependenciesRewriter, shouldRewritePom: Provider) { + pom.withXml { xml -> + if (shouldRewritePom.get()) + rewriter.rewritePomMppDependenciesToActualTargetModules(xml) + } + } + + private fun rewritePom(pom: OldMavenPom, rewriter: PomDependenciesRewriter, shouldRewritePom: Provider) { + pom.withXml { xml -> + if (shouldRewritePom.get()) + rewriter.rewritePomMppDependenciesToActualTargetModules(xml) + } + } + private fun rewriteMppDependenciesInPom(target: AbstractKotlinTarget) { val project = target.project - fun shouldRewritePoms(): Boolean = + val shouldRewritePoms = project.provider { PropertiesProvider(project).keepMppDependenciesIntactInPoms != true + } project.pluginManager.withPlugin("maven-publish") { project.extensions.configure(PublishingExtension::class.java) { publishing -> + val pomRewriter = PomDependenciesRewriter(project, target.kotlinComponents.single()) publishing.publications.withType(MavenPublication::class.java).all { publication -> - publication.pom.withXml { xml -> - if (shouldRewritePoms()) - project.rewritePomMppDependenciesToActualTargetModules(xml, target.kotlinComponents.single()) - } + rewritePom(publication.pom, pomRewriter, shouldRewritePoms) } } } @@ -435,10 +450,8 @@ internal abstract class AbstractKotlinPlugin( project.pluginManager.withPlugin("maven") { project.tasks.withType(Upload::class.java).all { uploadTask -> uploadTask.repositories.withType(MavenResolver::class.java).all { mavenResolver -> - mavenResolver.pom.withXml { xml -> - if (shouldRewritePoms()) - project.rewritePomMppDependenciesToActualTargetModules(xml, target.kotlinComponents.single()) - } + val pomRewriter = PomDependenciesRewriter(project, target.kotlinComponents.single()) + rewritePom(mavenResolver.pom, pomRewriter, shouldRewritePoms) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 8117bf158fd..8fc0ace3c2a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -13,8 +13,10 @@ import org.gradle.api.attributes.AttributeContainer import org.gradle.api.internal.FeaturePreviews import org.gradle.api.internal.plugins.DslObject import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.provider.Provider import org.gradle.api.publish.PublicationContainer import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPom import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal import org.gradle.api.tasks.SourceTask @@ -234,6 +236,18 @@ class KotlinMultiplatformPlugin( project.components.add(kotlinSoftwareComponent) } + private fun rewritePom( + pom: MavenPom, + pomRewriter: PomDependenciesRewriter, + shouldRewritePomDependencies: Provider, + includeOnlySpecifiedDependencies: Provider>? + ) { + pom.withXml { xml -> + if (shouldRewritePomDependencies.get()) + pomRewriter.rewritePomMppDependenciesToActualTargetModules(xml, includeOnlySpecifiedDependencies) + } + } + private fun AbstractKotlinTarget.createMavenPublications(publications: PublicationContainer) { components .map { gradleComponent -> gradleComponent to kotlinComponents.single { it.name == gradleComponent.name } } @@ -250,12 +264,16 @@ class KotlinMultiplatformPlugin( (this as MavenPublicationInternal).publishWithOriginalFileName() artifactId = kotlinComponent.defaultArtifactId - pom.withXml { xml -> - if (PropertiesProvider(project).keepMppDependenciesIntactInPoms != true) - project.rewritePomMppDependenciesToActualTargetModules(xml, kotlinComponent) { id -> - filterMetadataDependencies(this@createMavenPublications, id) - } - } + val pomRewriter = PomDependenciesRewriter(project, kotlinComponent) + val shouldRewritePomDependencies = + project.provider { PropertiesProvider(project).keepMppDependenciesIntactInPoms != true } + + rewritePom( + pom, + pomRewriter, + shouldRewritePomDependencies, + dependenciesForPomRewriting(this@createMavenPublications) + ) } (kotlinComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate = componentPublication @@ -269,23 +287,24 @@ class KotlinMultiplatformPlugin( * can't read Gradle module metadata won't resolve a dependency on an MPP to the granular metadata variant and won't then choose the * right dependencies for each source set, we put only the dependencies of the legacy common variant into the POM, i.e. commonMain API. */ - private fun filterMetadataDependencies(target: AbstractKotlinTarget, groupNameVersion: Triple): Boolean { - if (target !is KotlinMetadataTarget || !target.project.isKotlinGranularMetadataEnabled) { - return true + private fun dependenciesForPomRewriting(target: AbstractKotlinTarget): Provider>? = + if (target !is KotlinMetadataTarget || !target.project.isKotlinGranularMetadataEnabled) + null + else { + val commonMain = target.project.kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + if (commonMain == null) + null + else + target.project.provider { + val project = target.project + + // Only the commonMain API dependencies can be published for consumers who can't read Gradle project metadata + val commonMainApi = project.sourceSetDependencyConfigurationByScope(commonMain, KotlinDependencyScope.API_SCOPE) + val commonMainDependencies = commonMainApi.allDependencies + commonMainDependencies.map { ModuleCoordinates(it.group, it.name, it.version) }.toSet() + } } - val (group, name, _) = groupNameVersion - - val project = target.project - val commonMain = project.kotlinExtension.sourceSets?.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) - ?: return true - - // Only the commonMain API dependencies can be published for consumers who can't read Gradle project metadata - val commonMainApi = project.sourceSetDependencyConfigurationByScope(commonMain, KotlinDependencyScope.API_SCOPE) - - return commonMainApi.allDependencies.any { it.group == group && it.name == name } - } - private fun configureSourceSets(project: Project) = with(project.multiplatformExtension) { val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt index 9e3d852cba7..1bf5caae4c3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/mppDependencyRewritingUtils.kt @@ -14,82 +14,103 @@ import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.attributes.Usage import org.gradle.api.internal.component.SoftwareComponentInternal import org.gradle.api.internal.component.UsageContext +import org.gradle.api.provider.Provider import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent +import org.jetbrains.kotlin.gradle.utils.getValue -internal fun Project.rewritePomMppDependenciesToActualTargetModules( - pomXml: XmlProvider, - component: KotlinTargetComponent, - filterDependencies: (groupNameVersion: Triple) -> Boolean = { true } +internal data class ModuleCoordinates( + val group: String?, + val name: String, + val version: String? +) + +internal class PomDependenciesRewriter( + project: Project, + + @field:Transient + private val component: KotlinTargetComponent ) { - if (component !is SoftwareComponentInternal) - return - - val dependenciesNode = (pomXml.asNode().get("dependencies") as NodeList).filterIsInstance().singleOrNull() ?: return - - val dependencyNodes = (dependenciesNode.get("dependency") as? NodeList).orEmpty().filterIsInstance() - - val dependencyByNode = mutableMapOf() - - // Collect all the dependencies from the nodes: - val dependencies = dependencyNodes.map { dependencyNode -> - fun Node.getSingleChildValueOrNull(childName: String): String? = - ((get(childName) as NodeList?)?.singleOrNull() as Node?)?.text() - - val groupId = dependencyNode.getSingleChildValueOrNull("groupId") - val artifactId = dependencyNode.getSingleChildValueOrNull("artifactId") - val version = dependencyNode.getSingleChildValueOrNull("version") - (project.dependencies.module("$groupId:$artifactId:$version") as ModuleDependency) - .also { dependencyByNode[dependencyNode] = it } - }.toSet() // Get the dependencies mapping according to the component's UsageContexts: - val resultDependenciesForEachUsageContext = - component.usages.mapNotNull { usage -> + private val dependenciesMappingForEachUsageContext by project.provider { + (component as SoftwareComponentInternal).usages.mapNotNull { usage -> if (usage is KotlinUsageContext) - associateDependenciesWithActualModuleDependencies(usage, dependencies) + associateDependenciesWithActualModuleDependencies(usage) // We are only interested in dependencies that are mapped to some other dependencies: .filter { (from, to) -> Triple(from.group, from.name, from.version) != Triple(to.group, to.name, to.version) } else null } + } - // Rewrite the dependency nodes according to the mapping: - dependencyNodes.forEach { dependencyNode -> - val moduleDependency = dependencyByNode[dependencyNode] + fun rewritePomMppDependenciesToActualTargetModules( + pomXml: XmlProvider, + includeOnlySpecifiedDependencies: Provider>? = null + ) { + if (component !is SoftwareComponentInternal) + return - if (moduleDependency != null) { - val groupNameVersion = Triple(moduleDependency.group, moduleDependency.name, moduleDependency.version) - if (!filterDependencies(groupNameVersion)) { - dependenciesNode.remove(dependencyNode) - return@forEach - } + val dependenciesNode = (pomXml.asNode().get("dependencies") as NodeList).filterIsInstance().singleOrNull() ?: return + + val dependencyNodes = (dependenciesNode.get("dependency") as? NodeList).orEmpty().filterIsInstance() + + val dependencyByNode = mutableMapOf() + + // Collect all the dependencies from the nodes: + val dependencies = dependencyNodes.map { dependencyNode -> + fun Node.getSingleChildValueOrNull(childName: String): String? = + ((get(childName) as NodeList?)?.singleOrNull() as Node?)?.text() + + val groupId = dependencyNode.getSingleChildValueOrNull("groupId") + val artifactId = dependencyNode.getSingleChildValueOrNull("artifactId") + ?: error("unexpected dependency in POM with no artifact ID: $dependenciesNode") + val version = dependencyNode.getSingleChildValueOrNull("version") + (ModuleCoordinates(groupId, artifactId, version)).also { dependencyByNode[dependencyNode] = it } + }.toSet() + + val resultDependenciesForEachUsageContext = dependencies.associate { key -> + val map = dependenciesMappingForEachUsageContext.find { key in it } + val value = map?.get(key) ?: key + key to value } - val mapDependencyTo = resultDependenciesForEachUsageContext.find { moduleDependency in it }?.get(moduleDependency) + val includeOnlySpecifiedDependenciesSet = includeOnlySpecifiedDependencies?.get() - if (mapDependencyTo != null) { + // Rewrite the dependency nodes according to the mapping: + dependencyNodes.forEach { dependencyNode -> + val moduleDependency = dependencyByNode[dependencyNode] - fun Node.setChildNodeByName(name: String, value: String?) { - val childNode: Node? = (get(name) as NodeList?)?.firstOrNull() as Node? - if (value != null) { - (childNode ?: appendNode(name)).setValue(value) - } else { - childNode?.let { remove(it) } + if (moduleDependency != null) { + if (includeOnlySpecifiedDependenciesSet != null && moduleDependency !in includeOnlySpecifiedDependenciesSet) { + dependenciesNode.remove(dependencyNode) + return@forEach } } - dependencyNode.setChildNodeByName("groupId", mapDependencyTo.group) - dependencyNode.setChildNodeByName("artifactId", mapDependencyTo.name) - dependencyNode.setChildNodeByName("version", mapDependencyTo.version) + val mapDependencyTo = resultDependenciesForEachUsageContext.get(moduleDependency) + + if (mapDependencyTo != null) { + fun Node.setChildNodeByName(name: String, value: String?) { + val childNode: Node? = (get(name) as NodeList?)?.firstOrNull() as Node? + if (value != null) { + (childNode ?: appendNode(name)).setValue(value) + } else { + childNode?.let { remove(it) } + } + } + + dependencyNode.setChildNodeByName("groupId", mapDependencyTo.group) + dependencyNode.setChildNodeByName("artifactId", mapDependencyTo.name) + dependencyNode.setChildNodeByName("version", mapDependencyTo.version) + } } } } private fun associateDependenciesWithActualModuleDependencies( - usageContext: KotlinUsageContext, - moduleDependencies: Set -): Map { + usageContext: KotlinUsageContext +): Map { val compilation = usageContext.compilation val project = compilation.target.project @@ -119,17 +140,19 @@ private fun associateDependenciesWithActualModuleDependencies( } } - val resolvedModulesByRootModuleCoordinates = targetDependenciesConfiguration + return targetDependenciesConfiguration .allDependencies.withType(ModuleDependency::class.java) .associate { dependency -> + val coordinates = ModuleCoordinates(dependency.group, dependency.name, dependency.version) + val noMapping = coordinates to coordinates when (dependency) { is ProjectDependency -> { val dependencyProject = dependency.dependencyProject val dependencyProjectKotlinExtension = dependencyProject.multiplatformExtensionOrNull - ?: return@associate dependency to dependency + ?: return@associate noMapping val resolved = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)] - ?: return@associate dependency to dependency + ?: return@associate noMapping val resolvedToConfiguration = resolved.configuration val dependencyTargetComponent: KotlinTargetComponent = run { @@ -140,7 +163,7 @@ private fun associateDependenciesWithActualModuleDependencies( } } // Failed to find a matching component: - return@associate dependency to dependency + return@associate noMapping } val targetModulePublication = (dependencyTargetComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate @@ -149,49 +172,37 @@ private fun associateDependenciesWithActualModuleDependencies( // During Gradle POM generation, a project dependency is already written as the root module's coordinates. In the // dependencies mapping, map the root module to the target's module: - val rootModule = project.dependencies.module( - listOf( - rootModulePublication?.groupId ?: dependency.group, - rootModulePublication?.artifactId ?: dependencyProject.name, - rootModulePublication?.version ?: dependency.version - ).joinToString(":") - ) as ModuleDependency + val rootModule = ModuleCoordinates( + rootModulePublication?.groupId ?: dependency.group, + rootModulePublication?.artifactId ?: dependencyProject.name, + rootModulePublication?.version ?: dependency.version + ) - rootModule to project.dependencies.module( - listOf( - targetModulePublication?.groupId ?: dependency.group, - targetModulePublication?.artifactId ?: dependencyTargetComponent.defaultArtifactId, - targetModulePublication?.version ?: dependency.version - ).joinToString(":") - ) as ModuleDependency + rootModule to ModuleCoordinates( + targetModulePublication?.groupId ?: dependency.group, + targetModulePublication?.artifactId ?: dependencyTargetComponent.defaultArtifactId, + targetModulePublication?.version ?: dependency.version + ) } else -> { val resolvedDependency = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)] - ?: return@associate dependency to dependency + ?: return@associate noMapping if (resolvedDependency.moduleArtifacts.isEmpty() && resolvedDependency.children.size == 1) { // This is a dependency on a module that resolved to another module; map the original dependency to the target module val targetModule = resolvedDependency.children.single() - dependency to project.dependencies.module( - listOf( - targetModule.moduleGroup, - targetModule.moduleName, - targetModule.moduleVersion - ).joinToString(":") - ) as ModuleDependency + coordinates to ModuleCoordinates( + targetModule.moduleGroup, + targetModule.moduleName, + targetModule.moduleVersion + ) } else { - dependency to dependency + noMapping } } } - }.mapKeys { (key, _) -> Triple(key.group, key.name, key.version) } - - return moduleDependencies.associate { dependency -> - val key = Triple(dependency.group, dependency.name, dependency.version) - val value = resolvedModulesByRootModuleCoordinates[key] ?: dependency - dependency to value - } + } } private fun KotlinTargetComponent.findUsageContext(configurationName: String): UsageContext? {