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