diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/tcs/assertions.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/tcs/assertions.kt index cc51f9b330a..c970b8aeefb 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/tcs/assertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/tcs/assertions.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.gradle.idea.testFixtures.tcs import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency import kotlin.test.fail -fun Iterable.assertMatches(vararg notation: Any?): Iterable { +fun Iterable.assertMatches(vararg notation: Any?): Iterable { val thisList = toList() val matchers = notation.flatMap { buildIdeaKotlinDependencyMatchers(it) } @@ -51,3 +51,10 @@ fun Iterable.assertMatches(vararg notation: Any?): Iterabl } ) } + +fun Iterable.getOrFail(matcher: IdeaKotlinDependencyMatcher): T { + val candidates = filter { matcher.matches(it) } + if (candidates.isEmpty()) fail("No dependency matching '$matcher' found") + if (candidates.size > 1) fail("Multiple dependencies matching '$matcher' found: ${candidates.map { it.coordinates }}") + return candidates.single() +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppIdeDependencyResolutionIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppIdeDependencyResolutionIT.kt index 3243c4094df..2d7ae79728f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppIdeDependencyResolutionIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppIdeDependencyResolutionIT.kt @@ -16,15 +16,15 @@ import org.jetbrains.kotlin.gradle.idea.tcs.extras.* import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.IdeaKotlinDependencyMatcher import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.assertMatches import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.binaryCoordinates +import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.getOrFail import org.jetbrains.kotlin.gradle.testbase.* -import org.jetbrains.kotlin.gradle.util.kotlinNativeDistributionDependencies -import org.jetbrains.kotlin.gradle.util.replaceText -import org.jetbrains.kotlin.gradle.util.resolveIdeDependencies +import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget.* import org.junit.AssumptionViolatedException import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.io.TempDir +import java.io.File import java.nio.ByteBuffer import java.nio.file.Path import java.util.* @@ -277,8 +277,46 @@ class MppIdeDependencyResolutionIT : KGPBaseTest() { } } + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_6) + @GradleTest + fun `test dependency on java testFixtures and feature source sets`(gradleVersion: GradleVersion) { + project("kt-60053-dependencyOn-testFixtures", gradleVersion) { + build("publish") + + resolveIdeDependencies(":consumer") { dependencies -> + val jvmMainDependencies = dependencies["jvmMain"].filterIsInstance().assertMatches( + kotlinStdlibDependencies, + jetbrainsAnnotationDependencies, + binaryCoordinates("org.jetbrains.sample:producer:1.0.0"), + binaryCoordinates("org.jetbrains.sample:producer-foo:1.0.0"), + ) + + val jvmTestDependencies = dependencies["jvmTest"].filterIsInstance().assertMatches( + kotlinStdlibDependencies, + jetbrainsAnnotationDependencies, + binaryCoordinates("org.jetbrains.sample:producer:1.0.0"), + binaryCoordinates("org.jetbrains.sample:producer-foo:1.0.0"), + binaryCoordinates("org.jetbrains.sample:producer-test-fixtures:1.0.0"), + ) + + jvmMainDependencies.getOrFail(binaryCoordinates("org.jetbrains.sample:producer:1.0.0")).assertSingleSourcesJar() + jvmTestDependencies.getOrFail(binaryCoordinates("org.jetbrains.sample:producer:1.0.0")).assertSingleSourcesJar() + } + } + } + private fun Iterable.cinteropDependencies() = this.filterIsInstance().filter { it.klibExtra?.isInterop == true && !it.isNativeStdlib && !it.isNativeDistribution } + + private fun IdeaKotlinBinaryDependency.assertSingleSourcesJar(): File { + val sources = sourcesClasspath.toList() + if (sources.isEmpty()) fail("Missing -sources.jar") + if (sources.size > 1) fail("Multiple -sources.jar: $sources") + + return sources.single().also { sourcesFile -> + if (!sourcesFile.name.endsWith("-sources.jar")) fail("-sources.jar suffix expected. Found: ${sourcesFile.name}") + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/consumer/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/consumer/build.gradle.kts new file mode 100644 index 00000000000..3ab68c08859 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/consumer/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + maven(rootDir.resolve("repo")) + mavenLocal() + mavenCentral() +} + +kotlin { + jvm() + + sourceSets.jvmMain.dependencies { + implementation("org.jetbrains.sample:producer:1.0.0") + implementation("org.jetbrains.sample:producer:1.0.0") { + capabilities { + requireCapability("org.jetbrains.sample:producer-foo") + } + } + } + + sourceSets.jvmTest.dependencies { + implementation(project.dependencies.testFixtures("org.jetbrains.sample:producer:1.0.0")) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/build.gradle.kts new file mode 100644 index 00000000000..f40f94173f8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + `java-library` + `java-test-fixtures` + `maven-publish` +} + +group = "org.jetbrains.sample" +version = "1.0.0" + +publishing { + publications { + create("myLibrary", MavenPublication::class.java) { + from(components["java"]) + } + } + + repositories { + maven(rootDir.resolve("repo")) + } +} + + +java { + withSourcesJar() + registerFeature("foo") { + usingSourceSet(sourceSets.create("foo")) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/foo/java/Foo.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/foo/java/Foo.java new file mode 100644 index 00000000000..4ce430da24a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/foo/java/Foo.java @@ -0,0 +1,2 @@ +public class Foo { +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/main/java/Main.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/main/java/Main.java new file mode 100644 index 00000000000..70464179bc8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/main/java/Main.java @@ -0,0 +1,2 @@ +public class Main { +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/testFixtures/java/TestFixtures.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/testFixtures/java/TestFixtures.java new file mode 100644 index 00000000000..4fe7ff2c970 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/producer/src/testFixtures/java/TestFixtures.java @@ -0,0 +1,2 @@ +public class TestFixtures { +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/settings.gradle.kts new file mode 100644 index 00000000000..2dc76d8d61d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-60053-dependencyOn-testFixtures/settings.gradle.kts @@ -0,0 +1,3 @@ + +include(":producer") +include(":consumer") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeArtifactResolutionQuerySourcesAndDocumentationResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeArtifactResolutionQuerySourcesAndDocumentationResolver.kt index 249120cef2c..f45a89b32c2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeArtifactResolutionQuerySourcesAndDocumentationResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeArtifactResolutionQuerySourcesAndDocumentationResolver.kt @@ -11,6 +11,7 @@ import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.jvm.JvmLibrary import org.gradle.language.base.artifact.SourcesArtifact import org.gradle.language.java.artifact.JavadocArtifact +import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryCoordinates import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinResolvedBinaryDependency import org.jetbrains.kotlin.gradle.idea.tcs.extras.documentationClasspath @@ -19,7 +20,6 @@ import org.jetbrains.kotlin.gradle.idea.tcs.isKotlinCompileBinaryType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.ide.IdeAdditionalArtifactResolver -import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinBinaryCoordinates import org.jetbrains.kotlin.gradle.plugin.mpp.internal import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration import org.jetbrains.kotlin.gradle.plugin.sources.internal @@ -33,10 +33,11 @@ import org.jetbrains.kotlin.gradle.plugin.sources.internal * cc Anton Lakotka, Sebastian Sellmair */ internal object IdeArtifactResolutionQuerySourcesAndDocumentationResolver : IdeAdditionalArtifactResolver { + override fun resolve(sourceSet: KotlinSourceSet, dependencies: Set) { val binaryDependencies = dependencies.filterIsInstance() .filter { dependency -> dependency.isKotlinCompileBinaryType } - .groupBy { dependency -> dependency.coordinates?.copy(sourceSetName = null) } + .groupBy { dependency -> Coordinates(dependency.coordinates ?: return@groupBy null) } val project = sourceSet.project val configuration = selectConfiguration(sourceSet) @@ -50,9 +51,7 @@ internal object IdeArtifactResolutionQuerySourcesAndDocumentationResolver : IdeA } sourcesArtifacts.forEach { artifact -> - val artifactId = artifact.id.componentIdentifier as? ModuleComponentIdentifier ?: return@forEach - val artifactCoordinates = IdeaKotlinBinaryCoordinates(artifactId) - binaryDependencies[artifactCoordinates]?.forEach { dependency -> + binaryDependencies[Coordinates(artifact)]?.forEach { dependency -> dependency.sourcesClasspath.add(artifact.file) } } @@ -62,9 +61,7 @@ internal object IdeArtifactResolutionQuerySourcesAndDocumentationResolver : IdeA } javadocArtifacts.forEach { artifact -> - val artifactId = artifact.id.componentIdentifier as? ModuleComponentIdentifier ?: return@forEach - val artifactCoordinates = IdeaKotlinBinaryCoordinates(artifactId) - binaryDependencies[artifactCoordinates]?.forEach { dependency -> + binaryDependencies[Coordinates(artifact)]?.forEach { dependency -> dependency.documentationClasspath.add(artifact.file) } } @@ -75,4 +72,26 @@ internal object IdeArtifactResolutionQuerySourcesAndDocumentationResolver : IdeA return platformCompilation?.internal?.configurations?.compileDependencyConfiguration ?: sourceSet.internal.resolvableMetadataConfiguration } + + /** + * Specific 'Coordinates' type used to match previously resolved dependencies with their + * sources and javadoc artifact counterparts + */ + private data class Coordinates(private val coordinates: String) + + private fun Coordinates(coordinates: IdeaKotlinBinaryCoordinates): Coordinates? = when { + coordinates.capabilities.isEmpty() -> Coordinates("${coordinates.group}:${coordinates.module}:${coordinates.version}") + coordinates.capabilities.size == 1 -> coordinates.capabilities.single().run { Coordinates("$group:$name:$version") } + + /* + We do have a dependency that declares multiple capabilities. In this case we cannot use this resolver + to find the sources as we can only specify the componentId and not the explicit artifact + */ + else -> null + } + + private fun Coordinates(artifact: ResolvedArtifactResult): Coordinates? { + val id = artifact.id.componentIdentifier as? ModuleComponentIdentifier ?: return null + return Coordinates("${id.group}:${id.module}:${id.version}") + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver.kt index b0c5ca56c4a..2eac977e259 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver.kt @@ -15,11 +15,13 @@ import org.gradle.internal.resolve.ModuleVersionResolveException import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi import org.jetbrains.kotlin.gradle.idea.tcs.* import org.jetbrains.kotlin.gradle.idea.tcs.extras.artifactsClasspath +import org.jetbrains.kotlin.gradle.idea.tcs.extras.isIdeaProjectLevel import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver.Companion.gradleArtifact +import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinBinaryCapability import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinBinaryCoordinates import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinProjectCoordinates import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeBinaryDependencyResolver.ArtifactResolutionStrategy @@ -124,6 +126,7 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor( val unresolvedDependencies = artifacts.failures .onEach { reason -> sourceSet.project.logger.info("Failed to resolve platform dependency on ${sourceSet.name}", reason) } .map { reason -> + val selector = (reason as? ModuleVersionResolveException)?.selector as? ModuleComponentSelector /* Can't figure out the dependency here :( */ ?: return@map IdeaKotlinUnresolvedBinaryDependency( @@ -141,7 +144,8 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor( when (val componentId = artifact.id.componentIdentifier) { is ProjectComponentIdentifier -> { IdeaKotlinProjectArtifactDependency( - type = IdeaKotlinSourceDependency.Type.Regular, coordinates = IdeaKotlinProjectCoordinates(componentId) + type = IdeaKotlinSourceDependency.Type.Regular, + coordinates = IdeaKotlinProjectCoordinates(componentId) ).apply { artifactsClasspath.add(artifact.file) } @@ -149,7 +153,7 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor( is ModuleComponentIdentifier -> { IdeaKotlinResolvedBinaryDependency( - coordinates = IdeaKotlinBinaryCoordinates(componentId), + coordinates = IdeaKotlinBinaryCoordinates(componentId, artifact.variant.capabilities), binaryType = binaryType, classpath = IdeaKotlinClasspath(artifact.file), ) @@ -161,7 +165,8 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor( group = componentId.projectPath + "(${componentId.variant})", module = componentId.libraryName, version = null, - sourceSetName = null + sourceSetName = null, + capabilities = artifact.variant.capabilities.map(::IdeaKotlinBinaryCapability).toSet() ), classpath = IdeaKotlinClasspath(artifact.file) ) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt index 0ad00f3c199..b52b5b1d7c0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt @@ -53,4 +53,4 @@ internal object IdeTransformedMetadataDependencyResolver : IdeDependencyResolver } } } -} \ No newline at end of file +}