Skip to content

Commit

Permalink
Partially migrate KSP off AGP's legacy Variant API
Browse files Browse the repository at this point in the history
Use AGP's new addKspConfigurations() API when possible.

Bug: #2250
Test: AGP741IT and existing ProcessorClasspathConfigurationsTest
  • Loading branch information
scott-pollom committed Feb 4, 2025
1 parent 553adb6 commit b77377c
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 23 deletions.
6 changes: 6 additions & 0 deletions gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

description = "Kotlin Symbol Processor"
Expand Down Expand Up @@ -126,6 +127,8 @@ java {
resources.srcDir(testPropsOutDir)
}
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

tasks.named("compileTestKotlin").configure {
Expand Down Expand Up @@ -181,4 +184,7 @@ kotlin {
kotlin.srcDir(writeVersionSrcTask)
}
}
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package com.google.devtools.ksp.gradle

import com.android.build.api.AndroidPluginVersion
import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.SourceKind
Expand Down Expand Up @@ -49,7 +50,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 Expand Up @@ -155,4 +156,26 @@ object AndroidPluginIntegration {
resourcesOutputDir
)
}

/**
* Returns false for AGP versions 8.10.0-alpha03 or higher.
*
* Returns true for older AGP versions or when AGP version cannot be determined.
*/
fun Project.useLegacyVariantApi(): Boolean {
val agpVersion = try {
this.extensions
.findByType(com.android.build.api.variant.AndroidComponentsExtension::class.java)
?.pluginVersion
} catch (e: Exception) {
// Perhaps a version of AGP before pluginVersion API was added.
null
}

// Fall back to using the legacy Variant API if the AGP version can't be determined for now.
if (agpVersion == null) {
return true
}
return agpVersion < AndroidPluginVersion(8, 10, 0).alpha(3)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.google.devtools.ksp.gradle

import com.google.devtools.ksp.gradle.AndroidPluginIntegration.useLegacyVariantApi
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
Expand Down Expand Up @@ -100,13 +101,20 @@ class KspConfigurations(private val project: Project) {
// decorateKotlinProject here instead.
createAndroidSourceSetConfigurations(project, kotlinTarget = null)
}
project.pluginManager.withPlugin("com.android.base") {
if (!project.useLegacyVariantApi()) {
val androidComponents =
project.extensions.findByType(com.android.build.api.variant.AndroidComponentsExtension::class.java)
androidComponents?.addKspConfigurations(useGlobalConfiguration = allowAllTargetConfiguration)
}
}
}

