abstract class MuzzleExtension @Inject constructor(private val objectFactory: ObjectFactory) {
// ...
fun pass(action: Action<in MuzzleDirective>) {
val pass = objectFactory.newInstance(MuzzleDirective::class.java)
action.execute(pass)
postConstruct(pass)
pass.assertPass = true
directives.add(pass)
}
//...
}
open class MuzzleDirective {
// var name:String? = null
var additionalDeps: MutableList<String> = ArrayList()
// ... no gradle properties
}
the directive is later added to a task input
@get:Input
@get:Optional
val muzzleDirective: Property<MuzzleDirective> = objects.property()
And when running the task I noticed errors like that
* What went wrong:
Execution failed for task ':dd-java-agent:instrumentation:jetty-9:muzzle-AssertFail-org.eclipse.jetty-jetty-server-8.2.0.v20160908-between-10-and-12'.
> Cannot fingerprint input property 'muzzleDirective': value 'fail org.eclipse.jetty:jetty-server:8.2.0.v20160908' cannot be serialized.
The error is pretty straightforward especially when looking at the stacktrace:
Caused by: org.gradle.internal.snapshot.ValueSnapshottingException: Could not serialize value of type MuzzleDirective
at ...
... 72 more
Caused by: java.io.NotSerializableException: pkg.MuzzleDirective
So I just made the type implements Serializable and everything went smooth after.
open class MuzzleDirective : Serializable {
// fields
}
My question is whether it’s needed to make it so ?
I have an intuition that I didn’t work much yet, is that implementing Serializable is necessary when fields are using “raw” types like Boolean , String, List<String> ; while it might not be necessary when the files use “property wrappers” like Property<String>, SetProperty<String>, etc? But I could be completely wrong since I didn’t tested that.
I would like to create an MVCE, but it’s a bit of a big project, big plugin. Right the workaround of making this serializable seems to work. Also, I don’t want to make this consume your time. It’s probably not worth it even though it’d be nice to understand the cause in my setup.
But from the stacktrace the MuzzleDirective is passed to a JDK’s ObjectOutputStream which needs the object to be Serializable. Apparently this mechanism is the fallback, when everything else didn’t match/work in AbstractValueProcessor. Maybe no ValueSnapshotterSerializerRegistry could work ?
Using Gradle 8.14.3, and unsure Gradle 9 change things on this matter.
...
Caused by: org.gradle.internal.execution.InputFingerprinter$InputFingerprintingException: Cannot fingerprint input property 'muzzleDirective': value 'pass io.dropwizard:dropwizard-client:[,3)' cannot be serialized.
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputProperty(DefaultInputFingerprinter.java:114)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:315)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:63)
...
Caused by: org.gradle.internal.snapshot.ValueSnapshottingException: Could not serialize value of type MuzzleDirective
at org.gradle.internal.snapshot.impl.AbstractValueProcessor.newValueSerializationException(AbstractValueProcessor.java:223)
at org.gradle.internal.snapshot.impl.AbstractValueProcessor.javaSerialized(AbstractValueProcessor.java:214)
at org.gradle.internal.snapshot.impl.AbstractValueProcessor.javaSerialization(AbstractValueProcessor.java:205)
at org.gradle.internal.snapshot.impl.AbstractValueProcessor.processValue(AbstractValueProcessor.java:119)
at org.gradle.internal.snapshot.impl.DefaultValueSnapshotter.snapshot(DefaultValueSnapshotter.java:47)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputProperty(DefaultInputFingerprinter.java:107)
... 103 more
Caused by: java.io.NotSerializableException: datadog.gradle.plugin.muzzle.MuzzleDirective_Decorated
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.gradle.internal.snapshot.impl.AbstractValueProcessor.javaSerialized(AbstractValueProcessor.java:211)
... 107 more
Oh, wait, my test was bad.
My task did not have outputs, so the inputs were never fingerprinted as without output the task cannot be up-to-date.
Adding outputs.upToDateWhen { true } makes it fail:
open class MuzzleDirective {
var additionalDeps: MutableList<String> = ArrayList()
}
abstract class Foo : DefaultTask() {
@get:Input
@get:Optional
abstract val muzzleDirective: Property<MuzzleDirective>
init {
outputs.upToDateWhen { true }
}
@TaskAction
fun execute() {
muzzleDirective.get().additionalDeps.forEach { println("foo: $it") }
}
}
val foo by tasks.registering(Foo::class) {
muzzleDirective = objects.newInstance<MuzzleDirective>().apply {
additionalDeps.add("foo")
}
}
And yes, if Gradle needs to serialize something and cannot, the last try is Java Serialization.
So you can either make sure Gradle does not need to fall back to Java Serialization, or support Java Serialization.
OK that explains the behavior. This part is not yet migrated to gradle properties and needs to rely on Java serialization. I don’t know if I’ve seen that in the javadoc or userguide, but that is useful to know.
A mutable managed property is declared using a getter method of type Property<T> , where T can be any serializable type or a fully managed Gradle type. The property must not have any setter methods.