// Copyright (C) 2023 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

[[sec:handling-mutually-exclusive-deps]]
= Handling mutually exclusive dependencies

[[sub:capabilities]]
== Introduction to component capabilities

Often a dependency graph would accidentally contain multiple implementations of the same API.
This is particularly common with logging frameworks, where multiple bindings are available, and that one library chooses a binding when another transitive dependency chooses another.
Because those implementations live at different GAV coordinates, the build tool has usually no way to find out that there's a conflict between those libraries.
To solve this, Gradle provides the concept of _capability_.

It's illegal to find two components providing the same _capability_ in a single dependency graph.
Intuitively, it means that if Gradle finds two components that provide the same thing on classpath, it's going to fail with an error indicating what modules are in conflict.
In our example, it means that different bindings of a logging framework provide the same capability.

== Capability coordinates

A _capability_ is defined by a `(group, module, version)` triplet.
Each component defines an implicit capability corresponding to its GAV coordinates (group, artifact, version).
For example, the `org.apache.commons:commons-lang3:3.8` module has an implicit capability with group `org.apache.commons`, name `commons-lang3` and version `3.8`.
It is important to realize that capabilities are _versioned_.

[[sub:declaring-component-capabilities]]
== Declaring component capabilities

By default, Gradle will fail if two components in the dependency graph provide the same capability.
Because most modules are currently published without Gradle Module Metadata, capabilities are not always automatically discovered by Gradle.
It is however interesting to use _rules_ to declare component capabilities in order to discover conflicts as soon as possible, during the build instead of runtime.

A typical example is whenever a component is relocated at different coordinates in a new release.
For example, the ASM library lived at `asm:asm` coordinates until version `3.3.1`, then changed to `org.ow2.asm:asm` since `4.0`.
It is illegal to have both ASM \<= 3.3.1 and 4.0+ on the classpath, because they provide the same feature, it's just that the component has been relocated.
Because each component has an implicit capability corresponding to its GAV coordinates, we can "fix" this by having a rule that will declare that the `asm:asm` module provides the `org.ow2.asm:asm` capability:

.Conflict resolution by capability
====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/kotlin",files="build.gradle.kts[tags=fix_asm]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/groovy",files="build.gradle[tags=fix_asm]"]
====

Now the build is going to _fail_ whenever the two components are found in the same dependency graph.

[NOTE]
====
At this stage, Gradle will _only_ make more builds fail.
It will **not** automatically fix the problem for you, but it helps you realize that you have a problem.
It is recommended to write such rules in _plugins_ which are then applied to your builds.
Then, users _have to_ express their preferences, if possible, or fix the problem of having incompatible things on the classpath, as explained in the following section.
====

[[sub:selecting-between-candidates]]
== Selecting between candidates

At some point, a dependency graph is going to include either _incompatible modules_, or modules which are _mutually exclusive_.
For example, you may have different logger implementations and you need to choose one binding.
<<#sub:capabilities,Capabilities>> help _realizing_ that you have a conflict, but Gradle also provides tools to express how to solve the conflicts.

[[sub:selecting-preferred-capability-provider]]
=== Selecting between different capability candidates

In the relocation example above, Gradle was able to tell you that you have two versions of the same API on classpath: an "old" module and a "relocated" one.
Now we can solve the conflict by automatically choosing the component which has the highest capability version:

.Conflict resolution by capability versioning
====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/kotlin",files="build.gradle.kts[tags=use_highest_asm]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/groovy",files="build.gradle[tags=use_highest_asm]"]
====

However, fixing by choosing the highest capability version conflict resolution is not always suitable.
For a logging framework, for example, it doesn't matter what version of the logging frameworks we use, we should always select Slf4j.

In this case, we can fix it by explicitly selecting slf4j as the winner:

.Substitute log4j with slf4j
====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/kotlin",files="build.gradle.kts[tags=use_slf4j]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-declaringCapabilities/groovy",files="build.gradle[tags=use_slf4j]"]
====

Note that this approach works also well if you have multiple _Slf4j bindings_ on the classpath:
bindings are basically different logger implementations and you need only one.
However, the selected implementation may depend on the configuration being resolved.
For example, for tests, `slf4j-simple` may be enough but for production, `slf4-over-log4j` may be better.

--
Resolution can only be made in favor of a module _found_ in the graph.

The `select` method only accepts a module found in the _current_ candidates.
If the module you want to select is not part of the conflict, you can abstain from performing a selection, effectively not resolving _this_ conflict.
It might be that another conflict exists in the graph for the same capability and will have the module you want to select.

If no resolution is given for all conflicts on a given capability, the build will fail given the module chosen for resolution was not part of the graph at all.

In addition `select(null)` will result in an error and so should be avoided.
--

For more information, check out the link:{javadocPath}/org/gradle/api/artifacts/ResolutionStrategy.html#capabilitiesResolution[the capabilities resolution API].
