[Gradle] Setup kotlin sources directly on AndroidSourceSet for AGP >= 7.0

^KT-54033 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-09-19 13:26:47 +02:00
committed by Space
parent 958c7e72da
commit f286588e4c
11 changed files with 142 additions and 36 deletions
@@ -58,14 +58,19 @@ internal data class AndroidGradlePluginVersion(
}
companion object {
/**
* The currently applied/accessible Android Gradle Plugin version
*/
val current: AndroidGradlePluginVersion? = try {
val currentOrNull: AndroidGradlePluginVersion? = try {
AndroidGradlePluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
} catch (_: LinkageError) {
null
}
/**
* The currently applied/accessible Android Gradle Plugin version
*/
val current: AndroidGradlePluginVersion
get() = currentOrNull ?: throw IllegalStateException(
"Can't infer current AndroidGradlePluginVersion: Is the Android plugin applied?"
)
}
}
@@ -81,4 +86,4 @@ internal fun AndroidGradlePluginVersion?.isAtLeast(versionString: String): Boole
internal fun AndroidGradlePluginVersion?.isAtLeast(version: AndroidGradlePluginVersion): Boolean {
if (this == null) return false
return this >= version
}
}
@@ -64,7 +64,7 @@ internal object KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck {
object Default : AndroidGradlePluginVersionProvider {
override fun getAndroidGradlePluginVersion(): AndroidGradlePluginVersion? {
return AndroidGradlePluginVersion.current
return AndroidGradlePluginVersion.currentOrNull
}
}
}
@@ -737,7 +737,7 @@ internal open class KotlinAndroidPlugin(
private const val MINIMAL_SUPPORTED_AGP_VERSION = "3.6.4"
fun androidTargetHandler(): AndroidProjectHandler {
val tasksProvider = KotlinTasksProvider()
val androidGradlePluginVersion = AndroidGradlePluginVersion.current
val androidGradlePluginVersion = AndroidGradlePluginVersion.currentOrNull
if (androidGradlePluginVersion != null) {
if (androidGradlePluginVersion < MINIMAL_SUPPORTED_AGP_VERSION) {
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android
import org.jetbrains.kotlin.gradle.plugin.AndroidGradlePluginVersion
import org.jetbrains.kotlin.gradle.plugin.compareTo
import org.jetbrains.kotlin.gradle.plugin.sources.android.checker.*
import org.jetbrains.kotlin.gradle.plugin.sources.android.configurator.*
@@ -23,7 +25,10 @@ internal val singleTargetAndroidSourceSetLayout = KotlinAndroidSourceSetLayout(
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
AndroidSourceSetConventionConfigurator,
GradleConventionAddKotlinSourcesToAndroidSourceSetConfigurator,
Agp7AddKotlinSourcesToAndroidSourceSetConfigurator
.onlyIf { AndroidGradlePluginVersion.current >= "7.0.0" },
SingleTargetSourceDirConfigurator,
),
checker = KotlinAndroidSourceSetLayoutChecker()
@@ -35,7 +40,9 @@ internal val multiplatformAndroidSourceSetLayoutV1 = KotlinAndroidSourceSetLayou
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
AndroidSourceSetConventionConfigurator,
GradleConventionAddKotlinSourcesToAndroidSourceSetConfigurator,
Agp7AddKotlinSourcesToAndroidSourceSetConfigurator
.onlyIf { AndroidGradlePluginVersion.current >= "7.0.0" },
MultiplatformAndroidResourceDirConfigurator,
MultiplatformLayoutV1DependsOnConfigurator,
MultiplatformLayoutV1SourceDirConfigurator
@@ -53,6 +60,7 @@ internal val multiplatformAndroidSourceSetLayoutV2 = KotlinAndroidSourceSetLayou
AndroidKaptSourceSetConfigurator,
MultiplatformAndroidResourceDirConfigurator,
MultiplatformLayoutV2DependsOnConfigurator,
Agp7AddKotlinSourcesToAndroidSourceSetConfigurator,
MultiplatformLayoutV2SourceDirConfigurator,
MultiplatformLayoutV2DefaultManifestLocationConfigurator
),
@@ -6,13 +6,14 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android.checker
import com.android.Version
import org.jetbrains.kotlin.gradle.plugin.compareVersionNumbers
import org.jetbrains.kotlin.gradle.plugin.AndroidGradlePluginVersion
import org.jetbrains.kotlin.gradle.plugin.isAtLeast
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
internal object MultiplatformLayoutV2AgpRequirementChecker : KotlinAndroidSourceSetLayoutChecker {
private const val AGP_VERSION_MIN = "7.0.0"
internal val minimumRequiredAgpVersion = AndroidGradlePluginVersion(7, 0, 0)
override fun checkBeforeLayoutApplied(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
@@ -20,21 +21,23 @@ internal object MultiplatformLayoutV2AgpRequirementChecker : KotlinAndroidSource
layout: KotlinAndroidSourceSetLayout
) {
if (!isAgpRequirementMet()) {
diagnosticReporter.error(AgpRequirementNotMetDiagnostic(AGP_VERSION_MIN, Version.ANDROID_GRADLE_PLUGIN_VERSION))
diagnosticReporter.error(
AgpRequirementNotMetDiagnostic(minimumRequiredAgpVersion.toString(), Version.ANDROID_GRADLE_PLUGIN_VERSION)
)
}
}
internal fun isAgpRequirementMet(): Boolean {
return compareVersionNumbers(Version.ANDROID_GRADLE_PLUGIN_VERSION, AGP_VERSION_MIN) >= 0
return AndroidGradlePluginVersion.currentOrNull.isAtLeast(minimumRequiredAgpVersion)
}
internal data class AgpRequirementNotMetDiagnostic(
val requiredMinAgpVersion: String,
val minimumRequiredAgpVersion: String,
val currentAgpVersion: String
) : KotlinAndroidSourceSetLayoutChecker.Diagnostic {
override val message: String
get() = """
requires Android Gradle Plugin Version >= $requiredMinAgpVersion.
requires Android Gradle Plugin Version >= $minimumRequiredAgpVersion.
Found $currentAgpVersion
""".trimIndent()
}
@@ -0,0 +1,20 @@
/*
* 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceDirectorySet
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
object Agp7AddKotlinSourcesToAndroidSourceSetConfigurator: KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
val androidKotlinSourceDirectorySet = androidSourceSet.javaClass.getMethod("getKotlin")
.invoke(androidSourceSet) as AndroidSourceDirectorySet
androidKotlinSourceDirectorySet.srcDirs(target.project.provider { kotlinSourceSet.kotlin.srcDirs })
}
}
@@ -6,9 +6,7 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KOTLIN_DSL_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.addConvention
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
/*
@@ -16,7 +14,7 @@ Used by AGP com.android.build.gradle.internal.utils.syncAgpAndKgpSources to auto
from this KotlinSourceSet. This mechanism is deprecated.
*/
@Suppress("DEPRECATION")
internal object AndroidSourceSetConventionConfigurator : KotlinAndroidSourceSetConfigurator {
internal object GradleConventionAddKotlinSourcesToAndroidSourceSetConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
androidSourceSet.addConvention(KOTLIN_DSL_NAME, kotlinSourceSet)
}
@@ -27,6 +27,27 @@ internal interface KotlinAndroidSourceSetConfigurator {
) = Unit
}
internal fun KotlinAndroidSourceSetConfigurator.onlyIf(
condition: (target: KotlinAndroidTarget) -> Boolean
): KotlinAndroidSourceSetConfigurator {
return KotlinAndroidSourceSetConfigurationWithCondition(this, condition)
}
/* Conditional implementation */
private class KotlinAndroidSourceSetConfigurationWithCondition(
private val underlying: KotlinAndroidSourceSetConfigurator,
private val condition: (KotlinAndroidTarget) -> Boolean
) : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
if (condition(target)) underlying.configure(target, kotlinSourceSet, androidSourceSet)
}
override fun configureWithVariant(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variant: BaseVariant) {
if (condition(target)) underlying.configureWithVariant(target, kotlinSourceSet, variant)
}
}
/* Composite implementation */
internal fun KotlinAndroidSourceSetConfigurator(
@@ -5,18 +5,14 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceDirectorySet
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object MultiplatformLayoutV2SourceDirConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
val androidKotlinSourceDirectorySet = androidSourceSet.javaClass.getMethod("getKotlin")
.invoke(androidSourceSet) as AndroidSourceDirectorySet
androidKotlinSourceDirectorySet.srcDirs(target.project.provider { kotlinSourceSet.kotlin.srcDirs })
kotlinSourceSet.kotlin.srcDir(target.project.provider { androidSourceSet.java.srcDirs })
kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
}
}
@@ -197,6 +197,35 @@ class MultiplatformAndroidSourceSetLayoutV1Test {
assertSame(kotlin.sourceSets.getByName("androidAndroidTest"), androidTest.kotlinSourceSetByConvention())
}
@Test
fun `AndroidSourceSet kotlin AndroidSourceDirectorySet`() {
kotlin.android()
project.evaluate()
android.libraryVariants.all { variant ->
val main = variant.sourceSets.first { it.name == "main" }
assertEquals(
project.files("src/main/kotlin", "src/main/java", "src/androidMain/kotlin").toSet(),
main.kotlinDirectories.toSet()
)
}
android.unitTestVariants.all { variant ->
val test = variant.sourceSets.first { it.name == "test" }
assertEquals(
project.files("src/test/kotlin", "src/test/java", "src/androidTest/kotlin").toSet(),
test.kotlinDirectories.toSet()
)
}
android.testVariants.all { variant ->
val androidTest = variant.sourceSets.first { it.name == "androidTest" }
assertEquals(
project.files("src/androidTest/java", "src/androidAndroidTest/kotlin").toSet(),
androidTest.kotlinDirectories.toSet()
)
}
}
private val AndroidSourceSet.kotlinSourceSet
get() = project.findKotlinSourceSet(this) ?: fail("Missing KotlinSourceSet for AndroidSourceSet: $name")
}
@@ -7,14 +7,13 @@
package org.jetbrains.kotlin.gradle.sources.android
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.addBuildEventsListenerRegistryMock
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.utils.androidExtension
import org.junit.Test
import kotlin.test.BeforeTest
import kotlin.test.assertEquals
@@ -23,16 +22,16 @@ import kotlin.test.assertSame
class SingleTargetAndroidSourceSetLayoutTest {
private val project = ProjectBuilder.builder().build() as ProjectInternal
private val android: BaseExtension = run {
private val android: LibraryExtension = run {
addBuildEventsListenerRegistryMock(project)
project.plugins.apply(LibraryPlugin::class.java)
project.androidExtension
project.extensions.getByName("android") as LibraryExtension
}
@BeforeTest
fun setup() {
project.plugins.apply(KotlinAndroidPluginWrapper::class.java)
android.compileSdkVersion(31)
android.compileSdk = 31
}
@Test
@@ -57,17 +56,44 @@ class SingleTargetAndroidSourceSetLayoutTest {
@Test
fun `test - with flavors - AndroidSourceSet has associated KotlinSourceSet`() {
android.flavorDimensions("price", "market")
android.productFlavors {
it.create("free").dimension = "price"
it.create("paid").dimension = "price"
it.create("german").dimension = "market"
it.create("usa").dimension = "market"
}
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"
android.sourceSets.all { androidSourceSet ->
val kotlinSourceSet = project.getKotlinSourceSetOrFail(androidSourceSet)
assertEquals(androidSourceSet.name, kotlinSourceSet.name)
}
}
@Test
fun `AndroidSourceSet kotlin AndroidSourceDirectorySet`() {
project.evaluate()
android.libraryVariants.all { variant ->
val main = variant.sourceSets.first { it.name == "main" }
assertEquals(
project.files("src/main/kotlin", "src/main/java").toSet(),
main.kotlinDirectories.toSet()
)
}
android.unitTestVariants.all { variant ->
val test = variant.sourceSets.first { it.name == "test" }
assertEquals(
project.files("src/test/kotlin", "src/test/java").toSet(),
test.kotlinDirectories.toSet()
)
}
android.testVariants.all { variant ->
val androidTest = variant.sourceSets.first { it.name == "androidTest" }
assertEquals(
project.files("src/androidTest/kotlin", "src/androidTest/java").toSet(),
androidTest.kotlinDirectories.toSet()
)
}
}
}