private fun decorateKotlinProject(kotlin: KotlinProjectExtension, project: Project) {
when (kotlin) {
is KotlinSingleTargetExtension<*> -> decorateKotlinTarget(kotlin.target)
is KotlinSingleTargetExtension<*> -> decorateKotlinTarget(kotlin.target, isKotlinMultiplatform = false)
is KotlinMultiplatformExtension -> {
kotlin.targets.configureEach(::decorateKotlinTarget)
kotlin.targets.configureEach { decorateKotlinTarget(it, isKotlinMultiplatform = true) }

var reported = false
configurationForAll.dependencies.whenObjectAdded {
Expand Down Expand Up @@ -137,9 +145,11 @@ class KspConfigurations(private val project: Project) {
* there are slight differences between the two - Kotlin creates some extra sets with unexpected word ordering,
* and things get worse when you add product flavors. So, we use AGP sets as the source of truth.
*/
private fun decorateKotlinTarget(target: KotlinTarget) {
private fun decorateKotlinTarget(target: KotlinTarget, isKotlinMultiplatform: Boolean) {
if (target.platformType == KotlinPlatformType.androidJvm) {
createAndroidSourceSetConfigurations(target.project, target)
if (project.useLegacyVariantApi() || isKotlinMultiplatform) {
createAndroidSourceSetConfigurations(target.project, target)
}
} else {
target.compilations.configureEach { compilation ->
compilation.kotlinSourceSetsObservable.forAll { sourceSet ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.devtools.ksp.gradle

import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.gradle.AndroidPluginIntegration.useLegacyVariantApi
import com.google.devtools.ksp.gradle.model.builder.KspModelBuilder
import org.gradle.api.Action
import org.gradle.api.Project
Expand Down Expand Up @@ -253,17 +254,30 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool
val kotlinCompileProvider: TaskProvider<AbstractKotlinCompileTool<*>> =
project.locateTask(kotlinCompilation.compileKotlinTaskName) ?: return project.provider { emptyList() }
val kspExtension = project.extensions.getByType(KspExtension::class.java)
val kspConfigurations = kspConfigurations.find(kotlinCompilation)
val nonEmptyKspConfigurations = kspConfigurations.filter { it.allDependencies.isNotEmpty() }
if (nonEmptyKspConfigurations.isEmpty()) {
return project.provider { emptyList() }
}
if (kotlinCompileProvider.name == "compileKotlinMetadata") {
return project.provider { emptyList() }
}
if ((kotlinCompilation as? KotlinSharedNativeCompilation)?.platformType == KotlinPlatformType.common) {
return project.provider { emptyList() }
}
assert(kotlinCompileProvider.name.startsWith("compile"))
val kspTaskName = kotlinCompileProvider.name.replaceFirst("compile", "ksp")
val processorClasspath =
project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath").markResolvable()
if (kotlinCompilation.platformType != KotlinPlatformType.androidJvm ||
project.useLegacyVariantApi() ||
project.pluginManager.hasPlugin("kotlin-multiplatform")
) {
val nonEmptyKspConfigurations =
kspConfigurations.find(kotlinCompilation)
.filter { it.allDependencies.isNotEmpty() }
if (nonEmptyKspConfigurations.isEmpty()) {
return project.provider { emptyList() }
}
processorClasspath.extendsFrom(*nonEmptyKspConfigurations.toTypedArray())
} else if (processorClasspath.allDependencies.isEmpty()) {
return project.provider { emptyList() }
}

val target = kotlinCompilation.target.name
val sourceSetName = kotlinCompilation.defaultSourceSet.name
Expand Down Expand Up @@ -297,12 +311,6 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool
"$KSP_GROUP_ID:$KSP_COMPILER_PLUGIN_ID_NON_EMBEDDABLE:$KSP_VERSION"
)

assert(kotlinCompileProvider.name.startsWith("compile"))
val kspTaskName = kotlinCompileProvider.name.replaceFirst("compile", "ksp")

val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath")
.extendsFrom(*nonEmptyKspConfigurations.toTypedArray()).markResolvable()

val kspCachesDir = getKspCachesDir(project, sourceSetName, target)
fun configureAsKspTask(kspTask: KspTask, isIncremental: Boolean) {
// depends on the processor; if the processor changes, it needs to be reprocessed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ class SourceSetConfigurationsTest(val useKSP2: Boolean) {
it.startsWith("kapt") && !it.startsWith("kaptClasspath_")
}
val kspConfigurations = configurations.filter {
it.startsWith("ksp")
it.startsWith("ksp") && !it.endsWith("KotlinProcessorClasspath")
}
assertThat(kspConfigurations).containsExactlyElementsIn(
kaptConfigurations.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ data class TestConfig(
}

val androidBaseVersion by lazy {
kspProjectProperties["agpTestVersion"] as String
kspProjectProperties["agpBaseVersion"] as String
}

val mavenRepoPath = mavenRepoDir.path.replace(File.separatorChar, '/')
Expand Down
3 changes: 1 addition & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
org.gradle.jvmargs=-Duser.country=US -Dkotlin.daemon.jvm.options=-Xmx4096m -Dfile.encoding=UTF-8

kotlinBaseVersion=2.1.20-dev-8066
agpBaseVersion=7.3.1
agpTestVersion=8.7.1
agpBaseVersion=8.10.0-alpha03
intellijVersion=233.13135.128
junitVersion=4.13.1
junit5Version=5.8.2
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
16 changes: 14 additions & 2 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import com.google.devtools.ksp.RelativizingInternalPathProvider
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import kotlin.math.max

val junitVersion: String by project
val kotlinBaseVersion: String by project
val agpTestVersion: String by project
val agpBaseVersion: String by project
val aaCoroutinesVersion: String by project

plugins {
Expand All @@ -25,7 +26,7 @@ dependencies {
fun Test.configureCommonSettings() {
systemProperty("kotlinVersion", kotlinBaseVersion)
systemProperty("kspVersion", version)
systemProperty("agpVersion", agpTestVersion)
systemProperty("agpVersion", agpBaseVersion)
jvmArgumentProviders.add(
RelativizingInternalPathProvider(
"testRepo",
Expand Down Expand Up @@ -69,3 +70,14 @@ tasks.named<Test>("test") {
// Ensure that 'test' depends on 'compatibilityTest'
dependsOn(agpCompatibilityTest)
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,90 @@ class AGP741IT(useKSP2: Boolean) {
}
}

/**
* Similar to ProcessorClasspathConfigurationsTest.testConfigurationsForAndroidApp(), but we want to test with AGP
* version < 8.10.0 too because we use AGP's legacy Variant API in that case.
*/
@Test
fun testConfigurationsForAndroidApp() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root).withGradleVersion("7.6.3")

File(project.root, "gradle.properties").appendText("\nagpVersion=7.4.1")
File(project.root, "workload/build.gradle.kts").appendText(
"""
android {
flavorDimensions += listOf("tier", "region")
productFlavors {
create("free") {
dimension = "tier"
}
create("premium") {
dimension = "tier"
}
create("us") {
dimension = "region"
}
create("eu") {
dimension = "region"
}
}
}
configurations.matching { it.name.startsWith("ksp") && !it.name.endsWith("ProcessorClasspath") }.all {
// Make sure ksp configs are not empty.
project.dependencies.add(name, "androidx.room:room-compiler:2.4.2")
}
tasks.register("testConfigurations") {
// Resolve all tasks to trigger classpath config creation
dependsOn(tasks["tasks"])
doLast {
val freeUsDebugConfig = configurations["kspFreeUsDebugKotlinProcessorClasspath"]
val testFreeUsDebugConfig = configurations["kspFreeUsDebugUnitTestKotlinProcessorClasspath"]
val androidTestFreeUsDebugConfig =
configurations["kspFreeUsDebugAndroidTestKotlinProcessorClasspath"]
val freeUsDebugParentConfigs =
setOf(
"ksp",
"kspDebug",
"kspFree",
"kspUs",
"kspFreeUs",
"kspFreeUsDebug"
)
val testFreeUsDebugParentConfigs =
setOf(
"ksp",
"kspTest",
"kspTestDebug",
"kspTestFree",
"kspTestUs",
"kspTestFreeUs",
"kspTestFreeUsDebug"
)
val androidTestFreeUsDebugParentConfigs =
setOf(
"ksp",
"kspAndroidTest",
"kspAndroidTestDebug",
"kspAndroidTestFree",
"kspAndroidTestUs",
"kspAndroidTestFreeUs",
"kspAndroidTestFreeUsDebug"
)
require(freeUsDebugConfig.extendsFrom.map { it.name }.toSet() == freeUsDebugParentConfigs)
require(
testFreeUsDebugConfig.extendsFrom.map { it.name }.toSet() == testFreeUsDebugParentConfigs
)
require(
androidTestFreeUsDebugConfig.extendsFrom.map { it.name }.toSet() == androidTestFreeUsDebugParentConfigs
)
}
}
""".trimIndent()
)

gradleRunner.withArguments(":workload:testConfigurations").build()
}

companion object {
@JvmStatic
@Parameterized.Parameters(name = "KSP2={0}")
Expand Down

0 comments on commit b77377c

Please sign in to comment.