How do I do this in a configuration-cache-friendly...
# configuration-cache
t
How do I do this in a configuration-cache-friendly way?
Copy code
tasks.named("a").configure {
    doLast {
        if (tasks.named("b").map { it.state.failure != null }.get()) {
            println("b failed")
        }
    }
}
It fails because
tasks
pulls in an implicit
Project
. I tried pulling a TaskProvider through the boundary, but then I get:
Copy code
1 problem was found storing the configuration cache.
- Task `:a` of type `A`: cannot serialize object of type 'B', a subtype of 'org.gradle.api.Task', as these are not supported with the configuration cache.
I also tried this, but
TaskContainer
is not an injectable service:
Copy code
abstract class TaskFailedValueSource : ValueSource<Boolean, TaskFailedValueSource.Params> {
	@get:Inject abstract val taskContainer: TaskContainer

	interface Params : ValueSourceParameters {

		val taskName: Property<String>
	}

	override fun obtain(): Boolean =
		taskContainer.getByName(parameters.taskName.get()).state.failure != null
}
Am I missing a very obvious solution? Is it not possible?
Oh, I also tried putting the whole Provider<Boolean> outside, but then it gets resolved during CC-save, which is too early, the value is always
false
at that point.
v
I think what you need is a shared build service that you also register as operation completion listener. Then you can record the outcome of B in the service and query it from the service in A.
t
Pff, makes sense, thank you! But at the same time, it doesn't, that feels way too complex for a simple task like this.
🤷‍♂️ 1
Thank you again, this seems to work:
Copy code
// Note to reader: both registerIfAbsent and onTaskCompletion can be called many times, they're idempotent.
	val taskStateService = project.gradle.sharedServices
		.registerIfAbsent("taskState", TaskStateService::class.java) { }
	serviceOf<BuildEventsListenerRegistry>().onTaskCompletion(taskStateService)
	val bTaskPath = "${project.path}:b"

	tasks.named("a").configure {
		usesService(taskStateService)
		doLast {
			if (taskStateService.get().isFailed(bTaskPath)) {
				println("b failed")
			}
		}
	}

abstract class TaskStateService : BuildService<BuildServiceParameters.None>, OperationCompletionListener {
	private val state: MutableMap<String, TaskFinishEvent> = mutableMapOf()

	fun isFailed(taskPath: String): Boolean {
		val state = state[taskPath] ?: error("Task ${taskPath} is not complete yet.")
		return state.result is TaskFailureResult
	}

	override fun onFinish(event: FinishEvent) {
		if (event is TaskFinishEvent) {
			state[event.descriptor.taskPath] = event
		}
	}
}
v
Great, but be aware that
serviceOf
is internal API.
t
Yeah, I know, I'm too lazy to do that interface + objects dance for a hack that hopefully doesn't stay long, or if it does it's still an end project, not a plugin.
👌 1
For fun: I used this while integrating a Compose screenshot testing lib with version 0.0.1-alpha01, can't get lower than that I guess with a named version.