What is the proper way to handle up-to-date checks?


(Glenn) #1

I have a custom plugin where I define a task to unzip a tool. The tool’s filename varies, so I have a method that finds the package first. This task works fine.

project.task("unzipMyTool") << {
            File zipFile = findZipFile()
            File outputDir = project.file("D:\output")
              if (zipFile == null) {
                println("ERROR: No zipfile found to extract.")
            }
            else {
                project.copy {
                    from project.zipTree(zipFile)
                    into outputDir
                }
            }
}

I thought I could write this a bit simpler, like this:

project.task("unzipMyTool", type: Copy) << {
            File zipFile = findZipFile()
            File outputDir = project.file("D:\output")
              if (zipFile == null) {
                println("ERROR: No zipfile found to extract.")
            }
            else {
                    from project.zipTree(zipFile)
                    into outputDir
            }
}

But this version just prints out “:unzipMyTool UP-TO-DATE”.

As a guess I’ve also tried removing the ‘<<’ but then the task doesn’t work correctly because at configuration time “findZipFile()” may not return the correct result.

Why is the up-to-date behavior different for the second version of my task? Is it wrong to specify a type when you also add actions? What is the cleanest way to write a task like this?

Any advice appreciated.

Glenn


(Benjamin Muschko) #2

If you use the left shift operator, everything defined in your task closure is treated as action code (run during the execution phase of the build lifecycle). Inputs and outputs need to be defined during configuration time. Please have a look at the build lifecycle in the Gradle documentation. I am sure it will make things clearer.

You should be able to condense your code to the following:

task unzipMyTool(type: Copy) {
    from zipTree(findZipFile())
    into "D:\\\output"
}

You can still add actions to an enhanced task (a task that uses a custom task) using the methods doFirst or doLast.


(Glenn) #3

Thanks for the quick response. Actually your suggestion was my first attempt, but that resulted in:

> Neither path nor baseDir may be null or empty string. path=‘null’ basedir=‘D:\somewhere’

So I ended up with my own contraption to get around that :wink:

I simplified the code in my initial post a bit. In my plugin, the “findZipFile” method takes the “project” as parameter. Not sure if that matters.


(Benjamin Muschko) #4

I modified the code example slightly but can’t reproduce the issue you are seeing with Gradle 1.10 (the backslash has to be escaped - didn’t render correctly). What exactly does the method “findZipFile” do? What version of Gradle are you using?


(Glenn) #5

I’m using gradle 1.8. Tried a few more things. The problem is that at configuration time, findZipFile returns null. If I make it return an existing file, then the task succeeds.

There’s another task that will download the latest snapshot version of the tool from a repository. Because that filename varies (it contains a timestamp), I have the findZipFile method to figure out the name of that file, but it can only return something meaningful at execution time, after the zipfile is downloaded.


(Benjamin Muschko) #6

The values for task inputs and outputs need to resolvable during configuration time. If you are downloading the file via HTTP you might be able to make HTTP call to the server, retrieve the file name and use that for the input.


(Glenn) #7

There’s an additional constraint: I don’t want this task to execute every time the buildscript runs (configuration time).

There are a number of Jenkins jobs that will run the part of the buildscript that they’re interested in. The build job will only build artifacts and publish them to a repository. Test jobs will download, setup, run and report what they need to. Different kinds of tests have different needs and tasks. And for example the build job should not execute any of the test tasks.

That means I have to use actions (doLast) for most of my tasks and I loose gradle’s up-to-date support, which is ok, but hard to grasp.

Thanks :wink:


(Benjamin Muschko) #8

I am not sure I understand the first sentence. Only your configuration code will be executed every time the build is executed. Actions are only executed if the task is requested on the command line or if it is a task dependency of the requested task. So the only performance hit you’d see is the HTTP call to your server.

You might want to separate your build code into different build scripts in order to avoid having to execute configuration code of all tasks.


(Glenn) #9

Yes, splitting into different build scripts will help in some cases. But if the configuration code gives a problem, then the only way around it is to use doLast, right?

For existing tasks like Copy, how do I know what is considered configuration code? Is configuration only setting input/output arguments? Or does it depend on the task?


(Benjamin Muschko) #10

What exactly do you mean by “configuration code gives a problem”? Do you mean whether the required values are resolvable? If they are not resolvable that point of time, you’d usually go with a action, correct.

Custom tasks types usually define an action within the implementing class. Everything else is configuration code. What is exposed depends on the custom task implementation. It might expose properties or methods. It might help to implement a custom task as an example to get familiar with the concept.


(Glenn) #11

It’s clear now, thanks.

I already have implemented several custom tasks and plugins. I have read the gradle documentation and read the Gradle In Action book. Neither the documentation or the book mention that input/output values have to be resolvable during the configuration phase, but it’s logical now that you’ve pointed that out :slight_smile:


(Benjamin Muschko) #12

In “Gradle in Action” this is explained in chapter 4 in the box named “Task inputs/outputs evaluation”.