Rewrite project dependencies when publishing a multimodule MPP

When a new MPP is published without Gradle metadata, the project
dependencies should be replaced with specific module dependencies,
e.g. `sample-lib` with `sample-lib-jvm`, because a consumer won't be
able to resolve `sample-lib` in a variant-aware way.
This commit is contained in:
Sergey Igushkin
2018-09-20 14:36:39 +03:00
parent 05301e37d8
commit c3dd24e9b2
3 changed files with 131 additions and 9 deletions
@@ -774,4 +774,59 @@ class NewMultiplatformIT : BaseGradleIT() {
}
}
}
@Test
fun testPublishMultimoduleProjectWithNoMetadata() {
val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app")
with(libProject) {
setupWorkingDir()
appProject.setupWorkingDir()
appProject.projectDir.copyRecursively(projectDir.resolve("sample-app"))
projectDir.resolve("settings.gradle").writeText("include 'sample-app'") // also disables feature preview 'GRADLE_METADATA'
gradleBuildScript("sample-app").modify {
it.replace("'com.example:sample-lib:1.0'", "project(':')") + "\n" + """
apply plugin: 'maven-publish'
group = "com.exampleapp"
version = "1.0"
publishing {
repositories {
maven { url "file://${'$'}{rootProject.projectDir.absolutePath.replace('\\', '/')}/repo" }
}
}
""".trimIndent()
}
gradleBuildScript().appendText("\n" + """
publishing {
publications {
jvm6 {
groupId = "foo"
artifactId = "bar"
version = "42"
}
}
}
""".trimIndent())
build("publish") {
assertSuccessful()
assertFileContains(
"repo/com/exampleapp/sample-app-nodejs/1.0/sample-app-nodejs-1.0.pom",
"<groupId>com.example</groupId>",
"<artifactId>sample-lib-nodejs</artifactId>",
"<version>1.0</version>"
)
assertFileContains(
"repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.pom",
"<groupId>foo</groupId>",
"<artifactId>bar</artifactId>",
"<version>42</version>"
)
assertFileExists("repo/foo/bar/42/bar-42.jar")
}
}
}
}
@@ -135,7 +135,7 @@ class KotlinMultiplatformPlugin(
}
}
(this as MavenPublicationInternal).publishWithOriginalFileName()
artifactId = "${project.name}-${variant.target.name.toLowerCase()}"
artifactId = variant.target.defaultArtifactId
groupId = project.group.toString()
version = project.version.toString()
}
@@ -234,4 +234,6 @@ class KotlinMultiplatformPlugin(
companion object {
const val METADATA_TARGET_NAME = "metadata"
}
}
}
internal val KotlinTarget.defaultArtifactId get() = "${project.name}-${name.toLowerCase()}"
@@ -6,26 +6,25 @@
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.DependencyConstraint
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.PublishArtifact
import org.gradle.api.artifacts.*
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.attributes.Usage
import org.gradle.api.capabilities.Capability
import org.gradle.api.component.ComponentWithVariants
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
import org.jetbrains.kotlin.gradle.plugin.usageByName
import org.jetbrains.kotlin.utils.ifEmpty
class KotlinSoftwareComponent(
private val project: Project,
private val name: String,
private val kotlinTargets: Iterable<KotlinTarget>
) : SoftwareComponentInternal, ComponentWithVariants {
override fun getUsages(): Set<UsageContext> = emptySet()
override fun getVariants(): Set<KotlinTargetComponent> =
@@ -60,7 +59,10 @@ internal class KotlinPlatformUsageContext(
get() = project.configurations.getByName(dependencyConfigurationName)
override fun getDependencies(): MutableSet<out ModuleDependency> =
configuration.incoming.dependencies.withType(ModuleDependency::class.java)
if (publishWithGradleMetadata)
configuration.incoming.dependencies.withType(ModuleDependency::class.java)
else
rewriteMppDependenciesToTargetModuleDependencies(this, configuration).toMutableSet()
override fun getDependencyConstraints(): MutableSet<out DependencyConstraint> =
configuration.incoming.dependencyConstraints
@@ -77,3 +79,66 @@ internal class KotlinPlatformUsageContext(
// FIXME this is a stub for a function that is not present in the Gradle API that we compile against
fun getGlobalExcludes(): Set<Any> = emptySet()
}
private fun rewriteMppDependenciesToTargetModuleDependencies(
context: KotlinPlatformUsageContext,
configuration: Configuration
): Set<ModuleDependency> = with(context.kotlinTarget.project) {
val target = context.kotlinTarget
val moduleDependencies = configuration.incoming.dependencies.withType(ModuleDependency::class.java).ifEmpty { return emptySet() }
val targetMainCompilation = target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)
?: return moduleDependencies // Android is not yet supported
val targetCompileDependenciesConfiguration = project.configurations.getByName(
when (context.dependencyConfigurationName) {
target.apiElementsConfigurationName -> targetMainCompilation.compileDependencyConfigurationName
target.runtimeElementsConfigurationName ->
(targetMainCompilation as KotlinCompilationToRunnableFiles).runtimeDependencyConfigurationName
else -> error("unexpected configuration")
}
)
val resolvedCompileDependencies by lazy { // don't resolve if no project dependencies on MPP projects are found
targetCompileDependenciesConfiguration.resolvedConfiguration.lenientConfiguration.allModuleDependencies.associateBy {
Triple(it.moduleGroup, it.moduleName, it.moduleVersion)
}
}
moduleDependencies.map { dependency ->
when (dependency) {
!is ProjectDependency -> dependency
else -> {
val dependencyProject = dependency.dependencyProject
val dependencyProjectKotlinExtension = dependencyProject.multiplatformExtension
?: return@map dependency
if (dependencyProjectKotlinExtension.isGradleMetadataAvailable)
return@map dependency
val resolved = resolvedCompileDependencies[Triple(dependency.group, dependency.name, dependency.version)]
?: return@map dependency
val resolvedToConfiguration = resolved.configuration
val dependencyTarget = dependencyProjectKotlinExtension.targets.singleOrNull {
resolvedToConfiguration in setOf(
it.apiElementsConfigurationName,
it.runtimeElementsConfigurationName,
it.defaultConfigurationName
)
} ?: return@map dependency
val publicationDelegate = (dependencyTarget.component as KotlinVariant).publicationDelegate
dependencies.module(
listOf(
publicationDelegate?.groupId ?: dependency.group,
publicationDelegate?.artifactId ?: dependencyTarget.defaultArtifactId,
publicationDelegate?.version ?: dependency.version
).joinToString(":")
) as ModuleDependency
}
}
}.toSet()
}