Create FUS Gradle plugin
fus-statistics-gradle-plugin can be used by any other Gradle plugins to collect additional metrics for FUS. KT-59627
This commit is contained in:
committed by
Space Team
parent
4ed7504d87
commit
9de791b9ac
@@ -0,0 +1,19 @@
|
||||
## Description
|
||||
|
||||
Contains a plugin for Feature Usage Statistics(FUS). fus-statistics-gradle-plugin can be used by other Gradle plugins to
|
||||
collect additional metrics for FUS.
|
||||
|
||||
Statistics will be gathered in **kotlin-fus/<build_uid>** file. It is located in ${GRADLE_USER_HOME} directory by default.
|
||||
You can modify default directory by using the **kotlin.fus.statistics.path** property (for test purpose only).
|
||||
Created file will contain **key=value** rows fowled by **BUILD FINISHED**
|
||||
|
||||
Collection statistics in a file is enabled by default. Further step is to collect data only after approval - KT-59629.
|
||||
In any case, collected data will be sent only after user's agreement.
|
||||
|
||||
### Collect own data
|
||||
|
||||
**org.jetbrains.kotlin.fus-statistics-gradle-plugin** plugin should be applied and a new **UsesGradleBuildFusStatisticsService** task
|
||||
should be created to be able to collect additional data.
|
||||
In this case, the task will access **GradleBuildFusStatistics** and be able to report new metrics via **reportMetric**.
|
||||
|
||||
Analytics teams' approve is required for any collected data.
|
||||
@@ -0,0 +1,36 @@
|
||||
import plugins.KotlinBuildPublishingPlugin
|
||||
|
||||
plugins {
|
||||
id("gradle-plugin-common-configuration")
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
commonApi(project(":kotlin-gradle-plugin-api"))
|
||||
commonApi(project(":kotlin-gradle-plugin"))
|
||||
}
|
||||
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("fus-statistics-gradle-plugin") {
|
||||
id = "org.jetbrains.kotlin.fus-statistics-gradle-plugin"
|
||||
displayName = "FusStatisticsPlugin"
|
||||
description = displayName
|
||||
implementationClass = "org.jetbrains.kotlin.gradle.fus.FusStatisticsPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable releasing for this plugin
|
||||
// It is not intended to be released publicly
|
||||
tasks.withType<PublishToMavenRepository>()
|
||||
.configureEach {
|
||||
if (name.endsWith("PublicationTo${KotlinBuildPublishingPlugin.REPOSITORY_NAME}Repository")) {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("publishPlugins") {
|
||||
enabled = false
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package org.jetbrains.kotlin.gradle.fus
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.jetbrains.kotlin.gradle.fus.internal.GradleBuildFusStatisticsBuildService
|
||||
import javax.inject.Inject
|
||||
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* FusStatisticsPlugin is a Gradle plugin that registers a BuildService to collect and report
|
||||
* feature usage statistics during the build process.
|
||||
*/
|
||||
class FusStatisticsPlugin @Inject constructor(
|
||||
private val providerFactory: ProviderFactory
|
||||
) : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
GradleBuildFusStatisticsBuildService.registerIfAbsent(project)
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.gradle.fus
|
||||
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Internal
|
||||
|
||||
|
||||
/**
|
||||
* A service interface for build FUS statistics reporting.
|
||||
*/
|
||||
interface GradleBuildFusStatisticsService {
|
||||
|
||||
/**
|
||||
* Reports a metric by its name and optionally subproject.
|
||||
*
|
||||
* @param name the metric name
|
||||
* @param value the metric value.
|
||||
* @param subprojectName the subproject name for which the metric is being reported.
|
||||
*/
|
||||
fun reportMetric(name: String, value: String, subprojectName: String? = null)
|
||||
|
||||
/**
|
||||
* @see org.jetbrains.kotlin.gradle.fus.GradleBuildFusStatisticsService.reportMetric(java.lang.String, java.lang.String, java.lang.String)
|
||||
*/
|
||||
fun reportMetric(name: String, value: Number, subprojectName: String? = null)
|
||||
|
||||
/**
|
||||
* @see org.jetbrains.kotlin.gradle.fus.GradleBuildFusStatisticsService.reportMetric(java.lang.String, java.lang.String, java.lang.String)
|
||||
*/
|
||||
fun reportMetric(name: String, value: Boolean, subprojectName: String? = null)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This task interface provide access to GradleBuildFusStatisticsService.
|
||||
*
|
||||
* @property fusStatisticsBuildService holds an instance of GradleBuildFusStatisticsService.
|
||||
*/
|
||||
interface UsesGradleBuildFusStatisticsService : Task {
|
||||
@get:Internal
|
||||
val fusStatisticsBuildService: Property<GradleBuildFusStatisticsService>
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.fus.internal
|
||||
|
||||
/**
|
||||
* Dummy service is used to avoid data collection without user's consent
|
||||
*/
|
||||
internal abstract class DummyGradleBuildFusStatisticsService : GradleBuildFusStatisticsBuildService() {
|
||||
override fun reportMetric(name: String, value: Boolean, subprojectName: String?) {}
|
||||
|
||||
override fun reportMetric(name: String, value: Number, subprojectName: String?) {}
|
||||
|
||||
override fun reportMetric(name: String, value: String, subprojectName: String?) {}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.fus.internal
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.services.BuildService
|
||||
import org.gradle.api.services.BuildServiceParameters
|
||||
import org.jetbrains.kotlin.gradle.fus.GradleBuildFusStatisticsService
|
||||
import org.jetbrains.kotlin.gradle.fus.UsesGradleBuildFusStatisticsService
|
||||
import java.util.*
|
||||
|
||||
abstract class GradleBuildFusStatisticsBuildService : GradleBuildFusStatisticsService,
|
||||
BuildService<GradleBuildFusStatisticsBuildService.Parameters>, AutoCloseable {
|
||||
interface Parameters : BuildServiceParameters {
|
||||
val fusStatisticsRootDirPath: Property<String>
|
||||
val buildId: Property<String>
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var statisticsIsEnabled: Boolean = true //KT-59629 Wait for user confirmation before start to collect metrics
|
||||
private const val FUS_STATISTICS_PATH = "kotlin.fus.statistics.path"
|
||||
private val serviceClass = GradleBuildFusStatisticsBuildService::class.java
|
||||
private val serviceName = "${serviceClass.name}_${serviceClass.classLoader.hashCode()}"
|
||||
fun registerIfAbsent(project: Project): Provider<out GradleBuildFusStatisticsService>? {
|
||||
project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return it.service as Provider<GradleBuildFusStatisticsService>
|
||||
}
|
||||
|
||||
return (if (statisticsIsEnabled) {
|
||||
project.gradle.sharedServices.registerIfAbsent(serviceName, InternalGradleBuildFusStatisticsService::class.java) {
|
||||
val customPath: String = if (project.rootProject.hasProperty(FUS_STATISTICS_PATH)) {
|
||||
project.rootProject.property(FUS_STATISTICS_PATH) as String
|
||||
} else {
|
||||
project.gradle.gradleUserHomeDir.path
|
||||
}
|
||||
it.parameters.fusStatisticsRootDirPath.set(customPath)
|
||||
it.parameters.buildId.set(UUID.randomUUID().toString())
|
||||
}
|
||||
} else {
|
||||
project.gradle.sharedServices.registerIfAbsent(serviceName, DummyGradleBuildFusStatisticsService::class.java) {}
|
||||
}).also { configureTasks(project, it) }
|
||||
}
|
||||
|
||||
private fun configureTasks(project: Project, serviceProvider: Provider<out GradleBuildFusStatisticsBuildService>) {
|
||||
project.tasks.withType(UsesGradleBuildFusStatisticsService::class.java).configureEach { task ->
|
||||
task.fusStatisticsBuildService.value(serviceProvider).disallowChanges()
|
||||
task.usesService(serviceProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.fus.internal
|
||||
|
||||
|
||||
import org.gradle.api.logging.Logging
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal abstract class InternalGradleBuildFusStatisticsService : GradleBuildFusStatisticsBuildService() {
|
||||
|
||||
private val metrics = ConcurrentHashMap<Metric, Any>()
|
||||
private val log = Logging.getLogger(this.javaClass)
|
||||
|
||||
override fun close() {
|
||||
val reportFile = File(parameters.fusStatisticsRootDirPath.get(), STATISTICS_FOLDER_NAME)
|
||||
.also { Files.createDirectories(it.toPath()) }
|
||||
.resolve(parameters.buildId.get())
|
||||
reportFile.createNewFile()
|
||||
FileOutputStream(reportFile, true).bufferedWriter().use {
|
||||
for ((key, value) in metrics) {
|
||||
it.appendLine("$key=$value")
|
||||
}
|
||||
it.appendLine(BUILD_SESSION_SEPARATOR)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportMetric(name: String, value: Boolean, subprojectName: String?) {
|
||||
internalReportMetric(name, value, subprojectName)
|
||||
}
|
||||
|
||||
override fun reportMetric(name: String, value: String, subprojectName: String?) {
|
||||
internalReportMetric(name, value, subprojectName)
|
||||
}
|
||||
|
||||
override fun reportMetric(name: String, value: Number, subprojectName: String?) {
|
||||
internalReportMetric(name, value, subprojectName)
|
||||
}
|
||||
|
||||
private fun internalReportMetric(name: String, value: Any, subprojectName: String?) {
|
||||
val oldValue = metrics.getOrPut(Metric(name, subprojectName)) { value }
|
||||
if (oldValue != value) {
|
||||
log.warn("Try to override $name metric: current value is \"$oldValue\", new value is \"$value\"")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STATISTICS_FOLDER_NAME = "kotlin-fus"
|
||||
private const val BUILD_SESSION_SEPARATOR = "BUILD FINISHED"
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.fus.internal
|
||||
|
||||
internal data class Metric(val name: String, val projectHash: String?) : Comparable<Metric> {
|
||||
override fun compareTo(other: Metric) = compareValuesBy(this, other, { it.name }, { it.projectHash })
|
||||
|
||||
override fun toString(): String {
|
||||
val suffix = if (projectHash == null) "" else ".${projectHash}"
|
||||
return name + suffix
|
||||
}
|
||||
}
|
||||
@@ -320,6 +320,7 @@ tasks.withType<Test> {
|
||||
dependsOn(":gradle:android-test-fixes:install")
|
||||
dependsOn(":gradle:gradle-warnings-detector:install")
|
||||
dependsOn(":gradle:kotlin-compiler-args-properties:install")
|
||||
dependsOn(":libraries:tools:gradle:fus-statistics-gradle-plugin:install")
|
||||
dependsOn(":examples:annotation-processor-example:install")
|
||||
dependsOn(":kotlin-dom-api-compat:install")
|
||||
if (project.kotlinBuildProperties.isTeamcityBuild) {
|
||||
|
||||
+84
@@ -9,6 +9,7 @@ import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@DisplayName("Build FUS statistics")
|
||||
class BuildFusStatisticsIT : KGPDaemonsBaseTest() {
|
||||
@@ -42,4 +43,87 @@ class BuildFusStatisticsIT : KGPDaemonsBaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("smoke test for fus-statistics-gradle-plugin")
|
||||
@GradleTest
|
||||
fun smokeTestForFusStatisticsPlugin(gradleVersion: GradleVersion) {
|
||||
val metricName = "METRIC_NAME"
|
||||
val metricValue = 1
|
||||
project("simpleProject", gradleVersion) {
|
||||
buildGradle.modify {
|
||||
"""
|
||||
${applyFusStatisticPlugin(it)}
|
||||
|
||||
${createTestFusTaskClass()}
|
||||
|
||||
tasks.register("test-fus", TestFusTask.class).get().doLast {
|
||||
fusStatisticsBuildService.get().reportMetric("$metricName", $metricValue, null)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val reportRelativePath = "reports"
|
||||
build("test-fus", "-Pkotlin.fus.statistics.path=${projectPath.resolve(reportRelativePath).pathString}") {
|
||||
val fusReport = projectPath.getSingleFileInDir("$reportRelativePath/kotlin-fus")
|
||||
assertFileContains(
|
||||
fusReport,
|
||||
"METRIC_NAME=1",
|
||||
"BUILD FINISHED"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyFusStatisticPlugin(it: String) = it.replace(
|
||||
"plugins {",
|
||||
"""
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.fus-statistics-gradle-plugin" version "${'$'}kotlin_version"
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
private fun createTestFusTaskClass() = """import org.jetbrains.kotlin.gradle.fus.GradleBuildFusStatisticsService
|
||||
class TestFusTask extends DefaultTask implements org.jetbrains.kotlin.gradle.fus.UsesGradleBuildFusStatisticsService {
|
||||
private Property<GradleBuildFusStatisticsService> fusStatisticsBuildService = project.objects.property(GradleBuildFusStatisticsService.class)
|
||||
|
||||
org.gradle.api.provider.Property getFusStatisticsBuildService(){
|
||||
return fusStatisticsBuildService
|
||||
}
|
||||
|
||||
}"""
|
||||
|
||||
@DisplayName("test override metrics for fus-statistics-gradle-plugin")
|
||||
@GradleTest
|
||||
fun testMetricsOverrideForFusStatisticsPlugin(gradleVersion: GradleVersion) {
|
||||
val metricName = "METRIC_NAME"
|
||||
val metricValue = 1
|
||||
project("simpleProject", gradleVersion) {
|
||||
buildGradle.modify {
|
||||
"""
|
||||
${applyFusStatisticPlugin(it)}
|
||||
|
||||
${createTestFusTaskClass()}
|
||||
|
||||
tasks.register("test-fus", TestFusTask.class).get().doLast {
|
||||
fusStatisticsBuildService.get().reportMetric("$metricName", $metricValue, null)
|
||||
}
|
||||
|
||||
tasks.register("test-fus-second", TestFusTask.class).get().doLast {
|
||||
fusStatisticsBuildService.get().reportMetric("$metricName", 2, null)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
val reportRelativePath = "reports"
|
||||
build("test-fus", "test-fus-second", "-Pkotlin.fus.statistics.path=${projectPath.resolve(reportRelativePath).pathString}") {
|
||||
assertOutputContains("Try to override $metricName metric: current value is \"1\", new value is \"2\"")
|
||||
val fusReport = projectPath.getSingleFileInDir("$reportRelativePath/kotlin-fus")
|
||||
assertFileContains(
|
||||
fusReport,
|
||||
"METRIC_NAME=1",
|
||||
"BUILD FINISHED"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,10 @@ To reproduce locally build all artifacts first:
|
||||
|
||||
```shell
|
||||
# clean local m2 from old artifacts to avoid unrelated failures
|
||||
find ~/.m2/repository/org/jetbrains/kotlin -name "*-1.9.255*" -delete
|
||||
# up-to-date version is in defaultSnapshotVersion property and should be used instead of "*-2.0.255*"
|
||||
find ~/.m2/repository/org/jetbrains/kotlin -name "*-2.0.255*" -delete
|
||||
|
||||
./gradlew install
|
||||
cd libraries
|
||||
mvn install -DskipTests
|
||||
./mvnw install -DskipTests
|
||||
```
|
||||
|
||||
@@ -28,6 +28,7 @@ class ArtifactsTest {
|
||||
"org.jetbrains.kotlin.gradle-subplugin-example.gradle.plugin",
|
||||
"gradle-warnings-detector",
|
||||
"kotlin-compiler-args-properties",
|
||||
"fus-statistics-gradle-plugin",
|
||||
"kotlin-gradle-plugin-tcs-android",
|
||||
"kotlin-gradle-subplugin-example",
|
||||
"kotlin-java-example",
|
||||
@@ -35,6 +36,7 @@ class ArtifactsTest {
|
||||
"org.jetbrains.kotlin.test.fixes.android.gradle.plugin",
|
||||
"org.jetbrains.kotlin.test.gradle-warnings-detector.gradle.plugin",
|
||||
"org.jetbrains.kotlin.test.kotlin-compiler-args-properties.gradle.plugin",
|
||||
"org.jetbrains.kotlin.fus-statistics-gradle-plugin.gradle.plugin",
|
||||
)
|
||||
|
||||
@Test
|
||||
|
||||
@@ -117,6 +117,7 @@ class CodeConformanceTest : TestCase() {
|
||||
"libraries/tools/gradle/android-test-fixes/build",
|
||||
"libraries/tools/gradle/gradle-warnings-detector/build",
|
||||
"libraries/tools/gradle/kotlin-compiler-args-properties/build",
|
||||
"libraries/tools/gradle/fus-statistics-gradle-plugin/build",
|
||||
"libraries/tools/kotlin-allopen/build",
|
||||
"libraries/tools/kotlin-assignment/build",
|
||||
"libraries/tools/kotlin-gradle-build-metrics/build",
|
||||
|
||||
@@ -128,6 +128,7 @@ private val internalPlugins = setOf(
|
||||
"android-test-fixes",
|
||||
"gradle-warnings-detector",
|
||||
"kotlin-compiler-args-properties",
|
||||
"fus-statistics-gradle-plugin",
|
||||
)
|
||||
|
||||
private val testPlugins = internalPlugins + setOf(
|
||||
@@ -631,6 +632,7 @@ fun Project.addBomCheckTask() {
|
||||
project(":gradle:kotlin-compiler-args-properties").path,
|
||||
project(":kotlin-gradle-build-metrics").path,
|
||||
project(":kotlin-gradle-statistics").path,
|
||||
project(":libraries:tools:gradle:fus-statistics-gradle-plugin").path
|
||||
)
|
||||
val projectPath = this@addBomCheckTask.path
|
||||
|
||||
|
||||
+1
@@ -155,6 +155,7 @@ fun Project.configureKotlinCompilationOptions() {
|
||||
":gradle:android-test-fixes",
|
||||
":gradle:gradle-warnings-detector",
|
||||
":gradle:kotlin-compiler-args-properties",
|
||||
":libraries:tools:gradle:fus-statistics-gradle-plugin",
|
||||
":js:js.config",
|
||||
":kotlin-allopen",
|
||||
":kotlin-assignment",
|
||||
|
||||
@@ -217,6 +217,7 @@ include ":kotlin-imports-dumper-compiler-plugin",
|
||||
":gradle:kotlin-compiler-args-properties",
|
||||
":gradle:regression-benchmark-templates",
|
||||
":gradle:regression-benchmarks",
|
||||
":libraries:tools:gradle:fus-statistics-gradle-plugin",
|
||||
":kotlin-tooling-metadata",
|
||||
":kotlin-tooling-core",
|
||||
":kotlin-allopen",
|
||||
|
||||
Reference in New Issue
Block a user