1 /*
<lambda>null2  * Copyright 2024 The Android Open Source Project
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 androidx.build.testConfiguration
18 
19 import com.android.build.api.variant.ApkInstallGroup
20 import com.android.build.api.variant.ApkOutput
21 import javax.inject.Inject
22 import org.gradle.api.DefaultTask
23 import org.gradle.api.file.ConfigurableFileCollection
24 import org.gradle.api.file.DirectoryProperty
25 import org.gradle.api.file.FileSystemOperations
26 import org.gradle.api.file.RegularFileProperty
27 import org.gradle.api.provider.Property
28 import org.gradle.api.tasks.Input
29 import org.gradle.api.tasks.InputFile
30 import org.gradle.api.tasks.InputFiles
31 import org.gradle.api.tasks.Internal
32 import org.gradle.api.tasks.OutputDirectory
33 import org.gradle.api.tasks.OutputFile
34 import org.gradle.api.tasks.PathSensitive
35 import org.gradle.api.tasks.PathSensitivity
36 import org.gradle.api.tasks.SkipWhenEmpty
37 import org.gradle.api.tasks.TaskAction
38 import org.gradle.work.DisableCachingByDefault
39 
40 /** Copy APKs (from ApkOutputProviders) needed for building androidTest.zip */
41 @Suppress("UnstableApiUsage") // Working with ApkOutputProviders b/397701480
42 @DisableCachingByDefault(because = "Only filesystem operations")
43 abstract class CopyApksFromOutputProviderTask
44 @Inject
45 constructor(private val fileSystemOperations: FileSystemOperations) : DefaultTask() {
46 
47     /** File existence check to determine whether to run this task. */
48     @get:InputFiles
49     @get:SkipWhenEmpty
50     @get:PathSensitive(PathSensitivity.NONE)
51     abstract val androidTestSourceCode: ConfigurableFileCollection
52 
53     /** ApkOutputProviders output, contains all App APKs. */
54     @get:Internal abstract val apkOutput: Property<ApkOutput>
55 
56     /** Some Variants includes test APK into ApkOutput, excluding it by using this parameter. */
57     @get:InputFile
58     @get:PathSensitive(PathSensitivity.NONE)
59     abstract val excludeTestApk: RegularFileProperty
60 
61     /**
62      * Filename prefix for all output apks. Required for producing unique filenames over all
63      * projects.
64      *
65      * Resulting filename: <outputFilenamesPrefix>-<groupIndex>-<fileIndex>-<originalFileName>
66      */
67     @get:Input abstract val outputFilenamesPrefix: Property<String>
68 
69     @get:OutputDirectory abstract val outputDirectory: DirectoryProperty
70 
71     @get:OutputFile abstract val outputAppApksModel: RegularFileProperty
72 
73     @TaskAction
74     fun createApks() {
75         val outputDir = outputDirectory.get()
76 
77         // Cleanup old files - to remove stale APKs
78         fileSystemOperations.delete { it.delete(outputDir) }
79 
80         val fileNamePrefix = outputFilenamesPrefix.get()
81         val testApkSha256 = sha256(excludeTestApk.get().asFile)
82 
83         val resultApkGroups =
84             apkOutput.get().apkInstallGroups.mapIndexedNotNull { groupIndex, installGroup ->
85                 processApkInstallGroup(
86                     installGroup,
87                     testApkSha256,
88                     fileNamePrefix = "$fileNamePrefix-$groupIndex"
89                 )
90             }
91 
92         val model = AppApksModel(resultApkGroups)
93         outputAppApksModel.get().asFile.writeText(model.toJson())
94     }
95 
96     private fun processApkInstallGroup(
97         installGroup: ApkInstallGroup,
98         excludeSha256: String,
99         fileNamePrefix: String
100     ): ApkFileGroup? {
101         val outputDir = outputDirectory.get()
102         val resultApkFiles =
103             installGroup.apks.mapIndexedNotNull { fileIndex, file ->
104                 val inputFile = file.asFile
105                 val fileSha256 = sha256(inputFile)
106                 if (fileSha256 == excludeSha256) {
107                     // Some Variants includes test APK into ApkOutput, filter it.
108                     return@mapIndexedNotNull null
109                 }
110 
111                 val outputFileName = "$fileNamePrefix-$fileIndex-${inputFile.name}"
112                 val outputFile = outputDir.file(outputFileName).asFile
113 
114                 inputFile.copyTo(outputFile, overwrite = true)
115 
116                 return@mapIndexedNotNull ApkFile(name = outputFileName, sha256 = fileSha256)
117             }
118         return if (resultApkFiles.isEmpty()) {
119             null
120         } else {
121             ApkFileGroup(resultApkFiles)
122         }
123     }
124 }
125