Bug 1845354 - Refactor ApkSizeTask into a plugin using composite build
[gecko.git] / mobile / android / fenix / plugins / apksize / src / main / java / ApkSizePlugin.kt
blobdd42b265097c9cca02e387adbd54329aacffd3f6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import org.gradle.api.DefaultTask
6 import org.gradle.api.Plugin
7 import org.gradle.api.initialization.Settings
8 import org.gradle.api.tasks.Input
9 import org.gradle.api.tasks.TaskAction
10 import org.json.JSONArray
11 import org.json.JSONException
12 import org.json.JSONObject
13 import java.io.File
14 import java.nio.file.Files
15 import java.nio.file.Paths
17 class ApkSizePlugin : Plugin<Settings> {
18     override fun apply(settings: Settings) = Unit
21 /**
22  * Gradle task for determining the size of APKs and logging them in a perfherder compatible format.
23  */
24 open class ApkSizeTask : DefaultTask() {
25     /**
26      * Name of the build variant getting built.
27      */
28     @Input
29     var variantName: String? = null
31     /**
32      * List of APKs that get build for the build variant.
33      */
34     @Input
35     var apks: List<String>? = null
37     @TaskAction
38     fun logApkSize() {
39         val apkSizes = determineApkSizes()
40         if (apkSizes.isEmpty()) {
41             println("Couldn't determine APK sizes for perfherder")
42             return
43         }
45         val json = buildPerfherderJson(apkSizes) ?: return
47         println("PERFHERDER_DATA: $json")
48     }
50     private fun determineApkSizes(): Map<String, Long> {
51         val variantOutputPath = variantName?.removePrefix("fenix")?.toLowerCase()
52         val basePath = listOf(
53             "${project.projectDir}", "build", "outputs", "apk", "fenix", variantOutputPath
54         ).joinToString(File.separator)
56         return requireNotNull(apks).associateWith { apk ->
57             val rawPath = "$basePath${File.separator}$apk"
59             try {
60                 val path = Paths.get(rawPath)
61                 Files.size(path)
62             } catch (t: Throwable) {
63                 println("Could not determine size of $apk ($rawPath)")
64                 t.printStackTrace()
65                 0
66             }
67         }.filter { (_, size) -> size > 0 }
68     }
70     /**
71      * Returns perfherder compatible JSON for tracking the file size of APKs.
72      *
73      * ```
74      * {
75      *   "framework": {
76      *     "name": "build_metrics"
77      *   },
78      *   "suites": [
79      *     {
80      *       "name": "apk-size-[debug,nightly,beta,release]",
81      *       "lowerIsBetter": true,
82      *       "subtests": [
83      *         { "name": "app-arm64-v8a-debug.apk", "value": 98855735 },
84      *         { "name": "app-armeabi-v7a-debug.apk", "value": 92300031 },
85      *         { "name": "app-x86-debug.apk", "value": 103410909 },
86      *         { "name": "app-x86_64-debug.apk", "value": 102465675 }
87      *       ],
88      *       "value":98855735,
89      *       "shouldAlert":false
90      *     }
91      *   ]
92      * }
93      * ```
94      */
95     private fun buildPerfherderJson(apkSize: Map<String, Long>): JSONObject? {
96         return try {
97             val data = JSONObject()
99             val framework = JSONObject()
100             framework.put("name", "build_metrics")
101             data.put("framework", framework)
103             val suites = JSONArray()
105             val suite = JSONObject()
106             suite.put("name", "apk-size-$variantName")
107             suite.put("value", getSummarySize(apkSize))
108             suite.put("lowerIsBetter", true)
109             suite.put("alertChangeType", "absolute")
110             suite.put("alertThreshold", 100 * 1024)
112             val subtests = JSONArray()
113             apkSize.forEach { (apk, size) ->
114                 val subtest = JSONObject()
115                 subtest.put("name", apk)
116                 subtest.put("value", size)
117                 subtests.put(subtest)
118             }
119             suite.put("subtests", subtests)
121             suites.put(suite)
123             data.put("suites", suites)
125             data
126         } catch (e: JSONException) {
127             println("Couldn't generate perfherder JSON")
128             e.printStackTrace()
129             null
130         }
131     }
135  * Returns a summarized size for the APKs. This is the main value that is getting tracked. The size
136  * of the individual APKs will be reported as "subtests".
137  */
138 private fun getSummarySize(apkSize: Map<String, Long>): Long {
139     val arm64size = apkSize.keys.find { it.contains("arm64") }?.let { apk -> apkSize[apk] }
140     if (arm64size != null) {
141         // If available we will report the size of the arm64 APK as the summary. This is the most
142         // important and most installed APK.
143         return arm64size
144     }
146     // If there's no arm64 APK then we calculate a simple average.
147     return apkSize.values.sum() / apkSize.size