Skip to content

Commit 89ccd88

Browse files
committed
Move to only building APKs that are required for testing
Before this change we built and copied both debug and release build types for test apps even though most of the time we only need debug (except for benchmarks that use release apk) This reduces the number of tasks run for zipTestConfigsWithApks from 42,403 to 34,348. It also makes androidTest.zip file go from 4.9G to 2.9G in size. Performance to be seen when we land this in production, but it should help androidx_device_tests target. Bug: 280632101 Test: ./gradlew zipTestConfigsWithApks Change-Id: Id4c4a2622c4db902e6b5e611cee8113bcbd072e0
1 parent 5c54854 commit 89ccd88

File tree

5 files changed

+165
-163
lines changed

5 files changed

+165
-163
lines changed

buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ import androidx.build.checkapi.KmpApiTaskConfig
2929
import androidx.build.checkapi.LibraryApiTaskConfig
3030
import androidx.build.checkapi.configureProjectForApiTasks
3131
import androidx.build.dependencies.KOTLIN_VERSION
32-
import androidx.build.dependencyTracker.AffectedModuleDetector
3332
import androidx.build.docs.AndroidXKmpDocsImplPlugin
3433
import androidx.build.gradle.isRoot
3534
import androidx.build.license.configureExternalDependencyLicenseCheck
3635
import androidx.build.resources.configurePublicResourcesStub
3736
import androidx.build.sbom.validateAllArchiveInputsRecognized
3837
import androidx.build.studio.StudioTask
3938
import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
40-
import androidx.build.testConfiguration.addToTestZips
4139
import androidx.build.testConfiguration.configureTestConfigGeneration
4240
import com.android.build.api.artifact.SingleArtifact
4341
import com.android.build.api.dsl.ManagedVirtualDevice
@@ -54,7 +52,6 @@ import com.android.build.gradle.LibraryPlugin
5452
import com.android.build.gradle.TestExtension
5553
import com.android.build.gradle.TestPlugin
5654
import com.android.build.gradle.TestedExtension
57-
import com.android.build.gradle.internal.tasks.ListingFileRedirectTask
5855
import java.io.File
5956
import java.time.Duration
6057
import java.util.Locale
@@ -702,44 +699,13 @@ class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareCompo
702699
project.configureTestConfigGeneration(this)
703700
project.configureFtlRunner()
704701

705-
val buildTestApksTask = project.rootProject.tasks.named(BUILD_TEST_APKS_TASK)
706-
when (this) {
707-
is TestedExtension -> testVariants
708-
// app module defines variants for test module
709-
is TestExtension -> applicationVariants
710-
else -> throw IllegalStateException("Unsupported plugin type")
711-
}.all { variant ->
712-
buildTestApksTask.configure {
713-
it.dependsOn(variant.assembleProvider)
714-
}
715-
variant.configureApkZipping(project)
716-
}
717-
718702
// AGP warns if we use project.buildDir (or subdirs) for CMake's generated
719703
// build files (ninja build files, CMakeCache.txt, etc.). Use a staging directory that
720704
// lives alongside the project's buildDir.
721705
externalNativeBuild.cmake.buildStagingDirectory =
722706
File(project.buildDir, "../nativeBuildStaging")
723707
}
724708

