Refactor: move all tools for MPP dependencies rewriting to a utils file
This commit is contained in:
+1
-65
@@ -6,16 +6,11 @@
|
||||
package org.jetbrains.kotlin.gradle.plugin.mpp
|
||||
|
||||
import groovy.lang.Closure
|
||||
import groovy.util.Node
|
||||
import groovy.util.NodeList
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.XmlProvider
|
||||
import org.gradle.api.artifacts.ModuleDependency
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.attributes.AttributeContainer
|
||||
import org.gradle.api.internal.FeaturePreviews
|
||||
import org.gradle.api.internal.component.SoftwareComponentInternal
|
||||
import org.gradle.api.internal.file.FileResolver
|
||||
import org.gradle.api.internal.plugins.DslObject
|
||||
import org.gradle.api.plugins.JavaBasePlugin
|
||||
@@ -315,63 +310,4 @@ internal fun compilationsBySourceSet(project: Project): Map<KotlinSourceSet, Set
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.rewritePomMppDependenciesToActualTargetModules(
|
||||
pomXml: XmlProvider,
|
||||
component: KotlinTargetComponent
|
||||
) {
|
||||
if (component !is SoftwareComponentInternal)
|
||||
return
|
||||
|
||||
val dependenciesNodeList = pomXml.asNode().get("dependencies") as NodeList
|
||||
val dependencyNodes = dependenciesNodeList.filterIsInstance<Node>().flatMap {
|
||||
(it.get("dependency") as? NodeList).orEmpty()
|
||||
}.filterIsInstance<Node>()
|
||||
|
||||
val dependencyByNode = mutableMapOf<Node, ModuleDependency>()
|
||||
|
||||
// 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 ->
|
||||
if (usage is KotlinUsageContext)
|
||||
rewriteDependenciesToActualModuleDependencies(usage, dependencies)
|
||||
// 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]
|
||||
val mapDependencyTo = resultDependenciesForEachUsageContext.find { moduleDependency in it }?.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-125
@@ -16,7 +16,10 @@ import org.gradle.api.component.ComponentWithVariants
|
||||
import org.gradle.api.internal.component.SoftwareComponentInternal
|
||||
import org.gradle.api.internal.component.UsageContext
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
|
||||
import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations
|
||||
|
||||
open class KotlinSoftwareComponent(
|
||||
private val name: String,
|
||||
@@ -112,128 +115,4 @@ class DefaultKotlinUsageContext(
|
||||
override fun getCapabilities(): Set<Capability> = emptySet()
|
||||
|
||||
override fun getGlobalExcludes(): Set<ExcludeRule> = emptySet()
|
||||
}
|
||||
|
||||
internal fun rewriteDependenciesToActualModuleDependencies(
|
||||
usageContext: KotlinUsageContext,
|
||||
moduleDependencies: Set<ModuleDependency>
|
||||
): Map<ModuleDependency, ModuleDependency> {
|
||||
val compilation = usageContext.compilation
|
||||
val project = compilation.target.project
|
||||
|
||||
val targetDependenciesConfiguration = project.configurations.getByName(
|
||||
when (compilation) {
|
||||
is KotlinJvmAndroidCompilation -> {
|
||||
// TODO handle Android configuration names in a general way once we drop AGP < 3.0.0
|
||||
val variantName = compilation.name
|
||||
when (usageContext.usage.name) {
|
||||
Usage.JAVA_API -> variantName + "CompileClasspath"
|
||||
Usage.JAVA_RUNTIME_JARS -> variantName + "RuntimeClasspath"
|
||||
else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
|
||||
}
|
||||
}
|
||||
else -> when (usageContext.usage.name) {
|
||||
Usage.JAVA_API -> compilation.compileDependencyConfigurationName
|
||||
Usage.JAVA_RUNTIME_JARS -> (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyConfigurationName
|
||||
else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val resolvedDependencies by lazy {
|
||||
// don't resolve if no project dependencies on MPP projects are found
|
||||
targetDependenciesConfiguration.resolvedConfiguration.lenientConfiguration.allModuleDependencies.associateBy {
|
||||
Triple(it.moduleGroup, it.moduleName, it.moduleVersion)
|
||||
}
|
||||
}
|
||||
|
||||
val resolvedModulesByRootModuleCoordinates = targetDependenciesConfiguration
|
||||
.allDependencies.withType(ModuleDependency::class.java)
|
||||
.associate { dependency ->
|
||||
when (dependency) {
|
||||
is ProjectDependency -> {
|
||||
val dependencyProject = dependency.dependencyProject
|
||||
val dependencyProjectKotlinExtension = dependencyProject.multiplatformExtension
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
val resolved = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)]
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
val resolvedToConfiguration = resolved.configuration
|
||||
val dependencyTargetComponent: KotlinTargetComponent = run {
|
||||
dependencyProjectKotlinExtension.targets.forEach { target ->
|
||||
target.components.forEach { component ->
|
||||
if (component.findUsageContext(resolvedToConfiguration) != null)
|
||||
return@run component
|
||||
}
|
||||
}
|
||||
// Failed to find a matching component:
|
||||
return@associate dependency to dependency
|
||||
}
|
||||
|
||||
val targetModulePublication = (dependencyTargetComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate
|
||||
val rootModulePublication = dependencyProjectKotlinExtension.rootSoftwareComponent.publicationDelegate
|
||||
|
||||
// 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
|
||||
|
||||
rootModule to project.dependencies.module(
|
||||
listOf(
|
||||
targetModulePublication?.groupId ?: dependency.group,
|
||||
targetModulePublication?.artifactId ?: dependencyTargetComponent.defaultArtifactId,
|
||||
targetModulePublication?.version ?: dependency.version
|
||||
).joinToString(":")
|
||||
) as ModuleDependency
|
||||
}
|
||||
else -> {
|
||||
val resolvedDependency = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)]
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
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
|
||||
|
||||
} else {
|
||||
dependency to dependency
|
||||
}
|
||||
}
|
||||
}
|
||||
}.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
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KotlinTargetComponent.findUsageContext(configurationName: String): UsageContext? {
|
||||
val usageContexts = when (this) {
|
||||
is KotlinVariantWithMetadataDependency -> originalUsages
|
||||
is SoftwareComponentInternal -> usages
|
||||
else -> emptySet()
|
||||
}
|
||||
return usageContexts.find { usageContext ->
|
||||
if (usageContext !is KotlinUsageContext) return@find false
|
||||
val compilation = usageContext.compilation
|
||||
configurationName in compilation.relatedConfigurationNames ||
|
||||
configurationName == compilation.target.apiElementsConfigurationName ||
|
||||
configurationName == compilation.target.runtimeElementsConfigurationName ||
|
||||
configurationName == compilation.target.defaultConfigurationName
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.plugin.mpp
|
||||
|
||||
import groovy.util.Node
|
||||
import groovy.util.NodeList
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.XmlProvider
|
||||
import org.gradle.api.artifacts.ModuleDependency
|
||||
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.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
|
||||
|
||||
internal fun Project.rewritePomMppDependenciesToActualTargetModules(
|
||||
pomXml: XmlProvider,
|
||||
component: KotlinTargetComponent
|
||||
) {
|
||||
if (component !is SoftwareComponentInternal)
|
||||
return
|
||||
|
||||
val dependenciesNodeList = pomXml.asNode().get("dependencies") as NodeList
|
||||
val dependencyNodes = dependenciesNodeList.filterIsInstance<Node>().flatMap {
|
||||
(it.get("dependency") as? NodeList).orEmpty()
|
||||
}.filterIsInstance<Node>()
|
||||
|
||||
val dependencyByNode = mutableMapOf<Node, ModuleDependency>()
|
||||
|
||||
// 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 ->
|
||||
if (usage is KotlinUsageContext)
|
||||
associateDependenciesWithActualModuleDependencies(usage, dependencies)
|
||||
// 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]
|
||||
val mapDependencyTo = resultDependenciesForEachUsageContext.find { moduleDependency in it }?.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<ModuleDependency>
|
||||
): Map<ModuleDependency, ModuleDependency> {
|
||||
val compilation = usageContext.compilation
|
||||
val project = compilation.target.project
|
||||
|
||||
val targetDependenciesConfiguration = project.configurations.getByName(
|
||||
when (compilation) {
|
||||
is KotlinJvmAndroidCompilation -> {
|
||||
// TODO handle Android configuration names in a general way once we drop AGP < 3.0.0
|
||||
val variantName = compilation.name
|
||||
when (usageContext.usage.name) {
|
||||
Usage.JAVA_API -> variantName + "CompileClasspath"
|
||||
Usage.JAVA_RUNTIME_JARS -> variantName + "RuntimeClasspath"
|
||||
else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
|
||||
}
|
||||
}
|
||||
else -> when (usageContext.usage.name) {
|
||||
Usage.JAVA_API -> compilation.compileDependencyConfigurationName
|
||||
Usage.JAVA_RUNTIME_JARS -> (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyConfigurationName
|
||||
else -> error("Unexpected Usage for usage context: ${usageContext.usage}")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val resolvedDependencies by lazy {
|
||||
// don't resolve if no project dependencies on MPP projects are found
|
||||
targetDependenciesConfiguration.resolvedConfiguration.lenientConfiguration.allModuleDependencies.associateBy {
|
||||
Triple(it.moduleGroup, it.moduleName, it.moduleVersion)
|
||||
}
|
||||
}
|
||||
|
||||
val resolvedModulesByRootModuleCoordinates = targetDependenciesConfiguration
|
||||
.allDependencies.withType(ModuleDependency::class.java)
|
||||
.associate { dependency ->
|
||||
when (dependency) {
|
||||
is ProjectDependency -> {
|
||||
val dependencyProject = dependency.dependencyProject
|
||||
val dependencyProjectKotlinExtension = dependencyProject.multiplatformExtension
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
val resolved = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)]
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
val resolvedToConfiguration = resolved.configuration
|
||||
val dependencyTargetComponent: KotlinTargetComponent = run {
|
||||
dependencyProjectKotlinExtension.targets.forEach { target ->
|
||||
target.components.forEach { component ->
|
||||
if (component.findUsageContext(resolvedToConfiguration) != null)
|
||||
return@run component
|
||||
}
|
||||
}
|
||||
// Failed to find a matching component:
|
||||
return@associate dependency to dependency
|
||||
}
|
||||
|
||||
val targetModulePublication = (dependencyTargetComponent as? KotlinTargetComponentWithPublication)?.publicationDelegate
|
||||
val rootModulePublication = dependencyProjectKotlinExtension.rootSoftwareComponent.publicationDelegate
|
||||
|
||||
// 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
|
||||
|
||||
rootModule to project.dependencies.module(
|
||||
listOf(
|
||||
targetModulePublication?.groupId ?: dependency.group,
|
||||
targetModulePublication?.artifactId ?: dependencyTargetComponent.defaultArtifactId,
|
||||
targetModulePublication?.version ?: dependency.version
|
||||
).joinToString(":")
|
||||
) as ModuleDependency
|
||||
}
|
||||
else -> {
|
||||
val resolvedDependency = resolvedDependencies[Triple(dependency.group, dependency.name, dependency.version)]
|
||||
?: return@associate dependency to dependency
|
||||
|
||||
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
|
||||
|
||||
} else {
|
||||
dependency to dependency
|
||||
}
|
||||
}
|
||||
}
|
||||
}.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? {
|
||||
val usageContexts = when (this) {
|
||||
is KotlinVariantWithMetadataDependency -> originalUsages
|
||||
is SoftwareComponentInternal -> usages
|
||||
else -> emptySet()
|
||||
}
|
||||
return usageContexts.find { usageContext ->
|
||||
if (usageContext !is KotlinUsageContext) return@find false
|
||||
val compilation = usageContext.compilation
|
||||
configurationName in compilation.relatedConfigurationNames ||
|
||||
configurationName == compilation.target.apiElementsConfigurationName ||
|
||||
configurationName == compilation.target.runtimeElementsConfigurationName ||
|
||||
configurationName == compilation.target.defaultConfigurationName
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user