diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
index afd41ffa90c..1e9a92466ee 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/AbstractKotlinAndroidGradleTests.kt
@@ -21,6 +21,55 @@ open class KotlinAndroid36GradleIT : KotlinAndroid33GradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.AtLeast("6.0")
+ @Test
+ fun testAndroidMppSourceSets(): Unit = with(Project("new-mpp-android-source-sets", GradleVersionRequired.FOR_MPP_SUPPORT)) {
+ build("sourceSets") {
+ assertSuccessful()
+ assertContains("Java sources: [lib/src/androidTest/java, lib/src/androidAndroidTest/kotlin]")
+ assertContains("Java sources: [lib/src/androidTestDebug/java, lib/src/androidAndroidTestDebug/kotlin]")
+ assertContains("Java sources: [lib/src/debug/java, lib/src/androidDebug/kotlin, lib/src/debug/kotlin]")
+ assertContains("Java sources: [lib/src/main/java, lib/src/androidMain/kotlin, lib/src/main/kotlin]")
+ assertContains("Java sources: [lib/src/release/java, lib/src/androidRelease/kotlin, lib/src/release/kotlin]")
+ assertContains("Java sources: [lib/src/test/java, lib/src/androidTest/kotlin, lib/src/test/kotlin]")
+ assertContains("Java sources: [lib/src/testDebug/java, lib/src/androidTestDebug/kotlin, lib/src/testDebug/kotlin]")
+ assertContains("Java sources: [lib/src/testRelease/java, lib/src/androidTestRelease/kotlin, lib/src/testRelease/kotlin]")
+
+ assertContains("Android resources: [lib/src/main/res, lib/src/androidMain/res]")
+ assertContains("Assets: [lib/src/main/assets, lib/src/androidMain/assets]")
+ assertContains("AIDL sources: [lib/src/main/aidl, lib/src/androidMain/aidl]")
+ assertContains("RenderScript sources: [lib/src/main/rs, lib/src/androidMain/rs]")
+ assertContains("JNI sources: [lib/src/main/jni, lib/src/androidMain/jni]")
+ assertContains("JNI libraries: [lib/src/main/jniLibs, lib/src/androidMain/jniLibs]")
+ assertContains("Java-style resources: [lib/src/main/resources, lib/src/androidMain/resources]")
+
+ assertContains("Android resources: [lib/src/androidTestDebug/res, lib/src/androidAndroidTestDebug/res]")
+ assertContains("Assets: [lib/src/androidTestDebug/assets, lib/src/androidAndroidTestDebug/assets]")
+ assertContains("AIDL sources: [lib/src/androidTestDebug/aidl, lib/src/androidAndroidTestDebug/aidl]")
+ assertContains("RenderScript sources: [lib/src/androidTestDebug/rs, lib/src/androidAndroidTestDebug/rs]")
+ assertContains("JNI sources: [lib/src/androidTestDebug/jni, lib/src/androidAndroidTestDebug/jni]")
+ assertContains("JNI libraries: [lib/src/androidTestDebug/jniLibs, lib/src/androidAndroidTestDebug/jniLibs]")
+ assertContains("Java-style resources: [lib/src/androidTestDebug/resources, lib/src/androidAndroidTestDebug/resources]")
+ }
+
+ build("testDebug") {
+ assertFailed()
+ assertContains("CommonTest > fail FAILED")
+ assertContains("TestKotlin > fail FAILED")
+ assertContains("AndroidTestKotlin > fail FAILED")
+ assertContains("TestJava > fail FAILED")
+ }
+
+ build("assemble") {
+ assertSuccessful()
+ }
+
+ // Test for KT-35016: MPP should recognize android instrumented tests correctly
+ build("connectedAndroidTest") {
+ assertFailed()
+ assertContains("No connected devices!")
+ }
+ }
+
@Test
fun testAndroidWithNewMppApp() = with(Project("new-mpp-android", GradleVersionRequired.FOR_MPP_SUPPORT)) {
build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") {
@@ -600,7 +649,12 @@ fun getSomething() = 10
}
val libAndroidClassesOnlyUtilKt = project.projectDir.getFileByName("LibAndroidClassesOnlyUtil.kt")
- libAndroidClassesOnlyUtilKt.modify { it.replace("fun libAndroidClassesOnlyUtil(): String", "fun libAndroidClassesOnlyUtil(): CharSequence") }
+ libAndroidClassesOnlyUtilKt.modify {
+ it.replace(
+ "fun libAndroidClassesOnlyUtil(): String",
+ "fun libAndroidClassesOnlyUtil(): CharSequence"
+ )
+ }
project.build("assembleDebug", options = options) {
assertSuccessful()
val affectedSources = project.projectDir.getFilesByNames("LibAndroidClassesOnlyUtil.kt", "useLibAndroidClassesOnlyUtil.kt")
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/build.gradle.kts
new file mode 100644
index 00000000000..b342fc26bd3
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/build.gradle.kts
@@ -0,0 +1,22 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ google()
+ mavenLocal()
+ jcenter()
+ }
+
+ dependencies {
+ classpath(kotlin("gradle-plugin:${property("kotlin_version")}"))
+ classpath("com.android.tools.build:gradle:${property("android_tools_version")}")
+ }
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+ google()
+ mavenLocal()
+ jcenter()
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/gradle.properties
new file mode 100644
index 00000000000..29e08e8ca88
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/build.gradle.kts
new file mode 100644
index 00000000000..1da4a7944ab
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/build.gradle.kts
@@ -0,0 +1,38 @@
+plugins {
+ id("com.android.library")
+ kotlin("multiplatform")
+}
+
+android {
+ compileSdkVersion(28)
+ defaultConfig {
+ minSdkVersion(21)
+ targetSdkVersion(28)
+ testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
+ }
+}
+
+kotlin {
+ android()
+ macosX64("macos")
+
+ sourceSets {
+ getByName("commonMain").dependencies {
+ implementation(kotlin("stdlib-common"))
+ }
+
+ getByName("commonMain").dependencies {
+ implementation(kotlin("test"))
+ implementation(kotlin("test-annotations-common"))
+ }
+
+ getByName("androidMain").dependencies {
+ implementation(kotlin("stdlib-jdk8"))
+ }
+
+ getByName("androidAndroidTest").dependencies {
+ implementation(kotlin("test-junit"))
+ implementation("com.android.support.test:runner:1.0.2")
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/java/DontCompile.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/java/DontCompile.kt
new file mode 100644
index 00000000000..c1dd1455371
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/java/DontCompile.kt
@@ -0,0 +1 @@
+CANT COMPILE THIS FILE!
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/kotlin/AndroidAndroidTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/kotlin/AndroidAndroidTest.kt
new file mode 100644
index 00000000000..9b4292b805c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/kotlin/AndroidAndroidTest.kt
@@ -0,0 +1,13 @@
+import kotlin.test.Test
+
+/**
+ * Expected to fail!
+ */
+class AndroidAndroidTest {
+ @Test
+ fun fail() {
+ MainApiKotlin.sayHi()
+ MainApiJava.sayHi()
+ CommonApi.throwException()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/java/DontCompile.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/java/DontCompile.kt
new file mode 100644
index 00000000000..c1dd1455371
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/java/DontCompile.kt
@@ -0,0 +1 @@
+CANT COMPILE THIS FILE!
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/kotlin/AndroidMainApiKotlin.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/kotlin/AndroidMainApiKotlin.kt
new file mode 100644
index 00000000000..bd15d8c7f08
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/kotlin/AndroidMainApiKotlin.kt
@@ -0,0 +1,3 @@
+object AndroidMainApiKotlin {
+ fun sayHi() = println("HI")
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/java/AndroidTestJava.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/java/AndroidTestJava.kt
new file mode 100644
index 00000000000..741c8b54851
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/java/AndroidTestJava.kt
@@ -0,0 +1,10 @@
+import org.junit.Test
+
+class AndroidTestJava {
+ @Test
+ fun fail() {
+ MainApiJava.sayHi()
+ MainApiKotlin.sayHi()
+ CommonApi.throwException()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/kotlin/AndroidTestKotlin.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/kotlin/AndroidTestKotlin.kt
new file mode 100644
index 00000000000..22555afa1ee
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/kotlin/AndroidTestKotlin.kt
@@ -0,0 +1,10 @@
+import org.junit.Test
+
+class AndroidTestKotlin {
+ @Test
+ fun fail() {
+ MainApiJava.sayHi()
+ MainApiKotlin.sayHi()
+ CommonApi.throwException()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonMain/kotlin/CommonApi.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonMain/kotlin/CommonApi.kt
new file mode 100644
index 00000000000..1e27d79d392
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonMain/kotlin/CommonApi.kt
@@ -0,0 +1,3 @@
+object CommonApi {
+ fun throwException(): Unit = error("This is supposed to fail!")
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonTest/kotlin/CommonTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonTest/kotlin/CommonTest.kt
new file mode 100644
index 00000000000..59d80b0fc49
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonTest/kotlin/CommonTest.kt
@@ -0,0 +1,9 @@
+import kotlin.test.Test
+
+/* Expected to fail ! */
+class CommonTest {
+ @Test
+ fun fail() {
+ CommonApi.throwException()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/AndroidManifest.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..47760dbcf68
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/java/MainApiJava.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/java/MainApiJava.kt
new file mode 100644
index 00000000000..9eb4dd76e65
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/java/MainApiJava.kt
@@ -0,0 +1,3 @@
+object MainApiJava {
+ fun sayHi() = println("HI")
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/kotlin/MainApiKotlin.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/kotlin/MainApiKotlin.kt
new file mode 100644
index 00000000000..7c6c88cb49f
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/kotlin/MainApiKotlin.kt
@@ -0,0 +1,3 @@
+object MainApiKotlin {
+ fun sayHi() = println("HI")
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/java/TestJava.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/java/TestJava.kt
new file mode 100644
index 00000000000..c67e4607057
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/java/TestJava.kt
@@ -0,0 +1,13 @@
+import org.junit.Test
+
+class TestJava {
+
+ @Test
+ fun fail() {
+ MainApiKotlin.sayHi()
+ MainApiJava.sayHi()
+ AndroidMainApiKotlin.sayHi()
+ CommonApi.throwException()
+ }
+
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/kotlin/TestKotlin.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/kotlin/TestKotlin.kt
new file mode 100644
index 00000000000..e9d69714433
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/kotlin/TestKotlin.kt
@@ -0,0 +1,13 @@
+import kotlin.test.Test
+
+/**
+ * Expected to fail!
+ */
+class TestKotlin {
+ @Test
+ fun fail() {
+ MainApiKotlin.sayHi()
+ MainApiJava.sayHi()
+ CommonApi.throwException()
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/settings.gradle.kts
new file mode 100644
index 00000000000..da712f78ee0
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/settings.gradle.kts
@@ -0,0 +1,2 @@
+rootProject.name = "mpp-playgound"
+include(":lib")
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt
new file mode 100644
index 00000000000..98c07c3aba4
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010-2020 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("invisible_reference", "invisible_member", "FunctionName")
+
+package org.jetbrains.kotlin.gradle
+
+import com.android.build.gradle.LibraryExtension
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
+import kotlin.test.*
+
+class SyncKotlinAndAndroidSourceSetsTest {
+
+ private lateinit var project: ProjectInternal
+ private lateinit var kotlin: KotlinMultiplatformExtension
+ private lateinit var android: LibraryExtension
+
+ @BeforeTest
+ fun setup() {
+ project = ProjectBuilder.builder().build() as ProjectInternal
+ project.plugins.apply("kotlin-multiplatform")
+ project.plugins.apply("android-library")
+
+ /* Arbitrary minimal Android setup */
+ android = project.extensions.getByName("android") as LibraryExtension
+ android.compileSdkVersion(30)
+
+ /* Kotlin Setup */
+ kotlin = project.multiplatformExtension
+
+ }
+
+
+ @Test
+ fun `main source set with default settings`() {
+ kotlin.android()
+
+ val kotlinAndroidMainSourceSet = kotlin.sourceSets.getByName("androidMain")
+ val androidMainSourceSet = android.sourceSets.getByName("main")
+
+ assertEquals(
+ androidMainSourceSet.java.srcDirs.toSet(),
+ kotlinAndroidMainSourceSet.kotlin.srcDirs.toSet(),
+ "Expected all source directories being present in all models"
+ )
+ }
+
+ @Test
+ fun `test source set with default settings`() {
+ kotlin.android()
+
+ val kotlinAndroidTestSourceSet = kotlin.sourceSets.getByName("androidTest")
+ val testSourceSet = android.sourceSets.getByName("test")
+
+ assertEquals(
+ testSourceSet.java.srcDirs.toSet(),
+ kotlinAndroidTestSourceSet.kotlin.srcDirs.toSet(),
+ "Expected all source directories being present in all models"
+ )
+ }
+
+ @Test
+ fun `androidTest source set with default settings`() {
+ kotlin.android()
+
+ val kotlinAndroidAndroidTestSourceSet = kotlin.sourceSets.getByName("androidAndroidTest")
+ val androidTestSourceSet = android.sourceSets.getByName("androidTest")
+
+ assertTrue(
+ androidTestSourceSet.java.srcDirs.toSet().containsAll(kotlinAndroidAndroidTestSourceSet.kotlin.srcDirs),
+ "Expected all kotlin source directories being registered on AGP"
+ )
+
+ assertTrue(
+ project.file("src/androidTest/kotlin") !in kotlinAndroidAndroidTestSourceSet.kotlin.srcDirs,
+ "Expected no source directory of 'androidTest' kotlin source set (Unit Test) " +
+ "being present in 'androidAndroidTest' kotlin source set (Instrumented Test)"
+ )
+
+ assertTrue(
+ project.file("src/androidTest/kotlin") !in androidTestSourceSet.java.srcDirs,
+ "Expected no source directory of 'androidTest' kotlin source set (Unit Test) " +
+ "being present in 'androidTest' Android source set (Instrumented Test)"
+ )
+ }
+
+ @Test
+ fun `all source directories are disjoint in source sets`() {
+ kotlin.android()
+ project.evaluate()
+
+ kotlin.sourceSets.toSet().allPairs()
+ .forEach { (sourceSetA, sourceSetB) ->
+ val sourceDirsInBothSourceSets = sourceSetA.kotlin.srcDirs.intersect(sourceSetB.kotlin.srcDirs)
+ assertTrue(
+ sourceDirsInBothSourceSets.isEmpty(),
+ "Expected disjoint source directories in source sets. " +
+ "Found $sourceDirsInBothSourceSets present in ${sourceSetA.name}(Kotlin) and ${sourceSetB.name}(Kotlin)"
+ )
+ }
+
+ android.sourceSets.toSet().allPairs()
+ .forEach { (sourceSetA, sourceSetB) ->
+ val sourceDirsInBothSourceSets = sourceSetA.java.srcDirs.intersect(sourceSetB.java.srcDirs)
+ assertTrue(
+ sourceDirsInBothSourceSets.isEmpty(),
+ "Expected disjoint source directories in source sets. " +
+ "Found $sourceDirsInBothSourceSets present in ${sourceSetA.name}(Android) and ${sourceSetB.name}(Android)"
+ )
+ }
+ }
+
+ @Test
+ fun `sync includes user configuration`() {
+ kotlin.android()
+
+ val kotlinAndroidMain = kotlin.sourceSets.getByName("androidMain")
+ val androidMain = android.sourceSets.getByName("main")
+
+ kotlinAndroidMain.kotlin.srcDir(project.file("fromKotlin"))
+ androidMain.java.srcDir(project.file("fromAndroid"))
+
+ project.evaluate()
+
+ assertTrue(
+ kotlinAndroidMain.kotlin.srcDirs.containsAll(setOf(project.file("fromKotlin"), project.file("fromAndroid"))),
+ "Expected custom configured source directories being present on kotlin source set after evaluation"
+ )
+
+ assertTrue(
+ androidMain.java.srcDirs.containsAll(setOf(project.file("fromKotlin"), project.file("fromAndroid"))),
+ "Expected custom configured source directories being present on android source set after evaluation"
+ )
+ }
+}
+
+private fun Set.allPairs(): Sequence> {
+ val values = this.toList()
+ return sequence {
+ for (index in values.indices) {
+ val first = values[index]
+ for (remainingIndex in (index + 1)..values.lastIndex) {
+ val second = values[remainingIndex]
+ yield(first to second)
+ }
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
index 69564b3059a..288ea2be08f 100755
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt
@@ -777,25 +777,11 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
)
fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) {
+ syncKotlinAndAndroidSourceSets(kotlinAndroidTarget)
+
val project = kotlinAndroidTarget.project
val ext = project.extensions.getByName("android") as BaseExtension
- ext.sourceSets.all { sourceSet ->
- logger.kotlinDebug("Creating KotlinBaseSourceSet for source set $sourceSet")
- val kotlinSourceSet = project.kotlinExtension.sourceSets.maybeCreate(
- kotlinSourceSetNameForAndroidSourceSet(kotlinAndroidTarget, sourceSet.name)
- ).apply {
- createDefaultDependsOnEdges(kotlinAndroidTarget, sourceSet, this)
- kotlin.srcDir(project.file(project.file("src/${sourceSet.name}/kotlin")))
- kotlin.srcDirs(sourceSet.java.srcDirs)
- }
- sourceSet.addConvention(KOTLIN_DSL_NAME, kotlinSourceSet)
-
- ifKaptEnabled(project) {
- Kapt3GradleSubplugin.createAptConfigurationIfNeeded(project, sourceSet.name)
- }
- }
-
val kotlinOptions = KotlinJvmOptionsImpl()
project.whenEvaluated {
// TODO don't require the flag once there is an Android Gradle plugin build that supports desugaring of Long.hashCode and
@@ -852,21 +838,6 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
}
}
- private fun createDefaultDependsOnEdges(
- kotlinAndroidTarget: KotlinAndroidTarget,
- androidSourceSet: AndroidSourceSet,
- kotlinSourceSet: KotlinSourceSet
- ) {
- val commonSourceSetName = when (androidSourceSet.name) {
- "main" -> "commonMain"
- "test" -> "commonTest"
- "androidTest" -> "commonTest"
- else -> return
- }
- val commonSourceSet = kotlinAndroidTarget.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return
- kotlinSourceSet.dependsOn(commonSourceSet)
- }
-
/**
* The Android variants have their configurations extendsFrom relation set up in a way that only some of the configurations of the
* variants propagate the dependencies from production variants to test ones. To make this dependency propagation work for the Kotlin
@@ -1049,7 +1020,7 @@ internal fun configureJavaTask(
}
}
-private fun ifKaptEnabled(project: Project, block: () -> Unit) {
+internal fun ifKaptEnabled(project: Project, block: () -> Unit) {
var triggered = false
fun trigger() {
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt
new file mode 100644
index 00000000000..a04d0c40aa7
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010-2020 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.plugin.mpp
+
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.api.AndroidSourceSet
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
+import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
+import org.jetbrains.kotlin.gradle.plugin.*
+import org.jetbrains.kotlin.gradle.plugin.AbstractAndroidProjectHandler.Companion.kotlinSourceSetNameForAndroidSourceSet
+import org.jetbrains.kotlin.gradle.plugin.addConvention
+import java.io.File
+
+internal fun syncKotlinAndAndroidSourceSets(target: KotlinAndroidTarget) {
+ val project = target.project
+ val android = project.extensions.getByName("android") as BaseExtension
+
+ android.sourceSets.all { androidSourceSet ->
+ val kotlinSourceSetName = kotlinSourceSetNameForAndroidSourceSet(target, androidSourceSet.name)
+ val kotlinSourceSet = project.kotlinExtension.sourceSets.maybeCreate(kotlinSourceSetName)
+ createDefaultDependsOnEdges(target, kotlinSourceSet, androidSourceSet)
+ syncKotlinAndAndroidSourceDirs(target, kotlinSourceSet, androidSourceSet)
+ syncKotlinAndAndroidResources(target, kotlinSourceSet, androidSourceSet)
+ androidSourceSet.addConvention(KOTLIN_DSL_NAME, kotlinSourceSet)
+
+ ifKaptEnabled(project) {
+ Kapt3GradleSubplugin.createAptConfigurationIfNeeded(project, androidSourceSet.name)
+ }
+ }
+}
+
+private fun createDefaultDependsOnEdges(
+ target: KotlinAndroidTarget,
+ kotlinSourceSet: KotlinSourceSet,
+ androidSourceSet: AndroidSourceSet
+) {
+ val commonSourceSetName = when (androidSourceSet.name) {
+ "main" -> "commonMain"
+ "test" -> "commonTest"
+ "androidTest" -> "commonTest"
+ else -> return
+ }
+ val commonSourceSet = target.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return
+ kotlinSourceSet.dependsOn(commonSourceSet)
+}
+
+private fun syncKotlinAndAndroidSourceDirs(
+ target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet
+) {
+ val disambiguationClassifier = target.disambiguationClassifier
+
+ /*
+ Mitigate ambiguity!
+ Example: disambiguationClassifier="android"
+ Source Directory "src/androidTest/kotlin"
+ -- could be claimed by kotlin {android}Test (unit test)
+ -- could be claimed by Android androidTest (instrumented test)
+
+ The Kotlin source set would win in this scenario.
+ */
+ if (disambiguationClassifier == null || !androidSourceSet.name.startsWith(disambiguationClassifier)) {
+ kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
+ }
+
+ kotlinSourceSet.kotlin.srcDirs(*androidSourceSet.java.srcDirs.toTypedArray())
+ androidSourceSet.java.srcDirs(*kotlinSourceSet.kotlin.srcDirs.toTypedArray())
+
+ /*
+ Make sure to include user configuration as well.
+ Unfortunately, there does not exist any "ad-hoc" API like `all`.
+ Therefore we sync the directories once again after evaluation
+ */
+ target.project.whenEvaluated {
+ kotlinSourceSet.kotlin.srcDirs(*androidSourceSet.java.srcDirs.toTypedArray())
+ androidSourceSet.java.srcDirs(*kotlinSourceSet.kotlin.srcDirs.toTypedArray())
+ }
+}
+
+private fun syncKotlinAndAndroidResources(
+ target: KotlinAndroidTarget,
+ kotlinSourceSet: KotlinSourceSet,
+ androidSourceSet: AndroidSourceSet
+) {
+ val project = target.project
+
+ androidSourceSet.resources.srcDirs(*kotlinSourceSet.resources.toList().toTypedArray())
+ if (androidSourceSet.resources.srcDirs.isNotEmpty()) {
+ androidSourceSet.resources.srcDir(kotlinSourceSet.sourceFolderFor(project, "resources"))
+ kotlinSourceSet.resources.srcDirs(androidSourceSet.resources.srcDirs - kotlinSourceSet.resources.srcDirs)
+ }
+
+ if (androidSourceSet.assets.srcDirs.isNotEmpty()) {
+ androidSourceSet.assets.srcDir(kotlinSourceSet.sourceFolderFor(project, "assets"))
+ }
+
+ if (androidSourceSet.res.srcDirs.isNotEmpty()) {
+ androidSourceSet.res.srcDir(kotlinSourceSet.sourceFolderFor(project, "res"))
+ }
+
+ if (androidSourceSet.aidl.srcDirs.isNotEmpty()) {
+ androidSourceSet.aidl.srcDir(kotlinSourceSet.sourceFolderFor(project, "aidl"))
+ }
+
+ if (androidSourceSet.renderscript.srcDirs.isNotEmpty()) {
+ androidSourceSet.renderscript.srcDir(kotlinSourceSet.sourceFolderFor(project, "rs"))
+ }
+
+ if (androidSourceSet.jni.srcDirs.isNotEmpty()) {
+ androidSourceSet.jni.srcDir(kotlinSourceSet.sourceFolderFor(project, "jni"))
+ }
+
+ if (androidSourceSet.jniLibs.srcDirs.isNotEmpty()) {
+ androidSourceSet.jniLibs.srcDir(kotlinSourceSet.sourceFolderFor(project, "jniLibs"))
+ }
+}
+
+private fun KotlinSourceSet.sourceFolderFor(project: Project, type: String): File {
+ return project.file("src/${this.name}/$type")
+}