Do not collect FUS statistics when `BuildUidService` was not found

#KT-79576
diff --git a/libraries/tools/gradle/fus-statistics-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/fus/internal/BuildFinishFlowAction.kt b/libraries/tools/gradle/fus-statistics-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/fus/internal/BuildFinishFlowAction.kt
index 2b6a44c..110e1ea 100644
--- a/libraries/tools/gradle/fus-statistics-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/fus/internal/BuildFinishFlowAction.kt
+++ b/libraries/tools/gradle/fus-statistics-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/fus/internal/BuildFinishFlowAction.kt
@@ -33,6 +33,7 @@
         ) {
             it.parameters.configurationTimeMetrics.addAll(fusService.get().getConfigurationReportedMetrics())
             it.parameters.buildId.set(buildUid)
+            it.parameters.buildId.finalizeValue()
         }
     }
 }
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusPluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusPluginIT.kt
index 5f8d2d3..00a4efd 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusPluginIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusPluginIT.kt
@@ -50,7 +50,7 @@
 
     @DisplayName("with configuration cache and project isolation")
     @GradleTestVersions(
-        additionalVersions = [TestVersions.Gradle.G_8_0, TestVersions.Gradle.G_8_1],
+        minVersion = TestVersions.Gradle.G_8_14
     )
     @GradleTest
     fun withConfigurationCacheAndProjectIsolation(gradleVersion: GradleVersion) {
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt
index 9d1bdc7..145f539 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/FusStatisticsIT.kt
@@ -249,15 +249,15 @@
     @DisplayName("for project with buildSrc")
     @GradleTest
     @GradleTestVersions(
-        additionalVersions = [TestVersions.Gradle.G_8_0, TestVersions.Gradle.G_8_2]
+        minVersion = TestVersions.Gradle.G_8_14
     )
     fun testProjectWithBuildSrcForGradleVersion7(gradleVersion: GradleVersion) {
-        //KT-64022 there are a different build instances in buildSrc and rest project:
+        //KT-64022 there are different build instances in buildSrc and rest project:
         project(
             "instantExecutionWithBuildSrc",
             gradleVersion,
         ) {
-            build("compileKotlin", "-Pkotlin.session.logger.root.path=$projectPath") {
+            build("compileKotlin", "-Pkotlin.session.logger.root.path=$projectPath", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
                 assertFilesCombinedContains(
                     projectPath.resolve("kotlin-profile").listDirectoryEntries(),
                     *expectedMetrics,
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
index d3921d3..8a3ff3b 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt
@@ -78,7 +78,8 @@
         BuildFinishedListenerService.registerIfAbsent(project)
 
         val buildUidService = BuildUidService.registerIfAbsent(project.gradle)
-        BuildFusService.registerIfAbsent(project, pluginVersion, buildUidService)
+        val buildFinishBuildService = BuildFinishBuildService.registerIfAbsent(project, buildUidService, pluginVersion)
+        BuildFusService.registerIfAbsent(project, pluginVersion, buildUidService, buildFinishBuildService)
         PropertiesBuildService.registerIfAbsent(project)
 
         project.gradle.projectsEvaluated {
@@ -99,7 +100,7 @@
 
         BuildMetricsService.registerIfAbsent(project)
         KotlinNativeBundleBuildService.registerIfAbsent(project)
-        BuildFinishBuildService.registerIfAbsent(project, buildUidService, pluginVersion)
+
     }
 
     private fun addKotlinCompilerConfiguration(project: Project) {
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/StatisticsBuildFlowManager.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/StatisticsBuildFlowManager.kt
index 50559fa..5e13073 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/StatisticsBuildFlowManager.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/StatisticsBuildFlowManager.kt
@@ -14,6 +14,7 @@
 import org.gradle.api.tasks.Input
 import org.jetbrains.kotlin.gradle.fus.BuildUidService
 import org.jetbrains.kotlin.gradle.internal.report.BuildScanApi
+import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFinishBuildService
 import org.jetbrains.kotlin.gradle.plugin.statistics.FlowActionBuildFusService
 import org.jetbrains.kotlin.gradle.plugin.statistics.ConfigurationMetricParameterFlowActionBuildFusService
 import org.jetbrains.kotlin.gradle.plugin.statistics.MetricContainer
@@ -29,20 +30,27 @@
             project.objects.newInstance(StatisticsBuildFlowManager::class.java)
     }
 
-    fun subscribeForBuildResultAndConfigurationTimeMetrics(buildFusServiceProvider: Provider<FlowActionBuildFusService>) {
+    fun subscribeForBuildResultAndConfigurationTimeMetrics(
+        buildFusServiceProvider: Provider<FlowActionBuildFusService>,
+        buildUidServiceProvider: Provider<BuildUidService>,
+        buildFinishBuildService: Provider<BuildFinishBuildService>?
+    ) {
         flowScope.always(
             BuildFinishAndConfigurationTimeMetricsFlowAction::class.java
         ) { spec ->
             spec.parameters.buildFailed.set(flowProviders.buildWorkResult.map { it.failure.isPresent })
             spec.parameters.configurationTimeMetrics.addAll(buildFusServiceProvider.get().getConfigurationTimeMetrics())
+            spec.parameters.buildUidProperty.set(buildUidServiceProvider.map { it.buildId })
+            buildFinishBuildService?.get()
         }
     }
 
-    fun subscribeForBuildResult() {
+    fun subscribeForBuildResult(buildUidServiceProvider: Provider<BuildUidService>) {
         flowScope.always(
             BuildFinishFlowAction::class.java
         ) { spec ->
             spec.parameters.buildFailed.set(flowProviders.buildWorkResult.map { it.failure.isPresent })
+            spec.parameters.buildUidProperty.set(buildUidServiceProvider.map { it.buildId })
         }
     }
 
@@ -74,17 +82,18 @@
         @get:ServiceReference
         val buildFusServiceProperty: Property<ConfigurationMetricParameterFlowActionBuildFusService>
 
-        @get:ServiceReference
-        val buildUidServiceProperty: Property<BuildUidService?>
+        @get:Input
+        val buildUidProperty: Property<String>
 
         @get:Input
         val buildFailed: Property<Boolean>
     }
 
     override fun execute(parameters: Parameters) {
+//        val buildId = parameters.buildUidProperty.orNull ?: return
         parameters.buildFusServiceProperty.orNull?.recordBuildFinished(
             parameters.buildFailed.get(),
-            parameters.buildUidServiceProperty.orNull?.buildId ?: "unknown_id",
+//            buildId,
             parameters.buildFusServiceProperty.orNull?.parameters?.configurationMetrics?.orNull ?: emptyList()
         )
     }
@@ -95,8 +104,8 @@
         @get:ServiceReference
         val buildFusServiceProperty: Property<FlowActionBuildFusService>
 
-        @get:ServiceReference
-        val buildUidServiceProperty: Property<BuildUidService?>
+        @get:Input
+        val buildUidProperty: Property<String?>
 
         @get:Input
         val buildFailed: Property<Boolean>
@@ -106,9 +115,10 @@
     }
 
     override fun execute(parameters: Parameters) {
+//        val buildId = parameters.buildUidProperty.orNull ?: return
         parameters.buildFusServiceProperty.orNull?.recordBuildFinished(
             parameters.buildFailed.get(),
-            parameters.buildUidServiceProperty.orNull?.buildId ?: "unknown_id",
+//            buildId,
             parameters.configurationTimeMetrics.get()
         )
     }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt
index 2b31fe1..717f60c 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFinishBuildService.kt
@@ -13,11 +13,15 @@
 import org.gradle.api.provider.Provider
 import org.gradle.api.services.BuildService
 import org.gradle.api.services.BuildServiceParameters
+import org.gradle.tooling.events.FinishEvent
+import org.gradle.tooling.events.OperationCompletionListener
 import org.gradle.util.GradleVersion
 import org.jetbrains.kotlin.gradle.fus.BuildUidService
 import org.jetbrains.kotlin.gradle.logging.Errors
 import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
+import org.jetbrains.kotlin.gradle.logging.kotlinDebug
 import org.jetbrains.kotlin.gradle.logging.reportToIde
+import org.jetbrains.kotlin.gradle.plugin.BuildEventsListenerRegistryHolder
 import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
 import org.jetbrains.kotlin.gradle.utils.kotlinErrorsDir
 import org.jetbrains.kotlin.statistics.fileloggers.MetricsContainer
@@ -25,11 +29,15 @@
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.String
 
-internal abstract class BuildFinishBuildService : BuildService<BuildFinishBuildService.Parameters>, AutoCloseable {
+abstract class BuildFinishBuildService : BuildService<BuildFinishBuildService.Parameters>, AutoCloseable, OperationCompletionListener {
     protected val buildId = parameters.buildId.get()
     private val log = Logging.getLogger(this.javaClass)
     private val errorWasReported = AtomicBoolean(false)
 
+    init {
+        log.kotlinDebug("Initialize build service $serviceName: class \"${this.javaClass.simpleName}\", build \"$buildId\"")
+    }
+
     interface Parameters : BuildServiceParameters {
         val buildId: Property<String>
         val fusReportDirectory: Property<File>
@@ -41,19 +49,19 @@
         private val serviceName =
             "${BuildFinishBuildService::class.java.canonicalName}_${BuildFinishBuildService::class.java.classLoader.hashCode()}"
 
-        fun registerIfAbsent(project: Project, buildUidService: Provider<BuildUidService>, kotlinPluginVersion: String) {
+        fun registerIfAbsent(project: Project, buildUidService: Provider<BuildUidService>, kotlinPluginVersion: String): Provider<BuildFinishBuildService>? {
             if (!project.buildServiceShouldBeCreated) {
-                return
+                return null
             }
 
-            if (project.gradle.sharedServices.registrations.findByName(serviceName) != null) {
-                return
+            project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
+                @Suppress("UNCHECKED_CAST")
+                return (it.service as Provider<BuildFinishBuildService>)
             }
 
             val reportDir = project.getFusDirectoryFromPropertyService()
-            val useFlowAction = GradleVersion.current().baseVersion >= GradleVersion.version("8.1")
 
-            project.gradle.sharedServices.registerIfAbsent(serviceName, BuildFinishBuildService::class.java) { spec ->
+            val service = project.gradle.sharedServices.registerIfAbsent(serviceName, BuildFinishBuildService::class.java) { spec ->
                 spec.parameters.buildId.value(buildUidService.map { it.buildId }).disallowChanges()
                 spec.parameters.fusReportDirectory.value(reportDir).disallowChanges()
                 spec.parameters.errorDirs.add(project.kotlinErrorsDir)
@@ -62,11 +70,16 @@
                 }
                 spec.parameters.errorDirs.disallowChanges()
                 spec.parameters.kotlinVersion.value(kotlinPluginVersion).disallowChanges()
-            }.get()
-
-            if (useFlowAction) { //otherwise CloseActionBuildFusService.close() will aggregate fus metrics into single file
-                BuildFinishFlowProviderManager.getInstance(project).subscribeForBuildResults()
             }
+
+//            if (useFlowAction) { //otherwise, CloseActionBuildFusService.close() will aggregate fus metrics into a single file
+//                BuildFinishFlowProviderManager.getInstance(project).subscribeForBuildResults()
+//            }
+
+            //ensure BuildFinishBuildService is created at the same time as BuildFusService
+            BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(service)
+
+            return service
         }
 
         internal fun collectAllFusReportsIntoOne(
@@ -107,8 +120,13 @@
         }
     }
 
+    override fun onFinish(p0: FinishEvent?) {
+        //Do nothing
+    }
+
     override fun close() {
-        log.debug("Build service $serviceName closed for build $buildId")
+        log.kotlinDebug("Close build service $serviceName: class \"${this.javaClass.simpleName}\", build \"$buildId\"")
+        collectAllFusReportsIntoOne()
     }
 
     private fun File.errorFile() = resolve("errors-$buildId-${System.currentTimeMillis()}.log")
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
index cc726e1..ca7662a 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/BuildFusService.kt
@@ -52,7 +52,7 @@
     protected val buildId = parameters.buildId.get()
 
     init {
-        log.kotlinDebug("Initialize ${this.javaClass.simpleName}")
+        log.kotlinDebug("Initialize build service $serviceName: class \"${this.javaClass.simpleName}\", build \"$buildId\"")
         KotlinBuildStatsBeanService.recordBuildStart(buildId)
     }
 
@@ -95,9 +95,9 @@
             }
 
 
-        fun registerIfAbsent(project: Project, pluginVersion: String, buildUidService: Provider<BuildUidService>) =
+        fun registerIfAbsent(project: Project, pluginVersion: String, buildUidService: Provider<BuildUidService>, buildFinishBuildService: Provider<BuildFinishBuildService>?) =
             if (project.buildServiceShouldBeCreated) {
-                registerIfAbsentImpl(project, pluginVersion, buildUidService).also { serviceProvider ->
+                registerIfAbsentImpl(project, pluginVersion, buildUidService, buildFinishBuildService).also { serviceProvider ->
                     SingleActionPerProject.run(project, UsesBuildFusService::class.java.name) {
                         project.tasks.withType<UsesBuildFusService>().configureEach { task ->
                             task.buildFusService.value(serviceProvider).disallowChanges()
@@ -120,6 +120,7 @@
             project: Project,
             pluginVersion: String,
             buildUidService: Provider<BuildUidService>,
+            buildFinishBuildService: Provider<BuildFinishBuildService>?
         ): Provider<out BuildFusService<out Parameters>> {
 
             val isProjectIsolationEnabled = project.isProjectIsolationEnabled
@@ -155,7 +156,7 @@
             // when this OperationCompletionListener is called services can be already closed for Gradle 8,
             // so there is a change that no VariantImplementationFactory will be found
             val fusService = if (GradleVersion.current().baseVersion >= GradleVersion.version("8.9")) {
-                FlowActionBuildFusService.registerIfAbsentImpl(project, buildUidService, generalConfigurationMetricsProvider)
+                FlowActionBuildFusService.registerIfAbsentImpl(project, buildUidService, generalConfigurationMetricsProvider, buildFinishBuildService)
             } else if (GradleVersion.current().baseVersion >= GradleVersion.version("8.1")) {
                 ConfigurationMetricParameterFlowActionBuildFusService.registerIfAbsentImpl(
                     project,
@@ -196,7 +197,11 @@
 
     override fun close() {
         KotlinBuildStatsBeanService.closeServices()
-        log.kotlinDebug("Close ${this.javaClass.simpleName}")
+        log.kotlinDebug("Close build service $serviceName: class \"${this.javaClass.simpleName}\", build \"$buildId\"")
+    }
+
+    internal fun recordBuildFinished(buildFailed: Boolean, configurationMetrics: List<MetricContainer>) {
+        recordBuildFinished(buildFailed, buildId, configurationMetrics)
     }
 
     internal fun recordBuildFinished(buildFailed: Boolean, buildId: String, configurationMetrics: List<MetricContainer>) {
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ConfigurationMetricParameterFlowActionBuildFusService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ConfigurationMetricParameterFlowActionBuildFusService.kt
index d516131..e72955d 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ConfigurationMetricParameterFlowActionBuildFusService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/ConfigurationMetricParameterFlowActionBuildFusService.kt
@@ -29,7 +29,7 @@
                 //init value to avoid `java.lang.IllegalStateException: GradleScopeServices has been closed` exception on close
                 spec.parameters.configurationMetrics.add(MetricContainer())
             }.also {
-                StatisticsBuildFlowManager.getInstance(project).subscribeForBuildResult()
+                StatisticsBuildFlowManager.getInstance(project).subscribeForBuildResult(buildUidService)
             }
         }
     }
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/FlowActionBuildFusService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/FlowActionBuildFusService.kt
index 5d57354..2709a28 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/FlowActionBuildFusService.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/FlowActionBuildFusService.kt
@@ -31,6 +31,7 @@
             project: Project,
             buildUidService: Provider<BuildUidService>,
             generalConfigurationMetricsProvider: Provider<MetricContainer>,
+            buildFinishBuildService: Provider<BuildFinishBuildService>?
         ): Provider<out BuildFusService<out Parameters>> {
             return project.gradle.sharedServices.registerIfAbsent(serviceName, FlowActionBuildFusService::class.java) { spec ->
                 spec.parameters.generalConfigurationMetrics.set(generalConfigurationMetricsProvider)
@@ -38,7 +39,7 @@
                 spec.parameters.buildStatisticsConfiguration.set(KotlinBuildStatsConfiguration(project))
                 spec.parameters.buildId.value(buildUidService.map { it.buildId }).disallowChanges()
             }.also { buildService ->
-                StatisticsBuildFlowManager.getInstance(project).subscribeForBuildResultAndConfigurationTimeMetrics(buildService)
+                StatisticsBuildFlowManager.getInstance(project).subscribeForBuildResultAndConfigurationTimeMetrics(buildService, buildUidService, buildFinishBuildService)
             }
         }
     }