From 2c325c45e29f8326a89ffdbf2ad6f22e48ace2cf Mon Sep 17 00:00:00 2001 From: sellmair Date: Mon, 25 Nov 2019 11:12:10 +0100 Subject: [PATCH] Register Kotlin MPP source sets in AGP ^KT-43391 fixed --- .../AbstractKotlinAndroidGradleTests.kt | 56 ++++++- .../build.gradle.kts | 22 +++ .../gradle.properties | 1 + .../lib/build.gradle.kts | 38 +++++ .../androidAndroidTest/java/DontCompile.kt | 1 + .../kotlin/AndroidAndroidTest.kt | 13 ++ .../lib/src/androidMain/java/DontCompile.kt | 1 + .../kotlin/AndroidMainApiKotlin.kt | 3 + .../src/androidTest/java/AndroidTestJava.kt | 10 ++ .../androidTest/kotlin/AndroidTestKotlin.kt | 10 ++ .../lib/src/commonMain/kotlin/CommonApi.kt | 3 + .../lib/src/commonTest/kotlin/CommonTest.kt | 9 ++ .../lib/src/main/AndroidManifest.xml | 1 + .../lib/src/main/java/MainApiJava.kt | 3 + .../lib/src/main/kotlin/MainApiKotlin.kt | 3 + .../lib/src/test/java/TestJava.kt | 13 ++ .../lib/src/test/kotlin/TestKotlin.kt | 13 ++ .../settings.gradle.kts | 2 + .../SyncKotlinAndAndroidSourceSetsTest.kt | 152 ++++++++++++++++++ .../kotlin/gradle/plugin/KotlinPlugin.kt | 35 +--- .../mpp/syncKotlinAndAndroidSourceSets.kt | 123 ++++++++++++++ 21 files changed, 479 insertions(+), 33 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/gradle.properties create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/build.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/java/DontCompile.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidAndroidTest/kotlin/AndroidAndroidTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/java/DontCompile.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidMain/kotlin/AndroidMainApiKotlin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/java/AndroidTestJava.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/androidTest/kotlin/AndroidTestKotlin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonMain/kotlin/CommonApi.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/commonTest/kotlin/CommonTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/AndroidManifest.xml create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/java/MainApiJava.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/main/kotlin/MainApiKotlin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/java/TestJava.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/lib/src/test/kotlin/TestKotlin.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-android-source-sets/settings.gradle.kts create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt 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") +}