Hmm. But these are interfaces and all the concrete implementations I find in Gradle source are internal, not public apis and even these require as constructor parameters other objects from the internal api.
Is there a concrete example that shows how all this might be set up?
but I’d like to specify as the first parameter, the archive to which this code (which will be in a plugin) will belong (‘my archive’, i.o.w, what you’d get from getResourceAsStream() ) but I don’t see how to do that.
OK getting around to doing this, but I can’t actually get the task to execute. --info level output tells me that this is because there are no source files. This is indeed the case but I thought this is what TextResource was supposed to provide.
Your problem is you should not be extending Copy and specifically, you should not be overriding the copy() method. The issue here is that copy() is the task action. What you are doing is overriding this method with what is configuration logic and therefore the copy operation actually never happens.
What I would instead suggest is that you create a task of type Copy and simply configure it as such. Extending task types should only be done when you want to change their behavior. In this case that isn’t what you want. You want a copy task, you just want it configured a certain way.
One thing I’ve never fully understood is the difference between extending a task class and declaring a task with the type of a task class. Probably this is because most of the gradle user guide documentation is geared toward writers of build scripts as opposed to writers of plugins. There is a distinct lack of examples of doing what you suggest in a groovy class inside a plugin jar. I’ve been bitten over and over by this.
In any case, I have now tried still keeping my classes (which do add some important default behavior) but following your suggestion of not trying to do the configuration in a copy() override method. This succeeds, but I had to explicitly define the task’s destinationDir and then not use an into{} closure (which isn’t necessary once destinationDir is defined) in my configuration.
The pattern is pretty simple, put this inside a plugin.
public class MyPlugin implements Plugin<Project> {
public void apply(Project project) {
project.tasks.create("myCopyTask", Copy) {
// put custom task configuration here
}
}
}
Thanks, that’s simple enough. I was trying to avoid creating a separate plugin to do this task and creating the task in another preexisting plugin, but I’m having another buildshiip-only issue doing it my way, and I will now try to do this as you suggest. I will need two plugins to capture the two different flavors of this functionality, but that’s probably the best way to go.
So let me try to cast this remark of yours into a rule-of-thumb:
If you want to to create a version of an existing task for a plugin, don’t extend it via subclassing. Instead create it in a new plugin and apply that plugin. Inside the apply() method, do any configuration necessary.
That about right?
One minor quibble: this doesn’t work outside of build scripts: project.tasks.create("myCopyTask", Copy)
Instead, I had to import the Copy class, and then do project.tasks.create("myCopyTask", Copy.class)
Alas, my earlier thoughts that I’d solved this beast proved premature. Even though I am now building in accordance to the rule of thumb we laid down, i.e. do it in a plugin, I still don’t find any reliable way of getting gradle to know that there is a source file to be processed and therefore not to skip the copy task. Here is my latest code iteration:
Initially I had the call to project.resources.text.fromString()...asFile() in the task creation block and that didn’t work, logically enough, so I moved it where it is now, before the task creation.
That initially worked one time but subsequently did not., always because gradle thinks the task has no source files. How can I override this behavior or tell gradle “trust me, source file will be there when you need it”? There ought to be some way to do this.
Ok, the mysterious part of this has been solved. It works when a clean is not done as part of the build but it fails when a clean is done. In retrospect, this is obvious, as project.resources.text.fromString()...asFile() creates this file under ./build.
So how to get around this? If I create the file during the apply phase, before task creation, a clean will wipe it out and the task will see no source files. How can I create it as the first post-clean step in the build?
And then if I get tricky by putting the file creation in a doFirst{} block, I get null pointer exceptions when the task is evaluated.
Ok, so maybe I need to do everything in tasks. Then use the task outputs to avoid specifying variables that are initally null. But this doesn’t work either:
The central problem with the previous attempt seems to me to be that project.resources.text.fromString(...).asFile() creates a file whose name cannot be known until it is created, thus making it impossible to specify such a file as a task output in advance.
This insight led me to the solution, which is not to base my task on Copy at all. It is Copy that is demanding that there be an input file, in order to execute. Since, in my scenario there is no input file, Copy is an inappropriate choice, instead, this much simpler solution works:
It is necessary to know the path of the file we are creating in order to declare it as output, using outputs.file() (which was the answer to my previous question.). The DefaultTask does not do this automatically, as I presume Copy does. Now, the task executes.
While on this subject, I note the arguably ugly call to File.renameTo() in my solution. It would be nice-to-have a TextResource.asFile(File) method to handle this for us.