This message was deleted.
# plugin-development
s
This message was deleted.
m
I could also do the other way around and always write inputs to a
File
but that feels a bit off too
t
I tried doing this using
providers.fileContents
but gradle seemed to lose track of the task dependency. This was with gradle 7.6 so maybe there was a bug? but it seemed somewhat reasonable to me...
👀 1
m
There are only
Copy code
FileContents fileContents(RegularFile file);
    FileContents fileContents(Provider<RegularFile> file);
so it's only for files? Or do you mean implement
FileContents
directly? Without going through
providers
?
t
project.providers.fileContents(task.flatMap { it.output })
m
This turns a
Provider<RegularFile>
into a
Provider<String>
or
Provider<ByteArray>
right? But what I want is more a property type theat's neither
Property<RegularFile>
nor
Property<String>
. Ideally something like:
Copy code
// Serializable to serialize to CC
interface MyInput: Serializable {
    fun writeContentsTo(outputStream: OutputStream) {
        // Write the actual byte contents
        // If MyInput is backed by a file, reads the file
        // If MyInput is backed by a String, reads the chars array
    }
}

open class MyTask: DefaultTask() {
    val myProp: Property<MyInput>
}
So that the serialization function used by CC isn't the same as the one used by fingerprinting.
t
right. What I was doing was just deciding the input should be a string, because it could be either a string or a file
m
But then CC is going to serialize the whole string, right? If the input is 100MB I now have 100MB in the CC?
👍 1
Ideally I could configure the task with either: file input:
Copy code
task.register("myTask", MyTask::class.java) {
  myProp.set(task.flatMap { it.output.map { FileInput(it.asFile) } }
}
or string input:
Copy code
task.register("myTask", MyTask::class.java) {
  myProp.set(InMemoryInput("someInput"))
}
And if the input is "File" backed, only the file path is saved to CC. And if it's "Memory" backed then serialize everything
I have no idea if this is how things work actually. But this is what I would expect
t
why might you have either a file or a string?
m
Working on some boilerplate generator and I don't know how users are going to use/wire the generated code
I could force users into choosing how their inputs are going to be modeled but this feels something that could be abstracted away
t
what version of gradle do you support?
m
At this point, it could be 8.7 😄
t
right. I was going to suggest
Provider.filter()
to check if a file exists and if not, use something else, but then CC still has to read the whole file I think (based on a conversation I saw in another channel)
👍 1
v
if the input is "File" backed, only the file path is saved to CC. And if it's "Memory" backed then serialize everything
Maybe make the property be
Property<String>
and have some setters. If a file is set, set it to the
file:///
uri, if a string is set, set it to the `String`and require it does not start with
file:
, or set it to
string:<the given string>
. Then at execution time you can use the string or read the file.
m
Issue with that is that fingerprinting will only use the path instead of the whole file content
in the file case
Looks like it might be possible to contribute serializers with injected services or so but that seems quite involved, not even sure this is actually possible
I am this down the rabbit hole and it's getting late here so I think I'll just ask the users to chose
String
or
File
v
fingerprinting
of what? For up-to-date / build cache? You probably need additionally an input file property then for that?
1
But yeah, it is probably cleanest and easiest you just not try to squeeze it into one property.
Or maybe you can somehow have a type with a "get me the value" method as
@Nested
property type and then two subclasses, one with a file property, one with a string property?
No idea whether such a construct could work
m
then two subclasses, one with a file property, one with a string property?
This is exactly what I had in mind but easier said than done 😅
v
You think so? This seems to work fine so far:
Copy code
interface TheInput {
    fun retrieveInput(): String
}
abstract class FileInput : TheInput {
    @get:InputFile
    abstract val file: RegularFileProperty
    override fun retrieveInput() = file.get().asFile.readText()
}
abstract class StringInput : TheInput {
    @get:Input
    abstract val string: Property<String>
    override fun retrieveInput() = string.get()
}
abstract class Foo : DefaultTask() {
    @get:Nested
    abstract val input: Property<TheInput>

    init {
        outputs.upToDateWhen { true }
    }

    @TaskAction
    fun foo() = println(input.get().retrieveInput())
}
val foo by tasks.registering(Foo::class) {
    input = objects.newInstance<StringInput>().apply {
        string = "foo"
    }
}
val bar by tasks.registering(Foo::class) {
    input = objects.newInstance<FileInput>().apply {
        file = file("bar.txt")
    }
}
Of course for the
objects.newInstance
stuff some convenience method or setter or whatever can be used instead. The tasks are up-to-date if the input string and the content of the input file do not change and out of date if for example the file content changed. And the CC is always reused, whether the file content changes or not.
💙 1
m
Indeed 👍 Clear and simple thank you
I somewhat insisted on keeping the task up-to-date if the wiring changed from a String to a file with the same contents but that's something I can certainly live without
v
Yeah, for that I guess you either have to write the string to a file or live with the file contents be present in the CC and invalidating it if changed
👍 1