Gradle Task in subproject


(Andrew Jardine) #1

I’m not entirely sure what to ask here, so I’m going to describe what I am doing (which is perhaps wrong :)).

I have a multiproject configuration. At the top level of course I have a build.gradle and a settings.gradle. In each of my sub projects, I have a build.gradle.

In my root build.grade I am adding a task. The task is being added here so that it is available to all my sub projects . Now, when I run this task, I would run it within the context of one of my project. The goal is to take the jar that is generated after the build, and then copy it to another location. So if my two projects are ProjectX and ProjectY … when I run this task on ProjectY, I want the logic to detect that it is ProjectY that called the task, then to go to dist/lib and copy *.jar to the other location.

Right now, I’m starting off simple. I have

subprojects {
    task hello(type: Copy) {
        dependsOn build
        print  "test"
    }
}

when I run it from ProjectY though I see the “test” output more than once – I am guessing that it runs it for multiple projects.

So, is there a way to define this task globally (using the subprojects) but only have it run for the subproject that I am in?


(Chris Doré) #2

The call to print is happening during Gradle’s configuration phase, not during the task execution phase. To have print called during task execution:

subprojects {
    task hello(type: Copy) {
        dependsOn build
        doLast {  // or doFirst depending on the behaviour you want
            print  "test"
        }
    }
}

Here’s some more information about Gradle’s phases. https://docs.gradle.org/current/userguide/build_lifecycle.html


(Andrew Jardine) #3

Ugh – thanks for that reminder Chris. Fail. It’s been about a year since I have had to work with Gradle day to day – still, I can’t believe I forgot that. I have updated based on your suggestion, but I am not seeing the output. So if my structure looks like this –

Workspace
    modules
        apps
            projectx
                build.gradle
            projecty
                build.gradle
build.gradle (in workspace)

… if I run my wrapper found in the root – like this

$> ../../../gradlew -q hello

… shouldn’t I expect to see one “hello” output in the console?


(Chris Doré) #4

If you run it without -q, does the task actually execute? Perhaps it is up to date.


(Andrew Jardine) #5

Hi Chris,

Sorry for the delay in the reply. I did what you suggested and instead used

$> ../../../gradlew hello

… but I still don’t see the output. What I get is the following.

:assemble UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE
:modules:apps:sample-controlpanel-panel-app:hello UP-TO-DATE

BUILD SUCCESSFUL

Total time: 0.775 secs

any other suggestions?


(Andrew Jardine) #6

Oh wait – is it because everything is up to date and I have a dependsOn build and the build is listed as UP-TO-DATE?

EDIT: I’ve actually tried several things here including removing the depends on build and using the --rerun-tasks flag and I can’t seem to get the println statement in the doLast block to ouput. If I add prinln statements that are executed during the configuration phase, I see all of them of course but not the doLast (or even the doFirst when I added it to see as well). So at this stage I am not sure that it’s that my task is not running – but if it is, where the heck is my output!?


(Chris Doré) #7

Do you have any into and from calls in the configuration of the hello task? If not, then I believe a copy task will always be up-to-date and therefore will never execute.

Try removing type: Copy and you should start seeing your output. Alternatively, configure the copy task with some files to copy.


(Andrew Jardine) #8

Hi Chris,

Yep – removing the type:Copy allowed the task to run and the print statement to show. I thought I had read something about that last night, but then I thought removing it wasn’t an option because I did in the end want to Copy a file from one place to another. I guess I don’t need to necessarily use the Copy task though, I can just as easily just use the io API to do it own my own. Ok – I have a million more questions, but I think for the moment the best thing for me to do is read the book I just bought rather than ask you to hold my hand through the whole process.

Thanks so much for you help on this.


(Chris Doré) #9

You can certainly combine the Copy task type with extra doLast work. The problem you are encountering is that the Copy type is controlling up-to-date via task inputs and outputs. By not configuring the task to do any copies, the task ends up having no work to do and is therefore always up-to-date.

If the work you want to do in doLast should not be coupled to the Copy type’s up-to-date checking, then I see two options:

  1. Configure extra inputs and/or outputs on the task, which you control, and will participate in the up-to-date checking alongside what is defined by the Copy type. This is the way to go if your doLast work will modify the outputs produced by the copy, since the modified files will be properly tracked for future up-to-date checking.
  2. Create a separate task for the doLast work and have that task depend on the task doing the copy. This way, both tasks will have their own up-to-date checking. This is probably a better solution if the two tasks have independent sets of input and output files.

(Andrew Jardine) #10

Hi Chris,

Thanks again for taking the time to answer. Since you seem willing, maybe I can describe to you what I am hoping to achieve and you can tell me whether or not I am going about it right.

When I run the build task on one of my modules, the results are (of course) placed into the dist folder off the project root. The file produced is a jar (most of the time anyway). What I want to do with my take is have that jar copied from my /dist/*.jar to another location on my disk. I want to define this task in my root project gradle file so that it it automatically available to any (sub) project I create.

Being newish to gradle I thought the best approach was to define a subprojects block and then in there a task to do this job. It’s a copy take really so that is why I added the type. It depends on build of course and then I thought to use a doLast block because that way I know the build has been run before I go looking for the file to copy it.

Knowing all this now, am I on the right path?


(Chris Doré) #13

doLast is not executed after the build has run, it is executed as part of the build. Specifically, each task has a list of actions to be performed during that task’s execution and doLast appends to that list (doFirst prepends to the list). declaring that task X dependsOn another task is how you ensure that tasks are executed in the correct order.

It looks to me like you are on the right track. However, instead of depending on the build task, like in your hello example, it may be better to depend on the task that actually produces the jar you want to copy. That way, Gradle can make finer grained decisions about what tasks to execute. Unless you really do want/need everything that build does in order to satisfy your task. For example, do you really need tests to be executed in order to copy the jar?

If you decide to go down the road of depending on the task that produces the jar, then there’s an additional benefit you can take advantage of. Assuming that the producer task properly defines its outputs, then you can use from( producerTask ) in your copy task. Two nice things happen with that:

  1. When configuring the copy task you do not need to be concerned with the actual location of the jar. If the producer of the jar is reconfigured to change its output from the dist directory to a different location, then the copy task does not also need to be reconfigured, the defined outputs of the producer task will be used.
  2. A dependency on the producer task will be inferred and you do not need to explicitly declare it in the configuration of the copy task.

Here’s an example, using the java plugin’s jar task as the producer:

subprojects {
    apply plugin: 'java'
    task hello( type: Copy ) {
        from( tasks.jar )
        into( new File( buildDir, "${name}Outputs" ) )  // copies into build/helloOutputs
    }
}

With that, running the hello task, Gradle will ensure that only the tasks necessary to satisfy the jar task will be executed before the hello task, so things like unit tests will not be executed and the hello task will complete as quickly as possible.