How to resolve dependencies in execution phase?


(John Soman) #1

Hi, I’ve seen multiple times recommendation to avoid resolving dependencies at the configuration phase. And I understand why. It slows down my build considerably. But I struggle to be able to resolve any dependencies at execution time.

Consider this for example, in one of my tasks I do at configuration phase:

myTask(type: JavaExec) {
   ...
   def myClassPath = files(project.configurations.runtime.getFiles())
   ...
}

This of course causes dependency resolution to kick in. So I thought I can move it to execution phase with something like this:

myTask(type: JavaExec) { 
  ...
  def getClassPath = {files(project.configurations.runtime.getFiles())} 
  doLast {
     def myClassPath = getClassPath()
     ...
  } 
}

This does not trigger resolution at config time anymore but since it is task of type JavaExec it is failing to run with “no main class specified” as configuration isn’t ready at execution time.

And more generally, is there any documentation regarding recommendations around what should be done during configuration vs execution phase, and how? There’s some documentation explaining the lifecycle but would be good to have some more around good practices…

Thanks!


(Stefan Oehme) #2

Just don’t call getFiles. A Configuration already is a file collection.

myClasspath = configurations.runtime


(John Soman) #3

Thanks Stefan. Is there a similar way to resolve classpath for all subprojects in a multiproject setup? I’m looking for a way to concatenate all subproject’s classpaths as they are needed for this JavaExec task.

Tried this but getting “File collection does not allow modification”

myTask(type: JavaExec) { 
  ...
  mySubProjects().each{ classpath.add(it.configurations.runtime) }
  ... 
}

I also tried creating empty collection and adding file collections to it. Same problem…


(uklance) #4
configurations {
   javaExec
} 
mySubProjects().each {
   configurations.javaExec.extendsFrom it.configurations.runtime 
} 
myTask(type: JavaExec) {
   classpath = configurations.javaExec
   ... 
} 

(John Soman) #5

Thanks Lance! I found another way, which also seems to be working, with

mySubProjects().each{ classpath = classpath.plus(it.configurations.runtime) }


(uklance) #6

I was using a Configuration so that proper dependency resolution would take place


(John Soman) #7

How is that different to my approach?


(uklance) #8

You are just adding to a file collection, if for instance two projects had different versions of log4j then your classpath would have both log4j jars. Mine would use dependency resolution to choose one


(John Soman) #9

I see. That makes sense! And perhaps explains why I’m seeing some errors during runtime with my solution.

So I’m trying yours by adding it to my subprojects task

configure(mySubProjects()){
  ...   
  configurations {
       javaExec
    } 
    configurations.javaExec.extendsFrom it.configurations.runtime 
   ...
}

and then in myTask

task myTask(type: JavaExec) {
    ...
    classpath = configurations.javaExec
    ...
}

Which fails when I run it from the root project

gradle myTask

with
> Could not find property 'javaExec' on configuration container.

As I mentioned I’m trying…

Possibly I could add it on the root project too, in build.gradle:

configure(project) {
    configurations {
       javaExec
    } 
    configurations.javaExec.extendsFrom project.configurations.runtime 
}

Although I wish I didn’t have to do that (which actually relates to my other question:
does-root-in-multiproject-needs-to-be-configured)
it makes the error “task not found” go away,

But then I’m getting "Error: Could not find or load main class"
as your solution only includes external dependencies. And not my internal subprojects.

jars like log4j etc are there, but not my subprojectx.jar etc.

How could I include those too?


(uklance) #10

Your code is adding a “javaExec” configuration to all of the subprojects and not the root project.

Mine is creating a “javaExec” configuration in the root project.

Quite different


(John Soman) #11

If I put it the way you did above I’m getting

> Could not find property 'runtime' on configuration container.

If I put it only in root project like below

configure(project) { 
   ...
    configurations {
        javaExec
    }    
}

And then in mySubProjects

configure(mySubProjects()){
    configurations.javaExec.extendsFrom it.configurations.runtime 
}

I am getting:

> Could not find property 'javaExec' on configuration container.

The only way I made this whole thing work was resolving dependencies at configuration time, and using getFiles. Which Stefan suggested I didn’t have to…

task myTask(type: JavaExec) { 
    ... 
    def myClassPath = files(project.configurations.runtime.getFiles());
    mySubProjects().each{ myClassPath += it.jar.outputs.getFiles() }
    classpath = myClassPath
    ...
}

Then I can run this task from root project and it will work.
But it is both ugly and slow.
Any more ideas?


(uklance) #12
configure(mySubProjects()){
    configurations.javaExec.extendsFrom it.configurations.runtime 
}

This is wrong, it.configurations and configurations are the same thing in this closure. Getters in the closure are invoked on the subproject. This is different from my mySubProjects().each { ... } suggestion

You could probably just do it like this in the root project

def mySubProjects = [':foo', ':bar'] 
configurations {
   javaExec 
} 
dependencies {
   mySubProjects.each {
      javaExec project(path: it, configuration: 'runtime') 
   } 
} 
task myTask(type: JavaExec) {
   classpath = configurations.javaExec 
}