diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/ExternalAndroidTargetIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/ExternalAndroidTargetIT.kt
index 0c5e84bdb2c..faf209d705f 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/ExternalAndroidTargetIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/ExternalAndroidTargetIT.kt
@@ -6,12 +6,18 @@
package org.jetbrains.kotlin.gradle.android
import org.gradle.util.GradleVersion
-import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinSourceDependency.Type.Regular
-import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.*
+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.dependsOnDependency
+import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.friendSourceDependency
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.jetbrainsAnnotationDependencies
import org.jetbrains.kotlin.gradle.util.kotlinStdlibDependencies
import org.jetbrains.kotlin.gradle.util.resolveIdeDependencies
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.readText
+import kotlin.test.fail
@GradleTestVersions(minVersion = TestVersions.Gradle.G_8_1)
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_82)
@@ -81,4 +87,36 @@ class ExternalAndroidTargetIT : KGPBaseTest() {
}
}
}
+
+ @GradleAndroidTest
+ fun `test - simple project - pom dependencies rewritten`(
+ gradleVersion: GradleVersion, androidVersion: String, jdkVersion: JdkVersions.ProvidedJdk, @TempDir localRepoDir: Path,
+ ) {
+ project(
+ "externalAndroidTarget-project2project",
+ gradleVersion,
+ buildOptions = defaultBuildOptions.copy(androidVersion = androidVersion),
+ buildJdk = jdkVersion.location,
+ localRepoDir = localRepoDir
+ ) {
+ build("publish", buildOptions = buildOptions.copy(configurationCache = true)) {
+ val pomFile = localRepoDir.resolve("app/app-android/1.0/app-android-1.0.pom")
+ assertFileExists(pomFile)
+
+ fun String.removeWhiteSpaces() = replace("\\s+".toRegex(), "")
+ val pomText = pomFile.readText()
+ val expectedDependency = """
+
+ sample
+ tcs-android
+ 2.0
+ compile
+
+ """.trimIndent()
+
+ if (expectedDependency.removeWhiteSpaces() !in pomText.removeWhiteSpaces())
+ fail("Expected to find\n$expectedDependency\nIn POM file\n$pomText")
+ }
+ }
+ }
}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/build.gradle.kts
new file mode 100644
index 00000000000..be11214d48c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/build.gradle.kts
@@ -0,0 +1,43 @@
+plugins {
+ kotlin("multiplatform")
+ id("com.android.kotlin.multiplatform.library")
+ `maven-publish`
+}
+
+group = "app"
+version = "1.0"
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ google()
+}
+
+kotlin {
+ linuxX64()
+ linuxArm64()
+
+ androidLibrary {
+ compileSdk = 33
+ namespace = "org.jetbrains.sample"
+
+ withAndroidTestOnJvm()
+ sourceSets.getByName("androidTestOnJvm").dependencies {
+ implementation("junit:junit:4.13.2")
+ }
+ }
+
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(project(":lib"))
+ }
+ }
+ }
+}
+
+publishing {
+ repositories {
+ maven("")
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/build.gradle.kts
new file mode 100644
index 00000000000..7c9b487a6ba
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/build.gradle.kts
@@ -0,0 +1,36 @@
+plugins {
+ kotlin("multiplatform")
+ id("com.android.kotlin.multiplatform.library")
+ `maven-publish`
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ google()
+}
+
+kotlin {
+ linuxX64()
+ linuxArm64()
+
+ androidLibrary {
+ compileSdk = 33
+ namespace = "org.jetbrains.sample"
+ }
+}
+
+publishing {
+ repositories {
+ maven("")
+ }
+
+ publications {
+ val android by getting {
+ this as MavenPublication
+ artifactId = "tcs-android"
+ version = "2.0"
+ groupId = "sample"
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/src/commonMain/kotlin/CommonMain.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/src/commonMain/kotlin/CommonMain.kt
new file mode 100644
index 00000000000..4b52e0b66e6
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/lib/src/commonMain/kotlin/CommonMain.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2010-2023 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 lib
+
+fun commonMain() = 42
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/settings.gradle.kts
new file mode 100644
index 00000000000..e1a72365a33
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/settings.gradle.kts
@@ -0,0 +1,2 @@
+rootProject.name = "app"
+include(":lib")
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidMain/kotlin/AndroidMain.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidMain/kotlin/AndroidMain.kt
new file mode 100644
index 00000000000..f552460029e
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidMain/kotlin/AndroidMain.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2023 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.
+ */
+
+import android.content.Context
+import android.util.Log
+
+class AndroidMain(val context: Context) {
+ fun useContext() {
+ context.getSystemService(Context.LOCATION_SERVICE)
+ }
+
+ fun useLog() {
+ Log.d("test", CommonMain.toString())
+ }
+
+ companion object {
+ fun useCommonMain() {
+ println("useCommonMain: ${CommonMain}")
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidTestOnJvm/kotlin/AndroidTestOnJvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidTestOnJvm/kotlin/AndroidTestOnJvm.kt
new file mode 100644
index 00000000000..c8c920dd897
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/androidTestOnJvm/kotlin/AndroidTestOnJvm.kt
@@ -0,0 +1,14 @@
+import org.junit.Test
+
+/*
+ * Copyright 2010-2023 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.
+ */
+
+class AndroidTestOnJvm {
+ @Test
+ fun run() {
+ AndroidMain.useCommonMain()
+ org.junit.Assert.assertEquals("CommonMain", CommonMain.toString())
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/commonMain/kotlin/CommonMain.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/commonMain/kotlin/CommonMain.kt
new file mode 100644
index 00000000000..b91594832d4
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/externalAndroidTarget-project2project/src/commonMain/kotlin/CommonMain.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2010-2023 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.
+ */
+
+object CommonMain {
+ override fun toString(): String {
+ return "CommonMain"
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt
index bb852aeb8ba..df33162b562 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt
@@ -10,18 +10,18 @@ package org.jetbrains.kotlin.gradle.unitTests
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.DocsType
+import org.gradle.api.internal.component.SoftwareComponentInternal
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
-import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
+import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.hierarchy.KotlinSourceSetTreeClassifier
import org.jetbrains.kotlin.gradle.plugin.hierarchy.orNull
-import org.jetbrains.kotlin.gradle.plugin.launchInStage
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.CompilationAssociator
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.CompilationFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptorBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.external.createCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.external.createExternalKotlinTarget
+import org.jetbrains.kotlin.gradle.plugin.mpp.internal
+import org.jetbrains.kotlin.gradle.plugin.mpp.kotlinProjectStructureMetadata
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.gradle.utils.toMap
@@ -35,15 +35,21 @@ class ExternalKotlinTargetApiTests {
@Test
- fun `test - sourceSetClassifier - default`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourceSetClassifier - default`() = project.runLifecycleAwareTest {
val target = kotlin.createExternalKotlinTarget { defaults() }
- val compilation = target.createCompilation { defaults(kotlin) }
+ val mainCompilation = target.createCompilation { defaults(kotlin) }
+ val fakeCompilation = target.createCompilation {
+ defaults(kotlin)
+ compilationName = "fake"
+ defaultSourceSet = kotlin.sourceSets.create("fake")
+ }
- assertEquals(KotlinSourceSetTree("fake"), KotlinSourceSetTree.orNull(compilation))
+ assertEquals(KotlinSourceSetTree.main, KotlinSourceSetTree.orNull(mainCompilation))
+ assertEquals(KotlinSourceSetTree("fake"), KotlinSourceSetTree.orNull(fakeCompilation))
}
@Test
- fun `test - sourceSetClassifier - custom name`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourceSetClassifier - custom name`() = project.runLifecycleAwareTest {
val target = kotlin.createExternalKotlinTarget { defaults() }
val compilation = target.createCompilation {
defaults(kotlin)
@@ -54,7 +60,8 @@ class ExternalKotlinTargetApiTests {
}
@Test
- fun `test - sourceSetClassifier - custom property`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourceSetClassifier - custom property`() = project.runLifecycleAwareTest {
+ val kotlin = multiplatformExtension
val myProperty = project.objects.property()
val nullProperty = project.objects.property()
@@ -132,8 +139,9 @@ class ExternalKotlinTargetApiTests {
}
@Test
- fun `test - sourcesElements - default configuration`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourcesElements - default configuration`() = project.runLifecycleAwareTest {
val target = kotlin.createExternalKotlinTarget { defaults() }
+ target.createCompilation { defaults(kotlin) }
assertNotEquals(target.sourcesElementsConfiguration, target.sourcesElementsPublishedConfiguration)
@@ -165,7 +173,7 @@ class ExternalKotlinTargetApiTests {
}
@Test
- fun `test - sourcesElements - configure`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourcesElements - configure`() = project.runLifecycleAwareTest {
val testAttribute = Attribute.of("for.test", String::class.java)
val target = kotlin.createExternalKotlinTarget {
@@ -180,6 +188,7 @@ class ExternalKotlinTargetApiTests {
configuration.attributes.attribute(testAttribute, "sourcesElements-published")
}
}
+ target.createCompilation { defaults(kotlin) }
/* Check traces left before */
assertEquals("sourcesElements", target.sourcesElementsConfiguration.attributes.getAttribute(testAttribute))
@@ -187,22 +196,125 @@ class ExternalKotlinTargetApiTests {
}
@Test
- fun `test - sourcesElements - publication`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourcesElements - publication`() = project.runLifecycleAwareTest {
val target = kotlin.createExternalKotlinTarget { defaults() }
- val component = target.delegate.components.singleOrNull() ?: fail("Expected single 'component' for external target")
- component.usages.find { it.name == target.sourcesElementsPublishedConfiguration.name }
+ // Main/publishable compilation is required to have usages
+ target.createCompilation { defaults(kotlin) }
+
+ KotlinPluginLifecycle.Stage.AfterFinaliseCompilations.await()
+ val component = target.delegate.kotlinComponents.singleOrNull() ?: fail("Expected single 'component' for external target")
+
+ component.internal.usages.find { it.dependencyConfigurationName == target.sourcesElementsPublishedConfiguration.name }
?: fail("Missing sourcesElements usage")
}
@Test
- fun `test - sourcesElements - publication - withSourcesJar set to false`() = buildProjectWithMPP().runLifecycleAwareTest {
+ fun `test - sourcesElements - publication - withSourcesJar set to false`() = project.runLifecycleAwareTest {
val target = kotlin.createExternalKotlinTarget { defaults() }
target.withSourcesJar(false)
+ target.createCompilation { defaults(kotlin) }
+
val component = target.delegate.components.singleOrNull() ?: fail("Expected single 'component' for external target")
val sourcesUsage = component.usages.find { it.name.contains("sources", true) }
if (sourcesUsage != null) {
fail("Unexpected usage '${sourcesUsage.name} in target publication")
}
}
+
+ @Test
+ fun `test - gradle usage component contains the same usages as kotlin component`() = project.runLifecycleAwareTest {
+ val target = kotlin.createExternalKotlinTarget { defaults() }
+ target.createCompilation { defaults(kotlin) }
+
+ val kotlinComponent = target.delegate.kotlinComponents.singleOrNull()
+ ?: fail("Expected single 'kotlinComponent' for external target")
+
+ val gradleComponent = target.delegate.components.singleOrNull()
+ ?: fail("Expected single 'component' for external target")
+
+ configurationResult.await()
+
+ val kotlinUsagesNames = kotlinComponent.internal.usages.map { it.dependencyConfigurationName }
+ val gradleUsagesNames = (gradleComponent as SoftwareComponentInternal).usages.map { it.name }
+
+ if (kotlinUsagesNames.toSet() != gradleUsagesNames.toSet())
+ fail("Kotlin Usages ($kotlinUsagesNames) from Kotlin Component didn't match Usages from Gradle Component ($gradleUsagesNames)")
+ }
+
+ @Test
+ fun `test - gradle usage component contains the same usages as kotlin component after project evaluation`() {
+ val target = kotlin.createExternalKotlinTarget { defaults() }
+ target.createCompilation { defaults(kotlin) }
+ project.evaluate()
+
+ val kotlinComponent = target.kotlinComponents.singleOrNull() ?: fail("Expected single 'kotlinComponent' for external target")
+ val gradleComponent = target.components.singleOrNull() ?: fail("Expected single 'component' for external target")
+
+ if (kotlinComponent !is SoftwareComponentInternal) error("Expected 'kotlinComponent' to be 'SoftwareComponentInternal'")
+
+ // When kotlin usages is available corresponding gradle component usages should be also available
+ val kotlinUsagesNames = kotlinComponent.usages.map { it.name }
+ val gradleUsagesNames = gradleComponent.usages.map { it.name }
+
+ if (kotlinUsagesNames.toSet() != gradleUsagesNames.toSet())
+ fail("Kotlin Usages ($kotlinUsagesNames) from Kotlin Component didn't match Usages from Gradle Component ($gradleUsagesNames)")
+ }
+
+ @Test
+ fun `test - project structure metadata contains external target variants`() {
+ val target = kotlin.createExternalKotlinTarget { defaults() }
+ val mainCompilation = target.createCompilation { defaults(kotlin) }
+ val testCompilation = target.createCompilation {
+ defaults(kotlin)
+ compilationName = "test"
+ defaultSourceSet = kotlin.sourceSets.create("fakeTest")
+ }
+
+ kotlin.linuxX64()
+ kotlin.macosX64()
+
+ fun KotlinHierarchyBuilder.withFakeTarget() = withCompilations { it == mainCompilation || it == testCompilation }
+ kotlin.applyHierarchyTemplate {
+ sourceSetTrees(KotlinSourceSetTree.main, KotlinSourceSetTree.test)
+ common {
+ group("linuxAndFake") {
+ withLinux()
+ withFakeTarget()
+ }
+
+ withMacos()
+ }
+ }
+
+ project.evaluate()
+
+ val projectStructureMetadata = kotlin.kotlinProjectStructureMetadata
+
+ val fakeApiElementsSourceSets = projectStructureMetadata.sourceSetNamesByVariantName["fakeApiElements"]
+ ?: fail("Expected 'fakeApiElements' variant")
+ assertEquals(setOf("commonMain", "linuxAndFakeMain"), fakeApiElementsSourceSets)
+
+ val fakeRuntimeElementsSourceSets = projectStructureMetadata.sourceSetNamesByVariantName["fakeRuntimeElements"]
+ ?: fail("Expected 'fakeRuntimeElements' variant")
+ assertEquals(setOf("commonMain", "linuxAndFakeMain"), fakeRuntimeElementsSourceSets)
+ }
+
+ @Test
+ fun `test - published component contain user defined attributes`() {
+ val userAttribute = Attribute.of("userAttribute", String::class.java)
+
+ val target = kotlin.createExternalKotlinTarget { defaults() }
+ target.createCompilation { defaults(kotlin) }
+ target.attributes.attribute(userAttribute, "foo")
+
+ project.evaluate()
+
+ val component = target.internal.components.singleOrNull() ?: fail("Expected single component")
+ if (component.usages.isEmpty()) fail("Expected at least one usage")
+ component.usages.forEach { usage ->
+ if (!usage.attributes.contains(userAttribute)) fail("Usage '${usage.name}' does not contain user attribute")
+ assertEquals("foo", usage.attributes.getAttribute(userAttribute))
+ }
+ }
}