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 package dagger.hilt.android.plugin.task 17 18 import dagger.hilt.android.plugin.AndroidEntryPointClassTransformer 19 import dagger.hilt.android.plugin.HiltExtension 20 import dagger.hilt.android.plugin.util.capitalize 21 import dagger.hilt.android.plugin.util.getCompileKotlin 22 import dagger.hilt.android.plugin.util.isClassFile 23 import dagger.hilt.android.plugin.util.isJarFile 24 import java.io.File 25 import javax.inject.Inject 26 import org.gradle.api.Action 27 import org.gradle.api.DefaultTask 28 import org.gradle.api.Project 29 import org.gradle.api.file.ConfigurableFileCollection 30 import org.gradle.api.file.DirectoryProperty 31 import org.gradle.api.file.FileCollection 32 import org.gradle.api.provider.Property 33 import org.gradle.api.tasks.Classpath 34 import org.gradle.api.tasks.OutputDirectory 35 import org.gradle.api.tasks.TaskAction 36 import org.gradle.api.tasks.TaskProvider 37 import org.gradle.api.tasks.testing.Test 38 import org.gradle.workers.WorkAction 39 import org.gradle.workers.WorkParameters 40 import org.gradle.workers.WorkerExecutor 41 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper 42 43 /** 44 * Task that transform classes used by host-side unit tests. See b/37076369 45 */ 46 abstract class HiltTransformTestClassesTask @Inject constructor( 47 private val workerExecutor: WorkerExecutor 48 ) : DefaultTask() { 49 50 @get:Classpath 51 abstract val compiledClasses: ConfigurableFileCollection 52 53 @get:OutputDirectory 54 abstract val outputDir: DirectoryProperty 55 56 internal interface Parameters : WorkParameters { 57 val name: Property<String> 58 val compiledClasses: ConfigurableFileCollection 59 val outputDir: DirectoryProperty 60 } 61 62 abstract class WorkerAction : WorkAction<Parameters> { 63 override fun execute() { 64 val outputDir = parameters.outputDir.asFile.get() 65 outputDir.deleteRecursively() 66 outputDir.mkdirs() 67 68 val allInputs = parameters.compiledClasses.files.toList() 69 val classTransformer = AndroidEntryPointClassTransformer( 70 taskName = parameters.name.get(), 71 allInputs = allInputs, 72 sourceRootOutputDir = outputDir, 73 copyNonTransformed = false 74 ) 75 // Parse the classpath in reverse so that we respect overwrites, if it ever happens. 76 allInputs.reversed().forEach { 77 if (it.isDirectory) { 78 it.walkTopDown().forEach { file -> 79 if (file.isClassFile()) { 80 classTransformer.transformFile(file) 81 } 82 } 83 } else if (it.isJarFile()) { 84 classTransformer.transformJarContents(it) 85 } 86 } 87 } 88 } 89 90 @TaskAction 91 fun transformClasses() { 92 workerExecutor.noIsolation().submit(WorkerAction::class.java) { 93 it.compiledClasses.from(compiledClasses) 94 it.outputDir.set(outputDir) 95 it.name.set(name) 96 } 97 } 98 99 internal class ConfigAction( 100 private val outputDir: File, 101 private val inputClasspath: FileCollection 102 ) : Action<HiltTransformTestClassesTask> { 103 override fun execute(transformTask: HiltTransformTestClassesTask) { 104 transformTask.description = "Transforms AndroidEntryPoint annotated classes for JUnit tests." 105 transformTask.outputDir.set(outputDir) 106 transformTask.compiledClasses.from(inputClasspath) 107 } 108 } 109 110 companion object { 111 112 private const val TASK_PREFIX = "hiltTransformFor" 113 114 fun create( 115 project: Project, 116 @Suppress("DEPRECATION") unitTestVariant: com.android.build.gradle.api.UnitTestVariant, 117 extension: HiltExtension 118 ) { 119 if (!extension.enableTransformForLocalTests) { 120 // Not enabled, nothing to do here. 121 return 122 } 123 124 // TODO(danysantiago): Only use project compiled sources as input, and not all dependency jars 125 // Using 'null' key to obtain the full compile classpath since we are not using the 126 // registerPreJavacGeneratedBytecode() API that would have otherwise given us a key to get 127 // a classpath up to the generated bytecode associated with the key. 128 val inputClasspath = 129 project.files(unitTestVariant.getCompileClasspath(null)) 130 131 // Find the test sources Java compile task and add its output directory into our input 132 // classpath file collection. This also makes the transform task depend on the test compile 133 // task. 134 val testCompileTaskProvider = unitTestVariant.javaCompileProvider 135 inputClasspath.from(testCompileTaskProvider.map { it.destinationDirectory }) 136 137 // Similarly, if the Kotlin plugin is configured, find the test sources Kotlin compile task 138 // and add its output directory to our input classpath file collection. 139 project.plugins.withType(KotlinBasePluginWrapper::class.java) { 140 val kotlinCompileTaskProvider = getCompileKotlin(unitTestVariant, project) 141 inputClasspath.from(kotlinCompileTaskProvider.map { it.destinationDirectory }) 142 } 143 144 // Create and configure the transform task. 145 val outputDir = 146 project.buildDir.resolve("intermediates/hilt/${unitTestVariant.dirName}Output") 147 val hiltTransformProvider = project.tasks.register( 148 "$TASK_PREFIX${unitTestVariant.name.capitalize()}", 149 HiltTransformTestClassesTask::class.java, 150 ConfigAction(outputDir, inputClasspath) 151 ) 152 // Map the transform task's output to a file collection. 153 val outputFileCollection = 154 project.files(hiltTransformProvider.map { it.outputDir }) 155 156 // Configure test classpath by appending the transform output file collection to the start of 157 // the test classpath so they override the original ones. This also makes test task (the one 158 // that runs the tests) depend on the transform task. 159 160 @Suppress("UNCHECKED_CAST") 161 val testTaskProvider = project.tasks.named( 162 "test${unitTestVariant.name.capitalize()}" 163 ) as TaskProvider<Test> 164 testTaskProvider.configure { 165 it.classpath = outputFileCollection + it.classpath 166 } 167 } 168 } 169 } 170