What Does '->' (Arrow) Mean in Gradle's Dependency Graph

What does '-' (arrow) mean in gradle's dependency graph?

It means that dependency graph contains multiple dependencies with the same group and module but different versions for e.g. org.hamcrest:hamcrest-core. Gradle tries to resolve conflicted versions automatically - by default the latest version is chosen. On the left side of -> is the requested version, on the right the version that will be picked. Here similar question can be found.

How to understand the output of command 'gradle dependencies'?

+, -, | and \ are just used to draw the tree - it's a kind of ASCII art.

When it comes to (*) and -> please refer to this question and answer.


(*) - is used to indicate that particular dependency is described somewhere else in the tree

-> - is used to point the dependency that wins in version conflict.

What are real-world examples of Gradle's dependency graph?

This provocative question provided motivation for finally looking into Gradle. I still haven't used it, so I can only offer analysis noted while browsing the docs, not personal stories.

My first question was why a Gradle task dependency graph is guaranteed to be acyclic. I didn't find the answer to that, but a contrary case is easily constructed, so I'll presume that cycle detection is a validation that is run when the graph is built, and the build fails prior to execution of the first task if there are illegal cyclical dependencies. Without first building the graph, this failure condition might not be discovered until the build is nearly complete. Additionally, the detection routine would have to run after every task was executed, which would be very inefficient (as long as the graph was built incrementally and available globally, a depth-first search would only be required to find a starting point, and subsequent cycle evaluations would require minimal work, but the total work would still be greater than doing a single reduction on the entire set of relations at the outset). I'd chalk up early detection as a major benefit.

A task dependency can be lazy (see: 4.3 Task dependencies, and a related example in 13.14). Lazy task dependencies could not be evaluated correctly until the entire graph is built. The same is true for transitive (non-task) dependency resolution, which could cause innumerable problems, and require repeated recompilations as additional dependencies are discovered and resolved (also requiring repeated requests to a repository). The task rules feature (13.8) wouldn't be possible either. These issues, and likely many others, can be generalized by considering that Gradle uses a dynamic language, and can dynamically add and modify tasks, so prior to a first-pass evaluation, results could be non-deterministic since the execution path is built and modified during runtime, thus, different sequences of evaluation could produce arbitrarily different results if there are dependencies or behavioral directives that are unknown until later, because they haven't been created yet. (This may be worthy of investigating with some concrete examples. If it is true, then even two passes would not always be sufficient. If A -> B, B -> C, where C changes the behavior of A so that it no longer depends on B, then you have a problem. I hope there are some best practices on restricting metaprogramming with non-local scope, to not allow it in arbitrary tasks. A fun example would be a simulation of a time travel paradox, where a grandchild kills his grandfather or marries his grandmother, vividly illustrating some practical ethical principles!)

It can enable better status and progress reporting on a currently executing build. A TaskExecutionListener provides before/after hooks to the processing of each task, but without knowing the number of remaining tasks, there isn't much it could say about status other than "6 tasks completed. About to execute task foo." Instead, you could initialize a TaskExecutionListener with the number of tasks in gradle.taskGraph.whenReady, and then attach it to the TaskExecutionGraph. Now it could provide information to enable report details like "6 of 72 tasks completed. Now executing task foo. Estimated time remaining: 2h 38m." That would be useful to display on a console for a continuous integration server, or if Gradle was being used to orchestrate a large multi-project build and time estimates were crucial.

As pointed out by Jerry Bullard, the evaluation portion of the lifecycle is critical to determining the execution plan, which provides information about the environment, since the environment is partially determined by the execution context (Example 4.15 in the Configure by DAG section). Additionally, I could see this being useful for execution optimization. Independent subpaths could be safely handed to different threads. Walking algorithms for execution can be less memory intensive if they aren't naive (my intuition says that always walking the path with the most subpaths is going to lead to a larger stack than always preferring paths with the least subpaths).

An interesting use of this might be a situation where many components of a system are initially stubbed out to support demos and incremental development. Then during development, rather than updating the build configuration as each component becomes implemented, the build itself could determine if a subproject is ready for inclusion yet (perhaps it tries to grab the code, compile it, and run a pre-determined test suite). If it is, the evaluation stage would reveal this, and the appropriate tasks would be included, otherwise, it selects the tasks for the stubs. Perhaps there's a dependency on an Oracle database that isn't available yet, and you're using an embedded database in the meantime. You could let the build check the availability, transparently switch over when it can, and tell you that it switched databases, rather than you telling it. There could be a lot of creative uses along those lines.

Gradle looks awesome. Thanks for provoking some research!

How to read dependency tree generated by gradle

\--- and +--- are used to show the elements within a dependency tree. The only difference between +--- and \--- is that \--- is used to show the last element of the current dependecy level. So, it could look somthing like:

+--- org.apache.cxf:cxf-core:3.0.3 (*)
+--- javax.ws.rs:javax.ws.rs-api:2.0.1
+--- javax.annotation:javax.annotation-api:1.2
\--- org.apache.cxf:cxf-rt-transports-http:3.0.3 (*)

And this means, that cxf-rt-frontend-jaxrs depends on 4 libraries. And the cxf-rt-transports-http is the last one from the first level dependencies of the root.

Furthermore, you may have some transitive dependencies. That is the case, you have with your :api project, then the root project depends on :api and :api itself depends in org.codehaus.groovy:groovy-all:2.4.4.

How to use Gradle's dependency tree to resolve android support library version mismatch?

Specifically, when the tree says "com.android.support:support-v4:23.3.0 -> 24.0.0", what does it mean?

It means that a library has com.android.support:support-v4:23.3.0 as nested dependency but you are just using another and higher version of the same dependencies, in this case com.android.support:support-v4:24.0.0.

In other word your project is using the com.android.support:support-v4:24.0.0

why didn't this build.gradle throw errors prior to the Android Studio, Gradle plugin, and buildToolsVersion update mentioned earlier?

Because you have updated the Gradle plugin to 3.3 that has this kind of check.

How can I make sure all my dependencies are compatible with compileSdkVersion 23?

It is quite difficult to have.

The only way is to check all dependencies, but I suggest you using:

compileSdkVersion 25
targetSdkVersion 23

In general it is a good idea to use the latest version of buildToolsVersion in any case, independently by the version of support libraries used.

It is strongly recommended that you always compile with the latest SDK. It means that today you should use compileSdkVersion 25.

How to understand `org.ow2.asm:asm-tree:4.0 - 5.0.3 (*)` in `gradle dependencies`?


Have a look at this answer.


It means that among dependencies there are conflicted - with same group and artifactId. They are often transitively downloaded with dependencies specified. Gradle tries to resolve it automatically - by picking the latest version (signed with ->). Conflicting dependencies can be excluded.

Why did a specific jar file get included?

Dependencies of gradle-managed project have their own dependencies (they're called transitive). It may happen (and happens quite often) that two different dependencies has the same (group and module) dependency but in the different version). This is the case with commons-logging:commons-logging. In this case there are two transitive dependencies one versioned with 1.1.1 and the second one with 1.1.3. If both of the libraries will be included in the final artifact it may result in a conflict and exception. To prevent such situation gradle tries to resolve mentioned version resolution problems by picking (by default) the latest version. It's indicated with the right arrow -> see here. You can exclude transitive dependencies from a particular dependency. This chapter of manual might be useful.

Related Topics

Leave a reply