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

Support AGP's built-in Kotlin compilation #2097

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ subprojects {
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.add("-Xskip-prerelease-check")
compilerOptions {
freeCompilerArgs.add("-Xskip-prerelease-check")
// This should match JavaCompile's targetCompatibility (above)
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
}
}
}
22 changes: 21 additions & 1 deletion gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ val signingKey: String? by project
val signingPassword: String? by project

tasks.withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.add("-Xjvm-default=all-compatibility")
compilerOptions {
freeCompilerArgs.add("-Xjvm-default=all-compatibility")
// AGP dependency requires JVM_11 here
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}

plugins {
Expand Down Expand Up @@ -179,3 +183,19 @@ kotlin {
}
}
}

tasks.withType<JavaCompile>().configureEach {
// AGP dependency requires VERSION_11
sourceCompatibility = JavaVersion.VERSION_11.toString()
targetCompatibility = JavaVersion.VERSION_11.toString()
}

tasks.withType<Test>().configureEach {
// Java 17 is required to run tests with AGP
javaLauncher.set(
javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
}
)
}

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 @@ -48,14 +48,14 @@ class KspConfigurations(private val project: Project) {
}
}

private fun getAndroidConfigurationName(target: KotlinTarget, sourceSet: String): String {
private fun getAndroidConfigurationName(kotlinTargetName: String, 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, kotlinTargetName, nameWithoutMain)
}

private fun getKotlinConfigurationName(compilation: KotlinCompilation<*>, sourceSet: KotlinSourceSet): String {
Expand Down Expand Up @@ -86,6 +86,16 @@ 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) {
AndroidPluginIntegration.forEachAndroidSourceSet(project) { sourceSet ->
createConfiguration(
name = getAndroidConfigurationName(kotlinTargetName = "", sourceSet),
readableSetName = "$sourceSet (Android)"
)
}
}
}

