Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partially migrate KSP off AGP's legacy Variant API #2312

Merged
merged 1 commit into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading