Repository: square/anvil
Issue: Fails when using gradle worker api

Using the Gradle worker api, each worker gets it's own classpath, so trying to run Hephaestus fails with the following error message.

```
Cannot set the value of task ':some:module:compileReleaseKotlinCheckIncrementalCompilationHephaestus' property 'incrementalSignal' of type com.squareup.hephaestus.plugin.IncrementalSignal using a provider of type com.squareup.hephaestus.plugin.IncrementalSignal.
```

This is in a project with kapt using the worker API, and Room.

I've attempted to implement this in my [branch here](https://github.com/rharter/hephaestus/tree/rh/worker_registry), but need to take off for the day.

Changed files:
gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/IncrementalSignal.kt
gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/DisableIncrementalCompilationTask.kt
gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/HephaestusPlugin.kt

diff --git a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/DisableIncrementalCompilationTask.kt b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/DisableIncrementalCompilationTask.kt
index 14335bd..3a4b0fd 100644
--- a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/DisableIncrementalCompilationTask.kt
+++ b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/DisableIncrementalCompilationTask.kt
@@ -4,10 +4,8 @@ package com.squareup.hephaestus.plugin
 
 import org.gradle.api.DefaultTask
 import org.gradle.api.file.ConfigurableFileCollection
-import org.gradle.api.provider.Property
 import org.gradle.api.tasks.Classpath
 import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Internal
 import org.gradle.api.tasks.Optional
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
@@ -25,14 +23,13 @@ abstract class DisableIncrementalCompilationTask : DefaultTask() {
   @get:OutputFile @get:Optional
   val someFile = File(project.buildDir, "not-existing-file-because-gradle-needs-an-output")
 
-  @get:Internal
-  abstract val incrementalSignal: Property<IncrementalSignal>
-
-  private val projectPath = project.path
+  private val serviceKey: IncrementalSignalServiceKey = IncrementalSignalServiceKey(project.path)
 
   @TaskAction fun toggleFlag() {
     // Disable incremental compilation if something in the classpath changed. If nothing has changed
     // in the classpath, then this task wouldn't run at all and be skipped.
-    incrementalSignal.get().incremental[projectPath] = false
+    useIncrementalSignalService(serviceKey) {
+      it.incremental = false
+    }
   }
 }
diff --git a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/HephaestusPlugin.kt b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/HephaestusPlugin.kt
index 629b804..ae017b0 100644
--- a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/HephaestusPlugin.kt
+++ b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/HephaestusPlugin.kt
@@ -12,7 +12,6 @@ import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.plugins.AppliedPlugin
 import org.gradle.api.plugins.PluginManager
-import org.gradle.api.provider.Provider
 import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
 import org.jetbrains.kotlin.gradle.plugin.KaptExtension
 import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
@@ -115,26 +114,26 @@ open class HephaestusPlugin : Plugin<Project> {
     // compile task. If the plugin classpath changed, then DisableIncrementalCompilationTask sets
     // the signal to false.
     @Suppress("UnstableApiUsage")
-    val incrementalSignal = project.gradle.sharedServices
-        .registerIfAbsent("incrementalSignal", IncrementalSignal::class.java) { }
+    val incrementalSignalServiceKey = registerIncrementalSignalBuildService(project)
 
     if (isAndroidProject) {
       project.androidVariantsConfigure { variant ->
         val compileTaskName = "compile${variant.name.capitalize(US)}Kotlin"
-        disableIncrementalCompilationAction(project, incrementalSignal, compileTaskName)
+        disableIncrementalCompilationAction(project, incrementalSignalServiceKey, compileTaskName)
       }
     } else {
       // The Java plugin has two Kotlin tasks we care about: compileKotlin and compileTestKotlin.
-      disableIncrementalCompilationAction(project, incrementalSignal, "compileKotlin")
-      disableIncrementalCompilationAction(project, incrementalSignal, "compileTestKotlin")
+      disableIncrementalCompilationAction(project, incrementalSignalServiceKey, "compileKotlin")
+      disableIncrementalCompilationAction(project, incrementalSignalServiceKey, "compileTestKotlin")
     }
   }
 
   fun disableIncrementalCompilationAction(
     project: Project,
-    incrementalSignal: Provider<IncrementalSignal>,
+    incrementalSignalServiceKey: IncrementalSignalServiceKey,
     compileTaskName: String
   ) {
+
     // Disable incremental compilation, if the compiler plugin dependency isn't up-to-date.
     // This will trigger a full compilation of a module using Hephaestus even though its
     // source files might not have changed. This workaround is necessary, otherwise
@@ -146,16 +145,12 @@ open class HephaestusPlugin : Plugin<Project> {
       task.pluginClasspath.from(
           project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME)
       )
-      task.incrementalSignal.set(incrementalSignal)
     }
 
     project.tasks.named(compileTaskName, KotlinCompile::class.java) { compileTask ->
       compileTask.dependsOn(disableIncrementalCompilationTaskProvider)
     }
 
-    // We avoid a reference to the project in the doFirst.
-    val projectPath = project.path
-
     // If we merge the block below and the block above, it looks like
     // the kotlin compiler is generating byte code for
     // disableIncrementalCompilationTaskProvider to be visible from the doFirst block
@@ -164,7 +159,7 @@ open class HephaestusPlugin : Plugin<Project> {
       compileTask.doFirst {
         // If the signal is set, then the plugin classpath changed. Apply the setting that
         // DisableIncrementalCompilationTask requested.
-        val incremental = incrementalSignal.get().incremental[projectPath]
+        val incremental = getIncrementalSignalService(incrementalSignalServiceKey).incremental
         if (incremental != null) {
           compileTask.incremental = incremental
         }
diff --git a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/IncrementalSignal.kt b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/IncrementalSignal.kt
index a938c24..aa86b28 100644
--- a/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/IncrementalSignal.kt
+++ b/gradle-plugin/src/main/java/com/squareup/hephaestus/plugin/IncrementalSignal.kt
@@ -1,9 +1,78 @@
+@file:Suppress("UnstableApiUsage")
+
 package com.squareup.hephaestus.plugin
 
+import com.android.build.gradle.internal.workeractions.WorkerActionServiceRegistry
+import com.android.ide.common.process.ProcessException
+import com.google.common.io.Closer
+import org.gradle.api.Project
 import org.gradle.api.services.BuildService
 import org.gradle.api.services.BuildServiceParameters.None
+import java.io.IOException
+import java.util.UUID
+
+/** Used to get unique build service name. Each class loader will initialize it's onw version. */
+private val HEPHAESTUS_INCREMENTAL_SIGNAL_BUILD_SERVICE_NAME =
+    "hephaestus-incremental-signal-build-service" + UUID.randomUUID().toString()
+
+/**
+ * Registers incremental signal build service and returns a service key that can be used to access
+ * the service later.
+ */
+fun registerIncrementalSignalBuildService(project: Project): IncrementalSignalServiceKey {
+  val buildServiceProvider = project.gradle.sharedServices.registerIfAbsent(
+      HEPHAESTUS_INCREMENTAL_SIGNAL_BUILD_SERVICE_NAME,
+      IncrementalSignalBuildService::class.java
+  ) {}
+  return buildServiceProvider.get().registerIncrementalSignalService(project.path)
+}
+
+/**
+ * Service registry used to store IncrementalSignal services so they are accessible from the worker
+ * actions.
+ */
+var incrementalSignalServiceRegistry: WorkerActionServiceRegistry = WorkerActionServiceRegistry()
+
+/** Intended for use from worker actions. */
+@Throws(ProcessException::class, IOException::class)
+fun useIncrementalSignalService(
+    incrementalSignalServiceKey: IncrementalSignalServiceKey,
+    serviceRegistry: WorkerActionServiceRegistry = incrementalSignalServiceRegistry,
+    block: (IncrementalSignalService) -> Unit
+) = serviceRegistry.getService(incrementalSignalServiceKey).service.let(block)
+
+fun getIncrementalSignalService(
+    incrementalSignalServiceKey: IncrementalSignalServiceKey,
+    serviceRegistry: WorkerActionServiceRegistry = incrementalSignalServiceRegistry
+): IncrementalSignalService = serviceRegistry.getService(incrementalSignalServiceKey).service
+
+data class IncrementalSignalServiceKey(val projectPath: String)
+  : WorkerActionServiceRegistry.ServiceKey<IncrementalSignalService> {
+  override val type: Class<IncrementalSignalService> get() = IncrementalSignalService::class.java
+}
+
+data class IncrementalSignalService(var incremental: Boolean? = null)
 
 /** This signal is used to share state between the task above and Kotlin compile tasks. */
-abstract class IncrementalSignal : BuildService<None> {
-  val incremental = mutableMapOf<String, Boolean?>()
+abstract class IncrementalSignalBuildService : BuildService<None>, AutoCloseable {
+  private val registeredServices = mutableSetOf<IncrementalSignalServiceKey>()
+  private val closer = Closer.create()
+
+  @Synchronized
+  fun registerIncrementalSignalService(
+      projectPath: String,
+      serviceRegistry: WorkerActionServiceRegistry = incrementalSignalServiceRegistry
+  ): IncrementalSignalServiceKey {
+    val key = IncrementalSignalServiceKey(projectPath)
+
+    if (registeredServices.add(key)) {
+      closer.register(serviceRegistry.registerServiceAsCloseable(key, IncrementalSignalService()))
+    }
+
+    return key
+  }
+
+  override fun close() {
+    closer.close()
+  }
 }