Skip to content

Commit

Permalink
Support AGP's built-in Kotlin compilation
Browse files Browse the repository at this point in the history
Previously, the sourceSet-specific KSP configurations (e.g., kspTest)
weren't being created when AGP's built-in Kotlin compilation was
enabled.

This change also increases the AGP version to 8.7.0 in order to test
against a version of AGP with the built-in Kotlin support.

This change also configures the AGP compatibility tests (AGP731IT and
AGP741IT) to run one test at a time to avoid race conditions seen when
the old AGP versions were trying to download SDKs simultaneously.

Bug: b/362279380
Test: GradleCompilationTest
  • Loading branch information
scott-pollom committed Oct 8, 2024
1 parent 69060b2 commit 7cca0af
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 28 deletions.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object AndroidPluginIntegration {
private fun decorateAndroidExtension(project: Project, onSourceSet: (String) -> Unit) {
val sourceSets = when (val androidExt = project.extensions.getByName("android")) {
is BaseExtension -> androidExt.sourceSets
is CommonExtension<*, *, *, *> -> androidExt.sourceSets
is CommonExtension<*, *, *, *, *, *> -> androidExt.sourceSets
else -> throw RuntimeException("Unsupported Android Gradle plugin version.")
}
sourceSets.all {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ class KspConfigurations(private val project: Project) {
}.replaceFirstChar { it.lowercase() }
}

/**
* Returns a new or existing [Configuration] with the given [name], with applied properties.
*/
private fun createConfiguration(
name: String,
readableSetName: String,
): Configuration {
// maybeCreate to be future-proof, but we should never have a duplicate with current logic
return project.configurations.maybeCreate(name).apply {
description = "KSP dependencies for the '$readableSetName' source set."
isCanBeResolved = false // we'll resolve the processor classpath config
Expand All @@ -48,14 +50,19 @@ class KspConfigurations(private val project: Project) {
}
}

private fun getAndroidConfigurationName(target: KotlinTarget, sourceSet: String): String {
/**
* Returns the Android sourceSet-specific KSP configuration name given a [kotlinTarget] and [sourceSet].
*
* For single-platform, [kotlinTarget] can be null.
*/
private fun getAndroidConfigurationName(kotlinTarget: KotlinTarget?, sourceSet: String): String {
val isMain = sourceSet.endsWith("main", ignoreCase = true)
val nameWithoutMain = when {
isMain -> sourceSet.substring(0, sourceSet.length - 4)
else -> sourceSet
}
// Note: on single-platform, target name is conveniently set to "".
return configurationNameOf(PREFIX, target.name, nameWithoutMain)
return configurationNameOf(PREFIX, kotlinTarget?.name ?: "", nameWithoutMain)
}

private fun getKotlinConfigurationName(compilation: KotlinCompilation<*>, sourceSet: KotlinSourceSet): String {
Expand Down Expand Up @@ -86,6 +93,13 @@ class KspConfigurations(private val project: Project) {
// 1.6.0: decorateKotlinProject(project.kotlinExtension)?
decorateKotlinProject(project.extensions.getByName("kotlin") as KotlinProjectExtension, project)
}
// Create sourceSet-specific KSP configurations for the case when the KotlinBaseApiPlugin is applied instead
// of the KotlinBasePluginWrapper (e.g., when AGP's built-in Kotlin support is enabled).
project.plugins.withType(KotlinBaseApiPlugin::class.java) {
// FIXME: After KT-70897 is fixed and AGP's built-in Kotlin support adds a `kotlin` extension, call
// decorateKotlinProject here instead.
createAndroidSourceSetConfigurations(project, kotlinTarget = null)
}
}

private fun decorateKotlinProject(kotlin: KotlinProjectExtension, project: Project) {
Expand Down Expand Up @@ -125,12 +139,7 @@ class KspConfigurations(private val project: Project) {
*/
private fun decorateKotlinTarget(target: KotlinTarget) {
if (target.platformType == KotlinPlatformType.androidJvm) {
AndroidPluginIntegration.forEachAndroidSourceSet(target.project) { sourceSet ->
createConfiguration(
name = getAndroidConfigurationName(target, sourceSet),
readableSetName = "$sourceSet (Android)"
)
}
createAndroidSourceSetConfigurations(target.project, target)
} else {
target.compilations.configureEach { compilation ->
compilation.kotlinSourceSetsObservable.forAll { sourceSet ->
Expand Down Expand Up @@ -177,4 +186,18 @@ class KspConfigurations(private val project: Project) {
compilation.target.project.configurations.findByName(it)
}.toSet()
}

/**
* Creates the Android sourceSet-specific KSP configurations for the given [project] and [kotlinTarget]
*
* For single-platform, [kotlinTarget] can be null.
*/
private fun createAndroidSourceSetConfigurations(project: Project, kotlinTarget: KotlinTarget?) {
AndroidPluginIntegration.forEachAndroidSourceSet(project) { sourceSet ->
createConfiguration(
name = getAndroidConfigurationName(kotlinTarget, sourceSet),
readableSetName = "$sourceSet (Android)"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,19 @@ class GradleCompilationTest {

testRule.runner().withArguments().build()
}

/**
* Regression test for b/362279380
*/
@Test
fun androidGradlePluginBuiltInKotlin() {
testRule.setupAppAsAndroidApp(enableAgpBuiltInKotlinSupport = true)
testRule.appModule.dependencies.addAll(
listOf(
artifact(configuration = "ksp", "androidx.room:room-compiler:2.4.2"),
artifact(configuration = "kspTest", "androidx.room:room-compiler:2.4.2")
)
)
testRule.runner().withArguments(":app:assembleDebug").build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,23 @@ class KspIntegrationTestRule(

/**
* Sets up the app module as an android app, adding necessary plugin dependencies, a manifest
* file and necessary gradle configuration.
* file and necessary gradle configuration. If [enableAgpBuiltInKotlinSupport] is true, enable AGP's built-in Kotlin
* support instead of applying the Kotlin Android Gradle plugin.
*/
fun setupAppAsAndroidApp() {
fun setupAppAsAndroidApp(enableAgpBuiltInKotlinSupport: Boolean = false) {
testProject.appModule.plugins.addAll(
listOf(
PluginDeclaration.id("com.android.application", testConfig.androidBaseVersion),
PluginDeclaration.kotlin("android", testConfig.kotlinBaseVersion),
PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion)
)
)
if (enableAgpBuiltInKotlinSupport) {
testProject.appModule
.plugins
.add(PluginDeclaration.id("com.android.experimental.built-in-kotlin", testConfig.androidBaseVersion))
} else {
testProject.appModule.plugins.add(PluginDeclaration.kotlin("android", testConfig.kotlinBaseVersion))
}
addAndroidBoilerplate()
}

Expand All @@ -126,11 +133,15 @@ class KspIntegrationTestRule(
"""
android {
namespace = "com.example.kspandroidtestapp"
compileSdkVersion(31)
compileSdk = 31
defaultConfig {
minSdkVersion(24)
minSdk = 24
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${testConfig.kotlinBaseVersion}")
}
""".trimIndent()
)
testProject.appModule.moduleRoot.resolve("src/main/AndroidManifest.xml")
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
org.gradle.jvmargs=-Duser.country=US -Dkotlin.daemon.jvm.options=-Xmx4096m -Dfile.encoding=UTF-8

kotlinBaseVersion=2.1.0-dev-5441
agpBaseVersion=8.0.2
agpBaseVersion=8.7.0
intellijVersion=233.13135.103
junitVersion=4.13.1
junit5Version=5.8.2
Expand Down
33 changes: 31 additions & 2 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

tasks.withType<Test> {
maxParallelForks = max(1, Runtime.getRuntime().availableProcessors() / 2)
fun Test.configureCommonSettings() {
systemProperty("kotlinVersion", kotlinBaseVersion)
systemProperty("kspVersion", version)
systemProperty("agpVersion", agpBaseVersion)
Expand All @@ -38,3 +37,33 @@ tasks.withType<Test> {
dependsOn(":symbol-processing-cmdline:publishAllPublicationsToTestRepository")
dependsOn(":symbol-processing-aa-embeddable:publishAllPublicationsToTestRepository")
}

val agpCompatibilityTestClasses = listOf("**/AGP731IT.class", "**/AGP741IT.class")

// Create a new test task for the AGP compatibility tests
val agpCompatibilityTest by tasks.registering(Test::class) {
description = "Runs AGP compatibility tests with maxParallelForks = 1"
group = "verification"

// Include only the AGP compatibility tests
include(agpCompatibilityTestClasses)

// Set maxParallelForks to 1 to avoid race conditions when downloading SDKs with old AGPs
maxParallelForks = 1

// Apply common settings
configureCommonSettings()
}

tasks.named<Test>("test") {
maxParallelForks = max(1, Runtime.getRuntime().availableProcessors() / 2)

// Exclude test classes from agpCompatibilityTest
exclude(agpCompatibilityTestClasses)

// Apply common settings
configureCommonSettings()

// Ensure that 'test' depends on 'compatibilityTest'
dependsOn(agpCompatibilityTest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies {
android {
namespace = "com.example.kspandroidtestapp"
defaultConfig {
minSdkVersion(24)
minSdk = 24
}
compileSdk = 34
buildFeatures {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ dependencies {

android {
namespace = "com.example.myapplication"
compileSdkVersion(34)
compileSdk = 34
defaultConfig {
applicationId = "org.gradle.kotlin.dsl.samples.androidstudio"
minSdkVersion(34)
targetSdkVersion(34)
minSdk = 34
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ dependencies {

android {
namespace = "com.example.mylibrary"
compileSdkVersion(34)
compileSdk = 34
defaultConfig {
minSdkVersion(34)
targetSdkVersion(34)
minSdk = 34
targetSdk = 34
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ dependencies {

android {
namespace = "com.example.myapplication"
compileSdkVersion(34)
compileSdk = 34
defaultConfig {
applicationId = "org.gradle.kotlin.dsl.samples.androidstudio"
minSdkVersion(34)
targetSdkVersion(34)
minSdk = 34
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
Expand Down

0 comments on commit 7cca0af

Please sign in to comment.