I’m trying to write a custom task that merges objects from multiple files. Each file contains a number of objects, and when configuring the task I want to be able to specify (1) what files to retrieve the objects from, and (2) a function that processes the retrieved objects.
However, I’m unsure how best to go about writing a task like this, and my current attempt seems to fail with a serialization error when trying to determine if the task is up-to-date.
Below is a simple proof of concept build.gradle.kts
outlining the general idea of the task.
data class Data(
var name: String,
var value: String,
var num: Int
)
open class MyTask : DefaultTask() {
// map of lists of Data; normally each list is a separate JSON file
@get:Internal
val sourceData = mapOf(
"test" to listOf(
Data("hello", "value1", 1),
Data("test", "value2", 10)
),
"test2" to listOf(
Data("testing", "value3", 5)
),
"test3" to listOf(
Data("testing more", "value4", 5)
)
)
@get:Input
val dataSpecifiers = mutableListOf<Pair<String, Data.() -> Unit>>()
@get:Internal
lateinit var data: List<Data>
// dummy output to trigger gradle's up-to-date check
@get:OutputDirectory
val out = project.layout.buildDirectory
fun addData(source: String, action: Data.() -> Unit) {
dataSpecifiers.add(Pair(source, action))
}
@TaskAction
fun run() {
// retrieve the specified data objects and process them
// using the provided lambdas
data = dataSpecifiers.flatMap { (source, action) ->
sourceData[source]?.map {
it.action()
it
} ?: listOf()
}
// do things with data
println(data)
}
}
val myTask by tasks.registering(MyTask::class) {
addData("test2") {
if (value == "value1") {
num *= 5
}
}
addData("test3") {
}
}
Running this task once will print the processed data as expected, and running it again will result in Gradle correctly detecting it as up-to-date. Further, if I change num *= 5
to e.g. num *= 10
, Gradle will rerun the task, just like I want.
However, if I change the first argument of addData
from "test"
to e.g. "test2"
, I get the following error:
$ gradle myTask --stacktrace
> Task :myTask FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':myTask'.
> Unable to store input properties for task ':myTask'. Property 'dataSpecifiers' with value '[(test2, Build_gradle.Data.() -> kotlin.Unit), (test3, Build_gradle.Data.() -> kotlin.Unit)]' cannot be serialized.
* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':myTask'.
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:372)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:359)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:352)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:338)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.api.UncheckedIOException: Unable to store input properties for task ':myTask'. Property 'dataSpecifiers' with value '[(test2, Build_gradle.Data.() -> kotlin.Unit), (test3, Build_gradle.Data.() -> kotlin.Unit)]' cannot be serialized.
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$fingerprintInputProperties$2(CaptureStateBeforeExecutionStep.java:178)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.visitInputProperties(ExecuteActionsTaskExecuter.java:307)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.fingerprintInputProperties(CaptureStateBeforeExecutionStep.java:169)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionState(CaptureStateBeforeExecutionStep.java:149)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$captureExecutionStateOp$1(CaptureStateBeforeExecutionStep.java:104)
at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:40)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:37)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionStateOp(CaptureStateBeforeExecutionStep.java:103)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$execute$0(CaptureStateBeforeExecutionStep.java:78)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:78)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:53)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:78)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:78)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:39)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:40)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:28)
at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:33)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:192)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:184)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:114)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
... 23 more
Caused by: org.gradle.internal.snapshot.ValueSnapshottingException: Couldn't populate class kotlin.Pair
at org.gradle.internal.snapshot.impl.SerializedValueSnapshot.populateClass(SerializedValueSnapshot.java:108)
at org.gradle.internal.snapshot.impl.SerializedValueSnapshot.hasSameSerializedValue(SerializedValueSnapshot.java:72)
at org.gradle.internal.snapshot.impl.SerializedValueSnapshot.snapshot(SerializedValueSnapshot.java:53)
at org.gradle.internal.snapshot.impl.DefaultValueSnapshotter.snapshot(DefaultValueSnapshotter.java:59)
at org.gradle.internal.snapshot.impl.ListValueSnapshot.processList(ListValueSnapshot.java:49)
at org.gradle.internal.snapshot.impl.ListValueSnapshot.snapshot(ListValueSnapshot.java:34)
at org.gradle.internal.snapshot.impl.DefaultValueSnapshotter.snapshot(DefaultValueSnapshotter.java:59)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$fingerprintInputProperties$2(CaptureStateBeforeExecutionStep.java:175)
... 56 more
Caused by: java.lang.ClassNotFoundException: Build_gradle$myTask$2$1
at org.gradle.internal.io.ClassLoaderObjectInputStream.resolveClass(ClassLoaderObjectInputStream.java:40)
at org.gradle.internal.snapshot.impl.SerializedValueSnapshot.populateClass(SerializedValueSnapshot.java:106)
... 63 more
* Get more help at https://help.gradle.org
BUILD FAILED in 345ms
1 actionable task: 1 executed
Is there any way to make this work as I’d like?