1 /*
2  * 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.BuiltArtifactsLoader
20 import java.io.File
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.RegularFileProperty
26 import org.gradle.api.model.ObjectFactory
27 import org.gradle.api.provider.Property
28 import org.gradle.api.tasks.InputFiles
29 import org.gradle.api.tasks.Internal
30 import org.gradle.api.tasks.Optional
31 import org.gradle.api.tasks.OutputFile
32 import org.gradle.api.tasks.PathSensitive
33 import org.gradle.api.tasks.PathSensitivity
34 import org.gradle.api.tasks.SkipWhenEmpty
35 import org.gradle.api.tasks.TaskAction
36 import org.gradle.work.DisableCachingByDefault
37 
38 /** Copy single APK (from AGP artifacts) needed for building androidTest.zip */
39 @DisableCachingByDefault(because = "Only filesystem operations")
40 abstract class CopyApkFromArtifactsTask @Inject constructor(private val objects: ObjectFactory) :
41     DefaultTask() {
42 
43     /** File existence check to determine whether to run this task. */
44     @get:InputFiles
45     @get:SkipWhenEmpty
46     @get:PathSensitive(PathSensitivity.NONE)
47     abstract val androidTestSourceCode: ConfigurableFileCollection
48 
49     @get:InputFiles
50     @get:Optional
51     @get:PathSensitive(PathSensitivity.NONE)
52     abstract val appFolder: DirectoryProperty
53 
54     @get:InputFiles
55     @get:PathSensitive(PathSensitivity.NONE)
56     abstract val appFileCollection: ConfigurableFileCollection
57 
58     @get:Internal abstract val appLoader: Property<BuiltArtifactsLoader>
59 
60     @get:OutputFile abstract val outputAppApk: RegularFileProperty
61 
62     @get:OutputFile abstract val outputAppApksModel: RegularFileProperty
63 
64     @TaskAction
createApksnull65     fun createApks() {
66         // Decides where to load the app apk from, depending on whether appFolder or
67         // appFileCollection has been set.
68         val appDir =
69             if (appFolder.isPresent && appFileCollection.files.isEmpty()) {
70                 appFolder.get()
71             } else if (!appFolder.isPresent && appFileCollection.files.size == 1) {
72                 objects.directoryProperty().also { it.set(appFileCollection.files.first()) }.get()
73             } else {
74                 throw IllegalStateException(
75                     """
76                     App apk not specified or both appFileCollection and appFolder specified.
77                 """
78                         .trimIndent()
79                 )
80             }
81 
82         val appApk =
83             appLoader.get().load(appDir)
84                 ?: throw RuntimeException("Cannot load required APK for task: $name")
85 
86         val appApkBuiltArtifact = appApk.elements.single()
87         val destinationApk = outputAppApk.get().asFile
88         File(appApkBuiltArtifact.outputFile).copyTo(destinationApk, overwrite = true)
89 
90         val model =
91             singleFileAppApksModel(name = destinationApk.name, sha256 = sha256(destinationApk))
92         outputAppApksModel.get().asFile.writeText(model.toJson())
93     }
94 }
95