1 /* <lambda>null2 * Copyright (C) 2021 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.util 18 19 import dagger.hilt.android.plugin.root.AggregatedAnnotation 20 import java.io.ByteArrayOutputStream 21 import java.io.File 22 import java.util.zip.ZipEntry 23 import java.util.zip.ZipInputStream 24 import java.util.zip.ZipOutputStream 25 import org.gradle.api.artifacts.transform.CacheableTransform 26 import org.gradle.api.artifacts.transform.InputArtifact 27 import org.gradle.api.artifacts.transform.TransformAction 28 import org.gradle.api.artifacts.transform.TransformOutputs 29 import org.gradle.api.artifacts.transform.TransformParameters 30 import org.gradle.api.file.FileSystemLocation 31 import org.gradle.api.provider.Provider 32 import org.gradle.api.tasks.Classpath 33 34 /** 35 * A transform that outputs classes and jars containing only classes in key aggregating Hilt 36 * packages that are used to pass dependencies between compilation units. 37 */ 38 @CacheableTransform 39 abstract class AggregatedPackagesTransform : TransformAction<TransformParameters.None> { 40 // TODO(danysantiago): Make incremental by using InputChanges and try to use @CompileClasspath 41 @get:Classpath 42 @get:InputArtifact 43 abstract val inputArtifactProvider: Provider<FileSystemLocation> 44 45 override fun transform(outputs: TransformOutputs) { 46 val input = inputArtifactProvider.get().asFile 47 when { 48 input.isFile -> transformFile(outputs, input) 49 input.isDirectory -> input.walkInPlatformIndependentOrder().filter { it.isFile }.forEach { 50 transformFile(outputs, it) 51 } 52 else -> error("File/directory does not exist: ${input.absolutePath}") 53 } 54 } 55 56 private fun transformFile(outputs: TransformOutputs, file: File) { 57 if (file.isJarFile()) { 58 var atLeastOneEntry = false 59 // TODO(danysantiago): This is an in-memory buffer stream, consider using a temp file. 60 val tmpOutputStream = ByteArrayOutputStream() 61 ZipOutputStream(tmpOutputStream).use { outputStream -> 62 ZipInputStream(file.inputStream()).forEachZipEntry { inputStream, inputEntry -> 63 if (inputEntry.isClassFile()) { 64 val parentDirectory = inputEntry.name.substringBeforeLast('/') 65 val match = AggregatedAnnotation.AGGREGATED_PACKAGES.any { aggregatedPackage -> 66 parentDirectory.endsWith(aggregatedPackage) 67 } 68 if (match) { 69 outputStream.putNextEntry(ZipEntry(inputEntry.name)) 70 inputStream.copyTo(outputStream) 71 outputStream.closeEntry() 72 atLeastOneEntry = true 73 } 74 } 75 } 76 } 77 if (atLeastOneEntry) { 78 outputs.file(JAR_NAME).outputStream().use { tmpOutputStream.writeTo(it) } 79 } 80 } else if (file.isClassFile()) { 81 // If transforming a file, check if the parent directory matches one of the known aggregated 82 // packages structure. File and Path APIs are used to avoid OS-specific issues when comparing 83 // paths. 84 val parentDirectory: File = file.parentFile 85 val match = AggregatedAnnotation.AGGREGATED_PACKAGES.any { aggregatedPackage -> 86 parentDirectory.endsWith(aggregatedPackage) 87 } 88 if (match) { 89 outputs.file(file) 90 } 91 } 92 } 93 94 companion object { 95 // The output file name containing classes in the aggregated packages. 96 val JAR_NAME = "hiltAggregated.jar" 97 } 98 } 99