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
This commit is contained in:
Sergey Igushkin
2018-04-27 18:21:01 +03:00
parent 97d455729b
commit e82170a04e
2 changed files with 74 additions and 11 deletions
@@ -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)
}
}
}
@@ -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<Project> {
@@ -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<Project>()
private val platformKotlinTasksBySourceSetName = hashMapOf<String, AbstractKotlinCompile<*>>()
override fun apply(project: Project) {
project.tasks.filterIsInstance<AbstractKotlinCompile<*>>().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 <A, B> matchSymmetricallyByNames(
containerA: NamedDomainObjectContainer<A>,
containerB: NamedDomainObjectContainer<B>,
whenMatched: (A, B) -> Unit
) {
val matchedNames = mutableSetOf<String>()
fun <T, R> NamedDomainObjectContainer<T>.matchAllWith(other: NamedDomainObjectContainer<R>, 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<AbstractKotlinCompile<*>>()
.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