725-
/**
726-
* Configures the ZIP_TEST_CONFIGS_WITH_APKS_TASK to include the test apk if applicable
727-
*/
728-
@Suppress("DEPRECATION") // ApkVariant
729-
private fun com.android.build.gradle.api.ApkVariant.configureApkZipping(
730-
project: Project
731-
) {
732-
packageApplicationProvider.get().let { packageTask ->
733-
AffectedModuleDetector.configureTaskGuard(packageTask)
734-
addToTestZips(project, packageTask)
735-
}
736-
// This task needs to be guarded by AffectedModuleDetector due to guarding test
737-
// APK building above. It can only be removed if we stop using AMD for test APKs.
738-
project.tasks.withType(ListingFileRedirectTask::class.java).forEach {
739-
AffectedModuleDetector.configureTaskGuard(it)
740-
}
741-
}
742-
743709
private fun LibraryExtension.configureAndroidLibraryOptions(
744710
project: Project,
745711
androidXExtension: AndroidXExtension
@@ -850,17 +816,6 @@ class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareCompo
850816

851817
project.addAppApkToTestConfigGeneration()
852818
project.addAppApkToFtlRunner()
853-
854-
val buildTestApksTask = project.rootProject.tasks.named(BUILD_TEST_APKS_TASK)
855-
applicationVariants.all { variant ->
856-
// Using getName() instead of name due to b/150427408
857-
if (variant.buildType.name == "debug") {
858-
buildTestApksTask.configure {
859-
it.dependsOn(variant.assembleProvider)
860-
}
861-
}
862-
variant.configureApkZipping(project)
863-
}
864819
}
865820

866821
private fun Project.configureDependencyVerification(
@@ -1001,7 +956,6 @@ class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareCompo
1001956
}
1002957

1003958
companion object {
1004-
const val BUILD_TEST_APKS_TASK = "buildTestApks"
1005959
const val CREATE_LIBRARY_BUILD_INFO_FILES_TASK = "createLibraryBuildInfoFiles"
1006960
const val GENERATE_TEST_CONFIGURATION_TASK = "GenerateTestConfiguration"
1007961
const val ZIP_TEST_CONFIGS_WITH_APKS_TASK = "zipTestConfigsWithApks"
@@ -1097,14 +1051,6 @@ private fun Project.configureJavaCompilationWarnings(androidXExtension: AndroidX
10971051
}
10981052
}
10991053

1100-
/**
1101-
* Guarantees unique names for the APKs, and modifies some of the suffixes. The APK name is used
1102-
* to determine what gets run by our test runner
1103-
*/
1104-
fun String.renameApkForTesting(projectPath: String): String {
1105-
return "${projectPath.asFilenamePrefix()}_$this"
1106-
}
1107-
11081054
fun Project.hasBenchmarkPlugin(): Boolean {
11091055
return this.plugins.hasPlugin(BenchmarkPlugin::class.java)
11101056
}

buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ abstract class AndroidXRootImplPlugin : Plugin<Project> {
136136
}
137137
}
138138

