[KPM] Implement SimpleProjectToProjectDependencyResolutionTest

KT-51386
This commit is contained in:
sebastian.sellmair
2022-03-08 14:58:49 +01:00
committed by Space
parent e55bba9c4c
commit d7ce7387f7
9 changed files with 334 additions and 77 deletions
@@ -89,6 +89,7 @@ public final class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependency$Com
}
public final class org/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinDependencyKt {
public static final fun getPath (Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinSourceDependency;)Ljava/lang/String;
public static final fun isClasspathType (Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinResolvedBinaryDependency;)Z
public static final fun isDocumentationType (Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinResolvedBinaryDependency;)Z
public static final fun isSourcesType (Lorg/jetbrains/kotlin/gradle/kpm/idea/IdeaKotlinResolvedBinaryDependency;)Z
@@ -33,6 +33,9 @@ sealed interface IdeaKotlinSourceDependency : IdeaKotlinDependency {
val kotlinFragmentName: String
}
val IdeaKotlinSourceDependency.path: String
get() = "${buildId.takeIf { it != ":" }.orEmpty()}$projectPath/$kotlinModuleName/$kotlinFragmentName"
sealed interface IdeaKotlinBinaryCoordinates : Serializable {
val group: String
val module: String
@@ -70,7 +73,7 @@ data class IdeaKotlinSourceDependencyImpl(
) : IdeaKotlinSourceDependency {
override fun toString(): String {
return "project://$buildId:$projectPath:$kotlinModuleName:$kotlinFragmentName"
return "fragment: $path"
}
@InternalKotlinGradlePluginApi
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2022 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinResolvedBinaryDependency
import java.io.File
fun buildIdeaKotlinBinaryDependencyMatchers(notation: Any?): List<IdeaKotlinBinaryDependencyMatcher> {
return when (notation) {
null -> emptyList()
is IdeaKotlinBinaryDependencyMatcher -> listOf(notation)
is String -> listOf(IdeaKotlinBinaryDependencyMatcher.Coordinates(parseIdeaKotlinBinaryCoordinates(notation)))
is Regex -> listOf(IdeaKotlinBinaryDependencyMatcher.CoordinatesRegex(notation))
is File -> listOf(IdeaKotlinBinaryDependencyMatcher.BinaryFile(notation))
is Iterable<*> -> notation.flatMap { child -> buildIdeaKotlinBinaryDependencyMatchers(child) }
else -> error("Can't build ${IdeaKotlinBinaryDependencyMatcher::class.simpleName} from $notation")
}
}
interface IdeaKotlinBinaryDependencyMatcher : IdeaKotlinDependencyMatcher<IdeaKotlinBinaryDependency>{
class Coordinates(
private val coordinates: IdeaKotlinBinaryCoordinates
) : IdeaKotlinBinaryDependencyMatcher {
override val description: String = coordinates.toString()
override fun matches(dependency: IdeaKotlinBinaryDependency): Boolean {
return coordinates == dependency.coordinates
}
}
class CoordinatesRegex(
private val regex: Regex
) : IdeaKotlinBinaryDependencyMatcher {
override val description: String = regex.pattern
override fun matches(dependency: IdeaKotlinBinaryDependency): Boolean {
return regex.matches(dependency.coordinates.toString())
}
}
class BinaryFile(
private val binaryFile: File
) : IdeaKotlinBinaryDependencyMatcher {
override val description: String = binaryFile.path
override fun matches(dependency: IdeaKotlinBinaryDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency && dependency.binaryFile == binaryFile
}
}
class InDirectory(
private val parentFile: File
) : IdeaKotlinBinaryDependencyMatcher {
constructor(parentFilePath: String) : this(File(parentFilePath))
override val description: String = "$parentFile/**"
override fun matches(dependency: IdeaKotlinBinaryDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency &&
dependency.binaryFile.absoluteFile.normalize().canonicalPath.startsWith(
parentFile.absoluteFile.normalize().canonicalPath
)
}
}
}
@@ -5,71 +5,9 @@
package org.jetbrains.kotlin.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinBinaryDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinResolvedBinaryDependency
import java.io.File
fun buildIdeaKotlinDependencyMatchers(any: Any?): List<IdeaKotlinDependencyMatcher> {
return when (any) {
null -> emptyList()
is IdeaKotlinDependencyMatcher -> listOf(any)
is String -> listOf(IdeaKotlinDependencyMatcher.Coordinates(parseIdeaKotlinBinaryCoordinates(any)))
is Regex -> listOf(IdeaKotlinDependencyMatcher.CoordinatesRegex(any))
is File -> listOf(IdeaKotlinDependencyMatcher.BinaryFile(any))
is Iterable<*> -> any.flatMap { child -> buildIdeaKotlinDependencyMatchers(child) }
else -> error("Can't build ${IdeaKotlinDependencyMatcher::class.simpleName} from $any")
}
}
interface IdeaKotlinDependencyMatcher {
interface IdeaKotlinDependencyMatcher<in T : IdeaKotlinDependency> {
val description: String
fun matches(dependency: IdeaKotlinDependency): Boolean
class Coordinates(
private val coordinates: IdeaKotlinBinaryCoordinates
) : IdeaKotlinDependencyMatcher {
override val description: String = coordinates.toString()
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinBinaryDependency && coordinates == dependency.coordinates
}
}
class CoordinatesRegex(
private val regex: Regex
) : IdeaKotlinDependencyMatcher {
override val description: String = regex.pattern
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinBinaryDependency && regex.matches(dependency.coordinates.toString())
}
}
class BinaryFile(
private val binaryFile: File
) : IdeaKotlinDependencyMatcher {
override val description: String = binaryFile.path
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency && dependency.binaryFile == binaryFile
}
}
class InDirectory(
private val parentFile: File
) : IdeaKotlinDependencyMatcher {
constructor(parentFilePath: String) : this(File(parentFilePath))
override val description: String = "$parentFile/**"
override fun matches(dependency: IdeaKotlinDependency): Boolean {
return dependency is IdeaKotlinResolvedBinaryDependency &&
dependency.binaryFile.absoluteFile.normalize().canonicalPath.startsWith(
parentFile.absoluteFile.normalize().canonicalPath
)
}
}
fun matches(dependency: T): Boolean
}
@@ -0,0 +1,32 @@
package org.jetbrains.kotlin.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinSourceDependency
import org.jetbrains.kotlin.gradle.kpm.idea.path
fun buildIdeaKotlinSourceDependencyMatchers(notation: Any?): List<IdeaKotlinSourceDependencyMatcher> {
return when (notation) {
null -> emptyList()
is Iterable<*> -> notation.flatMap { buildIdeaKotlinSourceDependencyMatchers(it) }
is String -> listOf(IdeaKotlinSourceDependencyMatcher.FragmentPath(notation))
is Regex -> listOf(IdeaKotlinSourceDependencyMatcher.FragmentPathRegex(notation))
else -> error("Can't build ${IdeaKotlinSourceDependencyMatcher::class.simpleName} from $notation")
}
}
interface IdeaKotlinSourceDependencyMatcher : IdeaKotlinDependencyMatcher<IdeaKotlinSourceDependency> {
class FragmentPath(private val path: String) : IdeaKotlinSourceDependencyMatcher {
override val description: String = path
override fun matches(dependency: IdeaKotlinSourceDependency): Boolean {
return path == dependency.path
}
}
class FragmentPathRegex(private val pathRegex: Regex) : IdeaKotlinSourceDependencyMatcher {
override val description: String = pathRegex.pattern
override fun matches(dependency: IdeaKotlinSourceDependency): Boolean {
return pathRegex.matches(dependency.path)
}
}
}
@@ -3,6 +3,8 @@
* 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("DuplicatedCode")
package org.jetbrains.kotlin.gradle.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.*
@@ -24,7 +26,7 @@ fun IdeaKotlinModule.assertContainsFragment(name: String): IdeaKotlinFragment {
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String,
matchers: Set<IdeaKotlinDependencyMatcher>
matchers: Set<IdeaKotlinBinaryDependencyMatcher>
): Set<IdeaKotlinResolvedBinaryDependency> {
val resolvedBinaryDependencies = dependencies
.mapNotNull { dependency ->
@@ -57,7 +59,7 @@ fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
}
appendLine()
appendLine("Unexpected dependency coordinates:")
appendLine("${name}: Unexpected dependency coordinates:")
unexpectedResolvedBinaryDependencies.forEach { unexpectedDependency ->
appendLine("\"${unexpectedDependency.coordinates}\",")
}
@@ -65,14 +67,14 @@ fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
if (missingDependencies.isNotEmpty()) {
appendLine()
appendLine("Missing dependencies:")
appendLine("${name}: Missing dependencies:")
missingDependencies.forEach { missingDependency ->
appendLine(missingDependency.description)
}
}
appendLine()
appendLine("Resolved Dependency Coordinates:")
appendLine("${name}: Resolved Dependency Coordinates:")
resolvedBinaryDependencies.mapNotNull { it.coordinates }.forEach { coordinates ->
appendLine("\"$coordinates\",")
}
@@ -83,8 +85,61 @@ fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
@JvmName("assertResolvedBinaryDependenciesByAnyMatcher")
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String, matchers: Set<Any?>,
) = assertResolvedBinaryDependencies(binaryType, matchers.flatMap { buildIdeaKotlinDependencyMatchers(it) }.toSet())
) = assertResolvedBinaryDependencies(binaryType, matchers.flatMap { buildIdeaKotlinBinaryDependencyMatchers(it) }.toSet())
fun IdeaKotlinFragment.assertResolvedBinaryDependencies(
binaryType: String, vararg matchers: Any?
) = assertResolvedBinaryDependencies(binaryType, matchers.toSet())
fun IdeaKotlinFragment.assertSourceDependencies(matchers: Set<IdeaKotlinSourceDependencyMatcher>): Set<IdeaKotlinSourceDependency> {
val sourceDependencies = dependencies.filterIsInstance<IdeaKotlinSourceDependency>().toSet()
val unexpectedDependencies = sourceDependencies
.filter { dependency -> matchers.none { matcher -> matcher.matches(dependency) } }
val missingDependencies = matchers.filter { matcher ->
sourceDependencies.none { dependency -> matcher.matches(dependency) }
}
if (unexpectedDependencies.isEmpty() && missingDependencies.isEmpty()) {
return sourceDependencies
}
fail(
buildString {
if (unexpectedDependencies.isNotEmpty()) {
appendLine("${name}: Unexpected source dependencies found:")
unexpectedDependencies.forEach { unexpectedDependency ->
appendLine(unexpectedDependency)
}
appendLine()
appendLine("${name}: Unexpected source dependency paths:")
unexpectedDependencies.forEach { unexpectedDependency ->
appendLine("\"${unexpectedDependency.path}\",")
}
}
if (missingDependencies.isNotEmpty()) {
appendLine()
appendLine("${name}: Missing fragment dependencies:")
missingDependencies.forEach { missingDependency ->
appendLine(missingDependency.description)
}
}
appendLine()
appendLine("${name}: Resolved source dependency paths:")
sourceDependencies.forEach { dependency ->
appendLine("\"${dependency.path}\",")
}
}
)
}
@JvmName("assertSourceDependenciesByAnyMatcher")
fun IdeaKotlinFragment.assertSourceDependencies(matchers: Set<Any?>): Set<IdeaKotlinSourceDependency> =
assertSourceDependencies(matchers.flatMap { buildIdeaKotlinSourceDependencyMatchers(it) }.toSet())
fun IdeaKotlinFragment.assertSourceDependencies(vararg matchers: Any?) =
assertSourceDependencies(matchers.toSet())
@@ -16,7 +16,7 @@ import org.jetbrains.kotlin.commonizer.KonanDistribution
import org.jetbrains.kotlin.commonizer.platformLibsDir
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.enableDefaultStdlibDependency
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.IdeaKotlinDependencyMatcher
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.IdeaKotlinBinaryDependencyMatcher
import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -33,8 +33,8 @@ abstract class AbstractLightweightIdeaDependencyResolutionTest {
val Project.konanDistribution get() = KonanDistribution(project.konanHome)
fun Project.nativePlatformLibraries(target: KonanTarget): IdeaKotlinDependencyMatcher? =
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.platformLibsDir.resolve(target.name))
fun Project.nativePlatformLibraries(target: KonanTarget): IdeaKotlinBinaryDependencyMatcher? =
IdeaKotlinBinaryDependencyMatcher.InDirectory(project.konanDistribution.platformLibsDir.resolve(target.name))
.takeIf { target.enabledOnCurrentHost }
}
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2022 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", "DuplicatedCode")
package org.jetbrains.kotlin.gradle.kpm.idea
import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
import org.jetbrains.kotlin.gradle.kpm.buildIdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertContainsFragment
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertIsNotEmpty
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertSourceDependencies
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.junit.Test
class SimpleProjectToProjectDependencyResolutionTest : AbstractLightweightIdeaDependencyResolutionTest() {
@Test
fun `test - simple producer and consumer projects`() {
val root = buildProject()
val producer = buildProject { withParent(root).withName("producer") }
val consumer = buildProject { withParent(root).withName("consumer") }
consumer.projectDir.mkdirs()
consumer.projectDir.deleteOnExit()
producer.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val linuxX64 = fragments.create("linuxX64", KotlinLinuxX64Variant::class.java)
val iosX64 = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val iosArm64 = fragments.create("iosArm64", KotlinIosArm64Variant::class.java)
val macos64 = fragments.create("macosX64", KotlinMacosX64Variant::class.java)
val iosCommon = fragments.create("iosMain") {
it.refines(common)
iosX64.refines(it)
iosArm64.refines(it)
}
val appleCommon = fragments.create("appleCommon") {
it.refines(common)
iosCommon.refines(it)
macos64.refines(it)
}
fragments.create("nativeCommon") {
it.refines(common)
appleCommon.refines(it)
linuxX64.refines(it)
}
}
}
val consumerKotlin = consumer.applyKpmPlugin {
mainAndTest {
fragments.create("jvm", KotlinJvmVariant::class.java)
val linuxX64 = fragments.create("linuxX64", KotlinLinuxX64Variant::class.java)
val iosX64 = fragments.create("iosX64", KotlinIosX64Variant::class.java)
val macosX64 = fragments.create("macosX64", KotlinMacosX64Variant::class.java)
val appleCommon = fragments.create("appleCommon") {
it.refines(common)
iosX64.refines(it)
macosX64.refines(it)
}
fragments.create("nativeCommon") {
it.refines(common)
appleCommon.refines(it)
linuxX64.refines(it)
}
dependencies {
implementation(project(":producer"))
}
}
}
consumerKotlin.buildIdeaKotlinProjectModel().assertIsNotEmpty().modules.forEach { module ->
fun ifTestModule(vararg any: Any?) =
listOf(*any).takeIf { module.name == KotlinGradleModule.TEST_MODULE_NAME }
module.assertContainsFragment("common").assertSourceDependencies(
":producer/main/common",
ifTestModule(":consumer/main/common")
)
module.assertContainsFragment("jvm").assertSourceDependencies(
":producer/main/jvm",
":producer/main/common",
ifTestModule(
":consumer/main/common",
":consumer/main/jvm"
)
)
module.assertContainsFragment("nativeCommon").assertSourceDependencies(
":producer/main/common",
":producer/main/nativeCommon",
ifTestModule(
":consumer/main/common",
":consumer/main/nativeCommon"
)
)
module.assertContainsFragment("appleCommon").assertSourceDependencies(
":producer/main/common",
":producer/main/appleCommon",
":producer/main/nativeCommon",
ifTestModule(
":consumer/main/common",
":consumer/main/nativeCommon",
":consumer/main/appleCommon"
)
)
module.assertContainsFragment("linuxX64").assertSourceDependencies(
":producer/main/common",
":producer/main/nativeCommon",
":producer/main/linuxX64",
ifTestModule(
":consumer/main/common",
":consumer/main/nativeCommon",
":consumer/main/linuxX64"
)
)
module.assertContainsFragment("macosX64").assertSourceDependencies(
":producer/main/macosX64",
":producer/main/common",
":producer/main/appleCommon",
":producer/main/nativeCommon",
ifTestModule(
":consumer/main/common",
":consumer/main/nativeCommon",
":consumer/main/appleCommon",
":consumer/main/macosX64"
)
)
module.assertContainsFragment("iosX64").assertSourceDependencies(
":producer/main/iosX64",
":producer/main/common",
":producer/main/iosMain",
":producer/main/appleCommon",
":producer/main/nativeCommon",
ifTestModule(
":consumer/main/common",
":consumer/main/nativeCommon",
":consumer/main/appleCommon",
":consumer/main/iosX64"
)
)
}
}
}
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.enableDefaultStdlibDependency
import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
import org.jetbrains.kotlin.gradle.kpm.buildIdeaKotlinProjectModel
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKotlinDependency.Companion.CLASSPATH_BINARY_TYPE
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.IdeaKotlinDependencyMatcher
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.IdeaKotlinBinaryDependencyMatcher
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertContainsFragment
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertIsNotEmpty
import org.jetbrains.kotlin.gradle.kpm.idea.testFixtures.assertResolvedBinaryDependencies
@@ -58,7 +58,7 @@ class StdlibKotlinIdeaDependencyResolutionTest : AbstractLightweightIdeaDependen
/* native fragments only references dependencies from the native distribution */
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE, IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root)
CLASSPATH_BINARY_TYPE, IdeaKotlinBinaryDependencyMatcher.InDirectory(project.konanDistribution.root)
)
}
}
@@ -112,7 +112,7 @@ class StdlibKotlinIdeaDependencyResolutionTest : AbstractLightweightIdeaDependen
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root),
IdeaKotlinBinaryDependencyMatcher.InDirectory(project.konanDistribution.root),
/* Actually not correct as well, since those are jvm dependencies. Filtering is not easily possible here, as well */
"org.jetbrains.kotlin:kotlin-stdlib:1.6.10",
@@ -155,7 +155,7 @@ class StdlibKotlinIdeaDependencyResolutionTest : AbstractLightweightIdeaDependen
listOf("iosCommon", "iosX64", "iosArm64").forEach { fragmentName ->
module.assertContainsFragment(fragmentName).assertResolvedBinaryDependencies(
CLASSPATH_BINARY_TYPE,
IdeaKotlinDependencyMatcher.InDirectory(project.konanDistribution.root),
IdeaKotlinBinaryDependencyMatcher.InDirectory(project.konanDistribution.root),
)
}
}