DependenciesPlugin.kt (12037B)
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/. */ 4 5 import groovy.json.JsonBuilder 6 import org.gradle.api.Plugin 7 import org.gradle.api.flow.FlowAction 8 import org.gradle.api.flow.FlowParameters 9 import org.gradle.api.flow.FlowProviders 10 import org.gradle.api.flow.FlowScope 11 import org.gradle.api.initialization.Settings 12 import org.gradle.api.invocation.Gradle 13 import org.gradle.api.provider.Property 14 import org.gradle.api.services.BuildService 15 import org.gradle.api.services.BuildServiceParameters 16 import org.gradle.api.tasks.Input 17 import org.gradle.build.event.BuildEventsListenerRegistry 18 import org.gradle.internal.scopeids.id.BuildInvocationScopeId 19 import org.gradle.kotlin.dsl.always 20 import org.gradle.tooling.events.FinishEvent 21 import org.gradle.tooling.events.OperationCompletionListener 22 import org.gradle.tooling.events.task.TaskFailureResult 23 import org.gradle.tooling.events.task.TaskFinishEvent 24 import org.gradle.tooling.events.task.TaskSkippedResult 25 import org.gradle.tooling.events.task.TaskSuccessResult 26 import java.io.File 27 import java.time.Instant 28 import java.time.ZoneId 29 import java.time.ZonedDateTime 30 import java.time.format.DateTimeFormatter 31 import java.util.Optional 32 import java.util.concurrent.atomic.AtomicReference 33 import javax.inject.Inject 34 35 // If you ever need to force a toolchain rebuild (taskcluster) then edit the following comment. 36 // FORCE REBUILD 2024-05-02 37 38 interface BuildMetricsServiceParameters : BuildServiceParameters { 39 val topobjdir: Property<String> 40 val fileSuffix: Property<String> 41 } 42 43 abstract class BuildMetricsService @Inject constructor( 44 private val parameters: BuildMetricsServiceParameters 45 ) : BuildService<BuildMetricsServiceParameters>, OperationCompletionListener, AutoCloseable { 46 private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-SSS") 47 private val taskRecords = mutableListOf<Map<String, Any>>() 48 49 var invocationStart = 0L 50 var configStart = 0L 51 var configEnd = 0L 52 53 override fun onFinish(event: FinishEvent) { 54 if (event is TaskFinishEvent) { 55 val result = event.result 56 val startMs = result.startTime 57 val stopMs = result.endTime 58 59 val status = when (result) { 60 is TaskFailureResult -> "FAILED" 61 is TaskSkippedResult -> "SKIPPED" 62 is TaskSuccessResult -> when { 63 result.isUpToDate -> "UP-TO-DATE" 64 result.isFromCache -> "FROM-CACHE" 65 else -> "EXECUTED" 66 } 67 else -> "UNKNOWN" 68 } 69 70 taskRecords += mapOf( 71 "path" to event.descriptor.taskPath, 72 "start" to dateFormatter.format(Instant.ofEpochMilli(startMs).atZone(ZoneId.systemDefault())), 73 "stop" to dateFormatter.format(Instant.ofEpochMilli(stopMs).atZone(ZoneId.systemDefault())), 74 "duration" to String.format("%.3f", (stopMs - startMs) / 1_000.0), 75 "status" to status 76 ) 77 } 78 } 79 80 override fun close() { 81 val invocationEnd = System.currentTimeMillis() 82 val invocationDuration = String.format("%.3f", (invocationEnd - invocationStart) / 1_000.0) 83 84 val configStartFormatted = dateFormatter.format(Instant.ofEpochMilli(configStart).atZone(ZoneId.systemDefault())) 85 val configEndFormatted = dateFormatter.format(Instant.ofEpochMilli(configEnd).atZone(ZoneId.systemDefault())) 86 val configDuration = String.format("%.3f", (configEnd - configStart) / 1_000.0) 87 88 val content = mapOf( 89 "invocation" to mapOf( 90 "start" to dateFormatter.format(Instant.ofEpochMilli(invocationStart).atZone(ZoneId.systemDefault())), 91 "end" to dateFormatter.format(Instant.ofEpochMilli(invocationEnd).atZone(ZoneId.systemDefault())), 92 "duration" to invocationDuration 93 ), 94 "configPhase" to mapOf( 95 "start" to configStartFormatted, 96 "end" to configEndFormatted, 97 "duration" to configDuration 98 ), 99 "tasks" to taskRecords 100 ) 101 102 val topobjdir = parameters.topobjdir.get() 103 val outputDir = File(topobjdir, "gradle/build/metrics").apply { mkdirs() } 104 val fileSuffix = parameters.fileSuffix.get() 105 106 File(outputDir, "build-metrics-$fileSuffix.json") 107 .writeText(JsonBuilder(content).toPrettyString()) 108 } 109 } 110 111 /** 112 * Print Gradle errors in such a way that Treeherder includes them in its "Failure Summary". This 113 * approach is technically complicated but is compatible with the Gradle configuration cache. 114 * 115 * The unusual output is recognized by Treeherder even when Gradle is directly invoked without 116 * `mach` or `mozharness` logging. 117 */ 118 abstract class LogGradleErrorForTreeHerder : FlowAction<LogGradleErrorForTreeHerder.Parameters> { 119 interface Parameters : FlowParameters { 120 @get:Input 121 val failure: Property<Optional<Throwable>> 122 } 123 124 companion object { 125 private fun appendIndentedMessage( 126 sb: StringBuilder, 127 t: Throwable, 128 indentStr: String, 129 ) { 130 val message: String = (t.message ?: "").replace("(?m)^".toRegex(), indentStr) 131 if (message.isNotEmpty()) { 132 // We don't want the first line indented. 133 sb.append(message.substring(indentStr.length)) 134 } 135 sb.append("\n") 136 if (t.cause != null && t.cause != t) { 137 sb.append(indentStr) 138 sb.append("> ") 139 appendIndentedMessage(sb, t.cause!!, indentStr + " ") 140 } 141 } 142 143 fun getIndentedMessage(t: Throwable): String { 144 val sb = StringBuilder() 145 appendIndentedMessage(sb, t, "") 146 return sb.toString() 147 } 148 } 149 150 /** 151 * Print non-null `Throwable` with each line prefixed by "[gradle:error]: >". Each `cause` is 152 * printed indented, roughly matching Gradle's output. 153 * 154 * Treeherder recognizes such lines as errors and surfaces them in its "Failure Summary"; see 155 * https://github.com/mozilla/treeherder/blob/b04b64185e189a2d9e4c088b4be98d898c658e00/treeherder/log_parser/parsers.py#L75. 156 * Treeherder trims leading spaces; the extra ">" preserves indentation. 157 */ 158 override fun execute(parameters: Parameters) { 159 parameters.failure.get().map { t -> 160 getIndentedMessage(t).split("\n").forEach { 161 println("[gradle:error]: > ${it}") 162 } 163 } 164 } 165 } 166 167 abstract class DependenciesPlugin : Plugin<Settings> { 168 @get:Inject 169 protected abstract val flowScope: FlowScope 170 171 @get:Inject 172 protected abstract val flowProviders: FlowProviders 173 174 @get:Inject 175 protected abstract val buildEventsListenerRegistry: BuildEventsListenerRegistry 176 177 @get:Inject 178 protected abstract val buildInvocationScopeId: BuildInvocationScopeId 179 180 companion object { 181 private val rootGradleBuild = AtomicReference<Gradle?>(null) 182 @Volatile 183 private var buildMetricsInitialized = false 184 } 185 186 override fun apply(settings: Settings) { 187 flowScope.always(LogGradleErrorForTreeHerder::class) { 188 parameters.failure.set(flowProviders.buildWorkResult.map { result -> result.failure }) 189 } 190 191 // Initialize build metrics only if the buildMetrics property is set 192 settings.gradle.projectsEvaluated { 193 if (gradle.rootProject.hasProperty("buildMetrics")) { 194 initializeBuildMetrics(settings) 195 } 196 } 197 } 198 199 private fun initializeBuildMetrics(settings: Settings) { 200 val rootGradle = generateSequence(settings.gradle) { it.parent }.last() 201 202 // Only initialize the shared service once from the root gradle build 203 if (rootGradleBuild.compareAndSet(null, rootGradle)) { 204 rootGradle.taskGraph.whenReady { 205 val provider = rootGradle.sharedServices.registrations.getByName("buildMetricsService") 206 val service = provider.service.get() as BuildMetricsService 207 208 service.invocationStart = System.currentTimeMillis() 209 service.configStart = System.currentTimeMillis() 210 service.configEnd = System.currentTimeMillis() 211 } 212 } 213 214 // Register a task listener for all builds 215 val buildMetricsProvider = rootGradle.sharedServices.registerIfAbsent( 216 "buildMetricsService", 217 BuildMetricsService::class.java 218 ) { 219 @Suppress("UNCHECKED_CAST") 220 val mozconfig = rootGradle.extensions.extraProperties["mozconfig"] as Map<String, Any> 221 val topobjdir = mozconfig["topobjdir"] as String 222 // If the buildMetricsFileSuffix property is set, it overrides 223 // the buildInvocationScopeId as the file suffix 224 val fileSuffix = rootGradle.rootProject 225 .findProperty("buildMetricsFileSuffix") 226 ?.toString() 227 ?: buildInvocationScopeId.id.toString() 228 229 230 parameters.topobjdir.set(topobjdir) 231 parameters.fileSuffix.set(fileSuffix) 232 } 233 234 buildEventsListenerRegistry.onTaskCompletion(buildMetricsProvider) 235 } 236 } 237 238 // Synchronized dependencies used by (some) modules 239 @Suppress("Unused", "MaxLineLength") 240 object ComponentsDependencies { 241 val mozilla_appservices_fxaclient = "${ApplicationServicesConfig.groupId}:fxaclient:${ApplicationServicesConfig.version}" 242 val mozilla_appservices_nimbus = "${ApplicationServicesConfig.groupId}:nimbus:${ApplicationServicesConfig.version}" 243 val mozilla_appservices_autofill = "${ApplicationServicesConfig.groupId}:autofill:${ApplicationServicesConfig.version}" 244 val mozilla_appservices_logins = "${ApplicationServicesConfig.groupId}:logins:${ApplicationServicesConfig.version}" 245 val mozilla_appservices_places = "${ApplicationServicesConfig.groupId}:places:${ApplicationServicesConfig.version}" 246 val mozilla_appservices_syncmanager = "${ApplicationServicesConfig.groupId}:syncmanager:${ApplicationServicesConfig.version}" 247 val mozilla_remote_settings = "${ApplicationServicesConfig.groupId}:remotesettings:${ApplicationServicesConfig.version}" 248 val mozilla_appservices_push = "${ApplicationServicesConfig.groupId}:push:${ApplicationServicesConfig.version}" 249 val mozilla_appservices_search = "${ApplicationServicesConfig.groupId}:search:${ApplicationServicesConfig.version}" 250 val mozilla_appservices_tabs = "${ApplicationServicesConfig.groupId}:tabs:${ApplicationServicesConfig.version}" 251 val mozilla_appservices_suggest = "${ApplicationServicesConfig.groupId}:suggest:${ApplicationServicesConfig.version}" 252 val mozilla_appservices_httpconfig = "${ApplicationServicesConfig.groupId}:httpconfig:${ApplicationServicesConfig.version}" 253 val mozilla_appservices_init_rust_components = "${ApplicationServicesConfig.groupId}:init_rust_components:${ApplicationServicesConfig.version}" 254 val mozilla_appservices_full_megazord = "${ApplicationServicesConfig.groupId}:full-megazord:${ApplicationServicesConfig.version}" 255 val mozilla_appservices_full_megazord_libsForTests = "${ApplicationServicesConfig.groupId}:full-megazord-libsForTests:${ApplicationServicesConfig.version}" 256 257 val mozilla_appservices_errorsupport = "${ApplicationServicesConfig.groupId}:errorsupport:${ApplicationServicesConfig.version}" 258 val mozilla_appservices_rust_log_forwarder = "${ApplicationServicesConfig.groupId}:rust-log-forwarder:${ApplicationServicesConfig.version}" 259 val mozilla_appservices_sync15 = "${ApplicationServicesConfig.groupId}:sync15:${ApplicationServicesConfig.version}" 260 val mozilla_appservices_fxrelay = "${ApplicationServicesConfig.groupId}:relay:${ApplicationServicesConfig.version}" 261 }