From e82170a04eeceb7b23b2a9bb27d7de8cc83ae0a4 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Fri, 27 Apr 2018 18:21:01 +0300 Subject: [PATCH] Tracks and match source sets for MPP after the plugins are applied Since the custom source sets may be added after the plugins are applied in both common and platform module, we need to handle `.all { ... }` source sets in both modules and add common sources to the platform module once a match is found. Issue #KT-22510 Fixed --- .../kotlin/gradle/MultiplatformGradleIT.kt | 31 +++++++++++ .../plugin/KotlinMultiplatformPlugin.kt | 54 +++++++++++++++---- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt index 1aa90748b8b..428ca10fe0c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt @@ -256,4 +256,35 @@ class MultiplatformGradleIT : BaseGradleIT() { assertContains("$successMarker :lib:compileTestJava") } } + + @Test + fun testCustomSourceSets() = with(Project("multiplatformProject")) { + setupWorkingDir() + + val sourceSetName = "foo" + val sourceSetDeclaration = "\nsourceSets { $sourceSetName { } }" + + listOf("lib", "libJvm", "libJs").forEach { module -> + gradleBuildScript(module).appendText(sourceSetDeclaration) + } + + listOf( + "expect fun foo(): String" to "lib/src/$sourceSetName/kotlin", + "actual fun foo(): String = \"jvm\"" to "libJvm/src/$sourceSetName/kotlin", + "actual fun foo(): String = \"js\"" to "libJs/src/$sourceSetName/kotlin" + ).forEach { (code, path) -> + File(projectDir, path).run { + mkdirs(); + File(this, "Foo.kt").writeText(code) + } + } + + val customSourceSetCompileTasks = listOf(":lib" to "Common", ":libJs" to "2Js", ":libJvm" to "") + .map { (module, platform) -> "$module:compile${sourceSetName.capitalize()}Kotlin$platform" } + + build(*customSourceSetCompileTasks.toTypedArray()) { + assertSuccessful() + assertTasksExecuted(customSourceSetCompileTasks) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt index 2b25748d8ea..f6cbb7e3553 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt @@ -17,15 +17,12 @@ package org.jetbrains.kotlin.gradle.plugin import com.android.build.gradle.BaseExtension -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project +import org.gradle.api.* import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.file.SourceDirectorySet import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer -import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin { @@ -51,11 +48,8 @@ const val IMPLEMENT_DEPRECATION_WARNING = "The '$IMPLEMENT_CONFIG_NAME' configur open class KotlinPlatformImplementationPluginBase(platformName: String) : KotlinPlatformPluginBase(platformName) { private val commonProjects = arrayListOf() - private val platformKotlinTasksBySourceSetName = hashMapOf>() override fun apply(project: Project) { - project.tasks.filterIsInstance>().associateByTo(platformKotlinTasksBySourceSetName) { it.sourceSetName } - val implementConfig = project.configurations.create(IMPLEMENT_CONFIG_NAME) val expectedByConfig = project.configurations.create(EXPECTED_BY_CONFIG_NAME) @@ -107,16 +101,51 @@ open class KotlinPlatformImplementationPluginBase(platformName: String) : Kotlin "dependency to non-common project $commonProject") } - commonProject.sourceSets.all { commonSourceSet -> - // todo: warn if not found + // Since the two projects may add source sets in arbitrary order, and both may do that after the plugin is applied, + // we need to handle all source sets of the two projects and connect them once we get a match: + // todo warn if no match found + matchSymmetricallyByNames(commonProject.sourceSets, namedSourceSetsContainer(platformProject)) { commonSourceSet, _ -> addCommonSourceSetToPlatformSourceSet(commonSourceSet, platformProject) } } } + /** + * Applies [whenMatched] to pairs of items with the same name in [containerA] and [containerB], + * regardless of the order in which they are added to the containers. + */ + private fun matchSymmetricallyByNames( + containerA: NamedDomainObjectContainer, + containerB: NamedDomainObjectContainer, + whenMatched: (A, B) -> Unit + ) { + val matchedNames = mutableSetOf() + + fun NamedDomainObjectContainer.matchAllWith(other: NamedDomainObjectContainer, match: (T, R) -> Unit) { + this@matchAllWith.all { item -> + val itemName = this@matchAllWith.namer.determineName(item) + if (itemName !in matchedNames) { + val otherItem = other.findByName(itemName) + if (otherItem != null) { + matchedNames += itemName + match(item, otherItem) + } + } + } + } + containerA.matchAllWith(containerB) { a, b -> whenMatched(a, b) } + containerB.matchAllWith(containerA) { b, a -> whenMatched(a, b) } + } + + protected open fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> = + project.sourceSets + protected open fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: SourceSet, platformProject: Project) { - val platformTask = platformKotlinTasksBySourceSetName[commonSourceSet.name] - commonSourceSet.kotlin!!.srcDirs.forEach { platformTask?.source(it) } + val platformTask = platformProject.tasks + .filterIsInstance>() + .firstOrNull { it.sourceSetName == commonSourceSet.name } + + platformTask?.source(commonSourceSet.kotlin!!) } protected val SourceSet.kotlin: SourceDirectorySet? @@ -148,6 +177,9 @@ open class KotlinPlatformAndroidPlugin : KotlinPlatformImplementationPluginBase( super.apply(project) } + override fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> = + (project.extensions.getByName("android") as BaseExtension).sourceSets + override fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: SourceSet, platformProject: Project) { val androidExtension = platformProject.extensions.getByName("android") as BaseExtension val androidSourceSet = androidExtension.sourceSets.findByName(commonSourceSet.name) ?: return