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