Skip to content

Commit

Permalink
Update Hilt Android Gradle plugin for KSP support.
Browse files Browse the repository at this point in the history
Specifically the plugin will now find the KSP tasks (if available) and configure the annotation processor options suitable for Hilt in Gradle. Meanwhile no change was needed for classpath aggregation since AGP's JavaCompile task includes KSP outputs.

RELNOTES=N/A
PiperOrigin-RevId: 544169233
  • Loading branch information
danysantiago authored and Dagger Team committed Jun 28, 2023
1 parent c41ba9f commit 843a6a2
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 59 deletions.
1 change: 1 addition & 0 deletions java/dagger/hilt/android/plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ buildscript {
ext {
kotlin_version = "1.8.20"
agp_version = System.getenv('AGP_VERSION') ?: "7.2.0"
ksp_version = "$kotlin_version-1.0.11"
pluginArtifactId = 'hilt-android-gradle-plugin'
pluginId = 'com.google.dagger.hilt.android'
}
Expand Down
1 change: 1 addition & 0 deletions java/dagger/hilt/android/plugin/main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies {
implementation gradleApi()
compileOnly "com.android.tools.build:gradle:$agp_version"
compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
compileOnly "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"
implementation 'org.ow2.asm:asm:9.0'
implementation "com.squareup:javapoet:1.13.0"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dagger.hilt.android.plugin

import dagger.hilt.processor.internal.optionvalues.GradleProjectType
import org.gradle.api.tasks.Input
import org.gradle.process.CommandLineArgumentProvider

/**
* Plugin configured annotation processor options provider.
*/
internal class HiltCommandLineArgumentProvider(
@get:Input
val forKsp: Boolean,
@get:Input
val projectType: GradleProjectType,
@get:Input
val enableAggregatingTask: Boolean,
@get:Input
val disableCrossCompilationRootValidation: Boolean
): CommandLineArgumentProvider {

private val prefix = if (forKsp) "" else "-A"

override fun asArguments() = mutableMapOf<String, String>().apply {
// Enable Dagger's fast-init, the best mode for Hilt.
put("dagger.fastInit", "enabled")
// Disable @AndroidEntryPoint superclass validation.
put("dagger.hilt.android.internal.disableAndroidSuperclassValidation", "true")
// Report project type for root validation.
put("dagger.hilt.android.internal.projectType", projectType.toString())

// Disable the aggregating processor if aggregating task is enabled.
if (enableAggregatingTask) {
put("dagger.hilt.internal.useAggregatingRootProcessor", "false")
}
// Disable cross compilation root validation.
// The plugin option duplicates the processor flag because it is an input of the
// aggregating task.
if (disableCrossCompilationRootValidation) {
put("dagger.hilt.disableCrossCompilationRootValidation", "true")
}
}.map { (key, value) -> "$prefix$key=$value" }
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ import dagger.hilt.android.plugin.util.AggregatedPackagesTransform
import dagger.hilt.android.plugin.util.ComponentCompat
import dagger.hilt.android.plugin.util.CopyTransform
import dagger.hilt.android.plugin.util.SimpleAGPVersion
import dagger.hilt.android.plugin.util.addJavaTaskProcessorOptions
import dagger.hilt.android.plugin.util.addKaptTaskProcessorOptions
import dagger.hilt.android.plugin.util.addKspTaskProcessorOptions
import dagger.hilt.android.plugin.util.capitalize
import dagger.hilt.android.plugin.util.getAndroidComponentsExtension
import dagger.hilt.android.plugin.util.getKaptConfigName
import dagger.hilt.android.plugin.util.isKspTask
import dagger.hilt.processor.internal.optionvalues.GradleProjectType
import java.io.File
import javax.inject.Inject
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Attribute
Expand Down Expand Up @@ -288,9 +293,10 @@ class HiltGradlePlugin @Inject constructor(
// Add the JavaCompile task classpath and output dir to the config, the task's classpath
// will contain:
// * compileOnly dependencies
// * KAPT and Kotlinc generated bytecode
// * KAPT, KSP and Kotlinc generated bytecode
// * R.jar
// * Tested classes if the variant is androidTest
// TODO(danysantiago): Revisit to support K2 compiler
project.dependencies.add(
hiltCompileConfiguration.name,
project.files(variant.javaCompileProvider.map { it.classpath })
Expand All @@ -309,6 +315,7 @@ class HiltGradlePlugin @Inject constructor(
// are discoverable.
val apConfigurations: List<Configuration> = mutableListOf<Configuration>().apply {
add(variant.annotationProcessorConfiguration)
// TODO(danysantiago): Also add KSP config
project.configurations.findByName(getKaptConfigName(variant))?.let { add(it) }
}
config.extendsFrom(*apConfigurations.toTypedArray())
Expand Down Expand Up @@ -419,45 +426,30 @@ class HiltGradlePlugin @Inject constructor(

private fun configureProcessorFlags(project: Project, hiltExtension: HiltExtension) {
val androidExtension = project.baseExtension() ?: error("Android BaseExtension not found.")

val projectType = when (androidExtension) {
is AppExtension -> GradleProjectType.APP
is LibraryExtension -> GradleProjectType.LIBRARY
is TestExtension -> GradleProjectType.TEST
else -> error("Hilt plugin does not know how to configure '$this'")
}

// Pass annotation processor flags via a CommandLineArgumentProvider so that plugin
// options defined in the extension are populated from the user's build file. Checking the
// option too early would make it seem like it is never set.
androidExtension.defaultConfig.javaCompileOptions.annotationProcessorOptions
.compilerArgumentProvider(HiltCommandLineArgumentProvider(hiltExtension, projectType))
}

private class HiltCommandLineArgumentProvider(
private val hiltExtension: HiltExtension,
private val projectType: GradleProjectType
): CommandLineArgumentProvider {
override fun asArguments() = mutableListOf<Pair<String, String>>().apply {
// Pass annotation processor flag to enable Dagger's fast-init, the best mode for Hilt.
add("dagger.fastInit" to "enabled")
// Pass annotation processor flag to disable @AndroidEntryPoint superclass validation.
add("dagger.hilt.android.internal.disableAndroidSuperclassValidation" to "true")

add("dagger.hilt.android.internal.projectType" to projectType.toString())

// Pass annotation processor flag to disable the aggregating processor if aggregating
// task is enabled.
if (hiltExtension.enableAggregatingTask) {
add("dagger.hilt.internal.useAggregatingRootProcessor" to "false")
}
// Pass annotation processor flag to disable cross compilation root validation.
// The plugin option duplicates the processor flag because it is an input of the
// aggregating task.
if (hiltExtension.disableCrossCompilationRootValidation) {
add("dagger.hilt.disableCrossCompilationRootValidation" to "true")
getAndroidComponentsExtension(project).onAllVariants { component ->
// Pass annotation processor flags via a CommandLineArgumentProvider so that plugin
// options defined in the extension are populated from the user's build file.
val argsProducer: (Task) -> CommandLineArgumentProvider = { task ->
HiltCommandLineArgumentProvider(
forKsp = task.isKspTask(),
projectType = projectType,
enableAggregatingTask =
hiltExtension.enableAggregatingTask,
disableCrossCompilationRootValidation =
hiltExtension.disableCrossCompilationRootValidation
)
}
}.map { (key, value) -> "-A$key=$value" }
addJavaTaskProcessorOptions(project, component, argsProducer)
addKaptTaskProcessorOptions(project, component, argsProducer)
addKspTaskProcessorOptions(project, component, argsProducer)
}
}

private fun verifyDependencies(project: Project) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2023 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dagger.hilt.android.plugin.util

import com.google.devtools.ksp.gradle.KspTaskJvm
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.process.CommandLineArgumentProvider
import org.jetbrains.kotlin.gradle.internal.KaptTask

internal fun addJavaTaskProcessorOptions(
project: Project,
component: ComponentCompat,
produceArgProvider: (Task) -> CommandLineArgumentProvider
) = project.tasks.withType(JavaCompile::class.java) { task ->
if (task.name == "compile${component.name.capitalize()}JavaWithJavac") {
task.options.compilerArgumentProviders.add(produceArgProvider.invoke(task))
}
}

internal fun addKaptTaskProcessorOptions(
project: Project,
component: ComponentCompat,
produceArgProvider: (Task) -> CommandLineArgumentProvider
) = project.plugins.withId("kotlin-kapt") {
project.tasks.withType(KaptTask::class.java) { task ->
if (task.name == "kapt${component.name.capitalize()}Kotlin") {
val argProvider = produceArgProvider.invoke(task)
// TODO: Update once KT-58009 is fixed.
try {
// Because of KT-58009, we need to add a `listOf(argProvider)` instead
// of `argProvider`.
task.annotationProcessorOptionProviders.add(listOf(argProvider))
} catch (e: Throwable) {
// Once KT-58009 is fixed, adding `listOf(argProvider)` will fail, we will
// pass `argProvider` instead, which is the correct way.
task.annotationProcessorOptionProviders.add(argProvider)
}
}
}
}

internal fun addKspTaskProcessorOptions(
project: Project,
component: ComponentCompat,
produceArgProvider: (Task) -> CommandLineArgumentProvider
) = project.plugins.withId("com.google.devtools.ksp") {
project.tasks.withType(KspTaskJvm::class.java) { task ->
if (task.name == "ksp${component.name.capitalize()}Kotlin") {
task.commandLineArgumentProviders.add(produceArgProvider.invoke(task))
}
}
}

internal fun Task.isKspTask(): Boolean = try {
val kspTaskClass = Class.forName("com.google.devtools.ksp.gradle.KspTask")
kspTaskClass.isAssignableFrom(this::class.java)
} catch (ex: ClassNotFoundException) {
false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.google.devtools.ksp'
}

android {
Expand All @@ -21,21 +22,32 @@ android {
kotlinOptions {
jvmTarget = '11'
}
flavorDimensions "tier"
flavorDimensions "tier", "processorConfig"
productFlavors {
free {
dimension "tier"
}
premium {
dimension "tier"
}
withKapt {
dimension "processorConfig"
}
withKsp {
dimension "processorConfig"
}
}
}

kotlin {
jvmToolchain(11)
}

dependencies {
implementation project(':deep-android-lib')
implementation project(':deep-kotlin-lib')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kaptWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kspWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
}
25 changes: 18 additions & 7 deletions javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.dagger.hilt.android'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.devtools.ksp'

android {
compileSdkVersion 32
Expand All @@ -37,7 +38,7 @@ android {
shrinkResources true
}
}
flavorDimensions "tier"
flavorDimensions "tier", "processorConfig"
productFlavors {
free {
dimension "tier"
Expand All @@ -46,14 +47,17 @@ android {
dimension "tier"
matchingFallbacks = ["free"]
}
withKapt {
dimension "processorConfig"
}
withKsp {
dimension "processorConfig"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
testOptions {
unitTests.includeAndroidResources = true
}
Expand All @@ -71,6 +75,10 @@ android {
}
}

kotlin {
jvmToolchain(11)
}

hilt {
enableTransformForLocalTests = true
enableAggregatingTask = true
Expand All @@ -84,7 +92,8 @@ dependencies {
implementation 'androidx.activity:activity-ktx:1.5.0'

implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kaptWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kspWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'

testImplementation 'androidx.test.ext:junit:1.1.3'
testImplementation 'androidx.test:runner:1.4.0'
Expand All @@ -95,12 +104,14 @@ dependencies {
// TODO(bcorso): This multidex dep shouldn't be required -- it's a dep for the generated code.
testImplementation 'androidx.multidex:multidex:2.0.0'
testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT'
kaptTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kaptTestWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kspTestWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'

androidTestImplementation 'androidx.fragment:fragment-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'com.google.truth:truth:1.0.1'
androidTestImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT'
kaptAndroidTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kaptAndroidTestWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
kspAndroidTestWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
}
4 changes: 3 additions & 1 deletion javatests/artifacts/hilt-android/simpleKotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

buildscript {
ext {
kotlin_version = '1.8.0'
kotlin_version = '1.8.20'
ksp_version = '1.0.11'
agp_version = System.getenv('AGP_VERSION') ?: "7.1.2"
}
repositories {
Expand All @@ -27,6 +28,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:$agp_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$kotlin_version-$ksp_version"
classpath 'com.google.dagger:hilt-android-gradle-plugin:LOCAL-SNAPSHOT'
}
}
Expand Down
Loading

0 comments on commit 843a6a2

Please sign in to comment.