`ArtifactHandler.add`, `DependencyHandler.add` uses under the hood eager api

This post is mainly for discussion.

I was looking at some piece of code in old groovy dsl style. And I stumbled on the way this actually works ubder the hood, even in Gradle 9.1.0

artifacts {
  archives(shadowJar)
}

this calls almost immediately under the hood configurations.getByName("archives"). The ArtifactHandler interface don’t seem to have lazy API variant.

But I recognized this pattern in the DependencyHandler as well :

dependencies {
  latestDepTestImplementation group: ...
}

Under the hood the configuration is immediately accessed as well, configurationContainer.getByName(configurationName). Even using an explicit add method taking a string results in the same call. And there are no lazy variant (regarding the configuration).

This raises some questions there, as I understand that getByName is eager, it realizes the “named” element. Which makes basically other efforts in build files to reduce eager API to have less impacts if configurations are still created early via this seemingly innocent APIs.

Why is it like that ?

The great lazification was delayed from Gradle 9 to currently Gradle 10 (Provider API migration · Issue #28 · gradle/build-tool-roadmap · GitHub).
Maybe with that things would change further.

I have no idea why there the things are realized eagerly though even if you use providers.
Maybe a bug report - if none present - would be appropriate.

If you do in Groovy DSL

def fooConfiguration = configurations.dependencyScope("foo") {
   println("foo realized")
}
dependencies {
   foo("commons-io:commons-io:+")
}

it might be “correct” due to backwards compatibility to eagerly create the configuration.

But even if you use the provider in Kotlin DSL like

val foo = configurations.dependencyScope("foo") {
    println("foo realized")
}
dependencies {
    foo("commons-io:commons-io:+")
}

the configuration is still created eagerly.

If you for example use

val foo = configurations.dependencyScope("foo") {
    println("foo realized")
    dependencies.add(project.dependencies.create("commons-io:commons-io:+"))
}

Then the configuration is only realized when actually used.

Interesting! Thanks I completely missed the ConfigurationsContainer::dependencyScope. I noticed it’s still incubating, so that’s something to keep monitoring if any change happens.

It seems that DependencyScopeConfiguration extends a Configuration, so what’s the difference with (in pseudo code) with “regular” lazy api, e.g. :

configurations.named("foo") {
    println("foo realized")
    dependencies.add(project.dependencies.create("commons-io:commons-io:+"))
}

I will open issues on gradle if none already exists :slight_smile:

The dependencyScope is totally different from named, because it registers a new configuration set up as dependency bucket (non-resolvable, no-consumable).
If you want to get an existing, named is still the right thing to use.