User guide says "For dependencies from a Maven repository, the default configuration is the only available one anyway": what does it mean?


(Davide Cavestro) #1

In the Dependency configurations paragraph the Gradle user guide says: >In Gradle a dependency can have different configurations (as your project can have different configurations). If you don’t specify anything explicitly, Gradle uses the default configuration of the dependency. For dependencies from a Maven repository, the default configuration is the only available one anyway.

But what does For dependencies from a Maven repository, the default configuration is the only available one anyway really mean?

i.e. I’d expect that if a dependency resolved through a maven repo declares a runtime dependency, it will be reflected into the runtime configuration.


(Peter Niederwieser) #2

In Ivy, a module can have different “shapes” (called ‘configurations’). Depending on which shape you depend on, you get different artifacts and transitive dependencies. Maven doesn’t have such a concept, and as such a dependency is always (and automatically) on the ‘default’ configuration of a Maven module (from Gradle’s perspective).


(mauromol) #3

Hi Peter, thanks for the answer. It’s now clear to me what that sentence of the user guide says. However, I’d like to note that Gradle is great at handling these concept, but a bit of confusion can arise. This happens every time I want to add new configurations to my project to do something more complex. I’ll try to explain. When you declare:

dependencies {

compile group: ‘org.somegroup’, name: ‘somedependency’, version: ‘1.0’, configuration: ‘someConfiguration’ }

you are saying: my project depends on the configuration “someConfiguration” of “somedependency”. The sentence Davide cited just says that this type of dependency declaration is not allowed if “somedependency” comes from a Maven repository, because this does not have the concept of “configuration”, but just have a “default” one. So far so good. However, please note that “compile” is itself a configuration (brought on my Gradle project by the Java plugin). So that declaration is actually saying: my project configuration called “compile” contains a dependency on the “somedependency” module, for which in turn the “someConfiguration” must be taken. So a configuration for a project/module is both a way of exposing itself (you call it a “shape”) and a container of dependencies, which themselves in turn can be taken in shape or in another.

So, let’s go forth. Consider the following:

// the following is implicitly added by the Java plugin configurations {

compile }

dependencies {

compile project (’:anotherProject’) }

Here you are saying: my project has a “compile” configuration and this “compile” configuration contains a dependency on the “default” configuration of “anotherProject”.

The Java plugin translates these declaration into this: - my project shape/configuration called “archives” (? not default!) will expose a JAR with my project class files compiled using the dependencies declared its “compile” configuration - my project depends on the default configuration of anotherProject, that is: my project classpath is filled with the artifacts in the “archives” (? not default!) configuration of anotherProject but also with the transitive dependencies taken from the “compile” configuration of anotherProject - however, if you look at the dependencies of my project (using the dependencyInsight, the dependency report or alike), you’ll see that not only the “compile” configuration is crowded with the transitive artifacts coming from the “compile” configuration of anotherProject, but also my project “default” configuration is populated similarly (because it extends the “runtime” configuraiton which in turn extends the “compile” configuration)… At this point I have a big “?” in my head.

My project default configuration “contains” the dependencies in the “runtime” configuration", but when my project is required by another project in its default configuration, it is presented with the artifacts in its “archive” configuration plus the artifacts in its “compile” or “runtime” configurations depending on the type of dependency the other project declares on my project…

So one question would be: what is the “default” configuration of a Gradle project for? And why, when I declare a project dependency leaving the “default” configuration for that dependency, does Gradle actually use the artifacts of the “archives” configuration of that dependency?

That’s a bit confusing, I must admit. Especially when you try to use these concepts for your purposes, because it’s quite natural to define a configuration, with its own dependencies and its own artifacts, in a “linear” way:

configurations {

foo }

dependencies {

foo project (’:anotherProject’)

foo ‘groupid:somedependency:1.0’

}

