19 July 2022
This development log (devlog) includes work done since the last devlog, which mainly focuses on the Universal Model, more specifically on dependency buckets (aka Gradle
The progress was primarily technical; hence we think sharing the major roadblocks we overcame around using
Configuration in a nested hierarchy would be appropriate for this devlog.
When we talk about nested hierarchy, we mean several layers of domain object ownership.
For example, a component, variant or artifact can own a dependency bucket, which in turn, a component or variant can own artifacts, and finally, a component can own variants.
This kind of hierarchy causes a lot of headaches in terms of discovery.
In a fully lazy build, how does Gradle know it collected every outgoing
Configuration for a proper dependency resolution?
The short answer is it can’t!
The focus today is on how to configure
Configuration without realizing the world properly.
Stay tuned for more discussion on the discovery in the future.
Our first surprise was a misbehaving plugin.
In our example, the Koltin Gradle plugin realizes the dependencies of a
Configuration too early preventing a pull behaviour from our model using
Thankfully, we have an internal concept of finalized state, which we can use to propagate the dependencies from our buckets to their matching
Lesson: Plugin authors should avoid realizing
DomainObjectCollection too early.
To ensure we can finalize a
Configuration before Gradle performs its dependency resolution, we need some ways to receive a notification when Gradle is about to use the
The only hook available to us is
It would be wrong to think
Configuration#beforeResolve is the public API equivalent; the
Configuration is already immutable.
Thanks to the internal hook, we can perform additional configuration avoidance.
Resolving gap of the
Now that we can adequately avoid early configuration and finalize the
Configuration itself, are we done?
Unfortunately, we face our final surprise,
Configuration.s are not locked when Gradle visit their task dependencies.
Configuration behave much like a
We can iterate the resolved files or visit the task dependencies that produce those files.
In practice, visiting the task dependencies looks at
ProjectDependency as it’s the only kind of
Dependency that contains meaningful
Sadly for us, visiting the task dependencies does not lock the
Configuration and only looks at the current
Because of misbehaving plugins, we couldn’t lazily attach our dependencies via
The result is a bit confusing.
The tasks using the
Configuration won’t have the correct task dependencies but will still end up with the expected resolved files, which points to missing files.
In our Universal Model, we work around this issue by finalizing the dependency bucket upon collecting the incoming files.
However, in our JNI library plugin, we had to force the
Configuration to lock at key locations.
Note that, as of Gradle 7.4, Gradle will ignore task dependencies provided by
ProjectDependency when added via
Configuration#defaultDependencies actions as those actions execute during the