1 /* <lambda>null2 * Copyright (C) 2020 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.android.plugin 18 19 import com.android.build.api.component.Component 20 import com.android.build.api.extension.AndroidComponentsExtension 21 import com.android.build.api.instrumentation.FramesComputationMode 22 import com.android.build.api.instrumentation.InstrumentationScope 23 import com.android.build.gradle.AppExtension 24 import com.android.build.gradle.BaseExtension 25 import com.android.build.gradle.LibraryExtension 26 import com.android.build.gradle.TestExtension 27 import com.android.build.gradle.TestedExtension 28 import com.android.build.gradle.api.AndroidBasePlugin 29 import com.android.build.gradle.api.BaseVariant 30 import com.android.build.gradle.api.TestVariant 31 import com.android.build.gradle.api.UnitTestVariant 32 import dagger.hilt.android.plugin.util.CopyTransform 33 import dagger.hilt.android.plugin.util.SimpleAGPVersion 34 import java.io.File 35 import org.gradle.api.Plugin 36 import org.gradle.api.Project 37 import org.gradle.api.artifacts.component.ProjectComponentIdentifier 38 import org.gradle.api.attributes.Attribute 39 40 /** 41 * A Gradle plugin that checks if the project is an Android project and if so, registers a 42 * bytecode transformation. 43 * 44 * The plugin also passes an annotation processor option to disable superclass validation for 45 * classes annotated with `@AndroidEntryPoint` since the registered transform by this plugin will 46 * update the superclass. 47 */ 48 class HiltGradlePlugin : Plugin<Project> { 49 override fun apply(project: Project) { 50 var configured = false 51 project.plugins.withType(AndroidBasePlugin::class.java) { 52 configured = true 53 configureHilt(project) 54 } 55 project.afterEvaluate { 56 check(configured) { 57 // Check if configuration was applied, if not inform the developer they have applied the 58 // plugin to a non-android project. 59 "The Hilt Android Gradle plugin can only be applied to an Android project." 60 } 61 verifyDependencies(it) 62 } 63 } 64 65 private fun configureHilt(project: Project) { 66 val hiltExtension = project.extensions.create( 67 HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java 68 ) 69 configureCompileClasspath(project, hiltExtension) 70 if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(4, 2)) { 71 // Configures bytecode transform using older APIs pre AGP 4.2 72 configureTransform(project, hiltExtension) 73 } else { 74 // Configures bytecode transform using AGP 4.2 ASM pipeline. 75 configureTransformASM(project, hiltExtension) 76 } 77 configureProcessorFlags(project) 78 } 79 80 private fun configureCompileClasspath(project: Project, hiltExtension: HiltExtension) { 81 val androidExtension = project.extensions.findByType(BaseExtension::class.java) 82 ?: throw error("Android BaseExtension not found.") 83 when (androidExtension) { 84 is AppExtension -> { 85 // For an app project we configure the app variant and both androidTest and test variants, 86 // Hilt components are generated in all of them. 87 androidExtension.applicationVariants.all { 88 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 89 } 90 androidExtension.testVariants.all { 91 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 92 } 93 androidExtension.unitTestVariants.all { 94 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 95 } 96 } 97 is LibraryExtension -> { 98 // For a library project, only the androidTest and test variant are configured since 99 // Hilt components are not generated in a library. 100 androidExtension.testVariants.all { 101 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 102 } 103 androidExtension.unitTestVariants.all { 104 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 105 } 106 } 107 is TestExtension -> { 108 androidExtension.applicationVariants.all { 109 configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) 110 } 111 } 112 else -> error( 113 "Hilt plugin is unable to configure the compile classpath for project with extension " + 114 "'$androidExtension'" 115 ) 116 } 117 118 project.dependencies.apply { 119 registerTransform(CopyTransform::class.java) { spec -> 120 // Java/Kotlin library projects offer an artifact of type 'jar'. 121 spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar") 122 // Android library projects (with or without Kotlin) offer an artifact of type 123 // 'processed-jar', which AGP can offer as a jar. 124 spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "processed-jar") 125 spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) 126 } 127 } 128 } 129 130 private fun configureVariantCompileClasspath( 131 project: Project, 132 hiltExtension: HiltExtension, 133 androidExtension: BaseExtension, 134 variant: BaseVariant 135 ) { 136 if (!hiltExtension.enableExperimentalClasspathAggregation) { 137 // Option is not enabled, don't configure compile classpath. Note that the option can't be 138 // checked earlier (before iterating over the variants) since it would have been too early for 139 // the value to be populated from the build file. 140 return 141 } 142 143 if (androidExtension.lintOptions.isCheckReleaseBuilds && 144 SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(7, 0) 145 ) { 146 // Sadly we have to ask users to disable lint when enableExperimentalClasspathAggregation is 147 // set to true and they are not in AGP 7.0+ since Lint will cause issues during the 148 // configuration phase. See b/158753935 and b/160392650 149 error( 150 "Invalid Hilt plugin configuration: When 'enableExperimentalClasspathAggregation' is " + 151 "enabled 'android.lintOptions.checkReleaseBuilds' has to be set to false unless " + 152 "com.android.tools.build:gradle:7.0.0+ is used." 153 ) 154 } 155 156 if ( 157 listOf( 158 "android.injected.build.model.only", // Sent by AS 1.0 only 159 "android.injected.build.model.only.advanced", // Sent by AS 1.1+ 160 "android.injected.build.model.only.versioned", // Sent by AS 2.4+ 161 "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+ 162 "android.injected.build.model.v2", // Sent by AS 4.2+ 163 ).any { project.properties.containsKey(it) } 164 ) { 165 // Do not configure compile classpath when AndroidStudio is building the model (syncing) 166 // otherwise it will cause a freeze. 167 return 168 } 169 170 val runtimeConfiguration = if (variant is TestVariant) { 171 // For Android test variants, the tested runtime classpath is used since the test app has 172 // tested dependencies removed. 173 variant.testedVariant.runtimeConfiguration 174 } else { 175 variant.runtimeConfiguration 176 } 177 val artifactView = runtimeConfiguration.incoming.artifactView { view -> 178 view.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) 179 view.componentFilter { identifier -> 180 // Filter out the project's classes from the aggregated view since this can cause 181 // issues with Kotlin internal members visibility. b/178230629 182 if (identifier is ProjectComponentIdentifier) { 183 identifier.projectName != project.name 184 } else { 185 true 186 } 187 } 188 } 189 190 // CompileOnly config names don't follow the usual convention: 191 // <Variant Name> -> <Config Name> 192 // debug -> debugCompileOnly 193 // debugAndroidTest -> androidTestDebugCompileOnly 194 // debugUnitTest -> testDebugCompileOnly 195 // release -> releaseCompileOnly 196 // releaseUnitTest -> testReleaseCompileOnly 197 val compileOnlyConfigName = when (variant) { 198 is TestVariant -> 199 "androidTest${variant.name.substringBeforeLast("AndroidTest").capitalize()}CompileOnly" 200 is UnitTestVariant -> 201 "test${variant.name.substringBeforeLast("UnitTest").capitalize()}CompileOnly" 202 else -> 203 "${variant.name}CompileOnly" 204 } 205 project.dependencies.add(compileOnlyConfigName, artifactView.files) 206 } 207 208 @Suppress("UnstableApiUsage") 209 private fun configureTransformASM(project: Project, hiltExtension: HiltExtension) { 210 var warnAboutLocalTestsFlag = false 211 fun registerTransform(androidComponent: Component) { 212 if (hiltExtension.enableTransformForLocalTests && !warnAboutLocalTestsFlag) { 213 project.logger.warn( 214 "The Hilt configuration option 'enableTransformForLocalTests' is no longer necessary " + 215 "when com.android.tools.build:gradle:4.2.0+ is used." 216 ) 217 warnAboutLocalTestsFlag = true 218 } 219 androidComponent.transformClassesWith( 220 classVisitorFactoryImplClass = AndroidEntryPointClassVisitor.Factory::class.java, 221 scope = InstrumentationScope.PROJECT 222 ) { params -> 223 val classesDir = 224 File(project.buildDir, "intermediates/javac/${androidComponent.name}/classes") 225 params.additionalClassesDir.set(classesDir) 226 } 227 androidComponent.setAsmFramesComputationMode( 228 FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS 229 ) 230 } 231 232 val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) 233 androidComponents.onVariants { registerTransform(it) } 234 androidComponents.androidTests { registerTransform(it) } 235 androidComponents.unitTests { registerTransform(it) } 236 } 237 238 private fun configureTransform(project: Project, hiltExtension: HiltExtension) { 239 val androidExtension = project.extensions.findByType(BaseExtension::class.java) 240 ?: throw error("Android BaseExtension not found.") 241 androidExtension.registerTransform(AndroidEntryPointTransform()) 242 243 // Create and configure a task for applying the transform for host-side unit tests. b/37076369 244 val testedExtensions = project.extensions.findByType(TestedExtension::class.java) 245 testedExtensions?.unitTestVariants?.all { unitTestVariant -> 246 HiltTransformTestClassesTask.create( 247 project = project, 248 unitTestVariant = unitTestVariant, 249 extension = hiltExtension 250 ) 251 } 252 } 253 254 private fun configureProcessorFlags(project: Project) { 255 val androidExtension = project.extensions.findByType(BaseExtension::class.java) 256 ?: throw error("Android BaseExtension not found.") 257 // Pass annotation processor flag to disable @AndroidEntryPoint superclass validation. 258 androidExtension.defaultConfig.apply { 259 javaCompileOptions.apply { 260 annotationProcessorOptions.apply { 261 PROCESSOR_OPTIONS.forEach { (key, value) -> argument(key, value) } 262 } 263 } 264 } 265 } 266 267 private fun verifyDependencies(project: Project) { 268 // If project is already failing, skip verification since dependencies might not be resolved. 269 if (project.state.failure != null) { 270 return 271 } 272 val dependencies = project.configurations.flatMap { configuration -> 273 configuration.dependencies.map { dependency -> dependency.group to dependency.name } 274 } 275 if (!dependencies.contains(LIBRARY_GROUP to "hilt-android")) { 276 error(missingDepError("$LIBRARY_GROUP:hilt-android")) 277 } 278 if (!dependencies.contains(LIBRARY_GROUP to "hilt-android-compiler") && 279 !dependencies.contains(LIBRARY_GROUP to "hilt-compiler") 280 ) { 281 error(missingDepError("$LIBRARY_GROUP:hilt-compiler")) 282 } 283 } 284 285 companion object { 286 val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java) 287 const val DAGGER_ARTIFACT_TYPE_VALUE = "jar-for-dagger" 288 289 const val LIBRARY_GROUP = "com.google.dagger" 290 val PROCESSOR_OPTIONS = listOf( 291 "dagger.fastInit" to "enabled", 292 "dagger.hilt.android.internal.disableAndroidSuperclassValidation" to "true" 293 ) 294 val missingDepError: (String) -> String = { depCoordinate -> 295 "The Hilt Android Gradle plugin is applied but no $depCoordinate dependency was found." 296 } 297 } 298 } 299