/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.initialization.Settings import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.json.JSONArray import org.json.JSONException import org.json.JSONObject import java.io.File import java.nio.file.Files import java.nio.file.Paths class ApkSizePlugin : Plugin { override fun apply(settings: Settings) = Unit } /** * Gradle task for determining the size of APKs and logging them in a perfherder compatible format. */ open class ApkSizeTask : DefaultTask() { /** * Name of the build variant getting built. */ @Input var variantName: String? = null /** * List of APKs that get build for the build variant. */ @Input var apks: List? = null @TaskAction fun logApkSize() { val apkSizes = determineApkSizes() if (apkSizes.isEmpty()) { println("Couldn't determine APK sizes for perfherder") return } val json = buildPerfherderJson(apkSizes) ?: return println("PERFHERDER_DATA: $json") } private fun determineApkSizes(): Map { val variantOutputPath = variantName?.removePrefix("fenix")?.lowercase() val basePath = listOf( "${project.projectDir}", "build", "outputs", "apk", "fenix", variantOutputPath ).joinToString(File.separator) return requireNotNull(apks).associateWith { apk -> val rawPath = "$basePath${File.separator}$apk" try { val path = Paths.get(rawPath) Files.size(path) } catch (t: Throwable) { println("Could not determine size of $apk ($rawPath)") t.printStackTrace() 0 } }.filter { (_, size) -> size > 0 } } /** * Returns perfherder compatible JSON for tracking the file size of APKs. * * ``` * { * "framework": { * "name": "build_metrics" * }, * "suites": [ * { * "name": "apk-size-[debug,nightly,beta,release]", * "lowerIsBetter": true, * "subtests": [ * { "name": "app-arm64-v8a-debug.apk", "value": 98855735 }, * { "name": "app-armeabi-v7a-debug.apk", "value": 92300031 }, * { "name": "app-x86-debug.apk", "value": 103410909 }, * { "name": "app-x86_64-debug.apk", "value": 102465675 } * ], * "value":98855735, * "shouldAlert":false * } * ] * } * ``` */ private fun buildPerfherderJson(apkSize: Map): JSONObject? { return try { val data = JSONObject() val framework = JSONObject() framework.put("name", "build_metrics") data.put("framework", framework) val suites = JSONArray() val suite = JSONObject() suite.put("name", "apk-size-$variantName") suite.put("value", getSummarySize(apkSize)) suite.put("lowerIsBetter", true) suite.put("alertChangeType", "absolute") suite.put("alertThreshold", 1024 * 1024) // Debug variants do not have alerts if (variantName?.contains("debug", ignoreCase = true) == true) { suite.put("shouldAlert", false) } val subtests = JSONArray() apkSize.forEach { (apk, size) -> val subtest = JSONObject() subtest.put("name", apk) subtest.put("value", size) subtests.put(subtest) } suite.put("subtests", subtests) suites.put(suite) data.put("suites", suites) data } catch (e: JSONException) { println("Couldn't generate perfherder JSON") e.printStackTrace() null } } } /** * Returns a summarized size for the APKs. This is the main value that is getting tracked. The size * of the individual APKs will be reported as "subtests". */ private fun getSummarySize(apkSize: Map): Long { val arm64size = apkSize.keys.find { it.contains("arm64") }?.let { apk -> apkSize[apk] } if (arm64size != null) { // If available we will report the size of the arm64 APK as the summary. This is the most // important and most installed APK. return arm64size } // If there's no arm64 APK then we calculate a simple average. return apkSize.values.sum() / apkSize.size }