diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt index 8c89047c9b..a5145d37dd 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt @@ -17,9 +17,11 @@ package com.google.devtools.ksp.gradle +import com.android.build.api.variant.AndroidComponentsExtension import com.google.devtools.ksp.gradle.utils.allKotlinSourceSetsObservable import com.google.devtools.ksp.gradle.utils.canUseGeneratedKotlinApi import com.google.devtools.ksp.gradle.utils.enableProjectIsolationCompatibleCodepath +import com.google.devtools.ksp.gradle.utils.isAgpBuiltInKotlinUsed import com.google.devtools.ksp.impl.KotlinSymbolProcessing import com.google.devtools.ksp.processing.ExitCode import com.google.devtools.ksp.processing.KSPCommonConfig @@ -31,6 +33,7 @@ import com.google.devtools.ksp.processing.KspGradleLogger import org.gradle.api.DefaultTask import org.gradle.api.JavaVersion import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Attribute import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.LogLevel @@ -223,21 +226,38 @@ abstract class KspAATask @Inject constructor( } if (kotlinCompilation is KotlinJvmAndroidCompilation) { - // Workaround of a dependency resolution issue of AGP. - // FIXME: figure out how to filter or set variant attributes correctly. - val kaptGeneratedClassesDir = getKaptGeneratedClassesDir(project, sourceSetName) - val kspOutputDir = KspGradleSubplugin.getKspOutputDir(project, sourceSetName, target) - cfg.libraries.from( - project.files( - Callable { - kotlinCompileProvider.get().libraries.filter { - !kspOutputDir.get().asFile.isParentOf(it) && - !kaptGeneratedClassesDir.isParentOf(it) && - !(it.isDirectory && it.listFiles()?.isEmpty() == true) - } + if (project.isAgpBuiltInKotlinUsed()) { + // when legacy-kapt plugin is applied, we can't use KotlinCompile.libraries directly + // because it contains the kapt output classes dir leading to circular dependency + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + cfg.libraries.from(androidComponents.sdkComponents.bootClasspath) + cfg.libraries.from( + project.provider { + val configuration = project.configurations.getByName( + kotlinCompilation.compileDependencyConfigurationName + ) + configuration.incoming.artifactView { config -> + config.attributes.attribute( + Attribute.of("artifactType", String::class.java), "android-classes-jar" + ) + }.files } ) - ) + } else { + val kaptGeneratedClassesDir = getKaptGeneratedClassesDir(project, sourceSetName) + val kspOutputDir = KspGradleSubplugin.getKspOutputDir(project, sourceSetName, target) + cfg.libraries.from( + project.files( + Callable { + kotlinCompileProvider.get().libraries.filter { + !kspOutputDir.get().asFile.isParentOf(it) && + !kaptGeneratedClassesDir.isParentOf(it) && + !(it.isDirectory && it.listFiles()?.isEmpty() == true) + } + } + ) + ) + } } else { cfg.libraries.from( project.provider { diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidDataBindingBuiltInKotlinIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidDataBindingBuiltInKotlinIT.kt new file mode 100644 index 0000000000..b32d86a1cc --- /dev/null +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidDataBindingBuiltInKotlinIT.kt @@ -0,0 +1,34 @@ +package com.google.devtools.ksp.test + +import com.google.devtools.ksp.test.fixtures.TemporaryTestProject +import org.gradle.testkit.runner.GradleRunner +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +class AndroidDataBindingBuiltInKotlinIT { + @Rule + @JvmField + val project: TemporaryTestProject = TemporaryTestProject("android-data-binding-builtinkotlin") + + @Test + fun testPlaygroundAndroid() { + val gradleRunner = GradleRunner.create().withProjectDir(project.root) + + // Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details + gradleRunner.withArguments( + "clean", + ":app:assemble", + "--configuration-cache-problems=warn", + "--info", + "--stacktrace" + ) + .build().let { result -> + val output = result.output.lines() + val kspTask = output.filter { + it.contains(":app:kspDebugKotlin") + } + Assert.assertTrue(kspTask.isNotEmpty()) + } + } +} diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/LegacyKaptKspIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/LegacyKaptKspIT.kt new file mode 100644 index 0000000000..2ff922bdbe --- /dev/null +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/LegacyKaptKspIT.kt @@ -0,0 +1,31 @@ +package com.google.devtools.ksp.test + +import com.google.devtools.ksp.test.fixtures.TemporaryTestProject +import org.gradle.testkit.runner.GradleRunner +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +class LegacyKaptKspIT { + @Rule + @JvmField + val project: TemporaryTestProject = TemporaryTestProject("legacy-kapt", "playground") + + @Test + fun testPlaygroundAndroid() { + val gradleRunner = GradleRunner.create().withProjectDir(project.root) + gradleRunner.withArguments( + "clean", + ":app:testDebugUnitTest", + "--configuration-cache-problems=warn", + "--info", + "--stacktrace" + ).build().let { result -> + val output = result.output.lines() + val kspTask = output.filter { it.contains(":app:kspDebugKotlin") } + val kaptTask = output.filter { it.contains(":app:kaptDebugKotlin") } + Assert.assertTrue(kspTask.isNotEmpty()) + Assert.assertTrue(kaptTask.isNotEmpty()) + } + } +} diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/build.gradle.kts b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/build.gradle.kts new file mode 100644 index 0000000000..b6a0a6680c --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/build.gradle.kts @@ -0,0 +1,51 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("com.android.application") + id("com.google.dagger.hilt.android") version "2.57.2" + id("com.google.devtools.ksp") +} + +dependencies { + implementation("androidx.activity:activity:1.11.0") + implementation("com.google.dagger:hilt-android:2.57.2") + ksp("com.google.dagger:hilt-compiler:2.57.2") +} + +android { + namespace = "com.example.databinding" + compileSdk = 36 + + defaultConfig { + applicationId = "com.example.databinding" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + } + + buildFeatures { + dataBinding = true + } + + dataBinding { + enable = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + } +} + +hilt { + enableAggregatingTask = false +} + + diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/AndroidManifest.xml b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..f10f89ecc5 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/A.kt b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/A.kt new file mode 100644 index 0000000000..0abd30d1ff --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/A.kt @@ -0,0 +1,8 @@ +package com.example.databinding + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton class A @Inject constructor() { + val string = "a" +} diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/App.kt b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/App.kt new file mode 100644 index 0000000000..bb89bea434 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/App.kt @@ -0,0 +1,7 @@ +package com.example.databinding + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class App : Application() diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/BindingActivity.kt b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/BindingActivity.kt new file mode 100644 index 0000000000..a2d69318f2 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/BindingActivity.kt @@ -0,0 +1,20 @@ +package com.example.databinding + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.annotation.CallSuper +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding + +abstract class BindingActivity( + @get:LayoutRes private val layoutId: Int, +) : ComponentActivity() { + protected lateinit var binding: T + private set + + @CallSuper override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, layoutId) + } +} diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/MainActivity.kt b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/MainActivity.kt new file mode 100644 index 0000000000..4a89581a70 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/kotlin/MainActivity.kt @@ -0,0 +1,16 @@ +package com.example.databinding + +import android.os.Bundle +import com.example.databinding.databinding.ActivityMainBinding +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class MainActivity : BindingActivity(R.layout.activity_main) { + @Inject lateinit var a: A + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding.a = a + } +} diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/res/layout/activity_main.xml b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..2b1157cd6a --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/build.gradle.kts b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/build.gradle.kts new file mode 100644 index 0000000000..023f8a1191 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/build.gradle.kts @@ -0,0 +1,25 @@ +buildscript { + val testRepo: String by project + + repositories { + maven(testRepo) + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + google() + } +} + +plugins { + id("com.android.application") apply false + id("com.google.devtools.ksp") apply false +} + +allprojects { + val testRepo: String by project + repositories { + maven(testRepo) + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + google() + } +} diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/gradle.properties b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/gradle.properties new file mode 100644 index 0000000000..c132bcc68e --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/gradle.properties @@ -0,0 +1 @@ +android.newDsl=false \ No newline at end of file diff --git a/integration-tests/src/test/resources/android-data-binding-builtinkotlin/settings.gradle.kts b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/settings.gradle.kts new file mode 100644 index 0000000000..97c1a1ccd4 --- /dev/null +++ b/integration-tests/src/test/resources/android-data-binding-builtinkotlin/settings.gradle.kts @@ -0,0 +1,21 @@ +pluginManagement { + val kotlinVersion: String by settings + val kspVersion: String by settings + val testRepo: String by settings + val agpVersion: String by settings + plugins { + id("com.google.devtools.ksp") version kspVersion apply false + kotlin("jvm") version kotlinVersion apply false + id("com.android.application") version agpVersion apply false + id("com.android.library") version agpVersion apply false + } + repositories { + maven(testRepo) + gradlePluginPortal() + google() + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + } +} + +include(":app") diff --git a/integration-tests/src/test/resources/legacy-kapt/app/build.gradle.kts b/integration-tests/src/test/resources/legacy-kapt/app/build.gradle.kts new file mode 100644 index 0000000000..bd5bc8e0a2 --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/app/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("com.android.application") + id("com.android.legacy-kapt") + id("com.google.devtools.ksp") +} +dependencies { + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + ksp("androidx.room:room-compiler:2.4.2") + implementation("androidx.room:room-runtime:2.4.2") + implementation("androidx.appcompat:appcompat:1.6.1") +} +android { + namespace = "com.example.kspandroidtestapp" + defaultConfig { + minSdk = 24 + } + compileSdk = 34 + buildFeatures { + viewBinding = true + } + viewBinding { + enable = true + } +} diff --git a/integration-tests/src/test/resources/legacy-kapt/app/src/main/AndroidManifest.xml b/integration-tests/src/test/resources/legacy-kapt/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/app/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/legacy-kapt/app/src/main/kotlin/MainActivity.kt b/integration-tests/src/test/resources/legacy-kapt/app/src/main/kotlin/MainActivity.kt new file mode 100644 index 0000000000..c2114470b6 --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/app/src/main/kotlin/MainActivity.kt @@ -0,0 +1,14 @@ +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.example.kspandroidtestapp.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityMainBinding.inflate(layoutInflater) + + setContentView(binding.root) + } +} diff --git a/integration-tests/src/test/resources/legacy-kapt/app/src/main/res/layout/activity_main.xml b/integration-tests/src/test/resources/legacy-kapt/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..5b8db93264 --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/legacy-kapt/build.gradle.kts b/integration-tests/src/test/resources/legacy-kapt/build.gradle.kts new file mode 100644 index 0000000000..a47e4969ad --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/build.gradle.kts @@ -0,0 +1,26 @@ +buildscript { + val testRepo: String by project + + repositories { + maven(testRepo) + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + google() + } +} + +plugins { + id("com.android.application") apply false + id("com.android.legacy-kapt") apply false + id("com.google.devtools.ksp") apply false +} + +allprojects { + val testRepo: String by project + repositories { + maven(testRepo) + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + google() + } +} diff --git a/integration-tests/src/test/resources/legacy-kapt/gradle.properties b/integration-tests/src/test/resources/legacy-kapt/gradle.properties new file mode 100644 index 0000000000..c132bcc68e --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/gradle.properties @@ -0,0 +1 @@ +android.newDsl=false \ No newline at end of file diff --git a/integration-tests/src/test/resources/legacy-kapt/settings.gradle.kts b/integration-tests/src/test/resources/legacy-kapt/settings.gradle.kts new file mode 100644 index 0000000000..00d430989f --- /dev/null +++ b/integration-tests/src/test/resources/legacy-kapt/settings.gradle.kts @@ -0,0 +1,22 @@ +pluginManagement { + val kotlinVersion: String by settings + val kspVersion: String by settings + val testRepo: String by settings + val agpVersion: String by settings + plugins { + id("com.google.devtools.ksp") version kspVersion apply false + kotlin("jvm") version kotlinVersion apply false + id("com.android.legacy-kapt") version agpVersion apply false + id("com.android.application") version agpVersion apply false + id("com.android.library") version agpVersion apply false + } + repositories { + maven(testRepo) + gradlePluginPortal() + google() + mavenCentral() + maven("https://redirector.kotlinlang.org/maven/bootstrap/") + } +} + +include(":app")