139-
tasks.register(AndroidXImplPlugin.BUILD_TEST_APKS_TASK)
140-
141139
// NOTE: this task is used by the Github CI as well. If you make any changes here,
142140
// please update the .github/workflows files as well, if necessary.
143141
project.tasks.register(
@@ -150,6 +148,7 @@ abstract class AndroidXRootImplPlugin : Plugin<Project> {
150148
it.entryCompression = ZipEntryCompression.STORED
151149
// Archive is greater than 4Gb :O
152150
it.isZip64 = true
151+
it.isReproducibleFileOrder = true
153152
}
154153
project.tasks.register(
155154
ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
@@ -161,6 +160,7 @@ abstract class AndroidXRootImplPlugin : Plugin<Project> {
161160
it.entryCompression = ZipEntryCompression.STORED
162161
// Archive is greater than 4Gb :O
163162
it.isZip64 = true
163+
it.isReproducibleFileOrder = true
164164
}
165165

166166
AffectedModuleDetector.configure(gradle, this)

buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package androidx.build.testConfiguration
1818

1919
import androidx.build.dependencyTracker.ProjectSubset
20-
import androidx.build.renameApkForTesting
21-
import com.android.build.api.variant.BuiltArtifact
2220
import com.android.build.api.variant.BuiltArtifacts
2321
import com.android.build.api.variant.BuiltArtifactsLoader
2422
import java.io.File
@@ -78,18 +76,6 @@ abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
7876
@get:Input
7977
abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
8078

81-
@get:Input
82-
abstract val clientToTPath: Property<String>
83-
84-
@get:Input
85-
abstract val clientPreviousPath: Property<String>
86-
87-
@get:Input
88-
abstract val serviceToTPath: Property<String>
89-
90-
@get:Input
91-
abstract val servicePreviousPath: Property<String>
92-
9379
@get:Input
9480
abstract val minSdk: Property<Int>
9581

@@ -117,39 +103,60 @@ abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
117103
@get:OutputFile
118104
abstract val jsonClientToTServiceToTServiceTests: RegularFileProperty
119105

106+
@get:OutputFile
107+
abstract val previousClientApk: RegularFileProperty
108+
109+
@get:OutputFile
110+
abstract val totClientApk: RegularFileProperty
111+
112+
@get:OutputFile
113+
abstract val previousServiceApk: RegularFileProperty
114+
115+
@get:OutputFile
116+
abstract val totServiceApk: RegularFileProperty
117+
120118
@TaskAction
121119
fun generateAndroidTestZip() {
122-
val clientToTApk = resolveApk(clientToTFolder, clientToTLoader)
123-
val clientPreviousApk = resolveApk(clientPreviousFolder, clientPreviousLoader)
124-
val serviceToTApk = resolveApk(serviceToTFolder, serviceToTLoader)
125-
val servicePreviousApk = resolveApk(
126-
servicePreviousFolder, servicePreviousLoader
120+
val clientToTApk = totClientApk.get().asFile
121+
val clientToTSha256 = copyApkAndGetSha256(clientToTFolder, clientToTLoader, clientToTApk)
122+
val clientPreviousApk = previousClientApk.get().asFile
123+
val clientPreviousSha256 = copyApkAndGetSha256(
124+
clientPreviousFolder, clientPreviousLoader, clientPreviousApk
127125
)
126+
val serviceToTApk = totServiceApk.get().asFile
127+
val serviceToTSha256 = copyApkAndGetSha256(
128+
serviceToTFolder, serviceToTLoader, serviceToTApk
129+
)
130+
val servicePreviousApk = previousServiceApk.get().asFile
131+
val servicePreviousSha256 = copyApkAndGetSha256(
132+
servicePreviousFolder, servicePreviousLoader, servicePreviousApk
133+
)
134+
128135
writeConfigFileContent(
129-
clientApk = clientToTApk,
130-
serviceApk = serviceToTApk,
131-
clientPath = clientToTPath.get(),
132-
servicePath = serviceToTPath.get(),
136+
clientApkName = clientToTApk.name,
137+
serviceApkName = serviceToTApk.name,
138+
clientApkSha256 = clientToTSha256,
139+
serviceApkSha256 = serviceToTSha256,
133140
jsonClientOutputFile = jsonClientToTServiceToTClientTests,
134141
jsonServiceOutputFile = jsonClientToTServiceToTServiceTests,
135142
isClientPrevious = false,
136143
isServicePrevious = false
137144
)
138145
writeConfigFileContent(
139-
clientApk = clientToTApk,
140-
serviceApk = servicePreviousApk,
141-
clientPath = clientToTPath.get(),
142-
servicePath = servicePreviousPath.get(),
146+
clientApkName = clientToTApk.name,
147+
serviceApkName = servicePreviousApk.name,
148+
clientApkSha256 = clientToTSha256,
149+
serviceApkSha256 = servicePreviousSha256,
143150
jsonClientOutputFile = jsonClientToTServicePreviousClientTests,
144151
jsonServiceOutputFile = jsonClientToTServicePreviousServiceTests,
145152
isClientPrevious = false,
146153
isServicePrevious = true
147154
)
148155
writeConfigFileContent(
149-
clientApk = clientPreviousApk,
150-
serviceApk = serviceToTApk,
151-
clientPath = clientPreviousPath.get(),
152-
servicePath = serviceToTPath.get(),
156+
clientApkName = clientPreviousApk.name,
157+
serviceApkName = serviceToTApk.name,
158+
clientApkSha256 = clientPreviousSha256,
159+
serviceApkSha256 = serviceToTSha256,
153160
jsonClientOutputFile = jsonClientPreviousServiceToTClientTests,
154161
jsonServiceOutputFile = jsonClientPreviousServiceToTServiceTests,
155162
isClientPrevious = true,
@@ -165,26 +172,27 @@ abstract class GenerateMediaTestConfigurationTask : DefaultTask() {
165172
?: throw RuntimeException("Cannot load required APK for task: $name")
166173
}
167174

168-
private fun BuiltArtifact.resolveName(path: String): String {
169-
return outputFile.substringAfterLast("/").renameApkForTesting(path)
175+
private fun copyApkAndGetSha256(
176+
apkFolder: DirectoryProperty,
177+
apkLoader: Property<BuiltArtifactsLoader>,
178+
destination: File
179+
): String {
180+
val artifacts = apkLoader.get().load(apkFolder.get())
181+
?: throw RuntimeException("Cannot load required APK for task: $name")
182+
File(artifacts.elements.single().outputFile).copyTo(destination, overwrite = true)
183+
return sha256(destination)
170184
}
171185

172186
private fun writeConfigFileContent(
173-
clientApk: BuiltArtifacts,
174-
serviceApk: BuiltArtifacts,
175-
clientPath: String,
176-
servicePath: String,
187+
clientApkName: String,
188+
serviceApkName: String,
189+
clientApkSha256: String,
190+
serviceApkSha256: String,
177191
jsonClientOutputFile: RegularFileProperty,
178192
jsonServiceOutputFile: RegularFileProperty,
179193
isClientPrevious: Boolean,
180194
isServicePrevious: Boolean,
181195
) {
182-
val clientBuiltArtifact = clientApk.elements.single()
183-
val serviceBuiltArtifact = serviceApk.elements.single()
184-
val clientApkName = clientBuiltArtifact.resolveName(clientPath)
185-
val clientApkSha256 = sha256(File(clientBuiltArtifact.outputFile))
186-
val serviceApkName = serviceBuiltArtifact.resolveName(servicePath)
187-
val serviceApkSha256 = sha256(File(serviceBuiltArtifact.outputFile))
188196
createOrFail(jsonClientOutputFile).writeText(
189197
buildMediaJson(
190198
configName = jsonClientOutputFile.asFile.get().name,

buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
package androidx.build.testConfiguration
1818

1919
import androidx.build.dependencyTracker.ProjectSubset
20-
import androidx.build.renameApkForTesting
2120
import com.android.build.api.variant.BuiltArtifactsLoader
2221
import java.io.File
2322
import org.gradle.api.DefaultTask
23+
import org.gradle.api.GradleException
2424
import org.gradle.api.file.DirectoryProperty
2525
import org.gradle.api.file.RegularFileProperty
2626
import org.gradle.api.provider.ListProperty
2727
import org.gradle.api.provider.Property
28-
import org.gradle.api.tasks.CacheableTask
2928
import org.gradle.api.tasks.Input
3029
import org.gradle.api.tasks.InputFiles
3130
import org.gradle.api.tasks.Internal
@@ -34,14 +33,15 @@ import org.gradle.api.tasks.OutputFile
3433
import org.gradle.api.tasks.PathSensitive
3534
import org.gradle.api.tasks.PathSensitivity
3635
import org.gradle.api.tasks.TaskAction
36+
import org.gradle.work.DisableCachingByDefault
3737

3838
/**
3939
* Writes a configuration file in
4040
* <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
4141
* format that gets zipped alongside the APKs to be tested.
4242
* This config gets ingested by Tradefed.
4343
*/
44-
@CacheableTask
44+
@DisableCachingByDefault(because = "Doesn't benefit from caching")
4545
abstract class GenerateTestConfigurationTask : DefaultTask() {
4646

4747
@get:InputFiles
@@ -52,10 +52,6 @@ abstract class GenerateTestConfigurationTask : DefaultTask() {
5252
@get:Internal
5353
abstract val appLoader: Property<BuiltArtifactsLoader>
5454

55-
@get:Input
56-
@get:Optional
57-
abstract val appProjectPath: Property<String>
58-
5955
@get:InputFiles
6056
@get:PathSensitive(PathSensitivity.RELATIVE)
6157
abstract val testFolder: DirectoryProperty
@@ -96,6 +92,18 @@ abstract class GenerateTestConfigurationTask : DefaultTask() {
9692
@get:OutputFile
9793
abstract val constrainedOutputXml: RegularFileProperty
9894

95+
@get:OutputFile
96+
abstract val outputTestApk: RegularFileProperty
97+
98+
@get:OutputFile
99+
abstract val constrainedOutputTestApk: RegularFileProperty
100+
101+
@get:[OutputFile Optional]
102+
abstract val outputAppApk: RegularFileProperty
103+
104+
@get:[OutputFile Optional]
105+
abstract val constrainedOutputAppApk: RegularFileProperty
106+
99107
@TaskAction
100108
fun generateAndroidTestZip() {
101109
writeConfigFileContent(
@@ -125,9 +133,13 @@ abstract class GenerateTestConfigurationTask : DefaultTask() {
125133
?: throw RuntimeException("Cannot load required APK for task: $name")
126134
// We don't need to check hasBenchmarkPlugin because benchmarks shouldn't have test apps
127135
val appApkBuiltArtifact = appApk.elements.single()
128-
val appName = appApkBuiltArtifact.outputFile.substringAfterLast("/")
129-
.renameApkForTesting(appProjectPath.get())
130-
configBuilder.appApkName(appName)
136+
val destinationApk = if (isConstrained) {
137+
constrainedOutputAppApk.get().asFile
138+
} else {
139+
outputAppApk.get().asFile
140+
}
141+
File(appApkBuiltArtifact.outputFile).copyTo(destinationApk, overwrite = true)
142+
configBuilder.appApkName(destinationApk.name)
131143
.appApkSha256(sha256(File(appApkBuiltArtifact.outputFile)))
132144
}
133145
configBuilder.additionalApkKeys(additionalApkKeys.get())
@@ -181,16 +193,25 @@ abstract class GenerateTestConfigurationTask : DefaultTask() {
181193
val testApk = testLoader.get().load(testFolder.get())
182194
?: throw RuntimeException("Cannot load required APK for task: $name")
183195
val testApkBuiltArtifact = testApk.elements.single()
184-
val testName = testApkBuiltArtifact.outputFile
185-
.substringAfterLast("/")
186-
.renameApkForTesting(testProjectPath.get())
187-
configBuilder.testApkName(testName)
196+
val destinationApk = if (isConstrained) {
197+
constrainedOutputTestApk.get().asFile
198+
} else {
199+
outputTestApk.get().asFile
200+
}
201+
File(testApkBuiltArtifact.outputFile).copyTo(destinationApk, overwrite = true)
202+
configBuilder.testApkName(destinationApk.name)
188203
.applicationId(testApk.applicationId)
189204
.minSdk(minSdk.get().toString())
190205
.testRunner(testRunner.get())
191206
.testApkSha256(sha256(File(testApkBuiltArtifact.outputFile)))
192207
createOrFail(outputFile).writeText(configBuilder.buildXml())
193208
if (!isConstrained) {
209+
if (!outputJson.asFile.get().name.startsWith("_")) {
210+
// Prefixing json file names with _ allows us to collocate these files
211+
// inside of the androidTest.zip to make fetching them less expensive.
212+
throw GradleException("json output file names are expected to use _ prefix to, " +
213+
"currently set to ${outputJson.asFile.get().name}")
214+
}
194215
createOrFail(outputJson).writeText(configBuilder.buildJson())
195216
}
196217
}

0 commit comments

Comments
 (0)