Lazy configuration resolution with artifact repack

Hi all,

I’m trying to make configuration resolution as lazy as possible.
In particular, the following pattern emerges quite often in our build scripts:

apply plugin: 'java'

configurations {
    external
}

dependencies {
    external 'group:artifact:version'  // zip archive in some maven repo
}

jar {
    from zipTree(configurations.external.singleFile)
}

It does not look lazy at all - ‘external’ configuration is getting resolved even on ‘./gradlew classes’, which does not require ‘jar’ task at all. I’ve tried

jar {
    from configurations.external.asFileTree 
}

instead and it acts way better. Apparently, it cannot be transformed into zipTree:

jar {
    from zipTree(configurations.external.asFileTree)   // fails
}

Is there a way to get a zipTree without configuration resolution? Or maybe there’s some other way to get dependency content repacked (spec reuse, etc)?

Thanks in advance for your time and consideration.

Yes, that’s a common pattern:

The closure delays resolving the configuration unless it’s used.

It’s worth noting that if the input Configuration includes project dependencies you’ll need to add an explicit dependency to it to ensure all the task dependencies get added property.

task {
    dependsOn configurations.external
}
1 Like

Thank you, that’s the pattern I was looking for

I want to clarify this example a bit. I believe the configuration is still resolved during the configuration phase, but only when the jar task is in the task graph… and this still feels slightly wrong to me.

Here’s an example I derived from Peter’s referenced answer on the old forums:

configurations {
  api {
    incoming.beforeResolve { println "Resolving api configuration!" }
  }
}

repositories {
  jcenter()
}

dependencies {
  api 'junit:junit:4.12'
}

task extractApi(type: Sync) {
  doFirst { println 'Running extractApi task...'  } //just to guarantee this task has some console output

  from { // use of closure defers evaluation until execution time (KM apparently not??)
    configurations.api.collect { zipTree(it) }
  }
  into "$buildDir/api/"
}

task clean {
  doLast {
    delete "$buildDir/api/"
  }
}

Example usages:
$ gradle clean returns:

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Perfect, this is as expected.

$ gradle extractApi returns:

Resolving api configuration!

> Task :extractApi
Running extractApi task...

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Why is the resolution happening during configuration? Is this just semantics?

Note: if I change the from method on the Sync task to read from configurations.api, then Gradle behaves as I expect:

$ ./gradlew extractApi

> Task :extractApi
Resolved api configuration!
Running extractApi task...

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

So it seems that calling zipTree on a configuration prevents it from being resolved lazily.

It’s not, it’s happening during execution when snapshotting inputs for the :extractApi task, which is why it looks as though its happening before that task executes, but it’s certainly during the execution “phase”.

The clear evidence of this not happening during project configuration is that we do not see that configuration being resolved when running other tasks, such as clean, which means we are definitely not eagerly resolving that configuration.

1 Like