| /* |
| * Copyright (C) 2020 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 com.android.build.gradle.api.UnitTestVariant |
| import dagger.hilt.android.plugin.util.isClassFile |
| import dagger.hilt.android.plugin.util.isJarFile |
| import java.io.File |
| import javax.inject.Inject |
| import org.gradle.api.Action |
| import org.gradle.api.DefaultTask |
| import org.gradle.api.Project |
| import org.gradle.api.file.ConfigurableFileCollection |
| import org.gradle.api.file.DirectoryProperty |
| import org.gradle.api.file.FileCollection |
| import org.gradle.api.provider.Property |
| import org.gradle.api.tasks.Classpath |
| import org.gradle.api.tasks.OutputDirectory |
| import org.gradle.api.tasks.TaskAction |
| import org.gradle.api.tasks.TaskProvider |
| import org.gradle.api.tasks.compile.JavaCompile |
| import org.gradle.api.tasks.testing.Test |
| import org.gradle.workers.WorkAction |
| import org.gradle.workers.WorkParameters |
| import org.gradle.workers.WorkerExecutor |
| import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper |
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
| |
| /** |
| * Task that transform classes used by host-side unit tests. See b/37076369 |
| */ |
| @Suppress("UnstableApiUsage") |
| abstract class HiltTransformTestClassesTask @Inject constructor( |
| private val workerExecutor: WorkerExecutor |
| ) : DefaultTask() { |
| |
| @get:Classpath |
| abstract val compiledClasses: ConfigurableFileCollection |
| |
| @get:OutputDirectory |
| abstract val outputDir: DirectoryProperty |
| |
| internal interface Parameters : WorkParameters { |
| val name: Property<String> |
| val compiledClasses: ConfigurableFileCollection |
| val outputDir: DirectoryProperty |
| } |
| |
| abstract class WorkerAction : WorkAction<Parameters> { |
| override fun execute() { |
| val outputDir = parameters.outputDir.asFile.get() |
| outputDir.deleteRecursively() |
| outputDir.mkdirs() |
| |
| val allInputs = parameters.compiledClasses.files.toList() |
| val classTransformer = AndroidEntryPointClassTransformer( |
| taskName = parameters.name.get(), |
| allInputs = allInputs, |
| sourceRootOutputDir = outputDir, |
| copyNonTransformed = false |
| ) |
| // Parse the classpath in reverse so that we respect overwrites, if it ever happens. |
| allInputs.reversed().forEach { |
| if (it.isDirectory) { |
| it.walkTopDown().forEach { file -> |
| if (file.isClassFile()) { |
| classTransformer.transformFile(file) |
| } |
| } |
| } else if (it.isJarFile()) { |
| classTransformer.transformJarContents(it) |
| } |
| } |
| } |
| } |
| |
| @TaskAction |
| fun transformClasses() { |
| workerExecutor.noIsolation().submit(WorkerAction::class.java) { |
| it.compiledClasses.from(compiledClasses) |
| it.outputDir.set(outputDir) |
| it.name.set(name) |
| } |
| } |
| |
| internal class ConfigAction( |
| private val outputDir: File, |
| private val inputClasspath: FileCollection |
| ) : Action<HiltTransformTestClassesTask> { |
| override fun execute(transformTask: HiltTransformTestClassesTask) { |
| transformTask.description = "Transforms AndroidEntryPoint annotated classes for JUnit tests." |
| transformTask.outputDir.set(outputDir) |
| transformTask.compiledClasses.from(inputClasspath) |
| } |
| } |
| |
| companion object { |
| |
| private const val TASK_PREFIX = "hiltTransformFor" |
| |
| fun create( |
| project: Project, |
| unitTestVariant: UnitTestVariant, |
| extension: HiltExtension |
| ) { |
| if (!extension.enableTransformForLocalTests) { |
| // Not enabled, nothing to do here. |
| return |
| } |
| |
| // TODO(danysantiago): Only use project compiled sources as input, and not all dependency jars |
| // Using 'null' key to obtain the full compile classpath since we are not using the |
| // registerPreJavacGeneratedBytecode() API that would have otherwise given us a key to get |
| // a classpath up to the generated bytecode associated with the key. |
| val inputClasspath = |
| project.objects.fileCollection().from(unitTestVariant.getCompileClasspath(null)) |
| |
| // Find the test sources Java compile task and add its output directory into our input |
| // classpath file collection. This also makes the transform task depend on the test compile |
| // task. |
| @Suppress("UNCHECKED_CAST") |
| val testCompileTaskProvider = project.tasks.named( |
| "compile${unitTestVariant.name.capitalize()}JavaWithJavac" |
| ) as TaskProvider<JavaCompile> |
| inputClasspath.from(testCompileTaskProvider.map { it.destinationDirectory }) |
| |
| // Similarly, if the Kotlin plugin is configured, find the test sources Kotlin compile task |
| // and add its output directory to our input classpath file collection. |
| project.plugins.withType(KotlinBasePluginWrapper::class.java) { |
| @Suppress("UNCHECKED_CAST") |
| val kotlinCompileTaskProvider = project.tasks.named( |
| "compile${unitTestVariant.name.capitalize()}Kotlin" |
| ) as TaskProvider<KotlinCompile> |
| inputClasspath.from(kotlinCompileTaskProvider.map { it.destinationDirectory }) |
| } |
| |
| // Create and configure the transform task. |
| val outputDir = |
| project.buildDir.resolve("intermediates/hilt/${unitTestVariant.dirName}Output") |
| val hiltTransformProvider = project.tasks.register( |
| "$TASK_PREFIX${unitTestVariant.name.capitalize()}", |
| HiltTransformTestClassesTask::class.java, |
| ConfigAction(outputDir, inputClasspath) |
| ) |
| // Map the transform task's output to a file collection. |
| val outputFileCollection = |
| project.objects.fileCollection().from(hiltTransformProvider.map { it.outputDir }) |
| |
| // Configure test classpath by appending the transform output file collection to the start of |
| // the test classpath so they override the original ones. This also makes test task (the one |
| // that runs the tests) depend on the transform task. |
| @Suppress("UNCHECKED_CAST") |
| val testTaskProvider = project.tasks.named( |
| "test${unitTestVariant.name.capitalize()}" |
| ) as TaskProvider<Test> |
| testTaskProvider.configure { |
| it.classpath = outputFileCollection + it.classpath |
| } |
| } |
| } |
| } |