task fooJarTask(Type: Jar) {

// assembles the artifacts for the foo configuration }

artifacts {

foo fooJarTask }

That is: the foo configuration is used to expose an artifact that is built using the dependencies declared for the foo configuration itself.

I would appreciate a lot if you had some advices/thoughts to make all of this more clear.


(Peter Niederwieser) #4

The confusion arises because the same abstraction (namely ‘configuration’) is used for several purposes. This is part of our Ivy legacy, and we will introduce more fine-grained abstractions over time. (This is already happening with the incubating ‘maven-publish’ and ‘ivy-publish’ plugins, which introduce the concept of ‘publication’.)

As for the status quo, the ‘archives’ configuration is (only) used for publishing to a repository. The ‘default’ configuration is what you get for a project dependency that doesn’t explicitly name a configuration. Both ‘archives’ and ‘default’ extend ‘runtime’, which in turn extends ‘compile’. The Java plugin adds its Jar to the ‘runtime’ configuration (as an artifact, not a dependency). Therefore, the Jar will both get published and passed on to dependent projects. Other artifacts such as sources Jars, javadoc Jars, or signatures are typically only added to ‘archives’.


(Davide Cavestro) #5

Wow, very interesting observations. IMHO it would be worthwhile adding them to the user guide.


(mauromol) #6

Thanks Peter. It’s a bit more clear now. However I still have difficulties to understand what the “default” configuration is for.

When you declare:

dependencies {

compile ‘groupid1:dep1:1.0’

runtime ‘groupid2:dep2:1.1’

}

you’re saying: my project depends on dep1 for compiling and dep2 (+dep1) at runtime. When you have another project that depends on the default configuration of this:

  1. if it depends on this project (default shape) for compiling, it takes this project JAR + dep1.jar 2. if it depends on this project (default shape) at runtime, it takes this project JAR + dep1.jar + dep2.jar

hence, the concept of the plain “default” configuration as a container for all the runtime dependencies of this project seems useless to me, unless it is used for some other kind of project dependencies different from the Java ones. But even in this case, having its default configuration polluted with the runtime dependencies, without me having declared anything like:

dependencies {

default ‘groupid1:dep1:1.0’

default ‘groupid2:dep2:1.1’

}

sounds like a conventional choice to me. It could have been default==compile or default==test as well… Unless I’m missing something else.

Another source of confusion is that Maven does not have the “configuration” concept as a way to declare the shapes of a module, but does have the dependency scopes to declare the “meaning” of a dependency. Gradle, as we said, uses the concept of configuration for both purposes.


(Peter Niederwieser) #7

you’re saying: my project depends on dep1 for compiling and dep2 (+dep1) at runtime.

More precisely, what you are saying is that your project depends on the ‘default’ configuration of dep1 for compiling, and the ‘default’ configuration of dep2 at runtime. For Maven dependencies, this doesn’t make a difference, but for Ivy and project dependencies it does.

  1. if it depends on this project (default shape) for compiling, it takes this project JAR + dep1.jar
  1. if it depends on this project (default shape) at runtime, it takes this project JAR + dep1.jar + dep2.jar

Currently, Gradle will take JAR + dep1.jar + dep2.jar in both cases, because it simply takes ‘default’ (which extends ‘runtime’). It will get smarter in the future.

To understand how the status quo of configurations emerged, you’d have to look into Ivy and Gradle’s history. As I’ve said before, we have plans to gradually replace configurations with better fitting abstractions, and the incubating ‘maven-publish’ and ‘ivy-publish’ plugins are a first step in this direction.


(mauromol) #8

So, you mean that Gradle takes JAR+dep1.jar+dep2.jar in both cases, but subsequently filters these dependencies based on the dependency scope, in order to fill the compile classpath or the runtime classpath accordingly?

And, for curiosity, would the following be legal/correct/advisable or just inappropriate/useless/wrong?

dependencies {

compile group: ‘groupid1’, name: ‘dep1’, version: ‘1.0’, configuration: ‘compile’

runtime group: ‘groupid2’, name: ‘dep2’, version: ‘1.1’, configuration: ‘runtime’

} (assuming, of course, that dep1 and dep2 are coming from an Ivy repository)

That’s a good insight of the internals of Gradle, thank you. I look forward to see the new abstractions coming up to do some cleaning on these aspects.


(Peter Niederwieser) #9

So, you mean that Gradle takes JAR+dep1.jar+dep2.jar in both cases, but subsequently filters these dependencies based on the dependency scope, in order to fill the compile classpath or the runtime classpath accordingly?

Currently there is no such filtering.

And, for curiosity, would the following be legal/correct/advisable or just inappropriate/useless/wrong?

Doing something along these lines will lead to more accurate (compile) class paths, at the cost of more configuration effort (which includes handling corner cases). At some point, Gradle will make it easy to be (even more) accurate, both for Maven and Ivy dependencies.


(mauromol) #10

Currently there is no such filtering

But if it is so, I don’t understand how compilation may fail if I say:

dependencies {

runtime ‘groupid2:dep2:1.1’

}

and my project has some class that depends on classes in dep2.jar. Because it does fail, that’s for sure :slight_smile: Following your thought, my project should have dep2.jar in its default configuration, but not in its compile classpath, this is what I meant by “filtering”. And the presence of distinct compile and runtime classpaths is the reason for which I said that the presence of the default configuration seems useless to me, at least for Java projects.


(Peter Niederwieser) #11

But if it is so, I don’t understand how compilation may fail if I say

What I was trying to say is that when you depend on ‘‘groupid2:dep2:1.1’’, you’ll get its ‘default’ configuration, which usually boils down to ‘compile’ + ‘runtime’. If you add this dependency to your ‘runtime’ configuration, it won’t be used for compilation. But ‘‘groupid2:dep2:1.1’’ will (currently) always resolve to the same files, no matter if you add it to your ‘compile’ or your ‘runtime’ configuration.

And the presence of distinct compile and runtime classpaths is the reason for which I said that the presence of the default configuration seems useless to me, at least for Java projects.

The ‘default’ configuration comes from Ivy, where it’s customary to distinguish between “internal” (invisible) and “external” (visible) configurations. Having an indirection is useful, as it allows to vary one without the other.


(mauromol) #12

What I was trying to say is that when you depend on ‘groupid2:dep2:1.1’, you’ll get its default configuration, which usually boils down to compile + runtime. If you add this dependency to your runtime configuration, it won’t be used for compilation. But ‘groupid2:dep2:1.1’ will (currently) always resolve to the same files, no matter if you add it to your compile or your runtime configuration.

So, if I understand it correctly, if I have:

  • project1 with dependences { compile project(’:project2’) } - project2 with dependences { compile project(’:project3’) / runtime project (’:project4’) }

=> then the compile classpath of project1 will contain project2.jar + both project3.jar and project4.jar? If I had to bet, I would have said project4.jar weren’t in project1 compile classpath!


(Peter Niederwieser) #13

Ideally, even project3.jar wouldn’t, but currently, both project3.jar and project4.jar will.


(mauromol) #14

That’s interesting, thank you. project3.jar shouldn’t fall in project1 compile classpath if you consider dependences as not being transitive. However, I know by experience that even if project1 classes do not import and use any class in project3, the compiler may still emit some errors like “NoClassDefFound for <a class in project3.jar>, it is indirectly referenced by <another class in project2.jar>”. But this is yet another topic :slight_smile:

Thanks a lot for these clarifications!