tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }