Multi project builds and task foo(dependsOn: 'x') syntax?

Given the following project directory structure:


 .  ├── one  │   └── three  ├── two  ├── build.gradle  └── settings.gradle  

and following content in settings.gradle and build.gradle:

//settings.gradle
  include ':one', ':two', ':one:three'

and:

//build.gradle
  allprojects {
     task clean << {
      println "I am clean in ${project.path}"
    }
  }
    task foo(dependsOn: 'clean') << {
    println "I am foo!"
  }

we get the following execution output:

nadurra:gradletest mbjarland$ gradle -q clean
   I am clean in :
I am clean in :one
I am clean in :two
I am clean in :one:three

and:

nadurra:gradletest mbjarland$ gradle -q foo
  I am clean in :
I am foo!

I would like to reproduce the “find all subprojects where the task name exists and execute the task in the subproject - in dependency order” functionality from the “gradle clean” run using a dependsOn declaration.

How would I go about accomplishing this?

1 Like

An addendum. I can solve the problem by using the following build file:

//build.gradle
  allprojects {
     task clean << {
      println "I am clean in ${project.path}"
    }
  }
      project(':one').dependsOn(':one:three')
  project(':two').dependsOn(':one:three')
      task foo(dependsOn: 'clean') {
    subprojects.each {
       dependsOn("${it.path}:clean" )
    }
  }
      foo << {
    println "I am foo!"
  }

where the two lines with project dependencies are there to prove that the foo task still executes the relevant tasks in the correct project-dependency-order. With the above, the execution results are:

nadurra:gradletest mbjarland$ gradle -q clean
   I am clean in :
I am clean in :one:three
I am clean in :one
I am clean in :two

and:

nadurra:gradletest mbjarland$ gradle -q foo
  I am clean in :
I am clean in :one:three
I am clean in :one
I am clean in :two
I am foo!

which is what I wanted. This is however no longer quite as beautiful as the out-of-the-box command line equivalent. Is there a better and more concise way of accomplishing this result?

[EDIT: The above solution only works if all subprojects actually have the ‘clean’ task. It breaks with exceptions if any one of them lacks it]

ok, having a conversation with myself here. Seems that the perfect solution to this would have been the following syntax:

//root build.gradle
   task foo(dependsOn: getTasksByName('bar', true)) << {
    println "I am foo!"
  }

(where I want the foo task to depend on ‘bar’ tasks in all suprojects if the task is present in the subproject)

The getTaskByName(x, true) call is supposed to return a set of Task objects for the current project and all sub projects. This is exactly what I was looking for.

Unfortunately there is still a hurdle. I have created two custom build files at one/three/build.gradle and two/build.gradle with the following code:

task bar << {
    println "I am bar in ${project.path}"
    }

i.e. the projects :one:three and :two have the ‘bar’ task whereas the project :one doesn’t. However, executing the root project ‘:foo’ task gives the following result:

nadurra:gradletest mbjarland$ gradle -q foo
    I am bar in :one:three
I am foo!

note the missing execution of :two:bar! In fact using the given setup and following root build.gradle file:

project(':one').dependsOn(':one:three')
  project(':two').dependsOn(':one:three')
      task foo(dependsOn: getTasksByName('bar', true)) << {
    println "I am foo!"
  }
      println "THREE: " + project(":one:three").getTasksByName('bar', false)
  println "TWO:
 " + project(":two").getTasksByName('bar', false)

gives the following execution log:

nadurra:gradletest mbjarland$ gradle -q foo
  THREE: [task ':one:three:bar']
TWO:
 []
I am bar in :one:three
I am foo!

i.e. even an explicit call to getTasksByName on project ‘:two’ returns an empty set. A direct call “gradle :two:bar” does execute the task correctly.

What gives? To me this looks like a bug in the getTasksByName recursive selection…but I might be missing something here.

Any input on how to best accomplish the intended functionality or what the issue with my call to getTasksByName is would be much appreciated.

[EDIT: Turns out the behavior above is caused by the build life cycle phases. If I add code into an execution time task closure ala:

task thisworks << {
    allprojects.each { project ->
       println "thisworks> ${project.path.padLeft(17)} ->
 ${project.getTasksByName('bar', false)}"
      }
   }

the output is as expected. This does however as far as I can see disqualify the getTasksByName call from the use case “make your root project task depend on all tasks with the same name in your subprojects”.

It also leaves us without a proper solution to the initial problem since modifying task dependencies does not seem to work once you are in the execution phase]

Final post on this monologue rant.

Ok so it seems that a gradle.taskGraph.whenReady {} closure is too late in the game for adding dependencies to a task. Also seems like the config phase (“task foo { }”) is too early and the execution phase (“task foo << { }”) is too late.

So in analogy with the three little bears I needed to find something that is ‘just right’. Not too early as the subproject tasks would not be defined yet and not too late as it would be too late to add task dependencies.

As far as I can tell, there are two ways to solve this.

One:

task foo << {
    println "I am foo!"
  }
    gradle.afterProject {project, projectState ->
    if (project.tasks.findAll { it.name == 'bar' }) {
      foo.dependsOn(project.bar)
    }
  }

i.e. this makes the ‘foo’ task depend on any ‘bar’ tasks defined in any subprojects.

And two:

subprojects.each {
    dependsOn(it.path)
  }
  task foo(dependsOn: 'bar') << {
    println "I am foo!"
  }
  task bar << {
  }

i.e. a) make the root project depend on all subprojects and b) add the ‘bar’ task to the root project so that the dependsOn call doesn’t break.

If anybody has a prettier syntax for this I would much welcome the improvement.

2 Likes