[Gradle][KT-53342] Implement MultiplatformAndroidSourceSetLayoutV2Test

This commit is contained in:
sebastian.sellmair
2022-08-02 11:34:33 +02:00
committed by Space
parent e307b96671
commit f68c05c1fb
7 changed files with 287 additions and 29 deletions
@@ -113,6 +113,6 @@ fun Project.enableDefaultStdlibDependency(enabled: Boolean = true) {
project.propertiesExtension.set(KOTLIN_STDLIB_DEFAULT_DEPENDENCY, enabled.toString())
}
fun Project.setAndroidMultiplatformSourceSetLayoutVersion(version: Int) {
fun Project.setMultiplatformAndroidSourceSetLayoutVersion(version: Int) {
project.propertiesExtension.set(KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION, version.toString())
}
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.android.kotlinAndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV1
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV2
import org.jetbrains.kotlin.gradle.plugin.sources.android.singleTargetAndroidSourceSetLayout
import org.jetbrains.kotlin.gradle.setAndroidMultiplatformSourceSetLayoutVersion
import org.jetbrains.kotlin.gradle.setMultiplatformAndroidSourceSetLayoutVersion
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -27,10 +27,10 @@ class KotlinAndroidSourceSetLayoutExtensionTest {
project.plugins.apply(KotlinAndroidPluginWrapper::class.java)
assertEquals(singleTargetAndroidSourceSetLayout, project.kotlinAndroidSourceSetLayout)
project.setAndroidMultiplatformSourceSetLayoutVersion(1)
project.setMultiplatformAndroidSourceSetLayoutVersion(1)
assertEquals(singleTargetAndroidSourceSetLayout, project.kotlinAndroidSourceSetLayout)
project.setAndroidMultiplatformSourceSetLayoutVersion(2)
project.setMultiplatformAndroidSourceSetLayoutVersion(2)
assertEquals(singleTargetAndroidSourceSetLayout, project.kotlinAndroidSourceSetLayout)
}
@@ -43,14 +43,14 @@ class KotlinAndroidSourceSetLayoutExtensionTest {
"Expected v1 being set as default"
)
project.setAndroidMultiplatformSourceSetLayoutVersion(2)
project.setMultiplatformAndroidSourceSetLayoutVersion(2)
assertEquals(multiplatformAndroidSourceSetLayoutV2, project.kotlinAndroidSourceSetLayout)
project.setAndroidMultiplatformSourceSetLayoutVersion(1)
project.setMultiplatformAndroidSourceSetLayoutVersion(1)
assertEquals(multiplatformAndroidSourceSetLayoutV1, project.kotlinAndroidSourceSetLayout)
/* Test unhappy path: Layout version 0 is unknown/unsupported */
project.setAndroidMultiplatformSourceSetLayoutVersion(0)
project.setMultiplatformAndroidSourceSetLayoutVersion(0)
assertFailsWith<IllegalArgumentException> {
project.kotlinAndroidSourceSetLayout
}
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.android.findKotlinSourceSet
import org.jetbrains.kotlin.gradle.setAndroidMultiplatformSourceSetLayoutVersion
import org.jetbrains.kotlin.gradle.setMultiplatformAndroidSourceSetLayoutVersion
import kotlin.test.*
class MultiplatformAndroidSourceSetLayoutV1Test {
@@ -31,7 +31,7 @@ class MultiplatformAndroidSourceSetLayoutV1Test {
fun setup() {
project = ProjectBuilder.builder().build() as ProjectInternal
addBuildEventsListenerRegistryMock(project)
project.setAndroidMultiplatformSourceSetLayoutVersion(1)
project.setMultiplatformAndroidSourceSetLayoutVersion(1)
project.plugins.apply("kotlin-multiplatform")
project.plugins.apply("android-library")
@@ -141,7 +141,7 @@ class MultiplatformAndroidSourceSetLayoutV1Test {
kotlin.android()
project.evaluate()
kotlin.sourceSets.toSet().allPairs()
kotlin.sourceSets.toSet().generatePairs()
.forEach { (sourceSetA, sourceSetB) ->
val sourceDirsInBothSourceSets = sourceSetA.kotlin.srcDirs.intersect(sourceSetB.kotlin.srcDirs)
assertTrue(
@@ -151,7 +151,7 @@ class MultiplatformAndroidSourceSetLayoutV1Test {
)
}
android.sourceSets.toSet().allPairs()
android.sourceSets.toSet().generatePairs()
.forEach { (sourceSetA, sourceSetB) ->
val sourceDirsInBothSourceSets = sourceSetA.java.srcDirs.intersect(sourceSetB.java.srcDirs)
assertTrue(
@@ -200,16 +200,3 @@ class MultiplatformAndroidSourceSetLayoutV1Test {
private val AndroidSourceSet.kotlinSourceSet
get() = project.findKotlinSourceSet(this) ?: fail("Missing KotlinSourceSet for AndroidSourceSet: $name")
}
private fun <T> Set<T>.allPairs(): Sequence<Pair<T, T>> {
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)
}
}
}
}
@@ -0,0 +1,246 @@
/*
* Copyright 2010-2022 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("FunctionName", "DuplicatedCode")
package org.jetbrains.kotlin.gradle.sources.android
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.builder.model.SourceProvider
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.applyMultiplatformPlugin
import org.jetbrains.kotlin.gradle.plugin.forEachVariant
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidTest
import org.jetbrains.kotlin.gradle.plugin.sources.android.main
import org.jetbrains.kotlin.gradle.plugin.sources.android.test
import org.jetbrains.kotlin.gradle.setMultiplatformAndroidSourceSetLayoutVersion
import org.jetbrains.kotlin.gradle.utils.androidExtension
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class MultiplatformAndroidSourceSetLayoutV2Test {
private val project = ProjectBuilder.builder().build()
.run { this as ProjectInternal }
.also { project -> project.setMultiplatformAndroidSourceSetLayoutVersion(2) }
private val kotlin = project.applyMultiplatformPlugin()
private val android: LibraryExtension = run {
project.plugins.apply(LibraryPlugin::class.java)
project.androidExtension as LibraryExtension
}
init {
android.compileSdk = 31
}
@Test
fun `test - main SourceSet - is called 'androidMain'`() {
kotlin.android()
assertEquals("androidMain", project.getKotlinSourceSetOrFail(android.sourceSets.main).name)
}
@Test
fun `test - main SourceSet name - with custom targetName`() {
kotlin.android("foo")
assertEquals("fooMain", project.getKotlinSourceSetOrFail(android.sourceSets.main).name)
}
@Test
fun `test - unitTest SourceSet - is called 'androidUnitTest'`() {
kotlin.android()
assertEquals("androidUnitTest", project.getKotlinSourceSetOrFail(android.sourceSets.test).name)
}
@Test
fun `test - unitTest SourceSet name - with custom targetName`() {
kotlin.android("foo")
assertEquals("fooUnitTest", project.getKotlinSourceSetOrFail(android.sourceSets.test).name)
}
@Test
fun `test - instrumentedTest SourceSet - is called 'androidInstrumentedTest'`() {
kotlin.android()
assertEquals("androidInstrumentedTest", project.getKotlinSourceSetOrFail(android.sourceSets.androidTest).name)
}
@Test
fun `test instrumentedTest SourceSet - with custom targetName`() {
kotlin.android("foo")
assertEquals("fooInstrumentedTest", project.getKotlinSourceSetOrFail(android.sourceSets.androidTest).name)
}
@Test
fun `test SourceSet names - with two flavorDimensions`() {
kotlin.android()
android.flavorDimensions.add("market")
android.flavorDimensions.add("price")
android.productFlavors.create("german").dimension = "market"
android.productFlavors.create("usa").dimension = "market"
android.productFlavors.create("paid").dimension = "price"
android.productFlavors.create("free").dimension = "price"
project.evaluate()
fun assertKotlinSourceSetNameEquals(
androidSourceSetName: String, kotlinSourceSetName: String
) {
val androidSourceSet = android.sourceSets.getByName(androidSourceSetName)
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(
kotlinSourceSetName, kotlinSourceSet.name,
"Expected KotlinSourceSet name: $kotlinSourceSetName for AndroidSourceSet name: $androidSourceSetName"
)
}
assertKotlinSourceSetNameEquals("germanPaidDebug", "androidGermanPaidDebug")
assertKotlinSourceSetNameEquals("germanPaid", "androidGermanPaid")
assertKotlinSourceSetNameEquals("german", "androidGerman")
assertKotlinSourceSetNameEquals("testGermanPaidDebug", "androidUnitTestGermanPaidDebug")
assertKotlinSourceSetNameEquals("testGermanPaid", "androidUnitTestGermanPaid")
assertKotlinSourceSetNameEquals("testGerman", "androidUnitTestGerman")
assertKotlinSourceSetNameEquals("androidTestGermanPaidDebug", "androidInstrumentedTestGermanPaidDebug")
assertKotlinSourceSetNameEquals("androidTestGermanPaid", "androidInstrumentedTestGermanPaid")
assertKotlinSourceSetNameEquals("androidTestGerman", "androidInstrumentedTestGerman")
}
@Test
fun `test - all source directories - are disjoint in source sets`() {
kotlin.android()
android.flavorDimensions.add("market")
android.flavorDimensions.add("price")
android.productFlavors.create("german").dimension = "market"
android.productFlavors.create("usa").dimension = "market"
android.productFlavors.create("paid").dimension = "price"
android.productFlavors.create("free").dimension = "price"
project.evaluate()
kotlin.sourceSets.toSet().generatePairs()
.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().generatePairs()
.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 `test - default dependsOn edges`() {
kotlin.android()
project.evaluate()
android.libraryVariants.all { libraryVariant ->
libraryVariant.sourceSets.forEach { androidSourceSet ->
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(
setOf("commonMain"), kotlinSourceSet.dependsOn.map { it.name }.toSet(),
"Expected KotlinSourceSet: ${kotlinSourceSet.name} to dependsOn commonMain"
)
}
}
android.unitTestVariants.all { libraryVariant ->
libraryVariant.sourceSets.forEach { androidSourceSet ->
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(
setOf("commonTest"), kotlinSourceSet.dependsOn.map { it.name }.toSet(),
"Expected KotlinSourceSet: ${kotlinSourceSet.name} to dependsOn commonTest"
)
}
}
android.testVariants.all { libraryVariant ->
libraryVariant.sourceSets.forEach { androidSourceSet ->
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(
emptySet(), kotlinSourceSet.dependsOn.map { it.name }.toSet(),
"Expected KotlinSourceSet: ${kotlinSourceSet.name} to have no dependsOn edges"
)
}
}
}
@Test
fun `test - kotlin source directories - are in sync between KotlinSourceSet and AndroidSourceSet`() {
kotlin.android()
forEachVariant(project) { variant ->
variant.sourceSets.forEach { androidSourceSet ->
androidSourceSet as SourceProvider
/* Check if KotlinSourceSet and AndroidSourceSet contain the same srcDirs */
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(
androidSourceSet.kotlinDirectories.toSet(), kotlinSourceSet.kotlin.srcDirs,
"Expected same kotlin srcDirs for KotlinSourceSet: ${kotlinSourceSet.name} " +
"and AndroidSourceSet: ${androidSourceSet.name}"
)
/* Check if androidSourceSet.kotlinDirectories is live and includes sources from Kotlin */
val adhocSrcDir = project.file("src/${kotlinSourceSet.name}/adhoc/${variant.name}")
assertFalse(adhocSrcDir in androidSourceSet.kotlinDirectories)
kotlinSourceSet.kotlin.srcDir(adhocSrcDir)
assertTrue(adhocSrcDir in androidSourceSet.kotlinDirectories)
}
}
project.evaluate()
}
@Test
fun `test - kotlin source directories - supports AGP default location`() {
kotlin.android()
forEachVariant(project) { variant ->
variant.sourceSets.forEach { androidSourceSet ->
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertTrue(
project.file("src/${androidSourceSet.name}/kotlin") in kotlinSourceSet.kotlin.srcDirs,
"Expected 'src/${androidSourceSet.name}/kotlin to be supported by KotlinSourceSet: ${kotlinSourceSet.name}"
)
}
}
project.evaluate()
}
@Test
fun `test - main - default AndroidManifest location`() {
kotlin.android()
assertEquals(
project.file("src/androidMain/AndroidManifest.xml"),
android.sourceSets.main.manifest.srcFile,
"Expected default AndroidManifest to be placed in 'androidMain'"
)
/* Check if default can be overwritten */
android.sourceSets.main.manifest.srcFile(project.file("custom.xml"))
project.evaluate()
assertEquals(project.file("custom.xml"), android.sourceSets.main.manifest.srcFile)
}
}
@@ -37,7 +37,7 @@ class SingleTargetAndroidSourceSetLayoutTest {
@Test
fun `test - default configuration - AndroidSourceSet has associated KotlinSourceSet`() {
android.sourceSets.all { androidSourceSet -> project.assertSingleKotlinSourceSet(androidSourceSet) }
android.sourceSets.all { androidSourceSet -> project.getKotlinSourceSetOrFail(androidSourceSet) }
project.evaluate()
}
@@ -46,7 +46,7 @@ class SingleTargetAndroidSourceSetLayoutTest {
fun `test - default configuration - AndroidSourceSet has KotlinSourceSet as convention`() {
android.sourceSets.all { androidSourceSet ->
assertSame(
project.assertSingleKotlinSourceSet(androidSourceSet),
project.getKotlinSourceSetOrFail(androidSourceSet),
(androidSourceSet as org.gradle.api.internal.HasConvention).convention.plugins["kotlin"] as? KotlinSourceSet,
"Expected Convention 'kotlin' on AndroidSourceSet: ${androidSourceSet.name}"
)
@@ -66,7 +66,7 @@ class SingleTargetAndroidSourceSetLayoutTest {
}
android.sourceSets.all { androidSourceSet ->
val kotlinSourceSet = project.assertSingleKotlinSourceSet(androidSourceSet)
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(androidSourceSet.name, kotlinSourceSet.name)
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2022 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.sources.android
fun <T> Set<T>.generatePairs(): Sequence<Pair<T, T>> {
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)
}
}
}
}
@@ -8,13 +8,15 @@
package org.jetbrains.kotlin.gradle.sources.android
import com.android.build.gradle.api.AndroidSourceSet
import com.android.builder.model.SourceProvider
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull
import org.jetbrains.kotlin.gradle.utils.androidExtension
import kotlin.test.fail
fun Project.assertSingleKotlinSourceSet(androidSourceSet: AndroidSourceSet): KotlinSourceSet {
fun Project.getKotlinSourceSetOrFail(androidSourceSet: AndroidSourceSet): KotlinSourceSet {
val kotlinSourceSets = kotlinExtension.sourceSets
.filter { kotlinSourceSet -> kotlinSourceSet.androidSourceSetInfoOrNull?.androidSourceSetName == androidSourceSet.name }
@@ -30,4 +32,8 @@ fun Project.assertSingleKotlinSourceSet(androidSourceSet: AndroidSourceSet): Kot
}
return kotlinSourceSets.single()
}
}
fun Project.getKotlinSourceSetOrFail(androidSourceSet: SourceProvider): KotlinSourceSet {
return getKotlinSourceSetOrFail(androidExtension.sourceSets.getByName(androidSourceSet.name))
}