Which dependencies are finally added to the runtime artifact

Hi,

I am having a bit of an issue understanding dependencies of which configuration are added to the final runtime artifact generated from a gradle java project, or a gradle kotlin jvm/android project.

I understand as an application developer, i would have options to decide the configuration in task such as below , where i am essentially adding runtimeClasspath

jar {
    manifest {
        attributes "Main-Class": "com.baeldung.fatjar.Application"
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

However what happens if i dont have a custom jar task and i use say gradle jar or gradle jvmJar or for that matter what happens when vendor plugins are used such as shadow plugin or spring boot , running gradlew bootJar tasks.

Is there a default configuration that gradle would use underlying to manage dependencies. If so, how can i find that out?

I was looking at the plugin codebase , is it right to assume that these are the configurations that are finally used for the dependency graph.

So even projects such as GitHub - Kotlin/full-stack-web-jetbrains-night-sample: Full-stack demo application written with Kotlin MPP

where we have a server, kotlin serverside, i have able to get a runtimeClasspath configurations but for shared project where we see that configuration runtimeClasspath is absent , however jvmRuntimeClasspath is present

Regards

However what happens if i dont have a custom jar task and i use say gradle jar or gradle jvmJar

There will nothing be embedded in that case.
The jar by default only contains your code.
Gradle has no built-in functionality to build a fat jar, neither bad-practice ones like the shadow plugin builds, not good-practice ones like the spring boot plugin builds.

what happens when vendor plugins are used such as shadow plugin or spring boot

Depends on those plugins, how they are implemented, and how you configure them.
By default those should typically use runtimeClasspath as that is the classpath needed at runtime.

I was looking at the plugin codebase , is it right to assume that these are the configurations that are finally used for the dependency graph.

Each resolvable configuration can be used to build a dependency graph.
You can also build custom resolvable configurations that you then can use to build a depedency graph.
It is always the question of the concrete use-case.
For example the compileClasspath configurations contains everything needed to compile, the runtimeClasspath contains everything needed at runtime. So for configuring a compilation task you would use the former, for configuring a run task you would use the latter.

shadow plugin code you linked to

As you can see, it uses runtimeClasspath by default. The fallback is just ancient stuff you shouldn’t care about.

java plugin code you linked to

This is not about resolvable configurations, but about consumable configurations. Those consumed by other projects using the built project. There runtimeElements is what a consumer would use for running, apiElements is what consumers would use for compiling against.

for shared project where we see that configuration runtimeClasspath is absent , however jvmRuntimeClasspath is present

Well, that is a Kotlin Multiplatform project I guess, there you can have a JVM part, a JS part, a Native part, and so on, so there you do not have “one runtime classpath”, but can have multiple, so they are prefixed. But that is also nothing Gradle does, but the Kotlin Multiplatform plugin does. You could easily get a runtimeClasspath configuration in such a project too, it will just not contain anything useful. :smiley:

Thank you @Vampire , everything you said makes a lot of sense actually.

My only worry, or may be confusion is , for a maven project a dependency graph is always the output of mvn dependency:tree.

Gradle also provides a similar mechanism using gradlew dependencies, but since its at a configuration level, how can an application developer confidently say for my gradle app, this is the dependency tree. Is it always related to how we jar/package the artifact?

Thank you.

That’s not really correct.

mvn dependency:tree has a scope parameter (scope there is related to configuration in Gradle) with which you can say for which scope you want to see the tree and if you don’t specify the scope you get the tree for all scopes.

Same for Gradle, you can tell it to show the tree for one configuration, or if you don’t specify, it shows all configurations, just in a more clear and structured way than Maven does.

But yes, no matter whether it is Maven or Gradle, it always depends on the concrete build files what the end-result is. Those ouputs for both are not necessarily the final truth either way. And also a user of your application should not even ever touch your build and so never get to see these outputs anyway.

Noted and agreed.

One quick follow up, is there a way( as in some custom groovy task) for me to get which configuration has been used to create a jar for say plugin(java) or kotlin(jvm)

Or say if we specify a custom jar action that has the below, what happens if say compileclasspath has artifact say google:guava@1.2 and runtimeClasspath has say google:guava@1.1, what would be used in this case?

tasks.jar {

    val dependencies = configurations
                            .runtimeClasspath
                            .get()
                            .map(::zipTree)

    val dependencies1 = configurations
                            .compileClasspath
                            .get()
                            .map(::zipTree)
    from(dependencies)
    from(dependencies1)
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

Also i am looking at the diagram below, would this not also suggest that we can possibly look at only one configuration say runtime or compileclasspath for the actual dependency set ?

to get which configuration has been used to create a jar

As it fully depends on your build script - or the plugins you use, you should know it already.
You can hardly write a task that understands your build script semantically and answer that question.

Or say if we specify a custom jar action that has the below, what happens if say compileclasspath has artifact say google:guava@1.2 and runtimeClasspath has say google:guava@1.1, what would be used in this case?

Both, as you configured it that way.
Or actually you would get both as inputs but as you said to exclude duplicates, the files that are in both JARs you of course only get one of the versions.
If you want conflict resolution between those, you would create a new configuration, make it extend from the other two configurations, and then use that new configuration to get the files to include.

we can possibly look at only one configuration say runtime or compileclasspath for the actual dependency set

Sure, I already said you can.