• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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