How are Gradle Configuration features used and how do they work?

Viewed 96

I'm trying to understand Gradle dependency Configurations, and after digging into some of the documentation (for instance, https://docs.gradle.org/current/userguide/java_library_plugin.html), I'm left with a bunch of related questions*:

  • In Gradle, what's the distinction between a jar, an artifact, a dependency, and a module? What are some examples of artifacts, dependencies, and modules that are not jars? Artifacts that are not modules? etc...

  • What are resolvable Configurations? I discovered that if configuration x.canBeResolved then I can call x.resolve to get a set of paths to jars without the x.resolve method throwing an error (Is this all that marking a configuration as consumable gets you?). But aren't all configurations sets of modules, and so shouldn't I be able to see what those are? If so, how?

  • What are consumable Configurations? What does marking a configuration "consumable" allow you to do / prevent you from doing? What are some good examples of consumable Configurations? How would one take advantage of a Configuration that is/isn't consumable?

  • What other properties of Configurations are important and why (e.g., are attributes used for anything apart from variants)?

  • What are variants, and how are they created and used, and why would you create/use them? (See https://gradle.github.io/webinar-dep-mgmt-part-1/#/more-definitions).

  • How do the java and java-library plugins take advantage of these features of Configurations? In particular, how is the api configuration made or treated differently, under the hood, from the implementation configuration? Does this have anything to do with being resolvable/consumable?

  • What other questions should I be asking at this point?

Specific examples, and code, are especially appreciated.

*I realize that many of these questions could be asked independently, and if you have a good answer (or link) for a single one, please share. However, I'm trying to get the big picture, so I've stuck them together under a single heading. Also, I suspect I won't be able to understand the answers to some of the questions without having the answers to others--e.g. jars vs artifacts vs dependencies vs modules--and I'm trying to reduce question-answer latency and general confusion.

1 Answers

Here are answers to some of your questions.

In Gradle, what's the distinction between a jar, an artifact, a dependency, and a module?

Gradle calls each of these things 'components'.

First I'll answer a different question: How does Gradle distinguish between components?

It does that using flags, called 'variant attributes'. In short, these are string key-value flags, that describe things like 'the Maven coordinates are org.junit.jupiter:junit-jupiter:5.8.1' and 'this project is contains Java files'.

Attributes can be anything. Gradle has some standard attributes built in, or you can make your own.

So to answer the question, the distinction between jars, artifacts, dependencies, and modules depends on the attributes.

What are resolvable/consumable Configurations?

I'll quote the documentation here:

Configurations have at least 3 different roles:

  1. to declare dependencies

  2. as a consumer, to resolve a set of dependencies to files

  3. as a producer, to expose artifacts and their dependencies for consumption by other projects (such consumable configurations usually represent the variants the producer offers to its consumers)

There's also a table in the docs that shows the combinations. Here's a reproduction:

Configuration role can be resolved can be consumed
Bucket of dependencies false ❌ false ❌
Resolve for certain usage true ✅ false ❌
Exposed to consumers false ❌ true ✅
Legacy, don’t use true ✅ true ✅

Aside: I've always found 'Configurations' to be a poor word choice. To me it means more "this is settings I want to apply to a Gradle build", while Gradle means it more like "this is an assembly of items, organised in a certain way".

What are variants, and how are they created and used, and why would you create/use them?

Variants are created by setting different attributes on configurations. They're used by consumers that request a component, and have compatible attributes.

An analogy: a shape sorting toy

Think of it this way: Do you know that shape sorting box toy, for kids? There are wooden blocks in different shapes: cylinders, triangles, cubes, stars. And then there's a box with similarly shaped holes.

modules

The blocks are what Gradle calls modules. They're dependencies that a Gradle project requests, or produces.

attributes

The blocks have names that describe them. Square, rectangle, circle... these are Gradle attributes. The name 'square' could describe either the block, OR the corresponding hole in the box that the block slits slots into.

variants

Sometimes shapes are very similar to each other. Maybe one rectangular hole is slightly taller than another rectangular hole. These are variants. (The analogy breaks down a bit on this one - but I hope you get the principle: The attribute of 'rectangle' still applies, but one tiny thing has changed that makes it a variant rectangle.)

Sometimes blocks are different colours, but the hole doesn't care if a block is red or yellow or blue. So there might be differences variants that the consumer or producer don't care about.

configurations

The box is a 'consumable' configuration. It consumes blocks, but only blocks with attributes that match the attributes of the holes in the box. We can look inside the box, and find all the modules matching some attributes.

'Resolvable' configurations are kids playing, doing an action, trying to find matching blocks. They can only work with the blocks they can see, but their plan isn't to keep holding onto those blocks, but to provide them to the consuming box.

If we wanted, we could instruct the kid "only look for circular blocks". That's us applying 'attributes' to the kid. So, even if the box can accept more than just circular blocks, it will only get circular blocks, because that's all the kid has been instructed to do.

An example

There's an example of creating consuming/producing configurations on the Gradle website, but it was removed in version 7.4 because there's a new JaCoCo aggregation plugin. I think that's a bit of a shame because the example is still relevant for sharing output between projects using variant-aware configurations.

Fortunately it's still available directly, so here's the link: https://docs.gradle.org/7.3/samples/sample_jvm_multi_project_with_code_coverage.html

Related