private fun decorateKotlinProject(kotlin: KotlinProjectExtension, project: Project) {
Expand Down Expand Up @@ -127,7 +137,7 @@ class KspConfigurations(private val project: Project) {
if (target.platformType == KotlinPlatformType.androidJvm) {
AndroidPluginIntegration.forEachAndroidSourceSet(target.project) { sourceSet ->
createConfiguration(
name = getAndroidConfigurationName(target, sourceSet),
name = getAndroidConfigurationName(target.name, sourceSet),
readableSetName = "$sourceSet (Android)"
)
}
Expand Down Expand Up @@ -164,7 +174,7 @@ class KspConfigurations(private val project: Project) {
if (compilation.platformType == KotlinPlatformType.androidJvm) {
compilation as KotlinJvmAndroidCompilation
AndroidPluginIntegration.getCompilationSourceSets(compilation).mapTo(results) {
getAndroidConfigurationName(compilation.target, it)
getAndroidConfigurationName(compilation.target.name, it)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,19 @@ class GradleCompilationTest {
assertThat(result.output).contains("HAS LIBRARY: ")
assertThat(result.output).doesNotContain("app/build/generated/ksp/main/classes")
}

/**
* 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().withDebug(true).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 @@ -125,11 +132,16 @@ class KspIntegrationTestRule(
testProject.appModule.buildFileAdditions.add(
"""
android {
compileSdkVersion(31)
namespace = "com.example.kspandroidtestapp"
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 All @@ -138,8 +150,7 @@ class KspIntegrationTestRule(
}.writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kspandroidtestapp">
<manifest>
</manifest>
""".trimIndent()
)
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=7.3.1
agpBaseVersion=8.7.0-beta01
intellijVersion=233.13135.103
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,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
26 changes: 19 additions & 7 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.google.devtools.ksp.RelativizingInternalPathProvider
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import kotlin.math.max

val junitVersion: String by project
Expand All @@ -20,7 +21,7 @@ dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

tasks.withType<Test> {
tasks.withType<Test>().configureEach {
maxParallelForks = max(1, Runtime.getRuntime().availableProcessors() / 2)
systemProperty("kotlinVersion", kotlinBaseVersion)
systemProperty("kspVersion", version)
Expand All @@ -33,12 +34,23 @@ tasks.withType<Test> {
dependsOn(":symbol-processing-cmdline:publishAllPublicationsToTestRepository")
dependsOn(":symbol-processing-aa-embeddable:publishAllPublicationsToTestRepository")

// JDK_9 environment property is required.
// To add a custom location (if not detected automatically) follow https://docs.gradle.org/current/userguide/toolchains.html#sec:custom_loc
if (System.getenv("JDK_9") == null) {
val launcher9 = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(9))
// Java 17 is required to run tests with AGP
javaLauncher.set(
javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
}
environment["JDK_9"] = launcher9.map { it.metadata.installationPath }
)
}

tasks.withType<JavaCompile>().configureEach {
// ":gradle-plugin" dependency requires VERSION_11
sourceCompatibility = JavaVersion.VERSION_11.toString()
targetCompatibility = JavaVersion.VERSION_11.toString()
}

tasks.withType<KotlinCompile> {
compilerOptions {
// ":gradle-plugin" dependency requires JVM_11 here
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.google.devtools.ksp.test
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Assert
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -87,14 +88,15 @@ class IncrementalCPIT(val useKSP2: Boolean) {
fun testCPChangesForFunctions() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root)

gradleRunner.withArguments("clean", "assemble").build().let { result ->
// Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
gradleRunner.withArguments("clean", "assemble", "--no-configuration-cache").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
}

// Dummy changes
func2Dirty.forEach { (src, _) ->
File(project.root, src).appendText("\n\n")
gradleRunner.withArguments("assemble").build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").build().let { result ->
// Trivial changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
}
Expand All @@ -103,7 +105,7 @@ class IncrementalCPIT(val useKSP2: Boolean) {
// Value changes
func2Dirty.forEach { (src, _) ->
File(project.root, src).writeText("package p1\n\nfun MyTopFunc1(): Int = 1")
gradleRunner.withArguments("assemble").withDebug(true).build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").withDebug(true).build().let { result ->
// Value changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Expand All @@ -115,7 +117,7 @@ class IncrementalCPIT(val useKSP2: Boolean) {
// Signature changes
func2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nfun MyTopFunc1(): Double = 1.0")
gradleRunner.withArguments("assemble").build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Assert.assertEquals(expectedDirties, dirties)
Expand All @@ -133,14 +135,15 @@ class IncrementalCPIT(val useKSP2: Boolean) {
fun testCPChangesForProperties() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root)

gradleRunner.withArguments("clean", "assemble").build().let { result ->
// Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
gradleRunner.withArguments("clean", "assemble", "--no-configuration-cache").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
}

// Dummy changes
prop2Dirty.forEach { (src, _) ->
File(project.root, src).appendText("\n\n")
gradleRunner.withArguments("assemble").build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").build().let { result ->
// Trivial changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
}
Expand All @@ -149,7 +152,7 @@ class IncrementalCPIT(val useKSP2: Boolean) {
// Value changes
prop2Dirty.forEach { (src, _) ->
File(project.root, src).writeText("package p1\n\nval MyTopProp1: Int = 1")
gradleRunner.withArguments("assemble").withDebug(true).build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").withDebug(true).build().let { result ->
// Value changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Expand All @@ -161,7 +164,7 @@ class IncrementalCPIT(val useKSP2: Boolean) {
// Signature changes
prop2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nval MyTopProp1: Double = 1.0")
gradleRunner.withArguments("assemble").build().let { result ->
gradleRunner.withArguments("assemble", "--no-configuration-cache").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Assert.assertEquals(expectedDirties, dirties)
Expand Down Expand Up @@ -218,6 +221,7 @@ class IncrementalCPIT(val useKSP2: Boolean) {
toggleFlags("--no-configuration-cache")
}

@Ignore("https://github.com/google/ksp/issues/299")
@Test
fun toggleIncrementalFlagsWithConfigurationCache() {
toggleFlags("--configuration-cache")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ class KotlinConstsInJavaIT(useKSP2: Boolean) {
// FIXME: `clean` fails to delete files on windows.
Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)
gradleRunner.buildAndCheck(":workload:kspKotlin")
// Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
gradleRunner.buildAndCheck(":workload:kspKotlin", "--no-configuration-cache")

File(project.root, "workload/src/main/java/com/example/JavaClass.java").appendText("\n")
gradleRunner.buildAndCheck(":workload:kspKotlin")
gradleRunner.buildAndCheck(":workload:kspKotlin", "--no-configuration-cache")
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class OnErrorIT(useKSP2: Boolean) {
val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)

File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"createTwice\") }\n")
gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
// Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
gradleRunner.withArguments("clean", "assemble", "--no-configuration-cache").buildAndFail().let { result ->
val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }

Assert.assertTrue(
Expand All @@ -100,7 +101,8 @@ class OnErrorIT(useKSP2: Boolean) {

File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"createTwice\") }\n")
File(project.root, "gradle.properties").appendText("\nksp.return.ok.on.error=false")
gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
// Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
gradleRunner.withArguments("clean", "assemble", "--no-configuration-cache").buildAndFail().let { result ->
val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }

Assert.assertTrue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
}
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
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kspandroidtestapp">
<manifest>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ dependencies {
ksp(project(":processors"))
}

application {
mainClassName = "MainKt"
tasks.named<JavaExec>("run") {
mainClass.set("MainKt")
}
Loading
Loading