From 27aa3a573e6d5c7063f7a85af89939fa58bca09e Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Fri, 26 Apr 2019 18:20:51 +0300 Subject: [PATCH] Fix MPP source set dependencies missing in Android tests (KT-29343) Add Kotlin source set dependencies to the Android source sets, and also add aggregated Kotlin compilation dependencies to the dedicated Android source sets of the Android variants. Issue #KT-29343 Fixed --- .../AbstractKotlinAndroidGradleTests.kt | 59 +++++++++++++- .../kotlin/gradle/plugin/KotlinPlugin.kt | 81 +++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) 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 8118f78e599..bbcbadc597b 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 @@ -1,10 +1,7 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.logging.LogLevel -import org.jetbrains.kotlin.gradle.util.AGPVersion -import org.jetbrains.kotlin.gradle.util.getFileByName -import org.jetbrains.kotlin.gradle.util.getFilesByNames -import org.jetbrains.kotlin.gradle.util.modify +import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.Test import java.io.File @@ -211,6 +208,60 @@ open class KotlinAndroid32GradleIT : KotlinAndroid3GradleIT() { } } + @Test + fun testAndroidMppProductionDependenciesInTests() = with(Project("new-mpp-android", GradleVersionRequired.AtLeast("4.7"))) { + // Test the fix for KT-29343 + setupWorkingDir() + + gradleBuildScript("lib").appendText( + "\n" + """ + kotlin.sourceSets { + commonMain { + dependencies { + implementation kotlin("stdlib-common") + } + } + androidLibDebug { + dependencies { + implementation kotlin("reflect") + } + } + androidLibRelease { + dependencies { + implementation kotlin("test-junit") + } + } + } + """.trimIndent() + ) + + val kotlinVersion = defaultBuildOptions().kotlinVersion + testResolveAllConfigurations("lib") { + assertSuccessful() + + // commonMain: + assertContains(">> :lib:debugCompileClasspath --> kotlin-stdlib-common-$kotlinVersion.jar") + assertContains(">> :lib:releaseCompileClasspath --> kotlin-stdlib-common-$kotlinVersion.jar") + assertContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-stdlib-common-$kotlinVersion.jar") + assertContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-stdlib-common-$kotlinVersion.jar") + assertContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-stdlib-common-$kotlinVersion.jar") + + // androidLibDebug: + assertContains(">> :lib:debugCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + assertNotContains(">> :lib:releaseCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + assertContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + assertContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + assertNotContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar") + + // androidLibRelease: + assertNotContains(">> :lib:debugCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + assertContains(">> :lib:releaseCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + assertNotContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + assertNotContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + assertContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar") + } + } + @Test fun testCustomAttributesInAndroidTargets() = with(Project("new-mpp-android", GradleVersionRequired.AtLeast("5.0"))) { // Test the fix for KT-27714 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 80057558dfb..905d0956684 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 @@ -2,6 +2,7 @@ package org.jetbrains.kotlin.gradle.plugin import com.android.build.gradle.BaseExtension import com.android.build.gradle.BasePlugin +import com.android.build.gradle.api.AndroidSourceSet import com.android.build.gradle.api.BaseVariant import com.android.builder.model.SourceProvider import groovy.lang.Closure @@ -794,6 +795,86 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationT applySubplugins(project, compilation, variant, subpluginEnvironment) } checkAndroidAnnotationProcessorDependencyUsage(project) + + addKotlinDependenciesToAndroidSourceSets(project, kotlinAndroidTarget) + } + } + + /** + * 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 + * source set dependencies as well, we need to add them to the Android source sets' api/implementation-like configurations, + * not just the classpath-like configurations of the variants. + */ + private fun addKotlinDependenciesToAndroidSourceSets( + project: Project, + kotlinAndroidTarget: KotlinAndroidTarget + ) { + fun addDependenciesToAndroidSourceSet( + androidSourceSet: AndroidSourceSet, + apiConfigurationName: String, + implementationConfigurationName: String, + compileOnlyConfigurationName: String, + runtimeOnlyConfigurationName: String + ) { + project.addExtendsFromRelation(androidSourceSet.apiConfigurationName, apiConfigurationName) + project.addExtendsFromRelation(androidSourceSet.implementationConfigurationName, implementationConfigurationName) + project.addExtendsFromRelation(androidSourceSet.compileOnlyConfigurationName, compileOnlyConfigurationName) + project.addExtendsFromRelation(androidSourceSet.runtimeOnlyConfigurationName, runtimeOnlyConfigurationName) + } + + /** First, just add the dependencies from Kotlin source sets created for the Android source sets, + * see [org.jetbrains.kotlin.gradle.plugin.AbstractAndroidProjectHandler.configureTarget] + */ + (project.extensions.getByName("android") as BaseExtension).sourceSets.forEach { androidSourceSet -> + val kotlinSourceSetName = lowerCamelCaseName(kotlinAndroidTarget.disambiguationClassifier, androidSourceSet.name) + project.kotlinExtension.sourceSets.findByName(kotlinSourceSetName)?.let { kotlinSourceSet -> + addDependenciesToAndroidSourceSet( + androidSourceSet, + kotlinSourceSet.apiConfigurationName, + kotlinSourceSet.implementationConfigurationName, + kotlinSourceSet.compileOnlyConfigurationName, + kotlinSourceSet.runtimeOnlyConfigurationName + ) + } + } + + // Then also add the Kotlin compilation dependencies (which include the dependencies from all source sets that + // take part in the compilation) to Android source sets that are only included into a single variant corresponding + // to that compilation. This is needed in order for the dependencies to get propagated to + // the test variants; see KT-29343; + + // Trivial mapping of Android variants to Android source set names is impossible here, + // because some variants have their dedicated source sets with mismatching names, + // e.g. variant 'fooBarDebugAndroidTest' <-> source set 'androidTestFooBarDebug' + + // In single-platform projects, the Kotlin compilations already reference the Android plugin's configurations by the names, + // so there are no such separate things as the configurations of the compilations, and there's no need to setup the + // extendsFrom relationship. + if (kotlinAndroidTarget.disambiguationClassifier != null) { + + val sourceSetToVariants = mutableMapOf>().apply { + forEachVariant(project) { variant -> + for (sourceSet in getSourceProviders(variant)) { + val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue + getOrPut(androidSourceSet) { mutableListOf() }.add(variant) + } + } + } + + for ((androidSourceSet, variants) in sourceSetToVariants) { + val variant = variants.singleOrNull() + ?: continue // skip source sets that are included in multiple Android variants + + val compilation = kotlinAndroidTarget.compilations.getByName(getVariantName(variant)) + addDependenciesToAndroidSourceSet( + androidSourceSet, + compilation.apiConfigurationName, + compilation.implementationConfigurationName, + compilation.compileOnlyConfigurationName, + compilation.runtimeOnlyConfigurationName + ) + } } }